installed plugin Jetpack Protect
version 1.0.2
This commit is contained in:
@ -0,0 +1,809 @@
|
||||
# Changelog
|
||||
|
||||
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.37.0] - 2022-07-26
|
||||
### Changed
|
||||
- Jetpack Sync: Add Sync lock related info in Sync debug details. [#25140]
|
||||
- Updated package dependencies. [#25158]
|
||||
|
||||
### Fixed
|
||||
- Dedicated Sync: Enable sending of callables outside of admin context, since Dedicated Sync requests always work outside of admin scope. [#25143]
|
||||
|
||||
## [1.36.1] - 2022-07-06
|
||||
### Added
|
||||
- Add new WordPress core `block-templates` theme feature to `Defaults::$default_theme_support_whitelist` [#24960]
|
||||
|
||||
## [1.36.0] - 2022-06-28
|
||||
### Added
|
||||
- Posts: added a Sync call to make sure post content is up to date before publishing. [#24827]
|
||||
|
||||
### Changed
|
||||
- Minimum Sync Config: Update required modules and options [#24831]
|
||||
|
||||
### Fixed
|
||||
- Sync Table Checksums: Table checksum should be enabled depending on corresponding Sync modulee [#24772]
|
||||
|
||||
## [1.35.2] - 2022-06-21
|
||||
### Changed
|
||||
- Renaming master to trunk. [#24661]
|
||||
|
||||
## [1.35.1] - 2022-06-14
|
||||
### Added
|
||||
- Add a request lock to prevent multiple requests being spawned at once [#24734]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#24529]
|
||||
|
||||
## [1.35.0] - 2022-05-30
|
||||
### Changed
|
||||
- Sync: Add '_jetpack_blogging_prompt_key' to default post meta whitelist
|
||||
|
||||
## [1.34.0] - 2022-05-24
|
||||
### Changed
|
||||
- Dedicated Sync - Introduce custom endpoint for spawning Sync requests [#24468]
|
||||
- Sync: Add 'active_modules' to default whitelisted callables. [#24453]
|
||||
|
||||
## [1.33.1] - 2022-05-19
|
||||
### Removed
|
||||
- Removed dedicated sync custom endpoints pending error investigation [#24419]
|
||||
|
||||
## [1.33.0] - 2022-05-18
|
||||
### Changed
|
||||
- Dedicated Sync: Introduce custom endpoint for spawning Sync requests [#24344]
|
||||
|
||||
## [1.32.0] - 2022-05-10
|
||||
### Added
|
||||
- Search: add search options to option whitelist [#24167]
|
||||
|
||||
## [1.31.1] - 2022-05-04
|
||||
### Changed
|
||||
- Updated package dependencies. [#24095]
|
||||
- WordPress 6.1 Compatibilty [#24083]
|
||||
|
||||
### Deprecated
|
||||
- Moved the options class into Connection. [#24095]
|
||||
|
||||
## [1.31.0] - 2022-04-26
|
||||
### Added
|
||||
- Adds filter to get_themes callable
|
||||
|
||||
### Deprecated
|
||||
- Removed Heartbeat by hoisting it into Connection.
|
||||
|
||||
## [1.30.8] - 2022-04-19
|
||||
### Added
|
||||
- Added get_themes Callable to sync the list of installed themes on a site
|
||||
- Added get_themes to Sync defaults
|
||||
|
||||
### Changed
|
||||
- PHPCS: Fix `WordPress.Security.ValidatedSanitizedInput`
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.30.7] - 2022-04-12
|
||||
### Added
|
||||
- Adding new site option to be synced.
|
||||
|
||||
## [1.30.6] - 2022-04-06
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
### Fixed
|
||||
- Dedicated Sync: Only try to run the sender once if Dedicated Sync is enabled as it has its own requeueing mechanism.
|
||||
|
||||
## [1.30.5] - 2022-03-29
|
||||
### Changed
|
||||
- Microperformance: Use === null instead of is_null
|
||||
|
||||
## [1.30.4] - 2022-03-23
|
||||
### Changed
|
||||
- Enable syncing of dedicated_sync_enabled Sync setting
|
||||
|
||||
### Fixed
|
||||
- Dedicated Sync: Allow spawning request with expired Retry-After
|
||||
|
||||
## [1.30.3] - 2022-03-15
|
||||
### Changed
|
||||
- Search Sync Settings :: Add ETB taxonomy to allow list.
|
||||
|
||||
## [1.30.2] - 2022-03-08
|
||||
### Changed
|
||||
- Disallow syncing of _term_meta post_type
|
||||
|
||||
## [1.30.1] - 2022-03-02
|
||||
### Added
|
||||
- Dedicated Sync flow: Allow enabling or disabling via WPCOM response header
|
||||
|
||||
## [1.30.0] - 2022-02-22
|
||||
### Added
|
||||
- Add Sync dedicated request flow.
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.29.2] - 2022-02-09
|
||||
### Added
|
||||
- Allow sync package consumers to provide custom data settings.
|
||||
|
||||
### Fixed
|
||||
- Fixed some new PHPCS warnings.
|
||||
|
||||
## [1.29.1] - 2022-02-02
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.29.0] - 2022-01-25
|
||||
### Added
|
||||
- Jetpack Search: update the allowed post meta when search is active to include all indexable meta.
|
||||
|
||||
## [1.28.2] - 2022-01-18
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.28.1] - 2022-01-13
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.28.0] - 2022-01-04
|
||||
### Changed
|
||||
- Listener: Do not enqueue actions when the site is disconnected
|
||||
- Switch to pcov for code coverage.
|
||||
- Theme deletions: rely on Core WP hook now that the package requires WP 5.8.
|
||||
- Updated package dependencies
|
||||
- Updated package textdomain from `jetpack` to `jetpack-sync`.
|
||||
|
||||
## [1.27.6] - 2021-12-14
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.27.5] - 2021-11-30
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.27.4] - 2021-11-23
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [1.27.3] - 2021-11-16
|
||||
### Changed
|
||||
- Actions: add the do_only_first_initial_sync method which starts an initial sync only when one hasn't already been done
|
||||
|
||||
## [1.27.2] - 2021-11-09
|
||||
### Added
|
||||
- Constants: Now syncing Atomic platform constant
|
||||
|
||||
### Changed
|
||||
- Full Sync : limit included users to contributors and above (based on wp_user_limit)
|
||||
- Updated package dependencies.
|
||||
- User Checksums - limit scope to users with wp_user_level > 0
|
||||
|
||||
### Fixed
|
||||
- Fix PHP 8.1 deprecation warnings.
|
||||
|
||||
## [1.27.1] - 2021-11-02
|
||||
### Changed
|
||||
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
|
||||
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
|
||||
|
||||
## [1.27.0] - 2021-10-26
|
||||
### Added
|
||||
- Added the _wpas_feature_enabled meta key to the sync list
|
||||
- Sync Error Log to capture failed sync requests.
|
||||
|
||||
### Fixed
|
||||
- Check the return value of get_comment() before to use it.
|
||||
- Increase send timeout to 20 seconds allowing capture of WP.com 408 responses.
|
||||
|
||||
## [1.26.4] - 2021-10-13
|
||||
### Changed
|
||||
- Sync Checksums: Convert text fields to latin1 before generating checksum.
|
||||
- Updated package dependencies.
|
||||
|
||||
### Fixed
|
||||
- Sync Checksums - Update distinct clause to use $wpdb-> table names to accouunt for differences in prefixes.
|
||||
|
||||
## [1.26.3] - 2021-10-12
|
||||
### Changed
|
||||
- Updated package dependencies
|
||||
|
||||
### Removed
|
||||
- Remove initialization of the identity-crisis package. That will be handled by the Config package.
|
||||
|
||||
### Fixed
|
||||
- Reduce transient expiration for how often we check the state of the queue.
|
||||
- Sync Checksums - exclude locale from checksum if same as site setting
|
||||
- Sync Checksums - use distinct query when calculating count of Term Relationships
|
||||
|
||||
## [1.26.2] - 2021-09-28
|
||||
### Added
|
||||
- Add support for checksumming user-related tabled: wp_users and wp_usermeta
|
||||
|
||||
### Changed
|
||||
- Update annotations versions.
|
||||
- Updated package dependencies.
|
||||
|
||||
### Fixed
|
||||
- Resolve indirect modification notice.
|
||||
- Sync Checksums: utilize distinct clause in term counts.
|
||||
- Sync Queue: better handling of serialization issues and empty actions.
|
||||
|
||||
## [1.26.1] - 2021-09-03
|
||||
### Fixed
|
||||
- Add better checks if the WooCommerce tables should be enabled for checksum/fix.
|
||||
- Prevent PHP notices on queue_pull if all args are not set.
|
||||
|
||||
## [1.26.0] - 2021-08-30
|
||||
### Added
|
||||
- Add support for WooCommerce table to the checksum/fix process.
|
||||
- Enable support for utf8 conversion during checksum calculation.
|
||||
|
||||
### Changed
|
||||
- Don't run composer install on regular phpunit script
|
||||
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
|
||||
|
||||
### Fixed
|
||||
- Sync Checksums - ensure last object is included in histogram
|
||||
|
||||
## [1.25.0] - 2021-08-12
|
||||
### Added
|
||||
- Add package version tracking.
|
||||
- Add `wpcom_is_fse_activated` to sync list
|
||||
- Made /sync/object endpoint accessible over POST, not only GET, to allow fetching more items in a single request.
|
||||
|
||||
## [1.24.2] - 2021-08-02
|
||||
|
||||
- Reverted: Sync option for the Carousel to display colorized slide background.
|
||||
|
||||
## [1.24.1] - 2021-07-29
|
||||
### Changed
|
||||
- Utilize an import for WP_Error in all instances.
|
||||
|
||||
### Fixed
|
||||
- Fixed unqualified WP_Error use in the Rest_Sender class.
|
||||
|
||||
## [1.24.0] - 2021-07-27
|
||||
### Added
|
||||
- Add a package version constant.
|
||||
- Add Full Site Editing support to callback options.
|
||||
- Sync option for the Carousel to display colorized slide background.
|
||||
|
||||
### Fixed
|
||||
- Update Sender so it adheres to max upload bytes when not encoding items.
|
||||
|
||||
## [1.23.3] - 2021-07-16
|
||||
### Fixed
|
||||
- Update Options module to return jetpack_sync_settings_* values from the Settings class vs direct option lookup.
|
||||
|
||||
## [1.23.2] - 2021-07-13
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
### Fixed
|
||||
- Performance of Sync checksums degraded with the update to correlated subquery. This restricts its usage to term_taxonomy joins only."
|
||||
|
||||
## [1.23.1] - 2021-07-01
|
||||
### Changed
|
||||
- Checksum parent_table joins need distinct selection to account for possibility of multiple rows.
|
||||
|
||||
### Fixed
|
||||
- Update term_taxonomy checksum query to an allowed list vs disallowed
|
||||
|
||||
## [1.23.0] - 2021-06-29
|
||||
### Added
|
||||
- Add jetpack_idc_disonnect action to clear Sync options on disconnect.
|
||||
- Add support to callables to sync/object endpoint.
|
||||
- Enable sync/object endpoint support for theme-info.
|
||||
- Enhance updates module to support get_objects_by_id.
|
||||
- Expand sync/object to support constants.
|
||||
- Extend sync/object to support callables.
|
||||
- Implement v4 REST endpoints.
|
||||
- Initialize the IDC package in the Sync package.
|
||||
|
||||
### Removed
|
||||
- Remove product_cat from blocked taxonomies
|
||||
|
||||
## [1.22.0] - 2021-06-15
|
||||
### Changed
|
||||
- Sync: Adding the Identity_Crisis package.
|
||||
- Updated package dependencies.
|
||||
|
||||
### Deprecated
|
||||
- Deprecated URL methods in `Automattic\Jetpack\Sync\Functions` in favor of `Automattic\Jetpack\Connection\Urls`.
|
||||
|
||||
## [1.21.3] - 2021-05-25
|
||||
### Changed
|
||||
- Performance: If no Full Sync is in process early return before we update options.
|
||||
|
||||
### Fixed
|
||||
- Janitorial: avoid PHP notices in some edge-cases
|
||||
- Update Meta Module so get_object_by_id returns all meta values.
|
||||
|
||||
## [1.21.2] - 2021-04-27
|
||||
### Added
|
||||
- Added the password-checker package the the Sync package composer.json file.
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
### Fixed
|
||||
- Sync: removed references to the JETPACK__PLUGIN_DIR constant.
|
||||
- Sync Checksums : updated postmeta range query performance #19337.
|
||||
|
||||
## [1.21.1] - 2021-03-30
|
||||
### Added
|
||||
- Composer alias for dev-master, to improve dependencies
|
||||
- Implement a 60 second back-off for non-200 respones, if no retry-after header is present in the response.
|
||||
- Impose a max limit of 2MB on post meta values that are synced.
|
||||
- Impose a max limit of 5MB on post_content that can be synced.
|
||||
|
||||
### Changed
|
||||
- Sync: Use the new Password_Checker package instead of Jetpack_Password_Checker.
|
||||
- Update package dependencies.
|
||||
- Use the Heartbeat package to generate the stats array
|
||||
|
||||
### Fixed
|
||||
- Migrate locks to update_option to avaoid memcache inconsistencies that can be introduced by delete_option usage.
|
||||
- Update Sync Queue so that serialize is wrapped to catch errors
|
||||
|
||||
## [1.21.0] - 2021-02-23
|
||||
|
||||
- General: update WordPress version requirements to WP 5.6
|
||||
- Update Checksums to support blacklisted taxonomies.
|
||||
- Refactor Jetpack callables into the plugin using existing filter jetpack_sync_callable_whitelist
|
||||
- Wrap call_user_func in is_callable so that we don't trigger warnings for callables that don't exist.
|
||||
- Sync: Trigger initial sync on jetpack_site_registered
|
||||
- Update Comments checksum field to comment_date_gmt. We cannot use comment_content directly due to charset/filters.
|
||||
- Deprecate jetpack_json_wrap
|
||||
- Remove Sync's usage of wp_startswith
|
||||
|
||||
## [1.20.2] - 2021-02-08
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.20.1] - 2021-01-28
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.20.0] - 2021-01-26
|
||||
|
||||
- Sync Concurrency / Race Conditions
|
||||
- Sync: Prevent an PHP warning
|
||||
- Jetpack Sync: Checksums: Use a better way to fetch and validate fields against table
|
||||
- Add mirror-repo information to all current composer packages
|
||||
- Full Sync :: Reduce Concurrency.
|
||||
- Monorepo: Reorganize all projects
|
||||
- Various PHPCS and Cleanup
|
||||
|
||||
## [1.19.4] - 2021-01-18
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.19.3] - 2021-01-18
|
||||
|
||||
- Full Sync :: Reduce Concurrency.
|
||||
|
||||
## [1.19.2] - 2020-12-21
|
||||
|
||||
- Update the do_full_sync function to early return if we are in SYNC READ ONLY mode.
|
||||
- Return an empty array if the specified range is empty. (It was returning the checksum for the WHOLE dataset).
|
||||
|
||||
## [1.19.1] - 2020-12-17
|
||||
|
||||
## [1.19.0] - 2020-12-17
|
||||
|
||||
- sync: Improve sync checksum algorithm and endpoints
|
||||
- wp_get_environment_type as callable.
|
||||
- Disallow amp_validated_url as it is not site content but instead validation errors for amp mark-up.
|
||||
- Whitelist (allow) jetpack_sync_settings_* options to be synced
|
||||
- Re-order Sync default option whitelist (allowlist)
|
||||
|
||||
## [1.18.1] - 2020-11-24
|
||||
|
||||
- Version packages for release
|
||||
|
||||
## [1.18.0] - 2020-11-24
|
||||
|
||||
- Migrate jetpack_published_post to wp_after_insert_post hook
|
||||
- Check value to determine if we should enable sync after an action enqueuement.
|
||||
- General: update minimum required version to WordPress 5.5
|
||||
- Fix remaining phpcs warnings in most of requirelist
|
||||
- Update access of comment_status_to_approval_value to allow extension.
|
||||
- Update get_term Replicastore function to handle term_taxonomy_id option
|
||||
- Update get_terms to utilize ensure_taxonomy so that the Taxonomy is registered.
|
||||
- Addtion of note on explict return of null instead of false if option not found.
|
||||
- Alignment of comment_status_to_approval_value function. Addition of post-trashed status and cleanup of cases.
|
||||
- Alignment with implemenations. Call ensure_taxonomy to ensure Taxonomies have been initialized.
|
||||
- Call ensure_taxonomy within get_object_terms so that the taxonomy is registered before action is performed.
|
||||
- Updated PHPCS: Packages and Debugger
|
||||
|
||||
## [1.17.2] - 2020-11-05
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.17.1] - 2020-10-29
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.17.0] - 2020-10-27
|
||||
|
||||
- WPCOM Block Editor: Update meta key name
|
||||
- Resolve PHP Warning with array_filter usage in sync of action_links.
|
||||
- Sync: Seperate theme data ( name, version, slug and uri) from theme support data
|
||||
- Replaced intval() with (int) as part of issue #17432.
|
||||
- Replaced strval() with type casting (string) as part of issue #17432.
|
||||
- Replaced floatval() with type cast (float) as part of issue #17432.
|
||||
- Make XMLRPC methods available for blog token
|
||||
|
||||
## [1.16.4] - 2020-10-14
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.16.3] - 2020-10-09
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.16.2] - 2020-10-06
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.16.1] - 2020-10-01
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.16.0] - 2020-09-29
|
||||
|
||||
- Publicize: Allow publishing a post as a Twitter thread.
|
||||
- props @jmdodd - filter out set_object_terms actions that don't perform any update. Includes unit tests.
|
||||
- Sort Arrays by keys before generating callable checksums
|
||||
- Packages: avoid PHPCS warnings
|
||||
- Adding 'review' to whitelisted comment types
|
||||
- Disable Sync sending on Backup API Requests
|
||||
- Sync: stop trying to check for edit_comment capability
|
||||
- Added options to sync wc whitelist
|
||||
- Sync: Improve theme support syncing
|
||||
|
||||
## [1.15.1] - 2020-09-09
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.15.0] - 2020-08-26
|
||||
|
||||
- Sync: add Creative Mail configuration option to synced options
|
||||
- Extend sync_status endpoint with optional debug_details field
|
||||
- REST API endpoints: expand management endpoints
|
||||
- Sync: Fix nonce action string in theme edit sync
|
||||
- WP 5.5 Compat: Align Jetpack and Core's plugin autoupdates
|
||||
- use current user token to updateRole request
|
||||
- Resolve Sync Errors from empty edge case and WP.com returning concurrent_request_error
|
||||
- Rework Sender to properly return state during do_full_sync
|
||||
|
||||
## [1.14.4] - 2020-08-10
|
||||
|
||||
- WP 5.5 Compat: Align Jetpack and Core's plugin autoupdates
|
||||
|
||||
## [1.14.3] - 2020-08-10
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.14.2] - 2020-08-10
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.14.1] - 2020-08-10
|
||||
|
||||
- Resolve Sync Errors from empty edge case and WP.com returning concurrent_request_error
|
||||
|
||||
## [1.14.0] - 2020-07-28
|
||||
|
||||
- Core Compat: Site Environment
|
||||
- Unit Tests: fix tests according to changes in Core
|
||||
- Utilize the blog token vs master user token to send sync actions.
|
||||
|
||||
## [1.13.2] - 2020-07-06
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.13.1] - 2020-07-01
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.13.0] - 2020-06-30
|
||||
|
||||
- Block Flamingo Plugin post types in Jetpack Sync
|
||||
- Explicit single execution of do_full_sync from cron
|
||||
- Update to reference the property defined in the Jetpack Connection Manager class
|
||||
- PHPCS: Clean up the packages
|
||||
- WordAds: Add consent support for California Consumer Privacy Act (CCPA)
|
||||
- Sync: Add additional support for theme_support_whitelist
|
||||
|
||||
## [1.12.4] - 2020-06-02
|
||||
|
||||
- Revert "Fix `jetpack sync start` CLI command (#16010)"
|
||||
|
||||
## [1.12.3] - 2020-06-01
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.12.2] - 2020-06-01
|
||||
|
||||
- Fix `jetpack sync start` CLI command
|
||||
|
||||
## [1.12.1] - 2020-05-29
|
||||
|
||||
- Sync: Add additional support for theme_support_whitelist
|
||||
|
||||
## [1.12.0] - 2020-05-26
|
||||
|
||||
- Update ReplicaStore to call clean_comment_cache when comments are upserted or a reset is perofrmed.
|
||||
- Store the list of active plugins that uses connection in an option
|
||||
- Jetpack Sync :: Alternate non-blocking flow
|
||||
- Settings - Writing: add a toggle to Carousel so users can hide comment area
|
||||
- Sender needs to load consistently utilizing logic
|
||||
- Always delete items from the queue even if the buffer is no longer checked out.
|
||||
- Update the hook of Sync's Comment module to not send meta actions when the comment_type is not whitelisted.
|
||||
- Sync Comments apply whitelist to all actions
|
||||
|
||||
## [1.11.0] - 2020-04-28
|
||||
|
||||
- Correct inline documentation "Array" type
|
||||
- Filter out blacklisted post_types for deleted_post actions.
|
||||
- Publicize: Add jetpack_publicize_options
|
||||
- Blacklisting Post Types from Sync
|
||||
- Comments: update default comment type
|
||||
- Jetpack Sync: Split `jetpack_post_meta_batch_delete` in action to be called in chunks of 100 items, compared to all at once.
|
||||
- Update Sync limits based on analysis of data loss events.
|
||||
|
||||
## [1.10.0] - 2020-03-31
|
||||
|
||||
- Update dependencies to latest stable
|
||||
|
||||
## [1.9.0] - 2020-03-31
|
||||
|
||||
- Debugger: Add sync health progress bar
|
||||
- Add main network WPCOM blog ID to sync functions
|
||||
- Masterbar: send wpcom user ID to wpcom when attempting to log…
|
||||
- Sync: a better readme
|
||||
|
||||
## [1.8.0] - 2020-02-25
|
||||
|
||||
- Minileven: add options back as they still exist on sites
|
||||
- Sync: add queue size to actions
|
||||
- Mobile Theme: remove feature
|
||||
|
||||
## [1.7.6] - 2020-02-14
|
||||
|
||||
- get_sync_status does not properly account for unexpected states.
|
||||
|
||||
## [1.7.5] - 2020-02-14
|
||||
|
||||
- Empty Helper function for checkin handler
|
||||
- Sync Health: fix excessive data loss reports
|
||||
- Initial Sync Health Status Class and Data Loss Handler
|
||||
- Stop REST API Log entries from being synced
|
||||
|
||||
## [1.7.4+vip] - 2020-02-14
|
||||
|
||||
- Empty Helper function for checkin handler
|
||||
|
||||
## [1.7.4] - 2020-01-23
|
||||
|
||||
- Sync Chunk Keys need to be unique
|
||||
|
||||
## [1.7.3] - 2020-01-20
|
||||
|
||||
- Sync: ensure we run the initial sync on new connections
|
||||
|
||||
## [1.7.2] - 2020-01-17
|
||||
|
||||
- Sync Package: use Full_Sync_Immediately by default
|
||||
- Adding new managed WordPress hosts to be identified in class-functions.php.
|
||||
|
||||
## [1.7.1] - 2020-01-14
|
||||
|
||||
- Packages: Various improvements for wp.com or self-contained consumers
|
||||
|
||||
## [1.7.0] - 2020-01-14
|
||||
|
||||
- Trying to add deterministic initialization.
|
||||
|
||||
## [1.6.3] - 2020-01-07
|
||||
|
||||
- Fix git history.
|
||||
|
||||
## [1.6.2] - 2019-12-31
|
||||
|
||||
- Sync: Remove DEFAULT_SYNC_MODULES legacy map
|
||||
- Connection: Loose Comparison for Port Number in Signatures
|
||||
|
||||
## [1.6.1] - 2019-12-13
|
||||
|
||||
- tweak default sync settings
|
||||
|
||||
## [1.6.0] - 2019-12-02
|
||||
|
||||
- Sync: Full Sync: Send immediately.
|
||||
|
||||
## [1.5.1] - 2019-11-26
|
||||
|
||||
- Marked the xmlrpc_api_url method as deprecated.
|
||||
|
||||
## [1.5.0] - 2019-11-25
|
||||
|
||||
- Remove sync settings cache
|
||||
|
||||
## [1.4.0] - 2019-11-19
|
||||
|
||||
- Full Sync: Don't allow more than one request to enqueue
|
||||
- Sync: Update Max Int
|
||||
|
||||
## [1.3.4] - 2019-11-08
|
||||
|
||||
- Packages: Use classmap instead of PSR-4
|
||||
|
||||
## [1.3.3] - 2019-11-08
|
||||
|
||||
- Deprecate Jetpack::is_development_mode() in favor of the packaged Status()->is_development_mode()
|
||||
|
||||
## [1.3.2] - 2019-11-01
|
||||
|
||||
- Full Sync updates to allow full enqueuing of chunks.
|
||||
|
||||
## [1.3.1] - 2019-10-29
|
||||
|
||||
- PHPCS: Rest of the packages
|
||||
|
||||
## [1.3.0] - 2019-10-29
|
||||
|
||||
- Sync: Checkout Endpoint: Add `pop` argument 😱
|
||||
|
||||
## [1.2.1] - 2019-10-28
|
||||
|
||||
- Sync: Add Settings to enable/disable the sender for a particular queue
|
||||
|
||||
## [1.2.0] - 2019-10-24
|
||||
|
||||
- Sync: Fix how we enqueue term_relationships on full sync 🏝
|
||||
- WP 5.3: Use modern wp_timezone
|
||||
- Check for last_error when enqueuing IDs
|
||||
|
||||
## [1.1.1] - 2019-10-23
|
||||
|
||||
- Use spread operator instead of func_get_args
|
||||
|
||||
## [1.1.0] - 2019-10-07
|
||||
|
||||
- Sync: Ensure a post object is returned
|
||||
- PHPCS: Sync Functions
|
||||
- Sync: Bail initial sync if there is an ongoing full sync
|
||||
|
||||
## [1.0.2] - 2019-09-25
|
||||
|
||||
- Sync: Only allow white listed comment types to be inserted.
|
||||
- Sync: Move sync_object XML-RPC method from connection to sync
|
||||
- Sync: do not sync comments made via Action Scheduler
|
||||
- Docs: Unify usage of @package phpdoc tags
|
||||
|
||||
## [1.0.1] - 2019-09-14
|
||||
|
||||
## 1.0.0 - 2019-09-14
|
||||
|
||||
- Packages: Move sync to a classmapped package
|
||||
|
||||
[1.37.0]: https://github.com/Automattic/jetpack-sync/compare/v1.36.1...v1.37.0
|
||||
[1.36.1]: https://github.com/Automattic/jetpack-sync/compare/v1.36.0...v1.36.1
|
||||
[1.36.0]: https://github.com/Automattic/jetpack-sync/compare/v1.35.2...v1.36.0
|
||||
[1.35.2]: https://github.com/Automattic/jetpack-sync/compare/v1.35.1...v1.35.2
|
||||
[1.35.1]: https://github.com/Automattic/jetpack-sync/compare/v1.35.0...v1.35.1
|
||||
[1.35.0]: https://github.com/Automattic/jetpack-sync/compare/v1.34.0...v1.35.0
|
||||
[1.34.0]: https://github.com/Automattic/jetpack-sync/compare/v1.33.1...v1.34.0
|
||||
[1.33.1]: https://github.com/Automattic/jetpack-sync/compare/v1.33.0...v1.33.1
|
||||
[1.33.0]: https://github.com/Automattic/jetpack-sync/compare/v1.32.0...v1.33.0
|
||||
[1.32.0]: https://github.com/Automattic/jetpack-sync/compare/v1.31.1...v1.32.0
|
||||
[1.31.1]: https://github.com/Automattic/jetpack-sync/compare/v1.31.0...v1.31.1
|
||||
[1.31.0]: https://github.com/Automattic/jetpack-sync/compare/v1.30.8...v1.31.0
|
||||
[1.30.8]: https://github.com/Automattic/jetpack-sync/compare/v1.30.7...v1.30.8
|
||||
[1.30.7]: https://github.com/Automattic/jetpack-sync/compare/v1.30.6...v1.30.7
|
||||
[1.30.6]: https://github.com/Automattic/jetpack-sync/compare/v1.30.5...v1.30.6
|
||||
[1.30.5]: https://github.com/Automattic/jetpack-sync/compare/v1.30.4...v1.30.5
|
||||
[1.30.4]: https://github.com/Automattic/jetpack-sync/compare/v1.30.3...v1.30.4
|
||||
[1.30.3]: https://github.com/Automattic/jetpack-sync/compare/v1.30.2...v1.30.3
|
||||
[1.30.2]: https://github.com/Automattic/jetpack-sync/compare/v1.30.1...v1.30.2
|
||||
[1.30.1]: https://github.com/Automattic/jetpack-sync/compare/v1.30.0...v1.30.1
|
||||
[1.30.0]: https://github.com/Automattic/jetpack-sync/compare/v1.29.2...v1.30.0
|
||||
[1.29.2]: https://github.com/Automattic/jetpack-sync/compare/v1.29.1...v1.29.2
|
||||
[1.29.1]: https://github.com/Automattic/jetpack-sync/compare/v1.29.0...v1.29.1
|
||||
[1.29.0]: https://github.com/Automattic/jetpack-sync/compare/v1.28.2...v1.29.0
|
||||
[1.28.2]: https://github.com/Automattic/jetpack-sync/compare/v1.28.1...v1.28.2
|
||||
[1.28.1]: https://github.com/Automattic/jetpack-sync/compare/v1.28.0...v1.28.1
|
||||
[1.28.0]: https://github.com/Automattic/jetpack-sync/compare/v1.27.6...v1.28.0
|
||||
[1.27.6]: https://github.com/Automattic/jetpack-sync/compare/v1.27.5...v1.27.6
|
||||
[1.27.5]: https://github.com/Automattic/jetpack-sync/compare/v1.27.4...v1.27.5
|
||||
[1.27.4]: https://github.com/Automattic/jetpack-sync/compare/v1.27.3...v1.27.4
|
||||
[1.27.3]: https://github.com/Automattic/jetpack-sync/compare/v1.27.2...v1.27.3
|
||||
[1.27.2]: https://github.com/Automattic/jetpack-sync/compare/v1.27.1...v1.27.2
|
||||
[1.27.1]: https://github.com/Automattic/jetpack-sync/compare/v1.27.0...v1.27.1
|
||||
[1.27.0]: https://github.com/Automattic/jetpack-sync/compare/v1.26.4...v1.27.0
|
||||
[1.26.4]: https://github.com/Automattic/jetpack-sync/compare/v1.26.3...v1.26.4
|
||||
[1.26.3]: https://github.com/Automattic/jetpack-sync/compare/v1.26.2...v1.26.3
|
||||
[1.26.2]: https://github.com/Automattic/jetpack-sync/compare/v1.26.1...v1.26.2
|
||||
[1.26.1]: https://github.com/Automattic/jetpack-sync/compare/v1.26.0...v1.26.1
|
||||
[1.26.0]: https://github.com/Automattic/jetpack-sync/compare/v1.25.0...v1.26.0
|
||||
[1.25.0]: https://github.com/Automattic/jetpack-sync/compare/v1.24.2...v1.25.0
|
||||
[1.24.2]: https://github.com/Automattic/jetpack-sync/compare/v1.24.1...v1.24.2
|
||||
[1.24.1]: https://github.com/Automattic/jetpack-sync/compare/v1.24.0...v1.24.1
|
||||
[1.24.0]: https://github.com/Automattic/jetpack-sync/compare/v1.23.3...v1.24.0
|
||||
[1.23.3]: https://github.com/Automattic/jetpack-sync/compare/v1.23.2...v1.23.3
|
||||
[1.23.2]: https://github.com/Automattic/jetpack-sync/compare/v1.23.1...v1.23.2
|
||||
[1.23.1]: https://github.com/Automattic/jetpack-sync/compare/v1.23.0...v1.23.1
|
||||
[1.23.0]: https://github.com/Automattic/jetpack-sync/compare/v1.22.0...v1.23.0
|
||||
[1.22.0]: https://github.com/Automattic/jetpack-sync/compare/v1.21.3...v1.22.0
|
||||
[1.21.3]: https://github.com/Automattic/jetpack-sync/compare/v1.21.2...v1.21.3
|
||||
[1.21.2]: https://github.com/Automattic/jetpack-sync/compare/v1.21.1...v1.21.2
|
||||
[1.21.1]: https://github.com/Automattic/jetpack-sync/compare/v1.21.0...v1.21.1
|
||||
[1.21.0]: https://github.com/Automattic/jetpack-sync/compare/v1.20.2...v1.21.0
|
||||
[1.20.2]: https://github.com/Automattic/jetpack-sync/compare/v1.20.1...v1.20.2
|
||||
[1.20.1]: https://github.com/Automattic/jetpack-sync/compare/v1.20.0...v1.20.1
|
||||
[1.20.0]: https://github.com/Automattic/jetpack-sync/compare/v1.19.4...v1.20.0
|
||||
[1.19.4]: https://github.com/Automattic/jetpack-sync/compare/v1.19.3...v1.19.4
|
||||
[1.19.3]: https://github.com/Automattic/jetpack-sync/compare/v1.19.2...v1.19.3
|
||||
[1.19.2]: https://github.com/Automattic/jetpack-sync/compare/v1.19.1...v1.19.2
|
||||
[1.19.1]: https://github.com/Automattic/jetpack-sync/compare/v1.19.0...v1.19.1
|
||||
[1.19.0]: https://github.com/Automattic/jetpack-sync/compare/v1.18.1...v1.19.0
|
||||
[1.18.1]: https://github.com/Automattic/jetpack-sync/compare/v1.18.0...v1.18.1
|
||||
[1.18.0]: https://github.com/Automattic/jetpack-sync/compare/v1.17.2...v1.18.0
|
||||
[1.17.2]: https://github.com/Automattic/jetpack-sync/compare/v1.17.1...v1.17.2
|
||||
[1.17.1]: https://github.com/Automattic/jetpack-sync/compare/v1.17.0...v1.17.1
|
||||
[1.17.0]: https://github.com/Automattic/jetpack-sync/compare/v1.16.4...v1.17.0
|
||||
[1.16.4]: https://github.com/Automattic/jetpack-sync/compare/v1.16.3...v1.16.4
|
||||
[1.16.3]: https://github.com/Automattic/jetpack-sync/compare/v1.16.2...v1.16.3
|
||||
[1.16.2]: https://github.com/Automattic/jetpack-sync/compare/v1.16.1...v1.16.2
|
||||
[1.16.1]: https://github.com/Automattic/jetpack-sync/compare/v1.16.0...v1.16.1
|
||||
[1.16.0]: https://github.com/Automattic/jetpack-sync/compare/v1.15.1...v1.16.0
|
||||
[1.15.1]: https://github.com/Automattic/jetpack-sync/compare/v1.15.0...v1.15.1
|
||||
[1.15.0]: https://github.com/Automattic/jetpack-sync/compare/v1.14.4...v1.15.0
|
||||
[1.14.4]: https://github.com/Automattic/jetpack-sync/compare/v1.14.3...v1.14.4
|
||||
[1.14.3]: https://github.com/Automattic/jetpack-sync/compare/v1.14.2...v1.14.3
|
||||
[1.14.2]: https://github.com/Automattic/jetpack-sync/compare/v1.14.1...v1.14.2
|
||||
[1.14.1]: https://github.com/Automattic/jetpack-sync/compare/v1.14.0...v1.14.1
|
||||
[1.14.0]: https://github.com/Automattic/jetpack-sync/compare/v1.13.2...v1.14.0
|
||||
[1.13.2]: https://github.com/Automattic/jetpack-sync/compare/v1.13.1...v1.13.2
|
||||
[1.13.1]: https://github.com/Automattic/jetpack-sync/compare/v1.13.0...v1.13.1
|
||||
[1.13.0]: https://github.com/Automattic/jetpack-sync/compare/v1.12.4...v1.13.0
|
||||
[1.12.4]: https://github.com/Automattic/jetpack-sync/compare/v1.12.3...v1.12.4
|
||||
[1.12.3]: https://github.com/Automattic/jetpack-sync/compare/v1.12.2...v1.12.3
|
||||
[1.12.2]: https://github.com/Automattic/jetpack-sync/compare/v1.12.1...v1.12.2
|
||||
[1.12.1]: https://github.com/Automattic/jetpack-sync/compare/v1.12.0...v1.12.1
|
||||
[1.12.0]: https://github.com/Automattic/jetpack-sync/compare/v1.11.0...v1.12.0
|
||||
[1.11.0]: https://github.com/Automattic/jetpack-sync/compare/v1.10.0...v1.11.0
|
||||
[1.10.0]: https://github.com/Automattic/jetpack-sync/compare/v1.9.0...v1.10.0
|
||||
[1.9.0]: https://github.com/Automattic/jetpack-sync/compare/v1.8.0...v1.9.0
|
||||
[1.8.0]: https://github.com/Automattic/jetpack-sync/compare/v1.7.6...v1.8.0
|
||||
[1.7.6]: https://github.com/Automattic/jetpack-sync/compare/v1.7.5...v1.7.6
|
||||
[1.7.5]: https://github.com/Automattic/jetpack-sync/compare/v1.7.4+vip...v1.7.5
|
||||
[1.7.4+vip]: https://github.com/Automattic/jetpack-sync/compare/v1.7.4...v1.7.4+vip
|
||||
[1.7.4]: https://github.com/Automattic/jetpack-sync/compare/v1.7.3...v1.7.4
|
||||
[1.7.3]: https://github.com/Automattic/jetpack-sync/compare/v1.7.2...v1.7.3
|
||||
[1.7.2]: https://github.com/Automattic/jetpack-sync/compare/v1.7.1...v1.7.2
|
||||
[1.7.1]: https://github.com/Automattic/jetpack-sync/compare/v1.7.0...v1.7.1
|
||||
[1.7.0]: https://github.com/Automattic/jetpack-sync/compare/v1.6.3...v1.7.0
|
||||
[1.6.3]: https://github.com/Automattic/jetpack-sync/compare/v1.6.2...v1.6.3
|
||||
[1.6.2]: https://github.com/Automattic/jetpack-sync/compare/v1.6.1...v1.6.2
|
||||
[1.6.1]: https://github.com/Automattic/jetpack-sync/compare/v1.6.0...v1.6.1
|
||||
[1.6.0]: https://github.com/Automattic/jetpack-sync/compare/v1.5.1...v1.6.0
|
||||
[1.5.1]: https://github.com/Automattic/jetpack-sync/compare/v1.5.0...v1.5.1
|
||||
[1.5.0]: https://github.com/Automattic/jetpack-sync/compare/v1.4.0...v1.5.0
|
||||
[1.4.0]: https://github.com/Automattic/jetpack-sync/compare/v1.3.4...v1.4.0
|
||||
[1.3.4]: https://github.com/Automattic/jetpack-sync/compare/v1.3.3...v1.3.4
|
||||
[1.3.3]: https://github.com/Automattic/jetpack-sync/compare/v1.3.2...v1.3.3
|
||||
[1.3.2]: https://github.com/Automattic/jetpack-sync/compare/v1.3.1...v1.3.2
|
||||
[1.3.1]: https://github.com/Automattic/jetpack-sync/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/Automattic/jetpack-sync/compare/v1.2.1...v1.3.0
|
||||
[1.2.1]: https://github.com/Automattic/jetpack-sync/compare/v1.2.0...v1.2.1
|
||||
[1.2.0]: https://github.com/Automattic/jetpack-sync/compare/v1.1.1...v1.2.0
|
||||
[1.1.1]: https://github.com/Automattic/jetpack-sync/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/Automattic/jetpack-sync/compare/v1.0.2...v1.1.0
|
||||
[1.0.2]: https://github.com/Automattic/jetpack-sync/compare/v1.0.1...v1.0.2
|
||||
[1.0.1]: https://github.com/Automattic/jetpack-sync/compare/v1.0.0...v1.0.1
|
@ -0,0 +1,357 @@
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
===================================
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
@ -0,0 +1,38 @@
|
||||
# Security Policy
|
||||
|
||||
Full details of the Automattic Security Policy can be found on [automattic.com](https://automattic.com/security/).
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
**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.**
|
||||
|
||||
Our most critical targets are:
|
||||
|
||||
* Jetpack and the Jetpack composer packages (all within this repo)
|
||||
* Jetpack.com -- the primary marketing site.
|
||||
* cloud.jetpack.com -- a management site.
|
||||
* wordpress.com -- the shared management site for both Jetpack and WordPress.com sites.
|
||||
|
||||
For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic).
|
||||
|
||||
_Please note that the **WordPress software is a separate entity** from Automattic. Please report vulnerabilities for WordPress through [the WordPress Foundation's HackerOne page](https://hackerone.com/wordpress)._
|
||||
|
||||
## Guidelines
|
||||
|
||||
We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines:
|
||||
|
||||
* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines).
|
||||
* Pen-testing Production:
|
||||
* Please **setup a local environment** instead whenever possible. Most of our code is open source (see above).
|
||||
* If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC.
|
||||
* **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels.
|
||||
* To be eligible for a bounty, all of these guidelines must be followed.
|
||||
* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability.
|
||||
|
||||
We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties.
|
@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "automattic/jetpack-sync",
|
||||
"description": "Everything needed to allow syncing to the WP.com infrastructure.",
|
||||
"type": "jetpack-library",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"automattic/jetpack-connection": "^1.41",
|
||||
"automattic/jetpack-constants": "^1.6",
|
||||
"automattic/jetpack-identity-crisis": "^0.8",
|
||||
"automattic/jetpack-password-checker": "^0.2",
|
||||
"automattic/jetpack-roles": "^1.4",
|
||||
"automattic/jetpack-status": "^1.14"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.2",
|
||||
"yoast/phpunit-polyfills": "1.0.3",
|
||||
"automattic/wordbless": "@dev"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"phpunit": [
|
||||
"./vendor/phpunit/phpunit/phpunit --colors=always"
|
||||
],
|
||||
"test-coverage": [
|
||||
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
|
||||
],
|
||||
"test-php": [
|
||||
"@composer phpunit"
|
||||
],
|
||||
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-sync",
|
||||
"textdomain": "jetpack-sync",
|
||||
"version-constants": {
|
||||
"::PACKAGE_VERSION": "src/class-package-version.php"
|
||||
},
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.37.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"roots/wordpress-core-installer": true
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,415 @@
|
||||
<?php
|
||||
/**
|
||||
* The Data Settings class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* The Data_Settings class
|
||||
*/
|
||||
class Data_Settings {
|
||||
|
||||
/**
|
||||
* The data that must be synced for every synced site.
|
||||
*/
|
||||
const MUST_SYNC_DATA_SETTINGS = array(
|
||||
'jetpack_sync_modules' => array(
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Callables',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Constants',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync_Immediately', // enable Initial Sync on Site Connection.
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Options',
|
||||
),
|
||||
'jetpack_sync_callable_whitelist' => array(
|
||||
'site_url' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'site_url' ),
|
||||
'home_url' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'home_url' ),
|
||||
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
|
||||
'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
|
||||
'paused_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_paused_plugins' ),
|
||||
'paused_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_paused_themes' ),
|
||||
'timezone' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_timezone' ),
|
||||
'wp_get_environment_type' => 'wp_get_environment_type',
|
||||
'wp_max_upload_size' => 'wp_max_upload_size',
|
||||
'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
|
||||
),
|
||||
'jetpack_sync_constants_whitelist' => array(
|
||||
'ABSPATH',
|
||||
'ALTERNATE_WP_CRON',
|
||||
'ATOMIC_CLIENT_ID',
|
||||
'AUTOMATIC_UPDATER_DISABLED',
|
||||
'DISABLE_WP_CRON',
|
||||
'DISALLOW_FILE_EDIT',
|
||||
'DISALLOW_FILE_MODS',
|
||||
'EMPTY_TRASH_DAYS',
|
||||
'FS_METHOD',
|
||||
'IS_PRESSABLE',
|
||||
'PHP_VERSION',
|
||||
'WP_ACCESSIBLE_HOSTS',
|
||||
'WP_AUTO_UPDATE_CORE',
|
||||
'WP_CONTENT_DIR',
|
||||
'WP_CRON_LOCK_TIMEOUT',
|
||||
'WP_DEBUG',
|
||||
'WP_HTTP_BLOCK_EXTERNAL',
|
||||
'WP_MAX_MEMORY_LIMIT',
|
||||
'WP_MEMORY_LIMIT',
|
||||
'WP_POST_REVISIONS',
|
||||
),
|
||||
'jetpack_sync_options_whitelist' => array(
|
||||
/**
|
||||
* Sync related options
|
||||
*/
|
||||
'jetpack_sync_non_blocking',
|
||||
'jetpack_sync_non_public_post_stati',
|
||||
'jetpack_sync_settings_comment_meta_whitelist',
|
||||
'jetpack_sync_settings_post_meta_whitelist',
|
||||
'jetpack_sync_settings_post_types_blacklist',
|
||||
'jetpack_sync_settings_taxonomies_blacklist',
|
||||
'jetpack_sync_settings_dedicated_sync_enabled',
|
||||
/**
|
||||
* Connection related options
|
||||
*/
|
||||
'jetpack_connection_active_plugins',
|
||||
/**
|
||||
* Generic site options
|
||||
*/
|
||||
'blog_charset',
|
||||
'blog_public',
|
||||
'blogdescription',
|
||||
'blogname',
|
||||
'permalink_structure',
|
||||
'stylesheet',
|
||||
'time_format',
|
||||
'timezone_string',
|
||||
),
|
||||
);
|
||||
|
||||
const MODULE_FILTER_MAPPING = array(
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Options' => array(
|
||||
'jetpack_sync_options_whitelist',
|
||||
'jetpack_sync_options_contentless',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Constants' => array(
|
||||
'jetpack_sync_constants_whitelist',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Callables' => array(
|
||||
'jetpack_sync_callable_whitelist',
|
||||
'jetpack_sync_multisite_callable_whitelist',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Posts' => array(
|
||||
'jetpack_sync_post_meta_whitelist',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Comments' => array(
|
||||
'jetpack_sync_comment_meta_whitelist',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Users' => array(
|
||||
'jetpack_sync_capabilities_whitelist',
|
||||
),
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Import' => array(
|
||||
'jetpack_sync_known_importers',
|
||||
),
|
||||
);
|
||||
|
||||
const MODULES_FILTER_NAME = 'jetpack_sync_modules';
|
||||
|
||||
/**
|
||||
* The static data settings array which contains the aggregated data settings for
|
||||
* each sync filter.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $data_settings = array();
|
||||
|
||||
/**
|
||||
* The static array which contains the list of filter hooks that have already been set up.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $set_filter_hooks = array();
|
||||
|
||||
/**
|
||||
* Adds the data settings provided by a plugin to the Sync data settings.
|
||||
*
|
||||
* @param array $plugin_settings The array provided by the plugin. The array must use filters
|
||||
* from the DATA_FILTER_DEFAULTS list as keys.
|
||||
*/
|
||||
public function add_settings_list( $plugin_settings = array() ) {
|
||||
if ( empty( $plugin_settings ) || ! is_array( $plugin_settings ) ) {
|
||||
/*
|
||||
* No custom plugin settings, so use defaults for everything and bail early.
|
||||
*/
|
||||
$this->set_all_defaults();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_filters_custom_settings_and_hooks( $plugin_settings );
|
||||
|
||||
if ( ! did_action( 'jetpack_sync_add_required_data_settings' ) ) {
|
||||
$this->add_required_settings();
|
||||
/**
|
||||
* Fires when the required settings have been adding to the static
|
||||
* data_settings array.
|
||||
*
|
||||
* @since 1.29.2
|
||||
*
|
||||
* @module sync
|
||||
*/
|
||||
do_action( 'jetpack_sync_add_required_data_settings' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default values for sync modules and all sync data filters.
|
||||
*/
|
||||
private function set_all_defaults() {
|
||||
$this->add_sync_filter_setting( self::MODULES_FILTER_NAME, Modules::DEFAULT_SYNC_MODULES );
|
||||
|
||||
foreach ( array_keys( Default_Filter_Settings::DATA_FILTER_DEFAULTS ) as $filter ) {
|
||||
$this->add_sync_filter_setting( $filter, $this->get_default_setting_for_filter( $filter ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default settings for the given filter.
|
||||
*
|
||||
* @param string $filter The filter name.
|
||||
*
|
||||
* @return array The filter's default settings array.
|
||||
*/
|
||||
private function get_default_setting_for_filter( $filter ) {
|
||||
if ( self::MODULES_FILTER_NAME === $filter ) {
|
||||
return Modules::DEFAULT_SYNC_MODULES;
|
||||
}
|
||||
|
||||
return ( new Default_Filter_Settings() )->get_default_settings( $filter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the custom settings and sets up the necessary filter hooks.
|
||||
*
|
||||
* @param array $filters_settings The custom settings.
|
||||
*/
|
||||
private function add_filters_custom_settings_and_hooks( $filters_settings ) {
|
||||
if ( isset( $filters_settings[ self::MODULES_FILTER_NAME ] ) && is_array( $filters_settings[ self::MODULES_FILTER_NAME ] ) ) {
|
||||
$this->add_custom_filter_setting( self::MODULES_FILTER_NAME, $filters_settings[ self::MODULES_FILTER_NAME ] );
|
||||
$enabled_modules = $filters_settings[ self::MODULES_FILTER_NAME ];
|
||||
} else {
|
||||
$this->add_sync_filter_setting( self::MODULES_FILTER_NAME, Modules::DEFAULT_SYNC_MODULES );
|
||||
$enabled_modules = Modules::DEFAULT_SYNC_MODULES;
|
||||
}
|
||||
|
||||
$all_modules = Modules::DEFAULT_SYNC_MODULES;
|
||||
|
||||
foreach ( $all_modules as $module ) {
|
||||
if ( in_array( $module, $enabled_modules, true ) || in_array( $module, self::MUST_SYNC_DATA_SETTINGS['jetpack_sync_modules'], true ) ) {
|
||||
$this->add_filters_for_enabled_module( $module, $filters_settings );
|
||||
} else {
|
||||
$this->add_filters_for_disabled_module( $module );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters for the provided enabled module. If the settings provided custom filter settings
|
||||
* for the module's filters, those are used. Otherwise, the filter's default settings are used.
|
||||
*
|
||||
* @param string $module The module name.
|
||||
* @param array $filters_settings The settings for the filters.
|
||||
*/
|
||||
private function add_filters_for_enabled_module( $module, $filters_settings ) {
|
||||
$module_mapping = self::MODULE_FILTER_MAPPING;
|
||||
$filters_for_module = isset( $module_mapping[ $module ] ) ? $module_mapping[ $module ] : array();
|
||||
|
||||
foreach ( $filters_for_module as $filter ) {
|
||||
if ( isset( $filters_settings[ $filter ] ) ) {
|
||||
$this->add_custom_filter_setting( $filter, $filters_settings[ $filter ] );
|
||||
} else {
|
||||
$this->add_sync_filter_setting( $filter, $this->get_default_setting_for_filter( $filter ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters for the provided disabled module. The disabled module's associated filter settings are
|
||||
* set to an empty array.
|
||||
*
|
||||
* @param string $module The module name.
|
||||
*/
|
||||
private function add_filters_for_disabled_module( $module ) {
|
||||
$module_mapping = self::MODULE_FILTER_MAPPING;
|
||||
$filters_for_module = isset( $module_mapping[ $module ] ) ? $module_mapping[ $module ] : array();
|
||||
|
||||
foreach ( $filters_for_module as $filter ) {
|
||||
$this->add_custom_filter_setting( $filter, array() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided custom setting for a filter. If the filter setting isn't valid, the default
|
||||
* value is used.
|
||||
*
|
||||
* If the filter's hook hasn't already been set up, it gets set up.
|
||||
*
|
||||
* @param string $filter The filter.
|
||||
* @param array $setting The filter setting.
|
||||
*/
|
||||
private function add_custom_filter_setting( $filter, $setting ) {
|
||||
if ( ! $this->is_valid_filter_setting( $filter, $setting ) ) {
|
||||
/*
|
||||
* The provided setting isn't valid, so use the default for this filter.
|
||||
* We're using the default values so there's no need to set the filter hook.
|
||||
*/
|
||||
$this->add_sync_filter_setting( $filter, $this->get_default_setting_for_filter( $filter ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( static::$set_filter_hooks[ $filter ] ) ) {
|
||||
// First time a custom modules setting is provided, so set the filter hook.
|
||||
add_filter( $filter, array( $this, 'sync_data_filter_hook' ) );
|
||||
static::$set_filter_hooks[ $filter ] = 1;
|
||||
}
|
||||
|
||||
$this->add_sync_filter_setting( $filter, $setting );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the filter setting is valid. The setting array is in the correct format (associative or indexed).
|
||||
*
|
||||
* @param string $filter The filter to check.
|
||||
* @param array $filter_settings The filter settings.
|
||||
*
|
||||
* @return bool Whether the filter settings can be used.
|
||||
*/
|
||||
private function is_valid_filter_setting( $filter, $filter_settings ) {
|
||||
if ( ! is_array( $filter_settings ) ) {
|
||||
// The settings for each filter must be an array.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( empty( $filter_settings ) ) {
|
||||
// Empty settings are allowed.
|
||||
return true;
|
||||
}
|
||||
|
||||
$indexed_array = isset( $filter_settings[0] );
|
||||
if ( in_array( $filter, Default_Filter_Settings::ASSOCIATIVE_FILTERS, true ) && ! $indexed_array ) {
|
||||
return true;
|
||||
} elseif ( ! in_array( $filter, Default_Filter_Settings::ASSOCIATIVE_FILTERS, true ) && $indexed_array ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the data settings that are always required for every plugin that uses Sync.
|
||||
*/
|
||||
private function add_required_settings() {
|
||||
foreach ( self::MUST_SYNC_DATA_SETTINGS as $filter => $setting ) {
|
||||
// If the corresponding setting is already set and matches the default one, no need to proceed.
|
||||
if ( isset( static::$data_settings[ $filter ] ) && static::$data_settings[ $filter ] === $this->get_default_setting_for_filter( $filter ) ) {
|
||||
continue;
|
||||
}
|
||||
$this->add_custom_filter_setting( $filter, $setting );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided data setting for the provided filter.
|
||||
*
|
||||
* @param string $filter The filter name.
|
||||
* @param array $value The data setting.
|
||||
*/
|
||||
private function add_sync_filter_setting( $filter, $value ) {
|
||||
if ( ! isset( static::$data_settings[ $filter ] ) ) {
|
||||
static::$data_settings[ $filter ] = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( in_array( $filter, Default_Filter_Settings::ASSOCIATIVE_FILTERS, true ) ) {
|
||||
$this->add_associative_filter_setting( $filter, $value );
|
||||
} else {
|
||||
$this->add_indexed_filter_setting( $filter, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided data setting for the provided filter. This method handles
|
||||
* adding settings to data that is stored as an associative array.
|
||||
*
|
||||
* @param string $filter The filter name.
|
||||
* @param array $settings The data settings.
|
||||
*/
|
||||
private function add_associative_filter_setting( $filter, $settings ) {
|
||||
foreach ( $settings as $key => $item ) {
|
||||
if ( ! array_key_exists( $key, static::$data_settings[ $filter ] ) ) {
|
||||
static::$data_settings[ $filter ][ $key ] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided data setting for the provided filter. This method handles
|
||||
* adding settings to data that is stored as an indexed array.
|
||||
*
|
||||
* @param string $filter The filter name.
|
||||
* @param array $settings The data settings.
|
||||
*/
|
||||
private function add_indexed_filter_setting( $filter, $settings ) {
|
||||
static::$data_settings[ $filter ] = array_unique(
|
||||
array_merge(
|
||||
static::$data_settings[ $filter ],
|
||||
$settings
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback function added to the sync data filters. Combines the list in the $data_settings property
|
||||
* with any non-default values from the received array.
|
||||
*
|
||||
* @param array $filtered_values The data revieved from the filter.
|
||||
*
|
||||
* @return array The data settings for the filter.
|
||||
*/
|
||||
public function sync_data_filter_hook( $filtered_values ) {
|
||||
if ( ! is_array( $filtered_values ) ) {
|
||||
// Something is wrong with the input, so set it to an empty array.
|
||||
$filtered_values = array();
|
||||
}
|
||||
|
||||
$current_filter = current_filter();
|
||||
|
||||
if ( ! isset( static::$data_settings[ $current_filter ] ) ) {
|
||||
return $filtered_values;
|
||||
}
|
||||
|
||||
if ( in_array( $current_filter, Default_Filter_Settings::ASSOCIATIVE_FILTERS, true ) ) {
|
||||
$extra_filters = array_diff_key( $filtered_values, $this->get_default_setting_for_filter( $current_filter ) );
|
||||
$this->add_associative_filter_setting( $current_filter, $extra_filters );
|
||||
return static::$data_settings[ $current_filter ];
|
||||
}
|
||||
|
||||
$extra_filters = array_diff( $filtered_values, $this->get_default_setting_for_filter( $current_filter ) );
|
||||
$this->add_indexed_filter_setting( $current_filter, $extra_filters );
|
||||
return static::$data_settings[ $current_filter ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the $data_settings property to an empty array. This is useful for testing.
|
||||
*/
|
||||
public function empty_data_settings_and_hooks() {
|
||||
static::$data_settings = array();
|
||||
static::$set_filter_hooks = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the $data_settings property.
|
||||
*
|
||||
* @return array The data_settings property.
|
||||
*/
|
||||
public function get_data_settings() {
|
||||
return static::$data_settings;
|
||||
}
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
/**
|
||||
* Dedicated Sender.
|
||||
*
|
||||
* The class is responsible for spawning dedicated Sync requests.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use WP_Error;
|
||||
/**
|
||||
* Class to manage Sync spawning.
|
||||
* The purpose of this class is to provide the means to unblock Sync
|
||||
* from running in the shutdown hook of regular requests by spawning a
|
||||
* dedicated Sync request instead which will trigger Sync to run.
|
||||
*/
|
||||
class Dedicated_Sender {
|
||||
|
||||
/**
|
||||
* The transient name for storing the response code
|
||||
* after spawning a dedicated sync test request.
|
||||
*/
|
||||
const DEDICATED_SYNC_CHECK_TRANSIENT = 'jetpack_sync_dedicated_sync_spawn_check';
|
||||
|
||||
/**
|
||||
* Validation string to check if the endpoint is working correctly.
|
||||
*
|
||||
* This is extracted and not hardcoded, as we might want to change it in the future.
|
||||
*/
|
||||
const DEDICATED_SYNC_VALIDATION_STRING = 'DEDICATED SYNC OK';
|
||||
|
||||
/**
|
||||
* Option name to use to keep the current request lock.
|
||||
*
|
||||
* The option format is `microtime(true)`.
|
||||
*/
|
||||
const DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME = 'jetpack_sync_dedicated_spawn_lock';
|
||||
|
||||
/**
|
||||
* What's the timeout for the request lock in seconds.
|
||||
*
|
||||
* 5 seconds as default value seems sane, but we might want to adjust that in the future.
|
||||
*/
|
||||
const DEDICATED_SYNC_REQUEST_LOCK_TIMEOUT = 5;
|
||||
|
||||
/**
|
||||
* The query parameter name to use when passing the current lock id.
|
||||
*/
|
||||
const DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME = 'request_lock_id';
|
||||
|
||||
/**
|
||||
* Filter a URL to check if Dedicated Sync is enabled.
|
||||
* We need to remove slashes and then run it through `urldecode` as sometimes the
|
||||
* URL is in an encoded form, depending on server configuration.
|
||||
*
|
||||
* @param string $url The URL to filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prepare_url_for_dedicated_request_check( $url ) {
|
||||
return urldecode( $url );
|
||||
}
|
||||
/**
|
||||
* Check if this request should trigger Sync to run.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean True if this is a 'jetpack/v4/sync/spawn-sync', false otherwise.
|
||||
*/
|
||||
public static function is_dedicated_sync_request() {
|
||||
/**
|
||||
* Check $_SERVER['REQUEST_URI'] first, to see if we're in the right context.
|
||||
* This is done to make sure we can hook in very early in the initialization of WordPress to
|
||||
* be able to send sync requests to the backend as fast as possible, without needing to continue
|
||||
* loading things for the request.
|
||||
*/
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended
|
||||
$check_url = self::prepare_url_for_dedicated_request_check( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
if ( strpos( $check_url, 'jetpack/v4/sync/spawn-sync' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the above check failed, we might have an issue with detecting calls to the REST endpoint early on.
|
||||
* Sometimes, like when permalinks are disabled, the REST path is sent via the `rest_route` GET parameter.
|
||||
* We want to check it too, to make sure we managed to cover more cases and be more certain we actually
|
||||
* catch calls to the endpoint.
|
||||
*/
|
||||
if ( ! isset( $_GET['rest_route'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended
|
||||
$check_url = self::prepare_url_for_dedicated_request_check( wp_unslash( $_GET['rest_route'] ) );
|
||||
if ( strpos( $check_url, 'jetpack/v4/sync/spawn-sync' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to run Sync for a certain sync queue
|
||||
* through HTTP request that doesn't halt page loading.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue $queue Queue object.
|
||||
*
|
||||
* @return boolean|WP_Error True if spawned, WP_Error otherwise.
|
||||
*/
|
||||
public static function spawn_sync( $queue ) {
|
||||
if ( ! Settings::is_dedicated_sync_enabled() ) {
|
||||
return new WP_Error( 'dedicated_sync_disabled', 'Dedicated Sync flow is disabled.' );
|
||||
}
|
||||
|
||||
if ( $queue->is_locked() ) {
|
||||
return new WP_Error( 'locked_queue_' . $queue->id );
|
||||
}
|
||||
|
||||
if ( $queue->size() === 0 ) {
|
||||
return new WP_Error( 'empty_queue_' . $queue->id );
|
||||
}
|
||||
|
||||
// Return early if we've gotten a retry-after header response that is not expired.
|
||||
$retry_time = get_option( Actions::RETRY_AFTER_PREFIX . $queue->id );
|
||||
if ( $retry_time && $retry_time >= microtime( true ) ) {
|
||||
return new WP_Error( 'retry_after_' . $queue->id );
|
||||
}
|
||||
|
||||
// Don't sync if we are throttled.
|
||||
$sync_next_time = Sender::get_instance()->get_next_sync_time( $queue->id );
|
||||
if ( $sync_next_time > microtime( true ) ) {
|
||||
return new WP_Error( 'sync_throttled_' . $queue->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to acquire a request lock, so we don't spawn multiple requests at the same time.
|
||||
* This should prevent cases where sites might have limits on the amount of simultaneous requests.
|
||||
*/
|
||||
$request_lock = self::try_lock_spawn_request();
|
||||
if ( ! $request_lock ) {
|
||||
return new WP_Error( 'dedicated_request_lock', 'Unable to acquire request lock' );
|
||||
}
|
||||
|
||||
$url = rest_url( 'jetpack/v4/sync/spawn-sync' );
|
||||
$url = add_query_arg( 'time', time(), $url ); // Enforce Cache busting.
|
||||
$url = add_query_arg( self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME, $request_lock, $url );
|
||||
|
||||
$args = array(
|
||||
'cookies' => $_COOKIE,
|
||||
'blocking' => false,
|
||||
'timeout' => 0.01,
|
||||
/** This filter is documented in wp-includes/class-wp-http-streams.php */
|
||||
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
||||
);
|
||||
|
||||
$result = wp_remote_get( $url, $args );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire a request lock.
|
||||
*
|
||||
* To avoid spawning multiple requests at the same time, we need to have a quick lock that will
|
||||
* allow only a single request to continue if we try to spawn multiple at the same time.
|
||||
*
|
||||
* @return false|mixed|string
|
||||
*/
|
||||
public static function try_lock_spawn_request() {
|
||||
$current_microtime = (string) microtime( true );
|
||||
|
||||
$current_lock_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
|
||||
|
||||
if ( ! empty( $current_lock_value ) ) {
|
||||
// Check if time has passed to overwrite the lock - min 5s?
|
||||
if ( is_numeric( $current_lock_value ) && ( ( $current_microtime - $current_lock_value ) < self::DEDICATED_SYNC_REQUEST_LOCK_TIMEOUT ) ) {
|
||||
// Still in previous lock, quit
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the value is not numeric (float/current time), we want to just overwrite it and continue.
|
||||
}
|
||||
|
||||
// Update. We don't want it to autoload, as we want to fetch it right before the checks.
|
||||
\Jetpack_Options::update_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, $current_microtime, false );
|
||||
// Give some time for the update to happen
|
||||
usleep( wp_rand( 1000, 3000 ) );
|
||||
|
||||
$updated_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
|
||||
|
||||
if ( $updated_value === $current_microtime ) {
|
||||
return $current_microtime;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to release the request lock.
|
||||
*
|
||||
* @param string $lock_id The request lock that's currently being held.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function try_release_lock_spawn_request( $lock_id = '' ) {
|
||||
// Try to get the lock_id from the current request if it's not supplied.
|
||||
if ( empty( $lock_id ) ) {
|
||||
$lock_id = self::get_request_lock_id_from_request();
|
||||
}
|
||||
|
||||
// If it's still not a valid lock_id, throw an error and let the lock process figure it out.
|
||||
if ( empty( $lock_id ) || ! is_numeric( $lock_id ) ) {
|
||||
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 this is the flow that has the lock, let's release it so we can spawn other requests afterwards
|
||||
if ( (string) $lock_id === $current_lock_value ) {
|
||||
\Jetpack_Options::delete_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the request lock id from the current request.
|
||||
*
|
||||
* @return array|string|string[]|null
|
||||
*/
|
||||
public static function get_request_lock_id_from_request() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET[ self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME ] ) || ! is_numeric( $_GET[ self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
return wp_unslash( $_GET[ self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Sync spawning functionality by making a request to the
|
||||
* Sync spawning endpoint and storing the result (status code) in a transient.
|
||||
*
|
||||
* @since $$next_version$$
|
||||
*
|
||||
* @return bool True if we got a successful response, false otherwise.
|
||||
*/
|
||||
public static function can_spawn_dedicated_sync_request() {
|
||||
$dedicated_sync_check_transient = self::DEDICATED_SYNC_CHECK_TRANSIENT;
|
||||
|
||||
$dedicated_sync_response_body = get_transient( $dedicated_sync_check_transient );
|
||||
|
||||
if ( false === $dedicated_sync_response_body ) {
|
||||
$url = rest_url( 'jetpack/v4/sync/spawn-sync' );
|
||||
$url = add_query_arg( 'time', time(), $url ); // Enforce Cache busting.
|
||||
$args = array(
|
||||
'cookies' => $_COOKIE,
|
||||
'timeout' => 30,
|
||||
/** This filter is documented in wp-includes/class-wp-http-streams.php */
|
||||
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
||||
);
|
||||
|
||||
$response = wp_remote_get( $url, $args );
|
||||
$dedicated_sync_response_code = wp_remote_retrieve_response_code( $response );
|
||||
$dedicated_sync_response_body = trim( wp_remote_retrieve_body( $response ) );
|
||||
|
||||
/**
|
||||
* Limit the size of the body that we save in the transient to avoid cases where an error
|
||||
* occurs and a whole generated HTML page is returned. We don't need to store the whole thing.
|
||||
*
|
||||
* The regexp check is done to make sure we can detect the string even if the body returns some additional
|
||||
* output, like some caching plugins do when they try to pad the request.
|
||||
*/
|
||||
$regexp = '!' . preg_quote( self::DEDICATED_SYNC_VALIDATION_STRING, '!' ) . '!uis';
|
||||
if ( preg_match( $regexp, $dedicated_sync_response_body ) ) {
|
||||
$saved_response_body = self::DEDICATED_SYNC_VALIDATION_STRING;
|
||||
} else {
|
||||
$saved_response_body = time();
|
||||
}
|
||||
|
||||
set_transient( $dedicated_sync_check_transient, $saved_response_body, HOUR_IN_SECONDS );
|
||||
|
||||
// Send a bit more information to WordPress.com to help debugging issues.
|
||||
if ( $saved_response_body !== self::DEDICATED_SYNC_VALIDATION_STRING ) {
|
||||
$data = array(
|
||||
'timestamp' => microtime( true ),
|
||||
'response_code' => $dedicated_sync_response_code,
|
||||
'response_body' => $dedicated_sync_response_body,
|
||||
|
||||
// Send the flow type that was attempted.
|
||||
'sync_flow_type' => 'dedicated',
|
||||
);
|
||||
|
||||
$sender = Sender::get_instance();
|
||||
|
||||
$sender->send_action( 'jetpack_sync_flow_error_enable', $data );
|
||||
}
|
||||
}
|
||||
|
||||
return self::DEDICATED_SYNC_VALIDATION_STRING === $dedicated_sync_response_body;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* The Default Filter Settings class.
|
||||
*
|
||||
* This class provides the default whitelist values for the Sync data filters.
|
||||
* See the DATA_FILTER_DEFAULTS constant for the list of filters.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* The Default_Filter_Settings class
|
||||
*/
|
||||
class Default_Filter_Settings {
|
||||
|
||||
/**
|
||||
* The class that contains the default values of the filters.
|
||||
*/
|
||||
const DEFAULT_FILTER_CLASS = 'Automattic\Jetpack\Sync\Defaults';
|
||||
|
||||
/**
|
||||
* A map of each Sync filter name to the associated property name in the Defaults class.
|
||||
*/
|
||||
const DATA_FILTER_DEFAULTS = array(
|
||||
'jetpack_sync_options_whitelist' => 'default_options_whitelist',
|
||||
'jetpack_sync_options_contentless' => 'default_options_contentless',
|
||||
'jetpack_sync_constants_whitelist' => 'default_constants_whitelist',
|
||||
'jetpack_sync_callable_whitelist' => 'default_callable_whitelist',
|
||||
'jetpack_sync_multisite_callable_whitelist' => 'default_multisite_callable_whitelist',
|
||||
'jetpack_sync_post_meta_whitelist' => 'post_meta_whitelist',
|
||||
'jetpack_sync_comment_meta_whitelist' => 'comment_meta_whitelist',
|
||||
'jetpack_sync_capabilities_whitelist' => 'default_capabilities_whitelist',
|
||||
'jetpack_sync_known_importers' => 'default_known_importers',
|
||||
);
|
||||
|
||||
/**
|
||||
* The data associated with these filters are stored as associative arrays.
|
||||
* (All other filters store data as indexed arrays.)
|
||||
*/
|
||||
const ASSOCIATIVE_FILTERS = array(
|
||||
'jetpack_sync_callable_whitelist',
|
||||
'jetpack_sync_multisite_callable_whitelist',
|
||||
'jetpack_sync_known_importers',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the default data settings list for the provided filter.
|
||||
*
|
||||
* @param string $filter The filter name.
|
||||
*
|
||||
* @return array|false The default list of data settings. Returns false if the provided
|
||||
* filter doesn't not have an array of default settings.
|
||||
*/
|
||||
public function get_default_settings( $filter ) {
|
||||
if ( ! is_string( $filter ) || ! array_key_exists( $filter, self::DATA_FILTER_DEFAULTS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$property = self::DATA_FILTER_DEFAULTS[ $filter ];
|
||||
$class = self::DEFAULT_FILTER_CLASS;
|
||||
return $class::$$property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the default values for all of the filters shown
|
||||
* in DATA_FILTER_DEFAULTS.
|
||||
*
|
||||
* @return array The array containing all sync data filters and their default values.
|
||||
*/
|
||||
public function get_all_filters_default_settings() {
|
||||
$defaults = array();
|
||||
|
||||
foreach ( self::DATA_FILTER_DEFAULTS as $filter => $default_location ) {
|
||||
$defaults[ $filter ] = $this->get_default_settings( $filter );
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,675 @@
|
||||
<?php
|
||||
/**
|
||||
* Utility functions to generate data synced to wpcom
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Modules as Jetpack_Modules;
|
||||
|
||||
/**
|
||||
* Utility functions to generate data synced to wpcom
|
||||
*/
|
||||
class Functions {
|
||||
const HTTPS_CHECK_OPTION_PREFIX = 'jetpack_sync_https_history_';
|
||||
const HTTPS_CHECK_HISTORY = 5;
|
||||
|
||||
/**
|
||||
* Return array of Jetpack modules.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_modules() {
|
||||
if ( defined( 'JETPACK__PLUGIN_DIR' ) ) {
|
||||
require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php';
|
||||
|
||||
return \Jetpack_Admin::init()->get_modules();
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of taxonomies registered on the site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_taxonomies() {
|
||||
global $wp_taxonomies;
|
||||
$wp_taxonomies_without_callbacks = array();
|
||||
foreach ( $wp_taxonomies as $taxonomy_name => $taxonomy ) {
|
||||
$sanitized_taxonomy = self::sanitize_taxonomy( $taxonomy );
|
||||
if ( ! empty( $sanitized_taxonomy ) ) {
|
||||
$wp_taxonomies_without_callbacks[ $taxonomy_name ] = $sanitized_taxonomy;
|
||||
}
|
||||
}
|
||||
return $wp_taxonomies_without_callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of registered shortcodes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_shortcodes() {
|
||||
global $shortcode_tags;
|
||||
return array_keys( $shortcode_tags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any callback data since we will not be able to process it on our side anyways.
|
||||
*
|
||||
* @param \WP_Taxonomy $taxonomy \WP_Taxonomy item.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function sanitize_taxonomy( $taxonomy ) {
|
||||
|
||||
// Lets clone the taxonomy object instead of modifing the global one.
|
||||
$cloned_taxonomy = json_decode( wp_json_encode( $taxonomy ) );
|
||||
|
||||
// recursive taxonomies are no fun.
|
||||
if ( $cloned_taxonomy === null ) {
|
||||
return null;
|
||||
}
|
||||
// Remove any meta_box_cb if they are not the default wp ones.
|
||||
if ( isset( $cloned_taxonomy->meta_box_cb ) &&
|
||||
! in_array( $cloned_taxonomy->meta_box_cb, array( 'post_tags_meta_box', 'post_categories_meta_box' ), true ) ) {
|
||||
$cloned_taxonomy->meta_box_cb = null;
|
||||
}
|
||||
// Remove update call back.
|
||||
if ( isset( $cloned_taxonomy->update_count_callback ) &&
|
||||
$cloned_taxonomy->update_count_callback !== null ) {
|
||||
$cloned_taxonomy->update_count_callback = null;
|
||||
}
|
||||
// Remove rest_controller_class if it something other then the default.
|
||||
if ( isset( $cloned_taxonomy->rest_controller_class ) &&
|
||||
'WP_REST_Terms_Controller' !== $cloned_taxonomy->rest_controller_class ) {
|
||||
$cloned_taxonomy->rest_controller_class = null;
|
||||
}
|
||||
return $cloned_taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of registered post types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_post_types() {
|
||||
global $wp_post_types;
|
||||
|
||||
$post_types_without_callbacks = array();
|
||||
foreach ( $wp_post_types as $post_type_name => $post_type ) {
|
||||
$sanitized_post_type = self::sanitize_post_type( $post_type );
|
||||
if ( ! empty( $sanitized_post_type ) ) {
|
||||
$post_types_without_callbacks[ $post_type_name ] = $sanitized_post_type;
|
||||
}
|
||||
}
|
||||
return $post_types_without_callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes by cloning post type object.
|
||||
*
|
||||
* @param object $post_type \WP_Post_Type.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function sanitize_post_type( $post_type ) {
|
||||
// Lets clone the post type object instead of modifing the global one.
|
||||
$sanitized_post_type = array();
|
||||
foreach ( Defaults::$default_post_type_attributes as $attribute_key => $default_value ) {
|
||||
if ( isset( $post_type->{ $attribute_key } ) ) {
|
||||
$sanitized_post_type[ $attribute_key ] = $post_type->{ $attribute_key };
|
||||
}
|
||||
}
|
||||
return (object) $sanitized_post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about a synced post type.
|
||||
*
|
||||
* @param array $sanitized_post_type Array of args used in constructing \WP_Post_Type.
|
||||
* @param string $post_type Post type name.
|
||||
*
|
||||
* @return object \WP_Post_Type
|
||||
*/
|
||||
public static function expand_synced_post_type( $sanitized_post_type, $post_type ) {
|
||||
$post_type = sanitize_key( $post_type );
|
||||
$post_type_object = new \WP_Post_Type( $post_type, $sanitized_post_type );
|
||||
$post_type_object->add_supports();
|
||||
$post_type_object->add_rewrite_rules();
|
||||
$post_type_object->add_hooks();
|
||||
$post_type_object->register_taxonomies();
|
||||
return (object) $post_type_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns site's post_type_features.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_post_type_features() {
|
||||
global $_wp_post_type_features;
|
||||
|
||||
return $_wp_post_type_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return hosting provider.
|
||||
*
|
||||
* Uses a set of known constants, classes, or functions to help determine the hosting platform.
|
||||
*
|
||||
* @return string Hosting provider.
|
||||
*/
|
||||
public static function get_hosting_provider() {
|
||||
$hosting_provider_detection_methods = array(
|
||||
'get_hosting_provider_by_known_constant',
|
||||
'get_hosting_provider_by_known_class',
|
||||
'get_hosting_provider_by_known_function',
|
||||
);
|
||||
|
||||
$functions = new Functions();
|
||||
foreach ( $hosting_provider_detection_methods as $method ) {
|
||||
$hosting_provider = call_user_func( array( $functions, $method ) );
|
||||
if ( false !== $hosting_provider ) {
|
||||
return $hosting_provider;
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hosting provider using a set of known constants.
|
||||
*
|
||||
* @return mixed A host identifier string or false.
|
||||
*/
|
||||
public function get_hosting_provider_by_known_constant() {
|
||||
$hosting_provider_constants = array(
|
||||
'GD_SYSTEM_PLUGIN_DIR' => 'gd-managed-wp',
|
||||
'MM_BASE_DIR' => 'bh',
|
||||
'PAGELYBIN' => 'pagely',
|
||||
'KINSTAMU_VERSION' => 'kinsta',
|
||||
'FLYWHEEL_CONFIG_DIR' => 'flywheel',
|
||||
'IS_PRESSABLE' => 'pressable',
|
||||
'VIP_GO_ENV' => 'vip-go',
|
||||
);
|
||||
|
||||
foreach ( $hosting_provider_constants as $constant => $constant_value ) {
|
||||
if ( Constants::is_defined( $constant ) ) {
|
||||
if ( 'VIP_GO_ENV' === $constant && false === Constants::get_constant( 'VIP_GO_ENV' ) ) {
|
||||
continue;
|
||||
}
|
||||
return $constant_value;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hosting provider using a set of known classes.
|
||||
*
|
||||
* @return mixed A host identifier string or false.
|
||||
*/
|
||||
public function get_hosting_provider_by_known_class() {
|
||||
$hosting_provider = false;
|
||||
|
||||
switch ( true ) {
|
||||
case ( class_exists( '\\WPaaS\\Plugin' ) ):
|
||||
$hosting_provider = 'gd-managed-wp';
|
||||
break;
|
||||
}
|
||||
|
||||
return $hosting_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hosting provider using a set of known functions.
|
||||
*
|
||||
* @return mixed A host identifier string or false.
|
||||
*/
|
||||
public function get_hosting_provider_by_known_function() {
|
||||
$hosting_provider = false;
|
||||
|
||||
switch ( true ) {
|
||||
case ( function_exists( 'is_wpe' ) || function_exists( 'is_wpe_snapshot' ) ):
|
||||
$hosting_provider = 'wpe';
|
||||
break;
|
||||
}
|
||||
|
||||
return $hosting_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of allowed REST API post types.
|
||||
*
|
||||
* @return array Array of allowed post types.
|
||||
*/
|
||||
public static function rest_api_allowed_post_types() {
|
||||
/** This filter is already documented in class.json-api-endpoints.php */
|
||||
return apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of allowed REST API public metadata.
|
||||
*
|
||||
* @return array Array of allowed metadata.
|
||||
*/
|
||||
public static function rest_api_allowed_public_metadata() {
|
||||
/**
|
||||
* Filters the meta keys accessible by the REST API.
|
||||
*
|
||||
* @see https://developer.wordpress.com/2013/04/26/custom-post-type-and-metadata-support-in-the-rest-api/
|
||||
*
|
||||
* @module json-api
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 2.2.3
|
||||
*
|
||||
* @param array $whitelisted_meta Array of metadata that is accessible by the REST API.
|
||||
*/
|
||||
return apply_filters( 'rest_api_allowed_public_metadata', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if a site is using a version control system.
|
||||
*
|
||||
* @return bool
|
||||
**/
|
||||
public static function is_version_controlled() {
|
||||
|
||||
if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
}
|
||||
$updater = new \WP_Automatic_Updater();
|
||||
|
||||
return (bool) (string) $updater->is_vcs_checkout( ABSPATH );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the site has file write access false otherwise.
|
||||
*
|
||||
* @return bool
|
||||
**/
|
||||
public static function file_system_write_access() {
|
||||
if ( ! function_exists( 'get_filesystem_method' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/template.php';
|
||||
|
||||
$filesystem_method = get_filesystem_method();
|
||||
if ( 'direct' === $filesystem_method ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
if ( ! function_exists( 'request_filesystem_credentials' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
|
||||
ob_end_clean();
|
||||
if ( $filesystem_credentials_are_stored ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that is used when getting home or siteurl values. Decides
|
||||
* whether to get the raw or filtered value.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @param string $url_type URL to get, home or siteurl.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_raw_or_filtered_url( $url_type ) {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::get_raw_or_filtered_url' );
|
||||
return Urls::get_raw_or_filtered_url( $url_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the escaped home_url.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function home_url() {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::home_url' );
|
||||
return Urls::home_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the escaped siteurl.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function site_url() {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::site_url' );
|
||||
return Urls::site_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return main site URL with a normalized protocol.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function main_network_site_url() {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::main_network_site_url' );
|
||||
return Urls::main_network_site_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return main site WordPress.com site ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function main_network_site_wpcom_id() {
|
||||
/**
|
||||
* Return the current site WPCOM ID for single site installs
|
||||
*/
|
||||
if ( ! is_multisite() ) {
|
||||
return \Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the main network site WPCOM ID for multi-site installs
|
||||
*/
|
||||
$current_network = get_network();
|
||||
switch_to_blog( $current_network->blog_id );
|
||||
$wpcom_blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
restore_current_blog();
|
||||
return $wpcom_blog_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL with a normalized protocol.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @param callable $callable Function to retrieve URL option.
|
||||
* @param string $new_value URL Protocol to set URLs to.
|
||||
* @return string Normalized URL.
|
||||
*/
|
||||
public static function get_protocol_normalized_url( $callable, $new_value ) {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::get_protocol_normalized_url' );
|
||||
return Urls::get_protocol_normalized_url( $callable, $new_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL from option or PHP constant.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @param string $option_name (e.g. 'home').
|
||||
*
|
||||
* @return mixed|null URL.
|
||||
*/
|
||||
public static function get_raw_url( $option_name ) {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::get_raw_url' );
|
||||
return Urls::get_raw_url( $option_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize domains by removing www unless declared in the site's option.
|
||||
*
|
||||
* @deprecated 1.23.1
|
||||
*
|
||||
* @param string $option Option value from the site.
|
||||
* @param callable $url_function Function retrieving the URL to normalize.
|
||||
* @return mixed|string URL.
|
||||
*/
|
||||
public static function normalize_www_in_url( $option, $url_function ) {
|
||||
_deprecated_function( __METHOD__, '1.23.1', '\\Automattic\\Jetpack\\Connection\\Urls::normalize_www_in_url' );
|
||||
return Urls::normalize_www_in_url( $option, $url_function );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filtered value of get_plugins.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public static function get_plugins() {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
|
||||
return apply_filters( 'all_plugins', get_plugins() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom action link tags that the plugin is using
|
||||
* Ref: https://codex.wordpress.org/Plugin_API/Filter_Reference/plugin_action_links_(plugin_file_name)
|
||||
*
|
||||
* @param string $plugin_file_singular Particular plugin.
|
||||
* @return array of plugin action links (key: link name value: url)
|
||||
*/
|
||||
public static function get_plugins_action_links( $plugin_file_singular = null ) {
|
||||
// Some sites may have DOM disabled in PHP fail early.
|
||||
if ( ! class_exists( 'DOMDocument' ) ) {
|
||||
return array();
|
||||
}
|
||||
$plugins_action_links = get_option( 'jetpack_plugin_api_action_links', array() );
|
||||
if ( ! empty( $plugins_action_links ) ) {
|
||||
if ( $plugin_file_singular === null ) {
|
||||
return $plugins_action_links;
|
||||
}
|
||||
return ( isset( $plugins_action_links[ $plugin_file_singular ] ) ? $plugins_action_links[ $plugin_file_singular ] : null );
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the WP version as defined in the $wp_version global.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wp_version() {
|
||||
global $wp_version;
|
||||
return $wp_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return site icon url used on the site.
|
||||
*
|
||||
* @param int $size Size of requested icon in pixels.
|
||||
* @return mixed|string|void
|
||||
*/
|
||||
public static function site_icon_url( $size = 512 ) {
|
||||
$site_icon = get_site_icon_url( $size );
|
||||
return $site_icon ? $site_icon : get_option( 'jetpack_site_icon_url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return roles registered on the site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function roles() {
|
||||
$wp_roles = wp_roles();
|
||||
return $wp_roles->roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine time zone from WordPress' options "timezone_string"
|
||||
* and "gmt_offset".
|
||||
*
|
||||
* 1. Check if `timezone_string` is set and return it.
|
||||
* 2. Check if `gmt_offset` is set, formats UTC-offset from it and return it.
|
||||
* 3. Default to "UTC+0" if nothing is set.
|
||||
*
|
||||
* Note: This function is specifically not using wp_timezone() to keep consistency with
|
||||
* the existing formatting of the timezone string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_timezone() {
|
||||
$timezone_string = get_option( 'timezone_string' );
|
||||
|
||||
if ( ! empty( $timezone_string ) ) {
|
||||
return str_replace( '_', ' ', $timezone_string );
|
||||
}
|
||||
|
||||
$gmt_offset = get_option( 'gmt_offset', 0 );
|
||||
|
||||
$formatted_gmt_offset = sprintf( '%+g', (float) $gmt_offset );
|
||||
|
||||
$formatted_gmt_offset = str_replace(
|
||||
array( '.25', '.5', '.75' ),
|
||||
array( ':15', ':30', ':45' ),
|
||||
(string) $formatted_gmt_offset
|
||||
);
|
||||
|
||||
/* translators: %s is UTC offset, e.g. "+1" */
|
||||
return sprintf( __( 'UTC%s', 'jetpack-sync' ), $formatted_gmt_offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of paused themes.
|
||||
*
|
||||
* @return array|bool Array of paused themes or false if unsupported.
|
||||
*/
|
||||
public static function get_paused_themes() {
|
||||
$paused_themes = wp_paused_themes();
|
||||
return $paused_themes->get_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of paused plugins.
|
||||
*
|
||||
* @return array|bool Array of paused plugins or false if unsupported.
|
||||
*/
|
||||
public static function get_paused_plugins() {
|
||||
$paused_plugins = wp_paused_plugins();
|
||||
return $paused_plugins->get_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the theme's supported features.
|
||||
* Used for syncing the supported feature that we care about.
|
||||
*
|
||||
* @return array List of features that the theme supports.
|
||||
*/
|
||||
public static function get_theme_support() {
|
||||
global $_wp_theme_features;
|
||||
|
||||
$theme_support = array();
|
||||
foreach ( Defaults::$default_theme_support_whitelist as $theme_feature ) {
|
||||
$has_support = current_theme_supports( $theme_feature );
|
||||
if ( $has_support ) {
|
||||
$theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ];
|
||||
}
|
||||
}
|
||||
|
||||
return $theme_support;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the current theme is a Full Site Editing 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps data in a way so that we can distinguish between objects and array and also prevent object recursion.
|
||||
*
|
||||
* @since 1.21.0
|
||||
*
|
||||
* @param array|obj $any Source data to be cleaned up.
|
||||
* @param array $seen_nodes Built array of nodes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function json_wrap( &$any, $seen_nodes = array() ) {
|
||||
if ( is_object( $any ) ) {
|
||||
$input = get_object_vars( $any );
|
||||
$input['__o'] = 1;
|
||||
} else {
|
||||
$input = &$any;
|
||||
}
|
||||
|
||||
if ( is_array( $input ) ) {
|
||||
$seen_nodes[] = &$any;
|
||||
|
||||
$return = array();
|
||||
|
||||
foreach ( $input as $k => &$v ) {
|
||||
if ( ( is_array( $v ) || is_object( $v ) ) ) {
|
||||
if ( in_array( $v, $seen_nodes, true ) ) {
|
||||
continue;
|
||||
}
|
||||
$return[ $k ] = self::json_wrap( $v, $seen_nodes );
|
||||
} else {
|
||||
$return[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
return $any;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of installed themes
|
||||
*
|
||||
* @since 1.31.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_themes() {
|
||||
$current_stylesheet = get_stylesheet();
|
||||
$installed_themes = wp_get_themes();
|
||||
$synced_headers = array( 'Name', 'ThemeURI', 'Author', 'Version', 'Template', 'Status', 'TextDomain', 'RequiresWP', 'RequiresPHP' );
|
||||
$themes = array();
|
||||
foreach ( $installed_themes as $stylesheet => $theme ) {
|
||||
$themes[ $stylesheet ] = array();
|
||||
foreach ( $synced_headers as $header ) {
|
||||
$themes[ $stylesheet ][ $header ] = $theme->get( $header );
|
||||
}
|
||||
$themes[ $stylesheet ]['active'] = $stylesheet === $current_stylesheet;
|
||||
if ( method_exists( $theme, 'is_block_theme' ) ) {
|
||||
$themes[ $stylesheet ]['is_block_theme'] = $theme->is_block_theme();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Filters the output of Sync's get_theme callable
|
||||
*
|
||||
* @since 1.31.0
|
||||
*
|
||||
* @param array $themes The list of installed themes formatted in an array with a collection of information extracted from the Theme's headers
|
||||
*/
|
||||
return apply_filters( 'jetpack_sync_get_themes_callable', $themes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of active Jetpack modules.
|
||||
*
|
||||
* @since $$next_version$$
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_active_modules() {
|
||||
return ( new Jetpack_Modules() )->get_active();
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* Health class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* Health class.
|
||||
*/
|
||||
class Health {
|
||||
|
||||
/**
|
||||
* Prefix of the blog lock transient.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_OPTION = 'sync_health_status';
|
||||
|
||||
/**
|
||||
* Status key in option array.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION_STATUS_KEY = 'status';
|
||||
|
||||
/**
|
||||
* Timestamp key in option array.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OPTION_TIMESTAMP_KEY = 'timestamp';
|
||||
|
||||
/**
|
||||
* Unknown status code.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* Disabled status code.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_DISABLED = 'disabled';
|
||||
|
||||
/**
|
||||
* Out of sync status code.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_OUT_OF_SYNC = 'out_of_sync';
|
||||
|
||||
/**
|
||||
* In sync status code.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_IN_SYNC = 'in_sync';
|
||||
|
||||
/**
|
||||
* If sync is active, Health-related hooks will be initialized after plugins are loaded.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'jetpack_full_sync_end', array( __ClASS__, 'full_sync_end_update_status' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets health status code.
|
||||
*
|
||||
* @return string Sync Health Status
|
||||
*/
|
||||
public static function get_status() {
|
||||
$status = \Jetpack_Options::get_option( self::STATUS_OPTION );
|
||||
|
||||
if ( false === $status || ! is_array( $status ) || empty( $status[ self::OPTION_STATUS_KEY ] ) ) {
|
||||
return self::STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
switch ( $status[ self::OPTION_STATUS_KEY ] ) {
|
||||
case self::STATUS_DISABLED:
|
||||
case self::STATUS_OUT_OF_SYNC:
|
||||
case self::STATUS_IN_SYNC:
|
||||
return $status[ self::OPTION_STATUS_KEY ];
|
||||
default:
|
||||
return self::STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When the Jetpack plugin is upgraded, set status to disabled if sync is not enabled,
|
||||
* or to unknown, if the status has never been set before.
|
||||
*/
|
||||
public static function on_jetpack_upgraded() {
|
||||
if ( ! Settings::is_sync_enabled() ) {
|
||||
self::update_status( self::STATUS_DISABLED );
|
||||
return;
|
||||
}
|
||||
if ( false === self::is_status_defined() ) {
|
||||
self::update_status( self::STATUS_UNKNOWN );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the Jetpack plugin is activated, set status to disabled if sync is not enabled,
|
||||
* or to unknown.
|
||||
*/
|
||||
public static function on_jetpack_activated() {
|
||||
if ( ! Settings::is_sync_enabled() ) {
|
||||
self::update_status( self::STATUS_DISABLED );
|
||||
return;
|
||||
}
|
||||
self::update_status( self::STATUS_UNKNOWN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates sync health status with either a valid status, or an unknown status.
|
||||
*
|
||||
* @param string $status Sync Status.
|
||||
*
|
||||
* @return bool True if an update occoured, or false if the status didn't change.
|
||||
*/
|
||||
public static function update_status( $status ) {
|
||||
if ( self::get_status() === $status ) {
|
||||
return false;
|
||||
}
|
||||
// Default Status Option.
|
||||
$new_status = array(
|
||||
self::OPTION_STATUS_KEY => self::STATUS_UNKNOWN,
|
||||
self::OPTION_TIMESTAMP_KEY => microtime( true ),
|
||||
);
|
||||
|
||||
switch ( $status ) {
|
||||
case self::STATUS_DISABLED:
|
||||
case self::STATUS_OUT_OF_SYNC:
|
||||
case self::STATUS_IN_SYNC:
|
||||
$new_status[ self::OPTION_STATUS_KEY ] = $status;
|
||||
break;
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_option( self::STATUS_OPTION, $new_status );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Status has been previously set.
|
||||
*
|
||||
* @return bool is a Status defined
|
||||
*/
|
||||
public static function is_status_defined() {
|
||||
$status = \Jetpack_Options::get_option( self::STATUS_OPTION );
|
||||
|
||||
if ( false === $status || ! is_array( $status ) || empty( $status[ self::OPTION_STATUS_KEY ] ) ) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sync Status if Full Sync ended of Posts
|
||||
*
|
||||
* @param string $checksum The checksum that's currently being processed.
|
||||
* @param array $range The ranges of object types being processed.
|
||||
*/
|
||||
public static function full_sync_end_update_status( $checksum, $range ) {
|
||||
if ( isset( $range['posts'] ) ) {
|
||||
self::update_status( self::STATUS_IN_SYNC );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* An implementation of Automattic\Jetpack\Sync\Codec_Interface that uses gzip's DEFLATE
|
||||
* algorithm to compress objects serialized using json_encode.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* An implementation of Automattic\Jetpack\Sync\Codec_Interface that uses gzip's DEFLATE
|
||||
* algorithm to compress objects serialized using json_encode
|
||||
*/
|
||||
class JSON_Deflate_Array_Codec implements Codec_Interface {
|
||||
const CODEC_NAME = 'deflate-json-array';
|
||||
|
||||
/**
|
||||
* Return the name of the codec.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return self::CODEC_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an object.
|
||||
*
|
||||
* @param object $object Item to encode.
|
||||
* @return string
|
||||
*/
|
||||
public function encode( $object ) {
|
||||
return base64_encode( gzdeflate( $this->json_serialize( $object ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode compressed serialized value.
|
||||
*
|
||||
* @param string $input Item to decode.
|
||||
* @return array|mixed|object
|
||||
*/
|
||||
public function decode( $input ) {
|
||||
return $this->json_unserialize( gzinflate( base64_decode( $input ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize JSON
|
||||
*
|
||||
* @see https://gist.github.com/muhqu/820694
|
||||
*
|
||||
* @param string $any Value to serialize and wrap.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected function json_serialize( $any ) {
|
||||
return wp_json_encode( Functions::json_wrap( $any ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize JSON
|
||||
*
|
||||
* @param string $str JSON string.
|
||||
* @return array|object Unwrapped JSON.
|
||||
*/
|
||||
protected function json_unserialize( $str ) {
|
||||
return $this->json_unwrap( json_decode( $str, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a json_decode return.
|
||||
*
|
||||
* @param array|object $any json_decode object.
|
||||
* @return array|object
|
||||
*/
|
||||
private function json_unwrap( $any ) {
|
||||
if ( is_array( $any ) ) {
|
||||
foreach ( $any as $k => $v ) {
|
||||
if ( '__o' === $k ) {
|
||||
continue;
|
||||
}
|
||||
$any[ $k ] = $this->json_unwrap( $v );
|
||||
}
|
||||
|
||||
if ( isset( $any['__o'] ) ) {
|
||||
unset( $any['__o'] );
|
||||
$any = (object) $any;
|
||||
}
|
||||
}
|
||||
|
||||
return $any;
|
||||
}
|
||||
}
|
@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack's Sync Listener
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Roles;
|
||||
|
||||
/**
|
||||
* This class monitors actions and logs them to the queue to be sent.
|
||||
*/
|
||||
class Listener {
|
||||
const QUEUE_STATE_CHECK_TRANSIENT = 'jetpack_sync_last_checked_queue_state';
|
||||
const QUEUE_STATE_CHECK_TIMEOUT = 30; // 30 seconds.
|
||||
|
||||
/**
|
||||
* Sync queue.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $sync_queue;
|
||||
|
||||
/**
|
||||
* Full sync queue.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $full_sync_queue;
|
||||
|
||||
/**
|
||||
* Sync queue size limit.
|
||||
*
|
||||
* @var int size limit.
|
||||
*/
|
||||
private $sync_queue_size_limit;
|
||||
|
||||
/**
|
||||
* Sync queue lag limit.
|
||||
*
|
||||
* @var int Lag limit.
|
||||
*/
|
||||
private $sync_queue_lag_limit;
|
||||
|
||||
/**
|
||||
* Singleton implementation.
|
||||
*
|
||||
* @var Listener
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Get the Listener instance.
|
||||
*
|
||||
* @return Listener
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener constructor.
|
||||
*
|
||||
* This is necessary because you can't use "new" when you declare instance properties >:(
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->set_defaults();
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Listener init.
|
||||
*/
|
||||
private function init() {
|
||||
$handler = array( $this, 'action_handler' );
|
||||
$full_sync_handler = array( $this, 'full_sync_action_handler' );
|
||||
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module->init_listeners( $handler );
|
||||
$module->init_full_sync_listeners( $full_sync_handler );
|
||||
}
|
||||
|
||||
// Module Activation.
|
||||
add_action( 'jetpack_activate_module', $handler );
|
||||
add_action( 'jetpack_deactivate_module', $handler );
|
||||
|
||||
// Jetpack Upgrade.
|
||||
add_action( 'updating_jetpack_version', $handler, 10, 2 );
|
||||
|
||||
// Send periodic checksum.
|
||||
add_action( 'jetpack_sync_checksum', $handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get incremental sync queue.
|
||||
*/
|
||||
public function get_sync_queue() {
|
||||
return $this->sync_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full sync queue.
|
||||
*/
|
||||
public function get_full_sync_queue() {
|
||||
return $this->full_sync_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets queue size limit.
|
||||
*
|
||||
* @param int $limit Queue size limit.
|
||||
*/
|
||||
public function set_queue_size_limit( $limit ) {
|
||||
$this->sync_queue_size_limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue size limit.
|
||||
*/
|
||||
public function get_queue_size_limit() {
|
||||
return $this->sync_queue_size_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the queue lag limit.
|
||||
*
|
||||
* @param int $age Queue lag limit.
|
||||
*/
|
||||
public function set_queue_lag_limit( $age ) {
|
||||
$this->sync_queue_lag_limit = $age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of queue lag limit.
|
||||
*/
|
||||
public function get_queue_lag_limit() {
|
||||
return $this->sync_queue_lag_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a recheck of the queue limit.
|
||||
*/
|
||||
public function force_recheck_queue_limit() {
|
||||
delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->sync_queue->id );
|
||||
delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->full_sync_queue->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item can be added to the queue.
|
||||
*
|
||||
* Prevent adding items to the queue if it hasn't sent an item for 15 mins
|
||||
* AND the queue is over 1000 items long (by default).
|
||||
*
|
||||
* @param object $queue Sync queue.
|
||||
* @return bool
|
||||
*/
|
||||
public function can_add_to_queue( $queue ) {
|
||||
if ( ! Settings::is_sync_enabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$state_transient_name = self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $queue->id;
|
||||
|
||||
$queue_state = get_transient( $state_transient_name );
|
||||
|
||||
if ( false === $queue_state ) {
|
||||
$queue_state = array( $queue->size(), $queue->lag() );
|
||||
set_transient( $state_transient_name, $queue_state, self::QUEUE_STATE_CHECK_TIMEOUT );
|
||||
}
|
||||
|
||||
list( $queue_size, $queue_age ) = $queue_state;
|
||||
|
||||
return ( $queue_age < $this->sync_queue_lag_limit )
|
||||
||
|
||||
( ( $queue_size + 1 ) < $this->sync_queue_size_limit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Full sync action handler.
|
||||
*
|
||||
* @param mixed ...$args Args passed to the action.
|
||||
*/
|
||||
public function full_sync_action_handler( ...$args ) {
|
||||
$this->enqueue_action( current_filter(), $args, $this->full_sync_queue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler.
|
||||
*
|
||||
* @param mixed ...$args Args passed to the action.
|
||||
*/
|
||||
public function action_handler( ...$args ) {
|
||||
$this->enqueue_action( current_filter(), $args, $this->sync_queue );
|
||||
}
|
||||
|
||||
// add many actions to the queue directly, without invoking them.
|
||||
|
||||
/**
|
||||
* Bulk add action to the queue.
|
||||
*
|
||||
* @param string $action_name The name the full sync action.
|
||||
* @param array $args_array Array of chunked arguments.
|
||||
*/
|
||||
public function bulk_enqueue_full_sync_actions( $action_name, $args_array ) {
|
||||
$queue = $this->get_full_sync_queue();
|
||||
|
||||
/*
|
||||
* If we add any items to the queue, we should try to ensure that our script
|
||||
* can't be killed before they are sent.
|
||||
*/
|
||||
if ( function_exists( 'ignore_user_abort' ) ) {
|
||||
ignore_user_abort( true );
|
||||
}
|
||||
|
||||
$data_to_enqueue = array();
|
||||
$user_id = get_current_user_id();
|
||||
$currtime = microtime( true );
|
||||
$is_importing = Settings::is_importing();
|
||||
|
||||
foreach ( $args_array as $args ) {
|
||||
$previous_end = isset( $args['previous_end'] ) ? $args['previous_end'] : null;
|
||||
$args = isset( $args['ids'] ) ? $args['ids'] : $args;
|
||||
|
||||
/**
|
||||
* Modify or reject the data within an action before it is enqueued locally.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param array The action parameters
|
||||
*/
|
||||
$args = apply_filters( "jetpack_sync_before_enqueue_$action_name", $args );
|
||||
$action_data = array( $args );
|
||||
if ( $previous_end !== null ) {
|
||||
$action_data[] = $previous_end;
|
||||
}
|
||||
// allow listeners to abort.
|
||||
if ( false === $args ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data_to_enqueue[] = array(
|
||||
$action_name,
|
||||
$action_data,
|
||||
$user_id,
|
||||
$currtime,
|
||||
$is_importing,
|
||||
);
|
||||
}
|
||||
|
||||
$queue->add_all( $data_to_enqueue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the action.
|
||||
*
|
||||
* @param string $current_filter Current WordPress filter.
|
||||
* @param object $args Sync args.
|
||||
* @param string $queue Sync queue.
|
||||
*/
|
||||
public function enqueue_action( $current_filter, $args, $queue ) {
|
||||
// don't enqueue an action during the outbound http request - this prevents recursion.
|
||||
if ( Settings::is_sending() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! ( new Connection_Manager() )->is_connected() ) {
|
||||
// Don't enqueue an action if the site is disconnected.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action hook to execute when anything on the whitelist gets sent to the queue to sync.
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.9.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_action_before_enqueue' );
|
||||
|
||||
/**
|
||||
* Modify or reject the data within an action before it is enqueued locally.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array The action parameters
|
||||
*/
|
||||
$args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args );
|
||||
|
||||
// allow listeners to abort.
|
||||
if ( false === $args ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Periodically check the size of the queue, and disable adding to it if
|
||||
* it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped).
|
||||
*/
|
||||
if ( ! $this->can_add_to_queue( $queue ) ) {
|
||||
if ( 'sync' === $queue->id ) {
|
||||
$this->sync_data_loss( $queue );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we add any items to the queue, we should try to ensure that our script
|
||||
* can't be killed before they are sent.
|
||||
*/
|
||||
if ( function_exists( 'ignore_user_abort' ) ) {
|
||||
ignore_user_abort( true );
|
||||
}
|
||||
|
||||
if (
|
||||
'sync' === $queue->id ||
|
||||
in_array(
|
||||
$current_filter,
|
||||
array(
|
||||
'jetpack_full_sync_start',
|
||||
'jetpack_full_sync_end',
|
||||
'jetpack_full_sync_cancel',
|
||||
),
|
||||
true
|
||||
)
|
||||
) {
|
||||
$queue->add(
|
||||
array(
|
||||
$current_filter,
|
||||
$args,
|
||||
get_current_user_id(),
|
||||
microtime( true ),
|
||||
Settings::is_importing(),
|
||||
$this->get_actor( $current_filter, $args ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$queue->add(
|
||||
array(
|
||||
$current_filter,
|
||||
$args,
|
||||
get_current_user_id(),
|
||||
microtime( true ),
|
||||
Settings::is_importing(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// since we've added some items, let's try to load the sender so we can send them as quickly as possible.
|
||||
if ( ! Actions::$sender ) {
|
||||
add_filter( 'jetpack_sync_sender_should_load', __NAMESPACE__ . '\Actions::should_initialize_sender_enqueue', 10, 1 );
|
||||
if ( did_action( 'init' ) ) {
|
||||
Actions::add_sender_shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Data Loss Handler
|
||||
*
|
||||
* @param Queue $queue Sync queue.
|
||||
* @return boolean was send successful
|
||||
*/
|
||||
public function sync_data_loss( $queue ) {
|
||||
if ( ! Settings::is_sync_enabled() ) {
|
||||
return;
|
||||
}
|
||||
$updated = Health::update_status( Health::STATUS_OUT_OF_SYNC );
|
||||
|
||||
if ( ! $updated ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'timestamp' => microtime( true ),
|
||||
'queue_size' => $queue->size(),
|
||||
'queue_lag' => $queue->lag(),
|
||||
);
|
||||
|
||||
$sender = Sender::get_instance();
|
||||
return $sender->send_action( 'jetpack_sync_data_loss', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's actor.
|
||||
*
|
||||
* @param string $current_filter Current wp-admin page.
|
||||
* @param object $args Sync event.
|
||||
* @return array Actor information.
|
||||
*/
|
||||
public function get_actor( $current_filter, $args ) {
|
||||
if ( 'wp_login' === $current_filter ) {
|
||||
$user = get_user_by( 'ID', $args[1]->data->ID );
|
||||
} else {
|
||||
$user = wp_get_current_user();
|
||||
}
|
||||
|
||||
$roles = new Roles();
|
||||
$translated_role = $roles->translate_user_to_role( $user );
|
||||
|
||||
$actor = array(
|
||||
'wpcom_user_id' => null,
|
||||
'external_user_id' => isset( $user->ID ) ? $user->ID : null,
|
||||
'display_name' => isset( $user->display_name ) ? $user->display_name : null,
|
||||
'user_email' => isset( $user->user_email ) ? $user->user_email : null,
|
||||
'user_roles' => isset( $user->roles ) ? $user->roles : null,
|
||||
'translated_role' => $translated_role ? $translated_role : null,
|
||||
'is_cron' => defined( 'DOING_CRON' ) ? DOING_CRON : false,
|
||||
'is_rest' => defined( 'REST_API_REQUEST' ) ? REST_API_REQUEST : false,
|
||||
'is_xmlrpc' => defined( 'XMLRPC_REQUEST' ) ? XMLRPC_REQUEST : false,
|
||||
'is_wp_rest' => defined( 'REST_REQUEST' ) ? REST_REQUEST : false,
|
||||
'is_ajax' => defined( 'DOING_AJAX' ) ? DOING_AJAX : false,
|
||||
'is_wp_admin' => is_admin(),
|
||||
'is_cli' => defined( 'WP_CLI' ) ? WP_CLI : false,
|
||||
'from_url' => $this->get_request_url(),
|
||||
);
|
||||
|
||||
if ( $this->should_send_user_data_with_actor( $current_filter ) ) {
|
||||
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
|
||||
if ( defined( 'JETPACK__PLUGIN_DIR' ) ) {
|
||||
if ( ! function_exists( 'jetpack_protect_get_ip' ) ) {
|
||||
require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
|
||||
}
|
||||
$ip = jetpack_protect_get_ip();
|
||||
}
|
||||
|
||||
$actor['ip'] = $ip;
|
||||
$actor['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? filter_var( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : 'unknown';
|
||||
}
|
||||
|
||||
return $actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should user data be sent as the actor?
|
||||
*
|
||||
* @param string $current_filter The current WordPress filter being executed.
|
||||
* @return bool
|
||||
*/
|
||||
public function should_send_user_data_with_actor( $current_filter ) {
|
||||
$should_send = in_array( $current_filter, array( 'jetpack_wp_login', 'wp_logout', 'jetpack_valid_failed_login_attempt' ), true );
|
||||
/**
|
||||
* Allow or deny sending actor's user data ( IP and UA ) during a sync event
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.8.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param bool True if we should send user data
|
||||
* @param string The current filter that is performing the sync action
|
||||
*/
|
||||
return apply_filters( 'jetpack_sync_actor_user_data', $should_send, $current_filter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Listener defaults.
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->sync_queue = new Queue( 'sync' );
|
||||
$this->full_sync_queue = new Queue( 'full_sync' );
|
||||
$this->set_queue_size_limit( Settings::get_setting( 'max_queue_size' ) );
|
||||
$this->set_queue_lag_limit( Settings::get_setting( 'max_queue_lag' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request URL.
|
||||
*
|
||||
* @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'] ) ) {
|
||||
// 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']}" ) );
|
||||
}
|
||||
return is_admin() ? get_admin_url( get_current_blog_id() ) : home_url();
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Lock class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* Lock class
|
||||
*/
|
||||
class Lock {
|
||||
/**
|
||||
* Prefix of the blog lock transient.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOCK_PREFIX = 'jp_sync_lock_';
|
||||
|
||||
/**
|
||||
* Default Lifetime of the lock.
|
||||
* This is the expiration value as such we are setting it high to handle cases where there are
|
||||
* long running requests. Short expiration value leads to concurrent requests and performance issues.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const LOCK_TRANSIENT_EXPIRY = 180; // Seconds.
|
||||
|
||||
/**
|
||||
* Attempt to lock.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $name lock name.
|
||||
* @param int $expiry lock duration in seconds.
|
||||
*
|
||||
* @return boolean True if succeeded, false otherwise.
|
||||
*/
|
||||
public function attempt( $name, $expiry = self::LOCK_TRANSIENT_EXPIRY ) {
|
||||
$lock_name = self::LOCK_PREFIX . $name;
|
||||
$locked_time = get_option( $lock_name );
|
||||
|
||||
if ( $locked_time ) {
|
||||
// If expired update to false but don't send. Send will occurr in new request to avoid race conditions.
|
||||
if ( microtime( true ) > $locked_time ) {
|
||||
update_option( $lock_name, false, false );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$locked_time = microtime( true ) + $expiry;
|
||||
update_option( $lock_name, $locked_time, false );
|
||||
return $locked_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the lock.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $name lock name.
|
||||
* @param bool|float $lock_expiration lock expiration.
|
||||
*/
|
||||
public function remove( $name, $lock_expiration = false ) {
|
||||
$lock_name = self::LOCK_PREFIX . $name;
|
||||
|
||||
// Only remove lock if current value matches our lock.
|
||||
if ( true === $lock_expiration || (string) get_option( $lock_name ) === (string) $lock_expiration ) {
|
||||
update_option( $lock_name, false, false );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* This class hooks the main sync actions.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Sync\Actions as Sync_Actions;
|
||||
|
||||
/**
|
||||
* Jetpack Sync main class.
|
||||
*/
|
||||
class Main {
|
||||
|
||||
/**
|
||||
* Sets up event handlers for the Sync package. Is used from the Config package.
|
||||
*
|
||||
* @action plugins_loaded
|
||||
*/
|
||||
public static function configure() {
|
||||
if ( Actions::sync_allowed() ) {
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'on_plugins_loaded_early' ), 5 );
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'on_plugins_loaded_late' ), 90 );
|
||||
}
|
||||
|
||||
// Add REST endpoints.
|
||||
add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Sync\\REST_Endpoints', 'initialize_rest_api' ) );
|
||||
|
||||
// Add IDC disconnect action.
|
||||
add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'on_jetpack_idc_disconnect' ), 100 );
|
||||
|
||||
// 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 );
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all sync related data on Identity Crisis disconnect.
|
||||
*/
|
||||
public static function on_jetpack_idc_disconnect() {
|
||||
Sender::get_instance()->uninstall();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Sync data settings.
|
||||
*
|
||||
* @param array $data_settings An array containing the Sync data options. An empty array indicates that the default
|
||||
* values will be used for all Sync data.
|
||||
*/
|
||||
public static function set_sync_data_options( $data_settings = array() ) {
|
||||
( new Data_Settings() )->add_settings_list( $data_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the main sync actions.
|
||||
*
|
||||
* @action plugins_loaded
|
||||
*/
|
||||
public static function on_plugins_loaded_early() {
|
||||
/**
|
||||
* Additional Sync modules can be carried out into their own packages and they
|
||||
* will get their own config settings.
|
||||
*
|
||||
* For now additional modules are enabled based on whether the third party plugin
|
||||
* class exists or not.
|
||||
*/
|
||||
Sync_Actions::initialize_search();
|
||||
Sync_Actions::initialize_woocommerce();
|
||||
Sync_Actions::initialize_wp_super_cache();
|
||||
|
||||
// We need to define this here so that it's hooked before `updating_jetpack_version` is called.
|
||||
add_action( 'updating_jetpack_version', array( 'Automattic\\Jetpack\\Sync\\Actions', 'cleanup_on_upgrade' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after most of plugins_loaded hook functions have been run.
|
||||
*
|
||||
* @action plugins_loaded
|
||||
*/
|
||||
public static function on_plugins_loaded_late() {
|
||||
/*
|
||||
* Init after plugins loaded and before the `init` action. This helps with issues where plugins init
|
||||
* with a high priority or sites that use alternate cron.
|
||||
*/
|
||||
Sync_Actions::init();
|
||||
|
||||
// Enable non-blocking Jetpack Sync flow.
|
||||
$non_block_enabled = (bool) get_option( 'jetpack_sync_non_blocking', false );
|
||||
|
||||
/**
|
||||
* Filters the option to enable non-blocking sync.
|
||||
*
|
||||
* Default value is false, filter to true to enable non-blocking mode which will have
|
||||
* WP.com return early and use the sync/close endpoint to check-in processed items.
|
||||
*
|
||||
* @since 1.12.3
|
||||
*
|
||||
* @param bool $enabled Should non-blocking flow be enabled.
|
||||
*/
|
||||
$filtered = (bool) apply_filters( 'jetpack_sync_non_blocking', $non_block_enabled );
|
||||
|
||||
if ( $non_block_enabled !== $filtered ) {
|
||||
update_option( 'jetpack_sync_non_blocking', $filtered, false );
|
||||
}
|
||||
|
||||
// Initialize health-related hooks after plugins have loaded.
|
||||
Health::init();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Simple wrapper that allows enumerating cached static instances
|
||||
* of sync modules.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Sync\Modules\Module;
|
||||
|
||||
/**
|
||||
* A class to handle loading of sync modules.
|
||||
*/
|
||||
class Modules {
|
||||
|
||||
/**
|
||||
* Lists classnames of sync modules we load by default.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const DEFAULT_SYNC_MODULES = array(
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Constants',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Callables',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Network_Options',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Options',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Terms',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Menus',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Themes',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Users',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Import',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Posts',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Protect',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Comments',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Updates',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Attachments',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Meta',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Plugins',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Stats',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync_Immediately',
|
||||
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships',
|
||||
);
|
||||
|
||||
/**
|
||||
* Keeps track of initialized sync modules.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
private static $initialized_modules = null;
|
||||
|
||||
/**
|
||||
* Gets a list of initialized modules.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return Module[]
|
||||
*/
|
||||
public static function get_modules() {
|
||||
if ( null === self::$initialized_modules ) {
|
||||
self::$initialized_modules = self::initialize_modules();
|
||||
}
|
||||
|
||||
return self::$initialized_modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets defaults for all initialized modules.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function set_defaults() {
|
||||
foreach ( self::get_modules() as $module ) {
|
||||
$module->set_defaults();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of an initialized module. Returns false if given module has not been initialized.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param string $module_name A module name.
|
||||
*
|
||||
* @return bool|Automattic\Jetpack\Sync\Modules\Module
|
||||
*/
|
||||
public static function get_module( $module_name ) {
|
||||
foreach ( self::get_modules() as $module ) {
|
||||
if ( $module->name() === $module_name ) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and sets defaults for all declared modules.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function initialize_modules() {
|
||||
/**
|
||||
* Filters the list of class names of sync modules.
|
||||
* If you add to this list, make sure any classes implement the
|
||||
* Jetpack_Sync_Module interface.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
$modules = apply_filters( 'jetpack_sync_modules', self::DEFAULT_SYNC_MODULES );
|
||||
|
||||
$modules = array_map( array( __CLASS__, 'load_module' ), $modules );
|
||||
|
||||
return array_map( array( __CLASS__, 'set_module_defaults' ), $modules );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the given module class.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param string $module_class The classname of a Jetpack sync module.
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Modules\Module
|
||||
*/
|
||||
public static function load_module( $module_class ) {
|
||||
return new $module_class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets defaults for the given instance of a Jetpack sync module.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Modules\Module $module Instance of a Jetpack sync module.
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Modules\Module
|
||||
*/
|
||||
public static function set_module_defaults( $module ) {
|
||||
$module->set_defaults();
|
||||
if ( method_exists( $module, 'set_late_default' ) ) {
|
||||
add_action( 'init', array( $module, 'set_late_default' ), 90 );
|
||||
}
|
||||
return $module;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '1.37.0';
|
||||
|
||||
const PACKAGE_SLUG = 'sync';
|
||||
|
||||
/**
|
||||
* Adds the package slug and version to the package version tracker's data.
|
||||
*
|
||||
* @param array $package_versions The package version array.
|
||||
*
|
||||
* @return array The packge version array.
|
||||
*/
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
|
||||
return $package_versions;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync queue buffer.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* A buffer of items from the queue that can be checked out.
|
||||
*/
|
||||
class Queue_Buffer {
|
||||
/**
|
||||
* Sync queue buffer ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Sync items.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $items_with_ids;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Initializes the queue buffer.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Sync queue buffer ID.
|
||||
* @param array $items_with_ids Items for the buffer to work with.
|
||||
*/
|
||||
public function __construct( $id, $items_with_ids ) {
|
||||
$this->id = $id;
|
||||
$this->items_with_ids = $items_with_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sync items in the buffer, in an ID => value form.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool|array Sync items in the buffer.
|
||||
*/
|
||||
public function get_items() {
|
||||
return array_combine( $this->get_item_ids(), $this->get_item_values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the values of the sync items in the buffer.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Sync items values.
|
||||
*/
|
||||
public function get_item_values() {
|
||||
return Utils::get_item_values( $this->items_with_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the IDs of the sync items in the buffer.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Sync items IDs.
|
||||
*/
|
||||
public function get_item_ids() {
|
||||
return Utils::get_item_ids( $this->items_with_ids );
|
||||
}
|
||||
}
|
@ -0,0 +1,753 @@
|
||||
<?php
|
||||
/**
|
||||
* The class that describes the Queue for the sync package.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* A persistent queue that can be flushed in increments of N items,
|
||||
* and which blocks reads until checked-out buffers are checked in or
|
||||
* closed. This uses raw SQL for two reasons: speed, and not triggering
|
||||
* tons of added_option callbacks.
|
||||
*/
|
||||
class Queue {
|
||||
/**
|
||||
* The queue id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
/**
|
||||
* Keeps track of the rows.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $row_iterator;
|
||||
|
||||
/**
|
||||
* Queue constructor.
|
||||
*
|
||||
* @param string $id Name of the queue.
|
||||
*/
|
||||
public function __construct( $id ) {
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single item to the queue.
|
||||
*
|
||||
* @param object $item Event object to add to queue.
|
||||
*/
|
||||
public function add( $item ) {
|
||||
global $wpdb;
|
||||
$added = false;
|
||||
|
||||
// If empty, don't add.
|
||||
if ( empty( $item ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert all the items in a single SQL query. May be subject to query size limits!
|
||||
*
|
||||
* @param array $items Array of events to add to the queue.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function add_all( $items ) {
|
||||
global $wpdb;
|
||||
$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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the front-most item on the queue without checking it out.
|
||||
*
|
||||
* @param int $count Number of items to return when looking at the items.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function peek( $count = 1 ) {
|
||||
$items = $this->fetch_items( $count );
|
||||
if ( $items ) {
|
||||
return Utils::get_item_values( $items );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets items with particular IDs.
|
||||
*
|
||||
* @param array $item_ids Array of item IDs to retrieve.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function peek_by_id( $item_ids ) {
|
||||
$items = $this->fetch_items_by_id( $item_ids );
|
||||
if ( $items ) {
|
||||
return Utils::get_item_values( $items );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the queue lag.
|
||||
* 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.
|
||||
*
|
||||
* @return float|int|mixed|null
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}-%"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the 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}-%"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you know if there is any items in the queue.
|
||||
*
|
||||
* We use this peculiar implementation because it's much faster than count(*).
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to checkout the queue.
|
||||
*
|
||||
* @param int $buffer_size Size of the buffer to checkout.
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Queue_Buffer|bool|int|\WP_Error
|
||||
*/
|
||||
public function checkout( $buffer_size ) {
|
||||
if ( $this->get_checkout_id() ) {
|
||||
return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
|
||||
}
|
||||
|
||||
$buffer_id = uniqid();
|
||||
|
||||
$result = $this->set_checkout_id( $buffer_id );
|
||||
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$items = $this->fetch_items( $buffer_size );
|
||||
|
||||
if ( count( $items ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$buffer = new Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) );
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of items return the items ids.
|
||||
*
|
||||
* @param array $items List of item objects.
|
||||
*
|
||||
* @return array Ids of the items.
|
||||
*/
|
||||
public function get_ids( $items ) {
|
||||
return array_map(
|
||||
function ( $item ) {
|
||||
return $item->id;
|
||||
},
|
||||
$items
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop elements from the queue.
|
||||
*
|
||||
* @param int $limit Number of items to pop from the queue.
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function pop( $limit ) {
|
||||
$items = $this->fetch_items( $limit );
|
||||
|
||||
$ids = $this->get_ids( $items );
|
||||
|
||||
$this->delete( $ids );
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the items from the queue with a memory limit.
|
||||
*
|
||||
* This checks out rows until it either empties the queue or hits a certain memory limit
|
||||
* it loads the sizes from the DB first so that it doesn't accidentally
|
||||
* load more data into memory than it needs to.
|
||||
* The only way it will load more items than $max_size is if a single queue item
|
||||
* exceeds the memory limit, but in that case it will send that item by itself.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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();
|
||||
|
||||
$result = $this->set_checkout_id( $buffer_id );
|
||||
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get the map of buffer_id -> memory_size.
|
||||
global $wpdb;
|
||||
|
||||
$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
|
||||
);
|
||||
|
||||
if ( count( $items_with_size ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$total_memory = 0;
|
||||
$max_item_id = $items_with_size[0]->id;
|
||||
$min_item_id = $max_item_id;
|
||||
|
||||
foreach ( $items_with_size 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
|
||||
// we will exit on the next iteration instead.
|
||||
if ( $total_memory > $max_memory && $id > 0 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$max_item_id = $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
|
||||
);
|
||||
|
||||
$items = $wpdb->get_results( $query, OBJECT ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
foreach ( $items as $item ) {
|
||||
// @codingStandardsIgnoreStart
|
||||
$item->value = @unserialize( $item->value );
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
if ( count( $items ) === 0 ) {
|
||||
$this->delete_checkout_id();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$buffer = new Queue_Buffer( $buffer_id, $items );
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in the queue.
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue_Buffer $buffer Queue_Buffer object.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function checkin( $buffer ) {
|
||||
$is_valid = $this->validate_checkout( $buffer );
|
||||
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$this->delete_checkout_id();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the buffer.
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue_Buffer $buffer Queue_Buffer object.
|
||||
* @param null|array $ids_to_remove Ids to remove from the queue.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function close( $buffer, $ids_to_remove = null ) {
|
||||
$is_valid = $this->validate_checkout( $buffer );
|
||||
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
// Always delete ids_to_remove even when buffer is no longer checked-out.
|
||||
// They were processed by WP.com so safe to remove from queue.
|
||||
if ( $ids_to_remove !== null ) {
|
||||
$this->delete( $ids_to_remove );
|
||||
}
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$this->delete_checkout_id();
|
||||
|
||||
// By default clear all items in the buffer.
|
||||
if ( $ids_to_remove === null ) {
|
||||
$ids_to_remove = $buffer->get_item_ids();
|
||||
}
|
||||
|
||||
$this->delete( $ids_to_remove );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete elements from the queue.
|
||||
*
|
||||
* @param array $ids Ids to delete.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
private function delete( $ids ) {
|
||||
if ( 0 === count( $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
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all items from the queue.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function flush_all() {
|
||||
$items = Utils::get_item_values( $this->fetch_items() );
|
||||
$this->reset();
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the items from the queue.
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function get_all() {
|
||||
return $this->fetch_items();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces Checkin of the queue.
|
||||
* Use with caution, this could allow multiple processes to delete
|
||||
* and send from the queue at the same time
|
||||
*/
|
||||
public function force_checkin() {
|
||||
$this->delete_checkout_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the queue is locked.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
return (bool) $this->get_checkout_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks checkouts from the queue
|
||||
* tries to wait up to $timeout seconds for the queue to be empty.
|
||||
*
|
||||
* @param int $timeout The wait time in seconds for the queue to be empty.
|
||||
*
|
||||
* @return bool|int|\WP_Error
|
||||
*/
|
||||
public function lock( $timeout = 30 ) {
|
||||
$tries = 0;
|
||||
|
||||
while ( $this->has_any_items() && $tries < $timeout ) {
|
||||
sleep( 1 );
|
||||
++$tries;
|
||||
}
|
||||
|
||||
if ( 30 === $tries ) {
|
||||
return new WP_Error( 'lock_timeout', 'Timeout waiting for sync queue to empty' );
|
||||
}
|
||||
|
||||
if ( $this->get_checkout_id() ) {
|
||||
return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
|
||||
}
|
||||
|
||||
// Hopefully this means we can acquire a checkout?
|
||||
$result = $this->set_checkout_id( 'lock' );
|
||||
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the queue.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function unlock() {
|
||||
return $this->delete_checkout_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* This option is specifically chosen to, as much as possible, preserve time order
|
||||
* and minimise the possibility of collisions between multiple processes working
|
||||
* at the same time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_option_name_timestamp() {
|
||||
return sprintf( '%.6f', microtime( true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the checkout ID.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
private function get_checkout_id() {
|
||||
global $wpdb;
|
||||
$checkout_value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM $wpdb->options WHERE option_name = %s",
|
||||
$this->get_lock_option_name()
|
||||
)
|
||||
);
|
||||
|
||||
if ( $checkout_value ) {
|
||||
list( $checkout_id, $timestamp ) = explode( ':', $checkout_value );
|
||||
if ( (int) $timestamp > time() ) {
|
||||
return $checkout_id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checkout id.
|
||||
*
|
||||
* @param string $checkout_id The ID of the checkout.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
private function set_checkout_id( $checkout_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$expires = time() + Defaults::$default_sync_queue_lock_timeout;
|
||||
$updated_num = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
|
||||
"$checkout_id:$expires",
|
||||
$this->get_lock_option_name()
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $updated_num ) {
|
||||
$updated_num = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )",
|
||||
$this->get_lock_option_name(),
|
||||
"$checkout_id:$expires"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $updated_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the checkout ID.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
private function delete_checkout_id() {
|
||||
global $wpdb;
|
||||
// Rather than delete, which causes fragmentation, we update in place.
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
|
||||
'0:0',
|
||||
$this->get_lock_option_name()
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lock option name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_lock_option_name() {
|
||||
return "jpsq_{$this->id}_checkout";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next data row option name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_next_data_row_option_name() {
|
||||
$timestamp = $this->generate_option_name_timestamp();
|
||||
|
||||
// Row iterator is used to avoid collisions where we're writing data waaay fast in a single process.
|
||||
if ( PHP_INT_MAX === $this->row_iterator ) {
|
||||
$this->row_iterator = 0;
|
||||
} else {
|
||||
$this->row_iterator += 1;
|
||||
}
|
||||
|
||||
return 'jpsq_' . $this->id . '-' . $timestamp . '-' . $this->random_int . '-' . $this->row_iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the items in the queue.
|
||||
*
|
||||
* @param null|int $limit Limit to the number of items we fetch at once.
|
||||
*
|
||||
* @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
|
||||
);
|
||||
}
|
||||
|
||||
return $this->unserialize_values( $items );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return items with specific ids.
|
||||
*
|
||||
* @param array $items_ids Array of event ids.
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize item values.
|
||||
*
|
||||
* @param array $items Events from the Queue to be unserialized.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function unserialize_values( $items ) {
|
||||
array_walk(
|
||||
$items,
|
||||
function ( $item ) {
|
||||
// @codingStandardsIgnoreStart
|
||||
$item->value = @unserialize( $item->value );
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
);
|
||||
|
||||
return $items;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the buffer is still valid or an Error other wise.
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue_Buffer $buffer The Queue_Buffer.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private function validate_checkout( $buffer ) {
|
||||
if ( ! $buffer instanceof Queue_Buffer ) {
|
||||
return new WP_Error( 'not_a_buffer', 'You must checkin an instance of Automattic\\Jetpack\\Sync\\Queue_Buffer' );
|
||||
}
|
||||
|
||||
$checkout_id = $this->get_checkout_id();
|
||||
|
||||
if ( ! $checkout_id ) {
|
||||
return new WP_Error( 'buffer_not_checked_out', 'There are no checked out buffers' );
|
||||
}
|
||||
|
||||
// TODO: change to strict comparison.
|
||||
if ( $checkout_id != $buffer->id ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
|
||||
return new WP_Error( 'buffer_mismatch', 'The buffer you checked in was not checked out' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,847 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync package.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* This class will handle Sync v4 REST Endpoints.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*/
|
||||
class REST_Endpoints {
|
||||
|
||||
/**
|
||||
* Items pending send.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $items = array();
|
||||
|
||||
/**
|
||||
* Initialize REST routes.
|
||||
*/
|
||||
public static function initialize_rest_api() {
|
||||
|
||||
// Request a Full Sync.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/full-sync',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::full_sync_start',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'modules' => array(
|
||||
'description' => __( 'Data Modules that should be included in Full Sync', 'jetpack-sync' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
),
|
||||
'users' => array(
|
||||
'description' => __( 'User IDs to include in Full Sync or "initial"', 'jetpack-sync' ),
|
||||
'required' => false,
|
||||
),
|
||||
'posts' => array(
|
||||
'description' => __( 'Post IDs to include in Full Sync', 'jetpack-sync' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
),
|
||||
'comments' => array(
|
||||
'description' => __( 'Comment IDs to include in Full Sync', 'jetpack-sync' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Obtain Sync status.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/status',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::sync_status',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'fields' => array(
|
||||
'description' => __( 'Comma seperated list of additional fields that should be included in status.', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Update Sync health status.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/health',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::sync_health',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'status' => array(
|
||||
'description' => __( 'New Sync health status', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Obtain Sync settings.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/settings',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_sync_settings',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::update_sync_settings',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Retrieve Sync Object(s).
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/object',
|
||||
array(
|
||||
'methods' => WP_REST_Server::ALLMETHODS,
|
||||
'callback' => __CLASS__ . '::get_sync_objects',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'module_name' => array(
|
||||
'description' => __( 'Name of Sync module', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
'object_type' => array(
|
||||
'description' => __( 'Object Type', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
'object_ids' => array(
|
||||
'description' => __( 'Objects Identifiers', 'jetpack-sync' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Retrieve Sync Object(s).
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/now',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::do_sync',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'queue' => array(
|
||||
'description' => __( 'Name of Sync queue.', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Checkout Sync Objects.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/checkout',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::checkout',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
)
|
||||
);
|
||||
|
||||
// Checkin Sync Objects.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/close',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::close',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
)
|
||||
);
|
||||
|
||||
// Unlock Sync Queue.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/unlock',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::unlock_queue',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'queue' => array(
|
||||
'description' => __( 'Name of Sync queue.', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Retrieve range of Object Ids.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/object-id-range',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_object_id_range',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'sync_module' => array(
|
||||
'description' => __( 'Name of Sync module.', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'batch_size' => array(
|
||||
'description' => __( 'Size of batches', 'jetpack-sync' ),
|
||||
'type' => 'int',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Obtain table checksums.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/data-check',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::data_check',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'perform_text_conversion' => array(
|
||||
'description' => __( 'If text fields should be converted to latin1 in checksum calculation.', 'jetpack-sync' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Obtain histogram.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/data-histogram',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::data_histogram',
|
||||
'permission_callback' => __CLASS__ . '::verify_default_permissions',
|
||||
'args' => array(
|
||||
'columns' => array(
|
||||
'description' => __( 'Column mappings', 'jetpack-sync' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
),
|
||||
'object_type' => array(
|
||||
'description' => __( 'Object Type', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
'buckets' => array(
|
||||
'description' => __( 'Number of histogram buckets.', 'jetpack-sync' ),
|
||||
'type' => 'int',
|
||||
'required' => false,
|
||||
),
|
||||
'start_id' => array(
|
||||
'description' => __( 'Start ID for the histogram', 'jetpack-sync' ),
|
||||
'type' => 'int',
|
||||
'required' => false,
|
||||
),
|
||||
'end_id' => array(
|
||||
'description' => __( 'End ID for the histogram', 'jetpack-sync' ),
|
||||
'type' => 'int',
|
||||
'required' => false,
|
||||
),
|
||||
'strip_non_ascii' => array(
|
||||
'description' => __( 'Strip non-ascii characters?', 'jetpack-sync' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
),
|
||||
'shared_salt' => array(
|
||||
'description' => __( 'Shared Salt to use when generating checksum', 'jetpack-sync' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
'only_range_edges' => array(
|
||||
'description' => __( 'Should only range endges be returned', 'jetpack-sync' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
),
|
||||
'detailed_drilldown' => array(
|
||||
'description' => __( 'Do we want the checksum or object ids.', 'jetpack-sync' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
),
|
||||
'perform_text_conversion' => array(
|
||||
'description' => __( 'If text fields should be converted to latin1 in checksum calculation.', 'jetpack-sync' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Trigger Dedicated Sync request.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/sync/spawn-sync',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::spawn_sync',
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a Full Sync of specified modules.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function full_sync_start( $request ) {
|
||||
|
||||
$modules = $request->get_param( 'modules' );
|
||||
|
||||
// convert list of modules into array format of "$modulename => true".
|
||||
if ( ! empty( $modules ) ) {
|
||||
$modules = array_map( '__return_true', array_flip( $modules ) );
|
||||
}
|
||||
|
||||
// Process additional options.
|
||||
foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) {
|
||||
if ( 'users' === $module_name && 'initial' === $request->get_param( 'users' ) ) {
|
||||
$modules['users'] = 'initial';
|
||||
} elseif ( is_array( $request->get_param( $module_name ) ) ) {
|
||||
$ids = $request->get_param( $module_name );
|
||||
if ( count( $ids ) > 0 ) {
|
||||
$modules[ $module_name ] = $ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $modules ) ) {
|
||||
$modules = null;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'scheduled' => Actions::do_full_sync( $modules ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Sync's status.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function sync_status( $request ) {
|
||||
$fields = $request->get_param( 'fields' );
|
||||
return rest_ensure_response( Actions::get_sync_status( $fields ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return table checksums.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function data_check( $request ) {
|
||||
// Disable Sync during this call, so we can resolve faster.
|
||||
Actions::mark_sync_read_only();
|
||||
$store = new Replicastore();
|
||||
|
||||
$perform_text_conversion = false;
|
||||
if ( true === $request->get_param( 'perform_text_conversion' ) ) {
|
||||
$perform_text_conversion = true;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $store->checksum_all( $perform_text_conversion ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Histogram.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function data_histogram( $request ) {
|
||||
|
||||
// Disable Sync during this call, so we can resolve faster.
|
||||
Actions::mark_sync_read_only();
|
||||
|
||||
$args = $request->get_params();
|
||||
|
||||
if ( empty( $args['columns'] ) ) {
|
||||
$args['columns'] = null; // go with defaults.
|
||||
}
|
||||
|
||||
if ( false !== $args['strip_non_ascii'] ) {
|
||||
$args['strip_non_ascii'] = true;
|
||||
}
|
||||
|
||||
if ( true !== $args['perform_text_conversion'] ) {
|
||||
$args['perform_text_conversion'] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack: nullify the values of `start_id` and `end_id` if we're only requesting ranges.
|
||||
*
|
||||
* The endpoint doesn't support nullable values :(
|
||||
*/
|
||||
if ( true === $args['only_range_edges'] ) {
|
||||
if ( 0 === $args['start_id'] ) {
|
||||
$args['start_id'] = null;
|
||||
}
|
||||
|
||||
if ( 0 === $args['end_id'] ) {
|
||||
$args['end_id'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$store = new Replicastore();
|
||||
$histogram = $store->checksum_histogram( $args['object_type'], $args['buckets'], $args['start_id'], $args['end_id'], $args['columns'], $args['strip_non_ascii'], $args['shared_salt'], $args['only_range_edges'], $args['detailed_drilldown'], $args['perform_text_conversion'] );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'histogram' => $histogram,
|
||||
'type' => $store->get_checksum_type(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sync health.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function sync_health( $request ) {
|
||||
|
||||
switch ( $request->get_param( 'status' ) ) {
|
||||
case Health::STATUS_IN_SYNC:
|
||||
case Health::STATUS_OUT_OF_SYNC:
|
||||
Health::update_status( $request->get_param( 'status' ) );
|
||||
break;
|
||||
default:
|
||||
return new WP_Error( 'invalid_status', 'Invalid Sync Status Provided.' );
|
||||
}
|
||||
|
||||
// re-fetch so we see what's really being stored.
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => Health::get_status(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain Sync settings.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function get_sync_settings() {
|
||||
return rest_ensure_response( Settings::get_settings() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sync settings.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function update_sync_settings( $request ) {
|
||||
$args = $request->get_params();
|
||||
$sync_settings = Settings::get_settings();
|
||||
|
||||
foreach ( $args as $key => $value ) {
|
||||
if ( false !== $value ) {
|
||||
if ( is_numeric( $value ) ) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
|
||||
// special case for sending empty arrays - a string with value 'empty'.
|
||||
if ( 'empty' === $value ) {
|
||||
$value = array();
|
||||
}
|
||||
|
||||
$sync_settings[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
Settings::update_settings( $sync_settings );
|
||||
|
||||
// re-fetch so we see what's really being stored.
|
||||
return rest_ensure_response( Settings::get_settings() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Sync Objects.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function get_sync_objects( $request ) {
|
||||
$args = $request->get_params();
|
||||
|
||||
$module_name = $args['module_name'];
|
||||
// Verify valid Sync Module.
|
||||
$sync_module = Modules::get_module( $module_name );
|
||||
if ( ! $sync_module ) {
|
||||
return new WP_Error( 'invalid_module', 'You specified an invalid sync module' );
|
||||
}
|
||||
|
||||
Actions::mark_sync_read_only();
|
||||
|
||||
$codec = Sender::get_instance()->get_codec();
|
||||
Settings::set_is_syncing( true );
|
||||
$objects = $codec->encode( $sync_module->get_objects_by_id( $args['object_type'], $args['object_ids'] ) );
|
||||
Settings::set_is_syncing( false );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'objects' => $objects,
|
||||
'codec' => $codec->name(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Sync processing.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function do_sync( $request ) {
|
||||
|
||||
$queue_name = self::validate_queue( $request->get_param( 'queue' ) );
|
||||
if ( is_wp_error( $queue_name ) ) {
|
||||
return $queue_name;
|
||||
}
|
||||
|
||||
$sender = Sender::get_instance();
|
||||
$response = $sender->do_sync_for_queue( new Queue( $queue_name ) );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'response' => $response,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request sync data from specified queue.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function checkout( $request ) {
|
||||
$args = $request->get_params();
|
||||
$queue_name = self::validate_queue( $args['queue'] );
|
||||
|
||||
if ( is_wp_error( $queue_name ) ) {
|
||||
return $queue_name;
|
||||
}
|
||||
|
||||
$number_of_items = $args['number_of_items'];
|
||||
if ( $number_of_items < 1 || $number_of_items > 100 ) {
|
||||
return new WP_Error( 'invalid_number_of_items', 'Number of items needs to be an integer that is larger than 0 and less then 100', 400 );
|
||||
}
|
||||
|
||||
// REST Sender.
|
||||
$sender = new REST_Sender();
|
||||
|
||||
if ( 'immediate' === $queue_name ) {
|
||||
return rest_ensure_response( $sender->immediate_full_sync_pull( $number_of_items ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $sender->queue_pull( $queue_name, $number_of_items, $args ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a Sync queue.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function unlock_queue( $request ) {
|
||||
|
||||
$queue_name = $request->get_param( 'queue' );
|
||||
|
||||
if ( ! in_array( $queue_name, array( 'sync', 'full_sync' ), true ) ) {
|
||||
return new WP_Error( 'invalid_queue', 'Queue name should be sync or full_sync', 400 );
|
||||
}
|
||||
$queue = new Queue( $queue_name );
|
||||
|
||||
// False means that there was no lock to delete.
|
||||
$response = $queue->unlock();
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => $response,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkin Sync actions.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function close( $request ) {
|
||||
|
||||
$request_body = $request->get_params();
|
||||
$queue_name = self::validate_queue( $request_body['queue'] );
|
||||
|
||||
if ( is_wp_error( $queue_name ) ) {
|
||||
return $queue_name;
|
||||
}
|
||||
|
||||
if ( empty( $request_body['buffer_id'] ) ) {
|
||||
return new WP_Error( 'missing_buffer_id', 'Please provide a buffer id', 400 );
|
||||
}
|
||||
|
||||
if ( ! is_array( $request_body['item_ids'] ) ) {
|
||||
return new WP_Error( 'missing_item_ids', 'Please provide a list of item ids in the item_ids argument', 400 );
|
||||
}
|
||||
|
||||
// Limit to A-Z,a-z,0-9,_,- .
|
||||
$request_body['buffer_id'] = preg_replace( '/[^A-Za-z0-9]/', '', $request_body['buffer_id'] );
|
||||
$request_body['item_ids'] = array_filter( array_map( array( 'Automattic\Jetpack\Sync\REST_Endpoints', 'sanitize_item_ids' ), $request_body['item_ids'] ) );
|
||||
|
||||
$queue = new Queue( $queue_name );
|
||||
|
||||
$items = $queue->peek_by_id( $request_body['item_ids'] );
|
||||
|
||||
// Update Full Sync Status if queue is "full_sync".
|
||||
if ( 'full_sync' === $queue_name ) {
|
||||
$full_sync_module = Modules::get_module( 'full-sync' );
|
||||
$full_sync_module->update_sent_progress_action( $items );
|
||||
}
|
||||
|
||||
$buffer = new Queue_Buffer( $request_body['buffer_id'], $request_body['item_ids'] );
|
||||
$response = $queue->close( $buffer, $request_body['item_ids'] );
|
||||
|
||||
// Perform another checkout?
|
||||
if ( isset( $request_body['continue'] ) && $request_body['continue'] ) {
|
||||
if ( in_array( $queue_name, array( 'full_sync', 'immediate' ), true ) ) {
|
||||
// Send Full Sync Actions.
|
||||
Sender::get_instance()->do_full_sync();
|
||||
} else {
|
||||
// Send Incremental Sync Actions.
|
||||
if ( $queue->has_any_items() ) {
|
||||
Sender::get_instance()->do_sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => $response,
|
||||
'status' => Actions::get_sync_status(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve range of Object Ids for a specified Sync module.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function get_object_id_range( $request ) {
|
||||
|
||||
$module_name = $request->get_param( 'sync_module' );
|
||||
$batch_size = $request->get_param( 'batch_size' );
|
||||
|
||||
if ( ! self::is_valid_sync_module( $module_name ) ) {
|
||||
return new WP_Error( 'invalid_module', 'This sync module cannot be used to calculate a range.', 400 );
|
||||
}
|
||||
$module = Modules::get_module( $module_name );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'ranges' => $module->get_min_max_object_ids_for_batches( $batch_size ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is used by Sync to spawn a
|
||||
* dedicated Sync request which will trigger Sync to run.
|
||||
*
|
||||
* If Dedicated Sync is enabled, this callback should never run as
|
||||
* processing of Sync actions will occur earlier and exit.
|
||||
*
|
||||
* @see Actions::init
|
||||
* @see Sender::do_dedicated_sync_and_exit
|
||||
*
|
||||
* @since $$next_version$$
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public static function spawn_sync() {
|
||||
nocache_headers();
|
||||
|
||||
if ( ! Settings::is_dedicated_sync_enabled() ) {
|
||||
return new WP_Error(
|
||||
'dedicated_sync_disabled',
|
||||
'Dedicated Sync flow is disabled.',
|
||||
array( 'status' => 422 )
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'dedicated_sync_failed',
|
||||
'Failed to process Dedicated Sync request',
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that request has default permissions to perform sync actions.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*
|
||||
* @return bool Whether user has capability 'manage_options' or a blog token is used.
|
||||
*/
|
||||
public static function verify_default_permissions() {
|
||||
if ( current_user_can( 'manage_options' ) || Rest_Authentication::is_signed_with_blog_token() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$error_msg = esc_html__(
|
||||
'You do not have the correct user permissions to perform this action.
|
||||
Please contact your site admin if you think this is a mistake.',
|
||||
'jetpack-sync'
|
||||
);
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_sync', $error_msg, array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Queue name.
|
||||
*
|
||||
* @param string $value Queue Name.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
protected static function validate_queue( $value ) {
|
||||
if ( ! isset( $value ) ) {
|
||||
return new WP_Error( 'invalid_queue', 'Queue name is required', 400 );
|
||||
}
|
||||
|
||||
if ( ! in_array( $value, array( 'sync', 'full_sync', 'immediate' ), true ) ) {
|
||||
return new WP_Error( 'invalid_queue', 'Queue name should be sync, full_sync or immediate', 400 );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate name is a valid Sync module.
|
||||
*
|
||||
* @param string $module_name Name of Sync Module.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_valid_sync_module( $module_name ) {
|
||||
return in_array(
|
||||
$module_name,
|
||||
array(
|
||||
'comments',
|
||||
'posts',
|
||||
'terms',
|
||||
'term_relationships',
|
||||
'users',
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize Item Ids.
|
||||
*
|
||||
* @param string $item Sync item identifier.
|
||||
*
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
protected static function sanitize_item_ids( $item ) {
|
||||
// lets not delete any options that don't start with jpsq_sync- .
|
||||
if ( ! is_string( $item ) || substr( $item, 0, 5 ) !== 'jpsq_' ) {
|
||||
return null;
|
||||
}
|
||||
// Limit to A-Z,a-z,0-9,_,-,. .
|
||||
return preg_replace( '/[^A-Za-z0-9-_.]/', '', $item );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync package.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* This class will handle checkout of Sync queues for REST Endpoints.
|
||||
*
|
||||
* @since 1.23.1
|
||||
*/
|
||||
class REST_Sender {
|
||||
|
||||
/**
|
||||
* Items pending send.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $items = array();
|
||||
|
||||
/**
|
||||
* Checkout objects from the queue
|
||||
*
|
||||
* @param string $queue_name Name of Queue.
|
||||
* @param int $number_of_items Number of Items.
|
||||
* @param array $args arguments.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function queue_pull( $queue_name, $number_of_items, $args ) {
|
||||
$queue = new Queue( $queue_name );
|
||||
|
||||
if ( 0 === $queue->size() ) {
|
||||
return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 );
|
||||
}
|
||||
|
||||
$sender = Sender::get_instance();
|
||||
|
||||
// try to give ourselves as much time as possible.
|
||||
set_time_limit( 0 );
|
||||
|
||||
if ( ! empty( $args['pop'] ) ) {
|
||||
$buffer = new Queue_Buffer( 'pop', $queue->pop( $number_of_items ) );
|
||||
} else {
|
||||
// let's delete the checkin state.
|
||||
if ( $args['force'] ) {
|
||||
$queue->unlock();
|
||||
}
|
||||
$buffer = $this->get_buffer( $queue, $number_of_items );
|
||||
}
|
||||
// Check that the $buffer is not checkout out already.
|
||||
if ( is_wp_error( $buffer ) ) {
|
||||
return new WP_Error( 'buffer_open', "We couldn't get the buffer it is currently checked out", 400 );
|
||||
}
|
||||
|
||||
if ( ! is_object( $buffer ) ) {
|
||||
return new WP_Error( 'buffer_non-object', 'Buffer is not an object', 400 );
|
||||
}
|
||||
|
||||
$encode = isset( $args['encode'] ) ? $args['encode'] : true;
|
||||
|
||||
Settings::set_is_syncing( true );
|
||||
list( $items_to_send, $skipped_items_ids ) = $sender->get_items_to_send( $buffer, $encode );
|
||||
Settings::set_is_syncing( false );
|
||||
|
||||
return array(
|
||||
'buffer_id' => $buffer->id,
|
||||
'items' => $items_to_send,
|
||||
'skipped_items' => $skipped_items_ids,
|
||||
'codec' => $encode ? $sender->get_codec()->name() : null,
|
||||
'sent_timestamp' => time(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Sync items to local property.
|
||||
*/
|
||||
public function jetpack_sync_send_data_listener() {
|
||||
foreach ( func_get_args()[0] as $key => $item ) {
|
||||
$this->items[ $key ] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check out a buffer of full sync actions.
|
||||
*
|
||||
* @return array Sync Actions to be returned to requestor
|
||||
*/
|
||||
public function immediate_full_sync_pull() {
|
||||
// try to give ourselves as much time as possible.
|
||||
set_time_limit( 0 );
|
||||
|
||||
$original_send_data_cb = array( 'Automattic\Jetpack\Sync\Actions', 'send_data' );
|
||||
$temp_send_data_cb = array( $this, 'jetpack_sync_send_data_listener' );
|
||||
|
||||
Sender::get_instance()->set_enqueue_wait_time( 0 );
|
||||
remove_filter( 'jetpack_sync_send_data', $original_send_data_cb );
|
||||
add_filter( 'jetpack_sync_send_data', $temp_send_data_cb, 10, 6 );
|
||||
Sender::get_instance()->do_full_sync();
|
||||
remove_filter( 'jetpack_sync_send_data', $temp_send_data_cb );
|
||||
add_filter( 'jetpack_sync_send_data', $original_send_data_cb, 10, 6 );
|
||||
|
||||
return array(
|
||||
'items' => $this->items,
|
||||
'codec' => Sender::get_instance()->get_codec()->name(),
|
||||
'sent_timestamp' => time(),
|
||||
'status' => Actions::get_sync_status(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout items out of the sync queue.
|
||||
*
|
||||
* @param Queue $queue Sync Queue.
|
||||
* @param int $number_of_items Number of items to checkout.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
protected function get_buffer( $queue, $number_of_items ) {
|
||||
$start = time();
|
||||
$max_duration = 5; // this will try to get the buffer.
|
||||
|
||||
$buffer = $queue->checkout( $number_of_items );
|
||||
$duration = time() - $start;
|
||||
|
||||
while ( is_wp_error( $buffer ) && $duration < $max_duration ) {
|
||||
sleep( 2 );
|
||||
$duration = time() - $start;
|
||||
$buffer = $queue->checkout( $number_of_items );
|
||||
}
|
||||
|
||||
if ( false === $buffer ) {
|
||||
return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 );
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,971 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync sender.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* This class grabs pending actions from the queue and sends them
|
||||
*/
|
||||
class Sender {
|
||||
/**
|
||||
* Name of the option that stores the time of the next sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
|
||||
|
||||
/**
|
||||
* Sync timeout after a WPCOM error.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const WPCOM_ERROR_SYNC_DELAY = 60;
|
||||
|
||||
/**
|
||||
* Sync timeout after a queue has been locked.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const QUEUE_LOCKED_SYNC_DELAY = 10;
|
||||
|
||||
/**
|
||||
* Maximum bytes to checkout without exceeding the memory limit.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $dequeue_max_bytes;
|
||||
|
||||
/**
|
||||
* Maximum bytes in a single encoded item.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $upload_max_bytes;
|
||||
|
||||
/**
|
||||
* Maximum number of sync items in a single action.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $upload_max_rows;
|
||||
|
||||
/**
|
||||
* Maximum time for perfirming a checkout of items from the queue (in seconds).
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $max_dequeue_time;
|
||||
|
||||
/**
|
||||
* How many seconds to wait after sending sync items after exceeding the sync wait threshold (in seconds).
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $sync_wait_time;
|
||||
|
||||
/**
|
||||
* How much maximum time to wait for the checkout to finish (in seconds).
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $sync_wait_threshold;
|
||||
|
||||
/**
|
||||
* How much maximum time to wait for the sync items to be queued for sending (in seconds).
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $enqueue_wait_time;
|
||||
|
||||
/**
|
||||
* Incremental sync queue object.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var Automattic\Jetpack\Sync\Queue
|
||||
*/
|
||||
private $sync_queue;
|
||||
|
||||
/**
|
||||
* Full sync queue object.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var Automattic\Jetpack\Sync\Queue
|
||||
*/
|
||||
private $full_sync_queue;
|
||||
|
||||
/**
|
||||
* Codec object for encoding and decoding sync items.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var Automattic\Jetpack\Sync\Codec_Interface
|
||||
*/
|
||||
private $codec;
|
||||
|
||||
/**
|
||||
* The current user before we change or clear it.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var \WP_User
|
||||
*/
|
||||
private $old_user;
|
||||
|
||||
/**
|
||||
* Container for the singleton instance of this class.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var Automattic\Jetpack\Sync\Sender
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Retrieve the singleton instance of this class.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return Sender
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* This is necessary because you can't use "new" when you declare instance properties >:(
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->set_defaults();
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the sender.
|
||||
* Prepares the current user and initializes all sync modules.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function init() {
|
||||
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_set_user_from_token' ), 1 );
|
||||
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_clear_user_from_token' ), 20 );
|
||||
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( $this, 'register_jetpack_xmlrpc_methods' ) );
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module->init_before_send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if this is a XMLRPC request with a valid signature.
|
||||
* If so, changes the user to the new one.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function maybe_set_user_from_token() {
|
||||
$connection = new Manager();
|
||||
$verified_user = $connection->verify_xml_rpc_signature();
|
||||
if ( Constants::is_true( 'XMLRPC_REQUEST' ) &&
|
||||
! is_wp_error( $verified_user )
|
||||
&& $verified_user
|
||||
) {
|
||||
$old_user = wp_get_current_user();
|
||||
$this->old_user = isset( $old_user->ID ) ? $old_user->ID : 0;
|
||||
wp_set_current_user( $verified_user['user_id'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we used to have a previous current user, revert back to it.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function maybe_clear_user_from_token() {
|
||||
if ( isset( $this->old_user ) ) {
|
||||
wp_set_current_user( $this->old_user );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next sync time.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $queue_name Name of the queue.
|
||||
* @return float Timestamp of the next sync.
|
||||
*/
|
||||
public function get_next_sync_time( $queue_name ) {
|
||||
return (float) get_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next sync time.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $time Timestamp of the next sync.
|
||||
* @param string $queue_name Name of the queue.
|
||||
* @return boolean True if update was successful, false otherwise.
|
||||
*/
|
||||
public function set_next_sync_time( $time, $queue_name ) {
|
||||
return update_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, $time, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean|WP_Error True if this sync sending was successful, error object otherwise.
|
||||
*/
|
||||
public function do_full_sync() {
|
||||
$sync_module = Modules::get_module( 'full-sync' );
|
||||
if ( ! $sync_module ) {
|
||||
return;
|
||||
}
|
||||
// Full Sync Disabled.
|
||||
if ( ! Settings::get_setting( 'full_sync_sender_enabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't sync if request is marked as read only.
|
||||
if ( Constants::is_true( 'JETPACK_SYNC_READ_ONLY' ) ) {
|
||||
return new WP_Error( 'jetpack_sync_read_only' );
|
||||
}
|
||||
|
||||
// Sync not started or Sync finished.
|
||||
$status = $sync_module->get_status();
|
||||
if ( false === $status['started'] || ( ! empty( $status['started'] ) && ! empty( $status['finished'] ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->continue_full_sync_enqueue();
|
||||
// immediate full sync sends data in continue_full_sync_enqueue.
|
||||
if ( false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
|
||||
return $this->do_sync_and_set_delays( $this->full_sync_queue );
|
||||
} else {
|
||||
$status = $sync_module->get_status();
|
||||
// Sync not started or Sync finished.
|
||||
if ( false === $status['started'] || ( ! empty( $status['started'] ) && ! empty( $status['finished'] ) ) ) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the next sync items for sending.
|
||||
* Will not be done if the current request is a WP import one.
|
||||
* Will be delayed until the next sync time comes.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function continue_full_sync_enqueue() {
|
||||
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->get_next_sync_time( 'full-sync-enqueue' ) > microtime( true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Modules::get_module( 'full-sync' )->continue_enqueuing();
|
||||
|
||||
$this->set_next_sync_time( time() + $this->get_enqueue_wait_time(), 'full-sync-enqueue' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger incremental sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean|WP_Error True if this sync sending was successful, error object otherwise.
|
||||
*/
|
||||
public function do_sync() {
|
||||
if ( ! Settings::is_dedicated_sync_enabled() ) {
|
||||
$result = $this->do_sync_and_set_delays( $this->sync_queue );
|
||||
} else {
|
||||
$result = Dedicated_Sender::spawn_sync( $this->sync_queue );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger incremental sync and early exit on Dedicated Sync request.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param bool $do_real_exit If we should exit at the end of the request. We should by default.
|
||||
* In the context of running this in the REST API, we actually want to return an error.
|
||||
*
|
||||
* @return void|WP_Error
|
||||
*/
|
||||
public function do_dedicated_sync_and_exit( $do_real_exit = true ) {
|
||||
nocache_headers();
|
||||
|
||||
if ( ! Settings::is_dedicated_sync_enabled() ) {
|
||||
return new WP_Error( 'dedicated_sync_disabled', 'Dedicated Sync flow is disabled.' );
|
||||
}
|
||||
|
||||
if ( ! Dedicated_Sender::is_dedicated_sync_request() ) {
|
||||
return new WP_Error( 'non_dedicated_sync_request', 'Not a Dedicated Sync request.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an `OK` to show that Dedicated Sync is enabled and we can process events.
|
||||
* This is used to test the feature is working.
|
||||
*
|
||||
* @see \Automattic\Jetpack\Sync\Dedicated_Sender::can_spawn_dedicated_sync_request
|
||||
*/
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo Dedicated_Sender::DEDICATED_SYNC_VALIDATION_STRING;
|
||||
|
||||
// Try to disconnect the request as quickly as possible and process things in the background.
|
||||
$this->fastcgi_finish_request();
|
||||
|
||||
// Output not used right now. Try to release dedicated sync lock
|
||||
Dedicated_Sender::try_release_lock_spawn_request();
|
||||
|
||||
// Actually try to send Sync events.
|
||||
$result = $this->do_sync_and_set_delays( $this->sync_queue );
|
||||
|
||||
// If no errors occurred, re-spawn a dedicated Sync request.
|
||||
if ( true === $result ) {
|
||||
Dedicated_Sender::spawn_sync( $this->sync_queue );
|
||||
}
|
||||
|
||||
if ( $do_real_exit ) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger sync for a certain sync queue.
|
||||
* Responsible for setting next sync time.
|
||||
* Will not be delayed if the current request is a WP import one.
|
||||
* Will be delayed until the next sync time comes.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue $queue Queue object.
|
||||
*
|
||||
* @return boolean|WP_Error True if this sync sending was successful, error object otherwise.
|
||||
*/
|
||||
public function do_sync_and_set_delays( $queue ) {
|
||||
// Don't sync if importing.
|
||||
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
|
||||
return new WP_Error( 'is_importing' );
|
||||
}
|
||||
|
||||
// Don't sync if request is marked as read only.
|
||||
if ( Constants::is_true( 'JETPACK_SYNC_READ_ONLY' ) ) {
|
||||
return new WP_Error( 'jetpack_sync_read_only' );
|
||||
}
|
||||
|
||||
if ( ! Settings::is_sender_enabled( $queue->id ) ) {
|
||||
return new WP_Error( 'sender_disabled_for_queue_' . $queue->id );
|
||||
}
|
||||
|
||||
// Return early if we've gotten a retry-after header response.
|
||||
$retry_time = get_option( Actions::RETRY_AFTER_PREFIX . $queue->id );
|
||||
if ( $retry_time ) {
|
||||
// If expired update to false but don't send. Send will occurr in new request to avoid race conditions.
|
||||
if ( microtime( true ) > $retry_time ) {
|
||||
update_option( Actions::RETRY_AFTER_PREFIX . $queue->id, false, false );
|
||||
}
|
||||
return new WP_Error( 'retry_after' );
|
||||
}
|
||||
|
||||
// Don't sync if we are throttled.
|
||||
if ( $this->get_next_sync_time( $queue->id ) > microtime( true ) ) {
|
||||
return new WP_Error( 'sync_throttled' );
|
||||
}
|
||||
|
||||
$start_time = microtime( true );
|
||||
|
||||
Settings::set_is_syncing( true );
|
||||
|
||||
$sync_result = $this->do_sync_for_queue( $queue );
|
||||
|
||||
Settings::set_is_syncing( false );
|
||||
|
||||
$exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (float) $this->get_sync_wait_threshold();
|
||||
|
||||
if ( is_wp_error( $sync_result ) ) {
|
||||
if ( 'unclosed_buffer' === $sync_result->get_error_code() ) {
|
||||
$this->set_next_sync_time( time() + self::QUEUE_LOCKED_SYNC_DELAY, $queue->id );
|
||||
}
|
||||
if ( 'wpcom_error' === $sync_result->get_error_code() ) {
|
||||
$this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY, $queue->id );
|
||||
}
|
||||
} elseif ( $exceeded_sync_wait_threshold ) {
|
||||
// If we actually sent data and it took a while, wait before sending again.
|
||||
$this->set_next_sync_time( time() + $this->get_sync_wait_time(), $queue->id );
|
||||
}
|
||||
|
||||
return $sync_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next sync items to send.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param (array|Automattic\Jetpack\Sync\Queue_Buffer) $buffer_or_items Queue buffer or array of objects.
|
||||
* @param boolean $encode Whether to encode the items.
|
||||
* @return array Sync items to send.
|
||||
*/
|
||||
public function get_items_to_send( $buffer_or_items, $encode = true ) {
|
||||
// Track how long we've been processing so we can avoid request timeouts.
|
||||
$start_time = microtime( true );
|
||||
$upload_size = 0;
|
||||
$items_to_send = array();
|
||||
$items = is_array( $buffer_or_items ) ? $buffer_or_items : $buffer_or_items->get_items();
|
||||
if ( ! is_array( $items ) ) {
|
||||
$items = array();
|
||||
}
|
||||
|
||||
// Set up current screen to avoid errors rendering content.
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/screen.php';
|
||||
set_current_screen( 'sync' );
|
||||
$skipped_items_ids = array();
|
||||
/**
|
||||
* We estimate the total encoded size as we go by encoding each item individually.
|
||||
* This is expensive, but the only way to really know :/
|
||||
*/
|
||||
foreach ( $items as $key => $item ) {
|
||||
// Suspending cache addition help prevent overloading in memory cache of large sites.
|
||||
wp_suspend_cache_addition( true );
|
||||
/**
|
||||
* Modify the data within an action before it is serialized and sent to the server
|
||||
* For example, during full sync this expands Post ID's into full Post objects,
|
||||
* so that we don't have to serialize the whole object into the queue.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array The action parameters
|
||||
* @param int The ID of the user who triggered the action
|
||||
*/
|
||||
$item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
|
||||
wp_suspend_cache_addition( false );
|
||||
// Serialization usage can lead to empty, null or false action_name. Lets skip as there is no information to send.
|
||||
if ( empty( $item[0] ) || false === $item[1] ) {
|
||||
$skipped_items_ids[] = $key;
|
||||
continue;
|
||||
}
|
||||
$encoded_item = $this->codec->encode( $item );
|
||||
$upload_size += strlen( $encoded_item );
|
||||
if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
|
||||
break;
|
||||
}
|
||||
$items_to_send[ $key ] = $encode ? $encoded_item : $item;
|
||||
if ( microtime( true ) - $start_time > $this->max_dequeue_time ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $items_to_send, $skipped_items_ids, $items, microtime( true ) - $start_time );
|
||||
}
|
||||
|
||||
/**
|
||||
* If supported, flush all response data to the client and finish the request.
|
||||
* This allows for time consuming tasks to be performed without leaving the connection open.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function fastcgi_finish_request() {
|
||||
if ( function_exists( 'fastcgi_finish_request' ) && version_compare( phpversion(), '7.0.16', '>=' ) ) {
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform sync for a certain sync queue.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Queue $queue Queue object.
|
||||
*
|
||||
* @return boolean|WP_Error True if this sync sending was successful, error object otherwise.
|
||||
*/
|
||||
public function do_sync_for_queue( $queue ) {
|
||||
do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
|
||||
if ( $queue->size() === 0 ) {
|
||||
return new WP_Error( 'empty_queue_' . $queue->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Now that we're sure we are about to sync, try to ignore user abort
|
||||
* so we can avoid getting into a bad state.
|
||||
*/
|
||||
if ( function_exists( 'ignore_user_abort' ) ) {
|
||||
ignore_user_abort( true );
|
||||
}
|
||||
|
||||
/* Don't make the request block till we finish, if possible. */
|
||||
if ( Constants::is_true( 'REST_REQUEST' ) || Constants::is_true( 'XMLRPC_REQUEST' ) ) {
|
||||
$this->fastcgi_finish_request();
|
||||
}
|
||||
|
||||
$checkout_start_time = microtime( true );
|
||||
|
||||
$buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
|
||||
|
||||
if ( ! $buffer ) {
|
||||
// Buffer has no items.
|
||||
return new WP_Error( 'empty_buffer' );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $buffer ) ) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$checkout_duration = microtime( true ) - $checkout_start_time;
|
||||
|
||||
list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $buffer, true );
|
||||
if ( ! empty( $items_to_send ) ) {
|
||||
/**
|
||||
* Fires when data is ready to send to the server.
|
||||
* Return false or WP_Error to abort the sync (e.g. if there's an error)
|
||||
* The items will be automatically re-sent later
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array $data The action buffer
|
||||
* @param string $codec The codec name used to encode the data
|
||||
* @param double $time The current time
|
||||
* @param string $queue The queue used to send ('sync' or 'full_sync')
|
||||
* @param float $checkout_duration The duration of the checkout operation.
|
||||
* @param float $preprocess_duration The duration of the pre-process operation.
|
||||
* @param int $queue_size The size of the sync queue at the time of processing.
|
||||
*/
|
||||
Settings::set_is_sending( true );
|
||||
$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id, $checkout_duration, $preprocess_duration, $queue->size(), $buffer->id );
|
||||
Settings::set_is_sending( false );
|
||||
} else {
|
||||
$processed_item_ids = $skipped_items_ids;
|
||||
$skipped_items_ids = array();
|
||||
}
|
||||
|
||||
if ( 'non-blocking' !== $processed_item_ids ) {
|
||||
if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
|
||||
$checked_in_item_ids = $queue->checkin( $buffer );
|
||||
if ( is_wp_error( $checked_in_item_ids ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
|
||||
$queue->force_checkin();
|
||||
}
|
||||
if ( is_wp_error( $processed_item_ids ) ) {
|
||||
return new WP_Error( 'wpcom_error', $processed_item_ids->get_error_code() );
|
||||
}
|
||||
|
||||
// Returning a wpcom_error is a sign to the caller that we should wait a while before syncing again.
|
||||
return new WP_Error( 'wpcom_error', 'jetpack_sync_send_data_false' );
|
||||
} 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 );
|
||||
}
|
||||
// Also checkin any items that were skipped.
|
||||
if ( count( $skipped_items_ids ) > 0 ) {
|
||||
$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
|
||||
}
|
||||
$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
|
||||
/**
|
||||
* Allows us to keep track of all the actions that have been sent.
|
||||
* Allows us to calculate the progress of specific actions.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array $processed_actions The actions that we send successfully.
|
||||
*/
|
||||
do_action( 'jetpack_sync_processed_actions', $processed_items );
|
||||
$queue->close( $buffer, $processed_item_ids );
|
||||
// Returning a WP_Error is a sign to the caller that we should wait a while before syncing again.
|
||||
if ( $had_wp_error ) {
|
||||
return new WP_Error( 'wpcom_error', $wp_error->get_error_code() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately sends a single item without firing or enqueuing it
|
||||
*
|
||||
* @param string $action_name The action.
|
||||
* @param array $data The data associated with the action.
|
||||
*
|
||||
* @return Items processed. TODO: this doesn't make much sense anymore, it should probably be just a bool.
|
||||
*/
|
||||
public function send_action( $action_name, $data = null ) {
|
||||
if ( ! Settings::is_sender_enabled( 'full_sync' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Compose the data to be sent.
|
||||
$action_to_send = $this->create_action_to_send( $action_name, $data );
|
||||
|
||||
list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $action_to_send, true ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
Settings::set_is_sending( true );
|
||||
$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->get_codec()->name(), microtime( true ), 'immediate-send', 0, $preprocess_duration );
|
||||
Settings::set_is_sending( false );
|
||||
|
||||
/**
|
||||
* Allows us to keep track of all the actions that have been sent.
|
||||
* Allows us to calculate the progress of specific actions.
|
||||
*
|
||||
* @param array $processed_actions The actions that we send successfully.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_processed_actions', $action_to_send );
|
||||
|
||||
return $processed_item_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an synthetic action for direct sending to WPCOM during full sync (for example)
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $action_name The action.
|
||||
* @param array $data The data associated with the action.
|
||||
* @return array An array of synthetic sync actions keyed by current microtime(true)
|
||||
*/
|
||||
private function create_action_to_send( $action_name, $data ) {
|
||||
return array(
|
||||
(string) microtime( true ) => array(
|
||||
$action_name,
|
||||
$data,
|
||||
get_current_user_id(),
|
||||
microtime( true ),
|
||||
Settings::is_importing(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any object that is able to be synced.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args the synchronized object parameters.
|
||||
* @return string Encoded sync object.
|
||||
*/
|
||||
public function sync_object( $args ) {
|
||||
// For example: posts, post, 5.
|
||||
list( $module_name, $object_type, $id ) = $args;
|
||||
|
||||
$sync_module = Modules::get_module( $module_name );
|
||||
$codec = $this->get_codec();
|
||||
|
||||
return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register additional sync XML-RPC methods available to Jetpack for authenticated users.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.8.0
|
||||
*
|
||||
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
|
||||
* @return array Filtered XML-RPC methods.
|
||||
*/
|
||||
public function register_jetpack_xmlrpc_methods( $jetpack_methods ) {
|
||||
$jetpack_methods['jetpack.syncObject'] = array( $this, 'sync_object' );
|
||||
return $jetpack_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the incremental sync queue object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Queue Queue object.
|
||||
*/
|
||||
public function get_sync_queue() {
|
||||
return $this->sync_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full sync queue object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Queue Queue object.
|
||||
*/
|
||||
public function get_full_sync_queue() {
|
||||
return $this->full_sync_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the codec object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return Automattic\Jetpack\Sync\Codec_Interface Codec object.
|
||||
*/
|
||||
public function get_codec() {
|
||||
return $this->codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the codec object.
|
||||
* Use gzip deflate if supported.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_codec() {
|
||||
if ( function_exists( 'gzinflate' ) ) {
|
||||
$this->codec = new JSON_Deflate_Array_Codec();
|
||||
} else {
|
||||
$this->codec = new Simple_Codec();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and send all the checksums.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function send_checksum() {
|
||||
$store = new Replicastore();
|
||||
do_action( 'jetpack_sync_checksum', $store->checksum_all() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the incremental sync queue.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_sync_queue() {
|
||||
$this->sync_queue->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the full sync queue.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_full_sync_queue() {
|
||||
$this->full_sync_queue->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum bytes to checkout without exceeding the memory limit.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $size Maximum bytes to checkout.
|
||||
*/
|
||||
public function set_dequeue_max_bytes( $size ) {
|
||||
$this->dequeue_max_bytes = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum bytes in a single encoded item.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $max_bytes Maximum bytes in a single encoded item.
|
||||
*/
|
||||
public function set_upload_max_bytes( $max_bytes ) {
|
||||
$this->upload_max_bytes = $max_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of sync items in a single action.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $max_rows Maximum number of sync items.
|
||||
*/
|
||||
public function set_upload_max_rows( $max_rows ) {
|
||||
$this->upload_max_rows = $max_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sync wait time (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $seconds Sync wait time.
|
||||
*/
|
||||
public function set_sync_wait_time( $seconds ) {
|
||||
$this->sync_wait_time = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current sync wait time (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return int Sync wait time.
|
||||
*/
|
||||
public function get_sync_wait_time() {
|
||||
return $this->sync_wait_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enqueue wait time (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $seconds Enqueue wait time.
|
||||
*/
|
||||
public function set_enqueue_wait_time( $seconds ) {
|
||||
$this->enqueue_wait_time = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current enqueue wait time (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return int Enqueue wait time.
|
||||
*/
|
||||
public function get_enqueue_wait_time() {
|
||||
return $this->enqueue_wait_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sync wait threshold (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $seconds Sync wait threshold.
|
||||
*/
|
||||
public function set_sync_wait_threshold( $seconds ) {
|
||||
$this->sync_wait_threshold = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current sync wait threshold (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return int Sync wait threshold.
|
||||
*/
|
||||
public function get_sync_wait_threshold() {
|
||||
return $this->sync_wait_threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum time for perfirming a checkout of items from the queue (in seconds).
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $seconds Maximum dequeue time.
|
||||
*/
|
||||
public function set_max_dequeue_time( $seconds ) {
|
||||
$this->max_dequeue_time = $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the sync queues, codec and set the default settings.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->sync_queue = new Queue( 'sync' );
|
||||
$this->full_sync_queue = new Queue( 'full_sync' );
|
||||
$this->set_codec();
|
||||
|
||||
// Saved settings.
|
||||
Settings::set_importing( null );
|
||||
$settings = Settings::get_settings();
|
||||
$this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
|
||||
$this->set_upload_max_bytes( $settings['upload_max_bytes'] );
|
||||
$this->set_upload_max_rows( $settings['upload_max_rows'] );
|
||||
$this->set_sync_wait_time( $settings['sync_wait_time'] );
|
||||
$this->set_enqueue_wait_time( $settings['enqueue_wait_time'] );
|
||||
$this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
|
||||
$this->set_max_dequeue_time( Defaults::get_max_sync_execution_time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sync queues, modules and settings.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
$this->reset_sync_queue();
|
||||
$this->reset_full_sync_queue();
|
||||
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module->reset_data();
|
||||
}
|
||||
|
||||
foreach ( array( 'sync', 'full_sync', 'full-sync-enqueue' ) as $queue_name ) {
|
||||
delete_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name );
|
||||
}
|
||||
|
||||
Settings::reset_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup at the event of plugin uninstallation.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function uninstall() {
|
||||
// Lets delete all the other fun stuff like transient and option and the sync queue.
|
||||
$this->reset_data();
|
||||
|
||||
// Delete the full sync status.
|
||||
delete_option( 'jetpack_full_sync_status' );
|
||||
|
||||
// Clear the sync cron.
|
||||
wp_clear_scheduled_hook( 'jetpack_sync_cron' );
|
||||
wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync server.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Simple version of a Jetpack Sync Server - just receives arrays of events and
|
||||
* issues them locally with the 'jetpack_sync_remote_action' action.
|
||||
*/
|
||||
class Server {
|
||||
/**
|
||||
* Codec used to decode sync events.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var Automattic\Jetpack\Sync\Codec_Interface
|
||||
*/
|
||||
private $codec;
|
||||
|
||||
/**
|
||||
* Maximum time for processing sync actions.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_TIME_PER_REQUEST_IN_SECONDS = 15;
|
||||
|
||||
/**
|
||||
* Prefix of the blog lock transient.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BLOG_LOCK_TRANSIENT_PREFIX = 'jp_sync_req_lock_';
|
||||
|
||||
/**
|
||||
* Lifetime of the blog lock transient.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const BLOG_LOCK_TRANSIENT_EXPIRY = 60; // Seconds.
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* This is necessary because you can't use "new" when you declare instance properties >:(
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->codec = new JSON_Deflate_Array_Codec();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the codec instance.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param Automattic\Jetpack\Sync\Codec_Interface $codec Codec instance.
|
||||
*/
|
||||
public function set_codec( Codec_Interface $codec ) {
|
||||
$this->codec = $codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to lock the request when the server receives concurrent requests from the same blog.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $blog_id ID of the blog.
|
||||
* @param int $expiry Blog lock transient lifetime.
|
||||
* @return boolean True if succeeded, false otherwise.
|
||||
*/
|
||||
public function attempt_request_lock( $blog_id, $expiry = self::BLOG_LOCK_TRANSIENT_EXPIRY ) {
|
||||
$transient_name = $this->get_concurrent_request_transient_name( $blog_id );
|
||||
$locked_time = get_site_transient( $transient_name );
|
||||
if ( $locked_time ) {
|
||||
return false;
|
||||
}
|
||||
set_site_transient( $transient_name, microtime( true ), $expiry );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the blog lock transient name for a particular blog.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $blog_id ID of the blog.
|
||||
* @return string Name of the blog lock transient.
|
||||
*/
|
||||
private function get_concurrent_request_transient_name( $blog_id ) {
|
||||
return self::BLOG_LOCK_TRANSIENT_PREFIX . $blog_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the request lock from a particular blog ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $blog_id ID of the blog.
|
||||
*/
|
||||
public function remove_request_lock( $blog_id ) {
|
||||
delete_site_transient( $this->get_concurrent_request_transient_name( $blog_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process sync events.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $data Sync events.
|
||||
* @param object $token The auth token used to invoke the API.
|
||||
* @param int $sent_timestamp Timestamp (in seconds) when the actions were transmitted.
|
||||
* @param string $queue_id ID of the queue from which the event was sent (`sync` or `full_sync`).
|
||||
* @return array Processed sync events.
|
||||
*/
|
||||
public function receive( $data, $token = null, $sent_timestamp = null, $queue_id = null ) {
|
||||
$start_time = microtime( true );
|
||||
if ( ! is_array( $data ) ) {
|
||||
return new WP_Error( 'action_decoder_error', 'Events must be an array' );
|
||||
}
|
||||
|
||||
if ( $token && ! $this->attempt_request_lock( $token->blog_id ) ) {
|
||||
/**
|
||||
* Fires when the server receives two concurrent requests from the same blog
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param token The token object of the misbehaving site
|
||||
*/
|
||||
do_action( 'jetpack_sync_multi_request_fail', $token );
|
||||
|
||||
return new WP_Error( 'concurrent_request_error', 'There is another request running for the same blog ID' );
|
||||
}
|
||||
|
||||
$events = wp_unslash( array_map( array( $this->codec, 'decode' ), $data ) );
|
||||
$events_processed = array();
|
||||
|
||||
/**
|
||||
* Fires when an array of actions are received from a remote Jetpack site
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array Array of actions received from the remote site
|
||||
*/
|
||||
do_action( 'jetpack_sync_remote_actions', $events, $token );
|
||||
|
||||
foreach ( $events as $key => $event ) {
|
||||
list( $action_name, $args, $user_id, $timestamp, $silent ) = $event;
|
||||
|
||||
/**
|
||||
* Fires when an action is received from a remote Jetpack site
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param string $action_name The name of the action executed on the remote site
|
||||
* @param array $args The arguments passed to the action
|
||||
* @param int $user_id The external_user_id who did the action
|
||||
* @param bool $silent Whether the item was created via import
|
||||
* @param double $timestamp Timestamp (in seconds) when the action occurred
|
||||
* @param double $sent_timestamp Timestamp (in seconds) when the action was transmitted
|
||||
* @param string $queue_id ID of the queue from which the event was sent (sync or full_sync)
|
||||
* @param array $token The auth token used to invoke the API
|
||||
*/
|
||||
do_action( 'jetpack_sync_remote_action', $action_name, $args, $user_id, $silent, $timestamp, $sent_timestamp, $queue_id, $token );
|
||||
|
||||
$events_processed[] = $key;
|
||||
|
||||
if ( microtime( true ) - $start_time > self::MAX_TIME_PER_REQUEST_IN_SECONDS ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $token ) {
|
||||
$this->remove_request_lock( $token->blog_id );
|
||||
}
|
||||
|
||||
return $events_processed;
|
||||
}
|
||||
}
|
@ -0,0 +1,588 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync settings.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* Class to manage the sync settings.
|
||||
*/
|
||||
class Settings {
|
||||
/**
|
||||
* Prefix, used for the sync settings option names.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SETTINGS_OPTION_PREFIX = 'jetpack_sync_settings_';
|
||||
|
||||
/**
|
||||
* A whitelist of valid settings.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $valid_settings = array(
|
||||
'dequeue_max_bytes' => true,
|
||||
'upload_max_bytes' => true,
|
||||
'upload_max_rows' => true,
|
||||
'sync_wait_time' => true,
|
||||
'sync_wait_threshold' => true,
|
||||
'enqueue_wait_time' => true,
|
||||
'max_queue_size' => true,
|
||||
'max_queue_lag' => true,
|
||||
'queue_max_writes_sec' => true,
|
||||
'post_types_blacklist' => true,
|
||||
'taxonomies_blacklist' => true,
|
||||
'disable' => true,
|
||||
'network_disable' => true,
|
||||
'render_filtered_content' => true,
|
||||
'post_meta_whitelist' => true,
|
||||
'comment_meta_whitelist' => true,
|
||||
'max_enqueue_full_sync' => true,
|
||||
'max_queue_size_full_sync' => true,
|
||||
'sync_via_cron' => true,
|
||||
'cron_sync_time_limit' => true,
|
||||
'known_importers' => true,
|
||||
'term_relationships_full_sync_item_size' => true,
|
||||
'sync_sender_enabled' => true,
|
||||
'full_sync_sender_enabled' => true,
|
||||
'full_sync_send_duration' => true,
|
||||
'full_sync_limits' => true,
|
||||
'checksum_disable' => true,
|
||||
'dedicated_sync_enabled' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether WordPress is currently running an import.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var null|boolean
|
||||
*/
|
||||
public static $is_importing;
|
||||
|
||||
/**
|
||||
* Whether WordPress is currently running a WP cron request.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var null|boolean
|
||||
*/
|
||||
public static $is_doing_cron;
|
||||
|
||||
/**
|
||||
* Whether we're currently syncing.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var null|boolean
|
||||
*/
|
||||
public static $is_syncing;
|
||||
|
||||
/**
|
||||
* Whether we're currently sending sync items.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var null|boolean
|
||||
*/
|
||||
public static $is_sending;
|
||||
|
||||
/**
|
||||
* Retrieve all settings with their current values.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array All current settings.
|
||||
*/
|
||||
public static function get_settings() {
|
||||
$settings = array();
|
||||
foreach ( array_keys( self::$valid_settings ) as $setting ) {
|
||||
$settings[ $setting ] = self::get_setting( $setting );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the setting. It saves it if the setting doesn't exist, so that it gets
|
||||
* autoloaded on page load rather than re-queried every time.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param string $setting The setting name.
|
||||
* @return mixed The setting value.
|
||||
*/
|
||||
public static function get_setting( $setting ) {
|
||||
if ( ! isset( self::$valid_settings[ $setting ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( self::is_network_setting( $setting ) ) {
|
||||
if ( is_multisite() ) {
|
||||
$value = get_site_option( self::SETTINGS_OPTION_PREFIX . $setting );
|
||||
} else {
|
||||
// On single sites just return the default setting.
|
||||
return Defaults::get_default_setting( $setting );
|
||||
}
|
||||
} else {
|
||||
$value = get_option( self::SETTINGS_OPTION_PREFIX . $setting );
|
||||
}
|
||||
|
||||
if ( false === $value ) { // No default value is set.
|
||||
$value = Defaults::get_default_setting( $setting );
|
||||
if ( self::is_network_setting( $setting ) ) {
|
||||
update_site_option( self::SETTINGS_OPTION_PREFIX . $setting, $value );
|
||||
} else {
|
||||
// We set one so that it gets autoloaded.
|
||||
update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_numeric( $value ) ) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
$default_array_value = null;
|
||||
switch ( $setting ) {
|
||||
case 'post_types_blacklist':
|
||||
$default_array_value = Defaults::$blacklisted_post_types;
|
||||
break;
|
||||
case 'taxonomies_blacklist':
|
||||
$default_array_value = Defaults::$blacklisted_taxonomies;
|
||||
break;
|
||||
case 'post_meta_whitelist':
|
||||
$default_array_value = Defaults::get_post_meta_whitelist();
|
||||
break;
|
||||
case 'comment_meta_whitelist':
|
||||
$default_array_value = Defaults::get_comment_meta_whitelist();
|
||||
break;
|
||||
case 'known_importers':
|
||||
$default_array_value = Defaults::get_known_importers();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $default_array_value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$value = array_unique( array_merge( $value, $default_array_value ) );
|
||||
} else {
|
||||
$value = $default_array_value;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change multiple settings in the same time.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param array $new_settings The new settings.
|
||||
*/
|
||||
public static function update_settings( $new_settings ) {
|
||||
$validated_settings = array_intersect_key( $new_settings, self::$valid_settings );
|
||||
foreach ( $validated_settings as $setting => $value ) {
|
||||
|
||||
if ( self::is_network_setting( $setting ) ) {
|
||||
if ( is_multisite() && is_main_site() ) {
|
||||
$updated = update_site_option( self::SETTINGS_OPTION_PREFIX . $setting, $value );
|
||||
}
|
||||
} else {
|
||||
$updated = update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true );
|
||||
}
|
||||
|
||||
// If we set the disabled option to true, clear the queues.
|
||||
if ( ( 'disable' === $setting || 'network_disable' === $setting ) && (bool) $value ) {
|
||||
$listener = Listener::get_instance();
|
||||
$listener->get_sync_queue()->reset();
|
||||
$listener->get_full_sync_queue()->reset();
|
||||
}
|
||||
|
||||
// Do not enable Dedicated Sync if we cannot spawn a Dedicated Sync request.
|
||||
if ( 'dedicated_sync_enabled' === $setting && $updated && (bool) $value ) {
|
||||
if ( ! Dedicated_Sender::can_spawn_dedicated_sync_request() ) {
|
||||
update_option( self::SETTINGS_OPTION_PREFIX . $setting, 0, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the specified setting is a network setting.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param string $setting Setting name.
|
||||
* @return boolean Whether the setting is a network setting.
|
||||
*/
|
||||
public static function is_network_setting( $setting ) {
|
||||
return strpos( $setting, 'network_' ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for blacklisted post types.
|
||||
* Can be injected directly into a WHERE clause.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @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' ) ) ) . '\')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped values for disallowed post types.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Post type filter values
|
||||
*/
|
||||
public static function get_disallowed_post_types_structured() {
|
||||
return array(
|
||||
'post_type' => array(
|
||||
'operator' => 'NOT IN',
|
||||
'values' => array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for blacklisted taxonomies.
|
||||
* Can be injected directly into a WHERE clause.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @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' ) ) ) . "')";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for blacklisted post meta.
|
||||
* Can be injected directly into a WHERE clause.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @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' ) ) ) . '\')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for allowed post meta keys.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Meta keys filter values
|
||||
*/
|
||||
public static function get_allowed_post_meta_structured() {
|
||||
return array(
|
||||
'meta_key' => array(
|
||||
'operator' => 'IN',
|
||||
'values' => array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns structured SQL clause for blacklisted taxonomies.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array taxonomies filter values
|
||||
*/
|
||||
public static function get_blacklisted_taxonomies_structured() {
|
||||
return array(
|
||||
'taxonomy' => array(
|
||||
'operator' => 'NOT IN',
|
||||
'values' => array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns structured SQL clause for allowed taxonomies.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array taxonomies filter values
|
||||
*/
|
||||
public static function get_allowed_taxonomies_structured() {
|
||||
global $wp_taxonomies;
|
||||
|
||||
$allowed_taxonomies = array_keys( $wp_taxonomies );
|
||||
$allowed_taxonomies = array_diff( $allowed_taxonomies, self::get_setting( 'taxonomies_blacklist' ) );
|
||||
return array(
|
||||
'taxonomy' => array(
|
||||
'operator' => 'IN',
|
||||
'values' => array_map( 'esc_sql', $allowed_taxonomies ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for blacklisted comment meta.
|
||||
* Can be injected directly into a WHERE clause.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @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' ) ) ) . '\')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL-escaped values for allowed post meta keys.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Meta keys filter values
|
||||
*/
|
||||
public static function get_allowed_comment_meta_structured() {
|
||||
return array(
|
||||
'meta_key' => array(
|
||||
'operator' => 'IN',
|
||||
'values' => array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL-escaped values for allowed order_item meta keys.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Meta keys filter values
|
||||
*/
|
||||
public static function get_allowed_order_itemmeta_structured() {
|
||||
// Make sure that we only try to add the properties when the class exists.
|
||||
if ( ! class_exists( '\Automattic\Jetpack\Sync\Modules\WooCommerce' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$values = \Automattic\Jetpack\Sync\Modules\WooCommerce::$order_item_meta_whitelist;
|
||||
|
||||
return array(
|
||||
'meta_key' => array(
|
||||
'operator' => 'IN',
|
||||
'values' => array_map( 'esc_sql', $values ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped SQL for comments, excluding any spam comments.
|
||||
* Can be injected directly into a WHERE clause.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return string SQL WHERE clause.
|
||||
*/
|
||||
public static function get_comments_filter_sql() {
|
||||
return "comment_approved <> 'spam'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any settings options and clean up the current settings state.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function reset_data() {
|
||||
$valid_settings = self::$valid_settings;
|
||||
foreach ( $valid_settings as $option => $value ) {
|
||||
delete_option( self::SETTINGS_OPTION_PREFIX . $option );
|
||||
}
|
||||
self::set_importing( null );
|
||||
self::set_doing_cron( null );
|
||||
self::set_is_syncing( null );
|
||||
self::set_is_sending( null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the importing state.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param boolean $is_importing Whether WordPress is currently importing.
|
||||
*/
|
||||
public static function set_importing( $is_importing ) {
|
||||
// Set to NULL to revert to WP_IMPORTING, the standard behavior.
|
||||
self::$is_importing = $is_importing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether WordPress is currently importing.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether WordPress is currently importing.
|
||||
*/
|
||||
public static function is_importing() {
|
||||
if ( self::$is_importing !== null ) {
|
||||
return self::$is_importing;
|
||||
}
|
||||
|
||||
return defined( 'WP_IMPORTING' ) && WP_IMPORTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether sync is enabled.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether sync is enabled.
|
||||
*/
|
||||
public static function is_sync_enabled() {
|
||||
return ! ( self::get_setting( 'disable' ) || self::get_setting( 'network_disable' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the WP cron state.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param boolean $is_doing_cron Whether WordPress is currently doing WP cron.
|
||||
*/
|
||||
public static function set_doing_cron( $is_doing_cron ) {
|
||||
// Set to NULL to revert to WP_IMPORTING, the standard behavior.
|
||||
self::$is_doing_cron = $is_doing_cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether WordPress is currently doing WP cron.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether WordPress is currently doing WP cron.
|
||||
*/
|
||||
public static function is_doing_cron() {
|
||||
if ( self::$is_doing_cron !== null ) {
|
||||
return self::$is_doing_cron;
|
||||
}
|
||||
|
||||
return defined( 'DOING_CRON' ) && DOING_CRON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are currently syncing.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether we are currently syncing.
|
||||
*/
|
||||
public static function is_syncing() {
|
||||
return (bool) self::$is_syncing || ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the syncing state.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param boolean $is_syncing Whether we are currently syncing.
|
||||
*/
|
||||
public static function set_is_syncing( $is_syncing ) {
|
||||
self::$is_syncing = $is_syncing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we are currently sending sync items.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether we are currently sending sync items.
|
||||
*/
|
||||
public static function is_sending() {
|
||||
return (bool) self::$is_sending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sending state.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param boolean $is_sending Whether we are currently sending sync items.
|
||||
*/
|
||||
public static function set_is_sending( $is_sending ) {
|
||||
self::$is_sending = $is_sending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether should send from the queue
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param string $queue_id The queue identifier.
|
||||
*
|
||||
* @return boolean Whether sync is enabled.
|
||||
*/
|
||||
public static function is_sender_enabled( $queue_id ) {
|
||||
return (bool) self::get_setting( $queue_id . '_sender_enabled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether checksums are enabled.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether sync is enabled.
|
||||
*/
|
||||
public static function is_checksum_enabled() {
|
||||
return ! (bool) self::get_setting( 'checksum_disable' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether dedicated Sync flow is enabled.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return boolean Whether dedicated Sync flow is enabled.
|
||||
*/
|
||||
public static function is_dedicated_sync_enabled() {
|
||||
return (bool) self::get_setting( 'dedicated_sync_enabled' );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Simple codec for encoding and decoding sync objects.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* An implementation of Automattic\Jetpack\Sync\Codec_Interface that uses base64
|
||||
* algorithm to compress objects serialized using json_encode.
|
||||
*/
|
||||
class Simple_Codec extends JSON_Deflate_Array_Codec {
|
||||
/**
|
||||
* Name of the codec.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CODEC_NAME = 'simple';
|
||||
|
||||
/**
|
||||
* Retrieve the name of the codec.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string Name of the codec.
|
||||
*/
|
||||
public function name() {
|
||||
return self::CODEC_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a sync object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $object Sync object to encode.
|
||||
* @return string Encoded sync object.
|
||||
*/
|
||||
public function encode( $object ) {
|
||||
// This is intentionally using base64_encode().
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return base64_encode( $this->json_serialize( $object ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a sync object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $input Encoded sync object to decode.
|
||||
* @return mixed Decoded sync object.
|
||||
*/
|
||||
public function decode( $input ) {
|
||||
// This is intentionally using base64_decode().
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
return $this->json_unserialize( base64_decode( $input ) );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync for users.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
|
||||
use Automattic\Jetpack\Connection\XMLRPC_Async_Call;
|
||||
use Automattic\Jetpack\Roles;
|
||||
|
||||
/**
|
||||
* Class Users.
|
||||
*
|
||||
* Responsible for syncing user data changes.
|
||||
*/
|
||||
class Users {
|
||||
/**
|
||||
* Roles of all users, indexed by user ID.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $user_roles = array();
|
||||
|
||||
/**
|
||||
* Initialize sync for user data changes.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @todo Eventually, connection needs to be instantiated at the top level in the sync package.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'jetpack_user_authorized', array( 'Automattic\\Jetpack\\Sync\\Actions', 'do_initial_sync' ), 10, 0 );
|
||||
$connection = new Jetpack_Connection();
|
||||
if ( $connection->has_connected_user() ) {
|
||||
// Kick off synchronization of user role when it changes.
|
||||
add_action( 'set_user_role', array( __CLASS__, 'user_role_change' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize connected user role changes.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
*/
|
||||
public static function user_role_change( $user_id ) {
|
||||
$connection = new Jetpack_Connection();
|
||||
if ( $connection->is_user_connected( $user_id ) ) {
|
||||
self::update_role_on_com( $user_id );
|
||||
// Try to choose a new master if we're demoting the current one.
|
||||
self::maybe_demote_master_user( $user_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the role of a user by their ID.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @return string Role of the user.
|
||||
*/
|
||||
public static function get_role( $user_id ) {
|
||||
if ( isset( self::$user_roles[ $user_id ] ) ) {
|
||||
return self::$user_roles[ $user_id ];
|
||||
}
|
||||
|
||||
$current_user_id = get_current_user_id();
|
||||
wp_set_current_user( $user_id );
|
||||
$roles = new Roles();
|
||||
$role = $roles->translate_current_user_to_role();
|
||||
wp_set_current_user( $current_user_id );
|
||||
self::$user_roles[ $user_id ] = $role;
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the signed role of a user by their ID.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @return string Signed role of the user.
|
||||
*/
|
||||
public static function get_signed_role( $user_id ) {
|
||||
$connection = new Jetpack_Connection();
|
||||
return $connection->sign_role( self::get_role( $user_id ), $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the signed role and update it in WP.com for that user.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
*/
|
||||
public static function update_role_on_com( $user_id ) {
|
||||
$signed_role = self::get_signed_role( $user_id );
|
||||
XMLRPC_Async_Call::add_call( 'jetpack.updateRole', get_current_user_id(), $user_id, $signed_role );
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose a new master user if we're demoting the current one.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @todo Disconnect if there is no user with enough capabilities to be the master user.
|
||||
* @uses \WP_User_Query
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
*/
|
||||
public static function maybe_demote_master_user( $user_id ) {
|
||||
$master_user_id = (int) \Jetpack_Options::get_option( 'master_user' );
|
||||
$role = self::get_role( $user_id );
|
||||
if ( $user_id === $master_user_id && 'administrator' !== $role ) {
|
||||
$query = new \WP_User_Query(
|
||||
array(
|
||||
'fields' => array( 'ID' ),
|
||||
'role' => 'administrator',
|
||||
'orderby' => 'ID',
|
||||
'exclude' => array( $master_user_id ),
|
||||
)
|
||||
);
|
||||
$new_master = false;
|
||||
$connection = new Jetpack_Connection();
|
||||
foreach ( $query->results as $result ) {
|
||||
$found_user_id = absint( $result->ID );
|
||||
if ( $found_user_id && $connection->is_user_connected( $found_user_id ) ) {
|
||||
$new_master = $found_user_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $new_master ) {
|
||||
\Jetpack_Options::update_option( 'master_user', $new_master );
|
||||
}
|
||||
// TODO: else disconnect..?
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync utils.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* Class for sync utilities.
|
||||
*/
|
||||
class Utils {
|
||||
/**
|
||||
* Retrieve the values of sync items.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param array $items Array of sync items.
|
||||
* @return array Array of sync item values.
|
||||
*/
|
||||
public static function get_item_values( $items ) {
|
||||
return array_map( array( __CLASS__, 'get_item_value' ), $items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the IDs of sync items.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param array $items Array of sync items.
|
||||
* @return array Array of sync item IDs.
|
||||
*/
|
||||
public static function get_item_ids( $items ) {
|
||||
return array_map( array( __CLASS__, 'get_item_id' ), $items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a sync item.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @param array $item Sync item.
|
||||
* @return mixed Sync item value.
|
||||
*/
|
||||
private static function get_item_value( $item ) {
|
||||
return $item->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of a sync item.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @param array $item Sync item.
|
||||
* @return int Sync item ID.
|
||||
*/
|
||||
private static function get_item_id( $item ) {
|
||||
return $item->id;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface for encoding and decoding sync objects.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* Very simple interface for encoding and decoding input.
|
||||
* This is used to provide compression and serialization to messages.
|
||||
**/
|
||||
interface Codec_Interface {
|
||||
/**
|
||||
* Retrieve the name of the codec.
|
||||
* We send this with the payload so we can select the appropriate decoder at the other end.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string Name of the codec.
|
||||
*/
|
||||
public function name();
|
||||
|
||||
/**
|
||||
* Encode a sync object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $object Sync object to encode.
|
||||
* @return string Encoded sync object.
|
||||
*/
|
||||
public function encode( $object );
|
||||
|
||||
/**
|
||||
* Encode a sync object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $input Encoded sync object to decode.
|
||||
* @return mixed Decoded sync object.
|
||||
*/
|
||||
public function decode( $input );
|
||||
}
|
@ -0,0 +1,566 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync architecture prototype.
|
||||
*
|
||||
* To run tests: phpunit --testsuite sync --filter New_Sync
|
||||
*
|
||||
* @author Dan Walmsley
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync;
|
||||
|
||||
/**
|
||||
* A high-level interface for objects that store synced WordPress data.
|
||||
* Useful for ensuring that different storage mechanisms implement the
|
||||
* required semantics for storing all the data that we sync.
|
||||
*/
|
||||
interface Replicastore_Interface {
|
||||
/**
|
||||
* Empty and reset the replicastore.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Ran when full sync has just started.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
*/
|
||||
public function full_sync_start( $config );
|
||||
|
||||
/**
|
||||
* Ran when full sync has just finished.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $checksum Deprecated since 7.3.0.
|
||||
*/
|
||||
public function full_sync_end( $checksum );
|
||||
|
||||
/**
|
||||
* Retrieve the number of posts with a particular post status within a certain range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Prepare the SQL query before executing it.
|
||||
*
|
||||
* @param string $status Post status.
|
||||
* @param int $min_id Minimum post ID.
|
||||
* @param int $max_id Maximum post ID.
|
||||
*/
|
||||
public function post_count( $status = null, $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve the posts with a particular post status.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $status Post status.
|
||||
* @param int $min_id Minimum post ID.
|
||||
* @param int $max_id Maximum post ID.
|
||||
*/
|
||||
public function get_posts( $status = null, $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve a post object by the post ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Post ID.
|
||||
*/
|
||||
public function get_post( $id );
|
||||
|
||||
/**
|
||||
* Update or insert a post.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Post $post Post object.
|
||||
* @param bool $silent Whether to perform a silent action.
|
||||
*/
|
||||
public function upsert_post( $post, $silent = false );
|
||||
|
||||
/**
|
||||
* Delete a post by the post ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function delete_post( $post_id );
|
||||
|
||||
/**
|
||||
* Retrieve the checksum for posts within a range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $min_id Minimum post ID.
|
||||
* @param int $max_id Maximum post ID.
|
||||
*/
|
||||
public function posts_checksum( $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve the checksum for post meta within a range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $min_id Minimum post meta ID.
|
||||
* @param int $max_id Maximum post meta ID.
|
||||
*/
|
||||
public function post_meta_checksum( $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve the number of comments with a particular comment status within a certain range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $status Comment status.
|
||||
* @param int $min_id Minimum comment ID.
|
||||
* @param int $max_id Maximum comment ID.
|
||||
*/
|
||||
public function comment_count( $status = null, $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve the comments with a particular comment status.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $status Comment status.
|
||||
* @param int $min_id Minimum comment ID.
|
||||
* @param int $max_id Maximum comment ID.
|
||||
*/
|
||||
public function get_comments( $status = null, $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve a comment object by the comment ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Comment ID.
|
||||
*/
|
||||
public function get_comment( $id );
|
||||
|
||||
/**
|
||||
* Update or insert a comment.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Comment $comment Comment object.
|
||||
*/
|
||||
public function upsert_comment( $comment );
|
||||
|
||||
/**
|
||||
* Trash a comment by the comment ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
*/
|
||||
public function trash_comment( $comment_id );
|
||||
|
||||
/**
|
||||
* Mark a comment by the comment ID as spam.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
*/
|
||||
public function spam_comment( $comment_id );
|
||||
|
||||
/**
|
||||
* Delete a comment by the comment ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $comment_id Comment ID.
|
||||
*/
|
||||
public function delete_comment( $comment_id );
|
||||
|
||||
/**
|
||||
* Trash the comments of a post.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param array $statuses Post statuses.
|
||||
*/
|
||||
public function trashed_post_comments( $post_id, $statuses );
|
||||
|
||||
/**
|
||||
* Untrash the comments of a post.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function untrashed_post_comments( $post_id );
|
||||
|
||||
/**
|
||||
* Retrieve the checksum for comments within a range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $min_id Minimum comment ID.
|
||||
* @param int $max_id Maximum comment ID.
|
||||
*/
|
||||
public function comments_checksum( $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Retrieve the checksum for comment meta within a range.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $min_id Minimum comment meta ID.
|
||||
* @param int $max_id Maximum comment meta ID.
|
||||
*/
|
||||
public function comment_meta_checksum( $min_id = null, $max_id = null );
|
||||
|
||||
/**
|
||||
* Update the value of an option.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @param mixed $value Option value.
|
||||
*/
|
||||
public function update_option( $option, $value );
|
||||
|
||||
/**
|
||||
* Retrieve an option value based on an option name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Name of option to retrieve.
|
||||
* @param mixed $default Optional. Default value to return if the option does not exist.
|
||||
*/
|
||||
public function get_option( $option, $default = false );
|
||||
|
||||
/**
|
||||
* Remove an option by name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Name of option to remove.
|
||||
*/
|
||||
public function delete_option( $option );
|
||||
|
||||
/**
|
||||
* Change the info of the current theme.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $theme_info Theme info array.
|
||||
*/
|
||||
public function set_theme_info( $theme_info );
|
||||
|
||||
/**
|
||||
* Whether the current theme supports a certain feature.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $feature Name of the feature.
|
||||
*/
|
||||
public function current_theme_supports( $feature );
|
||||
|
||||
/**
|
||||
* Retrieve metadata for the specified object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Meta type.
|
||||
* @param int $object_id ID of the object.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param bool $single If true, return only the first value of the specified meta_key.
|
||||
*/
|
||||
public function get_metadata( $type, $object_id, $meta_key = '', $single = false );
|
||||
|
||||
/**
|
||||
* Stores remote meta key/values alongside an ID mapping key.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Meta type.
|
||||
* @param int $object_id ID of the object.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param mixed $meta_value Meta value.
|
||||
* @param int $meta_id ID of the meta.
|
||||
*/
|
||||
public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id );
|
||||
|
||||
/**
|
||||
* Delete metadata for the specified object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Meta type.
|
||||
* @param int $object_id ID of the object.
|
||||
* @param array $meta_ids IDs of the meta objects to delete.
|
||||
*/
|
||||
public function delete_metadata( $type, $object_id, $meta_ids );
|
||||
|
||||
/**
|
||||
* Delete metadata with a certain key for the specified objects.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Meta type.
|
||||
* @param array $object_ids IDs of the objects.
|
||||
* @param string $meta_key Meta key.
|
||||
*/
|
||||
public function delete_batch_metadata( $type, $object_ids, $meta_key );
|
||||
|
||||
/**
|
||||
* Retrieve value of a constant based on the constant name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $constant Name of constant to retrieve.
|
||||
*/
|
||||
public function get_constant( $constant );
|
||||
|
||||
/**
|
||||
* Set the value of a constant.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $constant Name of constant to retrieve.
|
||||
* @param mixed $value Value set for the constant.
|
||||
*/
|
||||
public function set_constant( $constant, $value );
|
||||
|
||||
/**
|
||||
* Retrieve the number of the available updates of a certain type.
|
||||
* Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Type of updates to retrieve.
|
||||
*/
|
||||
public function get_updates( $type );
|
||||
|
||||
/**
|
||||
* Set the available updates of a certain type.
|
||||
* Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Type of updates to set.
|
||||
* @param int $updates Total number of updates.
|
||||
*/
|
||||
public function set_updates( $type, $updates );
|
||||
|
||||
/**
|
||||
* Retrieve a callable value based on its name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $callable Name of the callable to retrieve.
|
||||
*/
|
||||
public function get_callable( $callable );
|
||||
|
||||
/**
|
||||
* Update the value of a callable.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $callable Callable name.
|
||||
* @param mixed $value Callable value.
|
||||
*/
|
||||
public function set_callable( $callable, $value );
|
||||
|
||||
/**
|
||||
* Retrieve a network option value based on a network option name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Name of network option to retrieve.
|
||||
*/
|
||||
public function get_site_option( $option );
|
||||
|
||||
/**
|
||||
* Update the value of a network option.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Network option name.
|
||||
* @param mixed $value Network option value.
|
||||
*/
|
||||
public function update_site_option( $option, $value );
|
||||
|
||||
/**
|
||||
* Remove a network option by name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Name of option to remove.
|
||||
*/
|
||||
public function delete_site_option( $option );
|
||||
|
||||
/**
|
||||
* Retrieve the terms from a particular taxonomy.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function get_terms( $taxonomy );
|
||||
|
||||
/**
|
||||
* Retrieve a particular term.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
* @param int $term_id ID of the term.
|
||||
* @param string $term_key ID Field `term_id` or `term_taxonomy_id`.
|
||||
*/
|
||||
public function get_term( $taxonomy, $term_id, $term_key = 'term_id' );
|
||||
|
||||
/**
|
||||
* Insert or update a term.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Term $term_object Term object.
|
||||
*/
|
||||
public function update_term( $term_object );
|
||||
|
||||
/**
|
||||
* Delete a term by the term ID and its corresponding taxonomy.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $term_id Term ID.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function delete_term( $term_id, $taxonomy );
|
||||
|
||||
/**
|
||||
* Retrieve all terms from a taxonomy that are related to an object with a particular ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $object_id Object ID.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function get_the_terms( $object_id, $taxonomy );
|
||||
|
||||
/**
|
||||
* Add/update terms of a particular taxonomy of an object with the specified ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $object_id The object to relate to.
|
||||
* @param string $taxonomy The context in which to relate the term to the object.
|
||||
* @param string|int|array $terms A single term slug, single term id, or array of either term slugs or ids.
|
||||
* @param bool $append Optional. If false will delete difference of terms. Default false.
|
||||
*/
|
||||
public function update_object_terms( $object_id, $taxonomy, $terms, $append );
|
||||
|
||||
/**
|
||||
* Remove certain term relationships from the specified object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor to not use interpolated values when preparing the SQL query.
|
||||
*
|
||||
* @param int $object_id ID of the object.
|
||||
* @param array $tt_ids Term taxonomy IDs.
|
||||
*/
|
||||
public function delete_object_terms( $object_id, $tt_ids );
|
||||
|
||||
/**
|
||||
* Retrieve the number of users.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function user_count();
|
||||
|
||||
/**
|
||||
* Retrieve a user object by the user ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function get_user( $user_id );
|
||||
|
||||
/**
|
||||
* Insert or update a user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_User $user User object.
|
||||
*/
|
||||
public function upsert_user( $user );
|
||||
|
||||
/**
|
||||
* Delete a user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function delete_user( $user_id );
|
||||
|
||||
/**
|
||||
* Update/insert user locale.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $locale The user locale.
|
||||
*/
|
||||
public function upsert_user_locale( $user_id, $locale );
|
||||
|
||||
/**
|
||||
* Delete user locale.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function delete_user_locale( $user_id );
|
||||
|
||||
/**
|
||||
* Retrieve the user locale.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function get_user_locale( $user_id );
|
||||
|
||||
/**
|
||||
* Retrieve the allowed mime types for the user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function get_allowed_mime_types( $user_id );
|
||||
|
||||
/**
|
||||
* Retrieve all the checksums we are interested in.
|
||||
* Currently that is posts, comments, post meta and comment meta.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function checksum_all();
|
||||
|
||||
/**
|
||||
* Retrieve the checksum histogram for a specific object type.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param int $buckets Number of buckets to split the objects to.
|
||||
* @param int $start_id Minimum object ID.
|
||||
* @param int $end_id Maximum object ID.
|
||||
*/
|
||||
public function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null );
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Attachments sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
/**
|
||||
* Class to handle sync for attachments.
|
||||
*/
|
||||
class Attachments extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'attachments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize attachment action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'add_attachment', array( $this, 'process_add' ) );
|
||||
add_action( 'attachment_updated', array( $this, 'process_update' ), 10, 3 );
|
||||
add_action( 'jetpack_sync_save_update_attachment', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_save_add_attachment', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_save_attach_attachment', $callable, 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the creation of a new attachment.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $attachment_id ID of the attachment.
|
||||
*/
|
||||
public function process_add( $attachment_id ) {
|
||||
$attachment = get_post( $attachment_id );
|
||||
/**
|
||||
* Fires when the client needs to sync an new attachment
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param int Attachment ID.
|
||||
* @param \WP_Post Attachment post object.
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_add_attachment', $attachment_id, $attachment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle updating an existing attachment.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param \WP_Post $attachment_after Attachment post object before the update.
|
||||
* @param \WP_Post $attachment_before Attachment post object after the update.
|
||||
*/
|
||||
public function process_update( $attachment_id, $attachment_after, $attachment_before ) {
|
||||
// Check whether attachment was added to a post for the first time.
|
||||
if ( 0 === $attachment_before->post_parent && 0 !== $attachment_after->post_parent ) {
|
||||
/**
|
||||
* Fires when an existing attachment is added to a post for the first time
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 6.6.0
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param \WP_Post $attachment_after Attachment post object after the update.
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_attach_attachment', $attachment_id, $attachment_after );
|
||||
} else {
|
||||
/**
|
||||
* Fires when the client needs to sync an updated attachment
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @param \WP_Post $attachment_after Attachment post object after the update.
|
||||
*
|
||||
* Previously this action was synced using jetpack_sync_save_add_attachment action.
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_update_attachment', $attachment_id, $attachment_after );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,642 @@
|
||||
<?php
|
||||
/**
|
||||
* Callables sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
use Automattic\Jetpack\Sync\Dedicated_Sender;
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
use Automattic\Jetpack\Sync\Functions;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for callables.
|
||||
*/
|
||||
class Callables extends Module {
|
||||
/**
|
||||
* Name of the callables checksum option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CALLABLES_CHECKSUM_OPTION_NAME = 'jetpack_callables_sync_checksum';
|
||||
|
||||
/**
|
||||
* Name of the transient for locking callables.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CALLABLES_AWAIT_TRANSIENT_NAME = 'jetpack_sync_callables_await';
|
||||
|
||||
/**
|
||||
* Whitelist for callables we want to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $callable_whitelist;
|
||||
|
||||
/**
|
||||
* For some options, we should always send the change right away!
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS = array(
|
||||
'jetpack_active_modules',
|
||||
'home', // option is home, callable is home_url.
|
||||
'siteurl',
|
||||
'jetpack_sync_error_idc',
|
||||
'paused_plugins',
|
||||
'paused_themes',
|
||||
|
||||
);
|
||||
|
||||
const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS_NEXT_TICK = array(
|
||||
'stylesheet',
|
||||
);
|
||||
/**
|
||||
* Setting this value to true will make it so that the callables will not be unlocked
|
||||
* but the lock will be removed after content is send so that callables will be
|
||||
* sent in the next request.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $force_send_callables_on_next_tick = false;
|
||||
|
||||
/**
|
||||
* For some options, the callable key differs from the option name/key
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const OPTION_NAMES_TO_CALLABLE_NAMES = array(
|
||||
// @TODO: Audit the other option names for differences between the option names and callable names.
|
||||
'home' => 'home_url',
|
||||
'siteurl' => 'site_url',
|
||||
'jetpack_active_modules' => 'active_modules',
|
||||
);
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'functions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
* Define the callable whitelist based on whether this is a single site or a multisite installation.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
if ( is_multisite() ) {
|
||||
$this->callable_whitelist = array_merge( Defaults::get_callable_whitelist(), Defaults::get_multisite_callable_whitelist() );
|
||||
} else {
|
||||
$this->callable_whitelist = Defaults::get_callable_whitelist();
|
||||
}
|
||||
$this->force_send_callables_on_next_tick = false; // Resets here as well mostly for tests.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize callables action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'jetpack_sync_callable', $callable, 10, 2 );
|
||||
add_action( 'current_screen', array( $this, 'set_plugin_action_links' ), 9999 ); // Should happen very late.
|
||||
|
||||
foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option ) {
|
||||
add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable' ) );
|
||||
add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable' ) );
|
||||
}
|
||||
|
||||
foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS_NEXT_TICK as $option ) {
|
||||
add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable_next_tick' ) );
|
||||
add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable_next_tick' ) );
|
||||
}
|
||||
|
||||
// Provide a hook so that hosts can send changes to certain callables right away.
|
||||
// Especially useful when a host uses constants to change home and siteurl.
|
||||
add_action( 'jetpack_sync_unlock_sync_callable', array( $this, 'unlock_sync_callable' ) );
|
||||
|
||||
// get_plugins and wp_version
|
||||
// gets fired when new code gets installed, updates etc.
|
||||
add_action( 'upgrader_process_complete', array( $this, 'unlock_plugin_action_link_and_callables' ) );
|
||||
add_action( 'update_option_active_plugins', array( $this, 'unlock_plugin_action_link_and_callables' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize callables action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_callables', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_callables' ) );
|
||||
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform module cleanup.
|
||||
* Deletes any transients and options that this module uses.
|
||||
* Usually triggered when uninstalling the plugin.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
delete_option( self::CALLABLES_CHECKSUM_OPTION_NAME );
|
||||
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
|
||||
|
||||
$url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
|
||||
foreach ( $url_callables as $callable ) {
|
||||
delete_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callable whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $callables The new callables whitelist.
|
||||
*/
|
||||
public function set_callable_whitelist( $callables ) {
|
||||
$this->callable_whitelist = $callables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the callable whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array The callables whitelist.
|
||||
*/
|
||||
public function get_callable_whitelist() {
|
||||
return $this->callable_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all callables as per the current callables whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array All callables.
|
||||
*/
|
||||
public function get_all_callables() {
|
||||
// get_all_callables should run as the master user always.
|
||||
$current_user_id = get_current_user_id();
|
||||
wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
|
||||
$callables = array_combine(
|
||||
array_keys( $this->get_callable_whitelist() ),
|
||||
array_map( array( $this, 'get_callable' ), array_values( $this->get_callable_whitelist() ) )
|
||||
);
|
||||
wp_set_current_user( $current_user_id );
|
||||
return $callables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular callable.
|
||||
* Used as a wrapper to standartize invocation.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param callable $callable Callable to invoke.
|
||||
* @return mixed Return value of the callable, null if not callable.
|
||||
*/
|
||||
private function get_callable( $callable ) {
|
||||
if ( is_callable( $callable ) ) {
|
||||
return call_user_func( $callable );
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the callable actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all callables to the server
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean Whether to expand callables (should always be true)
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_callables', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the callable actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $status This Module Full Sync Status.
|
||||
*
|
||||
* @return array This Module Full Sync Status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $status ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_callables', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_callables' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock callables so they would be available for syncing again.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlock_sync_callable() {
|
||||
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock callables on the next tick.
|
||||
* Sometime the true callable values are only present on the next tick.
|
||||
* When switching themes for example.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlock_sync_callable_next_tick() {
|
||||
$this->force_send_callables_on_next_tick = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock callables and plugin action links.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function unlock_plugin_action_link_and_callables() {
|
||||
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
|
||||
delete_transient( 'jetpack_plugin_api_action_links_refresh' );
|
||||
add_filter( 'jetpack_check_and_send_callables', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and store the plugin action links if on the plugins page.
|
||||
*
|
||||
* @uses \DOMDocument
|
||||
* @uses libxml_use_internal_errors
|
||||
* @uses mb_convert_encoding
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_plugin_action_links() {
|
||||
if (
|
||||
! class_exists( '\DOMDocument' ) ||
|
||||
! function_exists( 'libxml_use_internal_errors' ) ||
|
||||
! function_exists( 'mb_convert_encoding' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_screeen = get_current_screen();
|
||||
|
||||
$plugins_action_links = array();
|
||||
// Is the transient lock in place?
|
||||
$plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh', false );
|
||||
if ( ! empty( $plugins_lock ) && ( isset( $current_screeen->id ) && 'plugins' !== $current_screeen->id ) ) {
|
||||
return;
|
||||
}
|
||||
$plugins = array_keys( Functions::get_plugins() );
|
||||
foreach ( $plugins as $plugin_file ) {
|
||||
/**
|
||||
* Plugins often like to unset things but things break if they are not able to.
|
||||
*/
|
||||
$action_links = array(
|
||||
'deactivate' => '',
|
||||
'activate' => '',
|
||||
'details' => '',
|
||||
'delete' => '',
|
||||
'edit' => '',
|
||||
);
|
||||
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
|
||||
$action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, null, 'all' );
|
||||
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
|
||||
$action_links = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, null, 'all' );
|
||||
// Verify $action_links is still an array to resolve warnings from filters not returning an array.
|
||||
if ( is_array( $action_links ) ) {
|
||||
$action_links = array_filter( $action_links );
|
||||
} else {
|
||||
$action_links = array();
|
||||
}
|
||||
$formatted_action_links = null;
|
||||
if ( ! empty( $action_links ) && count( $action_links ) > 0 ) {
|
||||
$dom_doc = new \DOMDocument();
|
||||
foreach ( $action_links as $action_link ) {
|
||||
// The @ is not enough to suppress errors when dealing with libxml,
|
||||
// we have to tell it directly how we want to handle errors.
|
||||
libxml_use_internal_errors( true );
|
||||
$dom_doc->loadHTML( mb_convert_encoding( $action_link, 'HTML-ENTITIES', 'UTF-8' ) );
|
||||
libxml_use_internal_errors( false );
|
||||
|
||||
$link_elements = $dom_doc->getElementsByTagName( 'a' );
|
||||
if ( 0 === $link_elements->length ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link_element = $link_elements->item( 0 );
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
if ( $link_element->hasAttribute( 'href' ) && $link_element->nodeValue ) {
|
||||
$link_url = trim( $link_element->getAttribute( 'href' ) );
|
||||
|
||||
// Add the full admin path to the url if the plugin did not provide it.
|
||||
$link_url_scheme = wp_parse_url( $link_url, PHP_URL_SCHEME );
|
||||
if ( empty( $link_url_scheme ) ) {
|
||||
$link_url = admin_url( $link_url );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$formatted_action_links[ $link_element->nodeValue ] = $link_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $formatted_action_links ) {
|
||||
$plugins_action_links[ $plugin_file ] = $formatted_action_links;
|
||||
}
|
||||
}
|
||||
// Cache things for a long time.
|
||||
set_transient( 'jetpack_plugin_api_action_links_refresh', time(), DAY_IN_SECONDS );
|
||||
update_option( 'jetpack_plugin_api_action_links', $plugins_action_links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a certain callable should be sent.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $callable_checksums Callable checksums.
|
||||
* @param string $name Name of the callable.
|
||||
* @param string $checksum A checksum of the callable.
|
||||
* @return boolean Whether to send the callable.
|
||||
*/
|
||||
public function should_send_callable( $callable_checksums, $name, $checksum ) {
|
||||
$idc_override_callables = array(
|
||||
'main_network_site',
|
||||
'home_url',
|
||||
'site_url',
|
||||
);
|
||||
if ( in_array( $name, $idc_override_callables, true ) && \Jetpack_Options::get_option( 'migrate_for_idc' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! $this->still_valid_checksum( $callable_checksums, $name, $checksum );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the callables if we're supposed to.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function maybe_sync_callables() {
|
||||
$callables = $this->get_all_callables();
|
||||
if ( ! apply_filters( 'jetpack_check_and_send_callables', false ) ) {
|
||||
/**
|
||||
* Treating Dedicated Sync requests a bit differently from normal. We want to send callables
|
||||
* normally with all Sync actions, no matter if they were with admin action origin or not,
|
||||
* since Dedicated Sync runs out of bound and the requests are never coming from an admin.
|
||||
*/
|
||||
if ( ! is_admin() && ! Dedicated_Sender::is_dedicated_sync_request() ) {
|
||||
// If we're not an admin and we're not doing cron and this isn't WP_CLI, don't sync anything.
|
||||
if ( ! Settings::is_doing_cron() && ! Jetpack_Constants::get_constant( 'WP_CLI' ) ) {
|
||||
return;
|
||||
}
|
||||
// If we're not an admin and we are doing cron, sync the Callables that are always supposed to sync ( See https://github.com/Automattic/jetpack/issues/12924 ).
|
||||
$callables = $this->get_always_sent_callables();
|
||||
}
|
||||
if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) {
|
||||
if ( $this->force_send_callables_on_next_tick ) {
|
||||
$this->unlock_sync_callable();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $callables ) ) {
|
||||
return;
|
||||
}
|
||||
// No need to set the transiant we are trying to remove it anyways.
|
||||
if ( ! $this->force_send_callables_on_next_tick ) {
|
||||
set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_callables_wait_time );
|
||||
}
|
||||
|
||||
$callable_checksums = (array) \Jetpack_Options::get_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
|
||||
$has_changed = false;
|
||||
// Only send the callables that have changed.
|
||||
foreach ( $callables as $name => $value ) {
|
||||
$checksum = $this->get_check_sum( $value );
|
||||
|
||||
// Explicitly not using Identical comparison as get_option returns a string.
|
||||
if ( $value !== null && $this->should_send_callable( $callable_checksums, $name, $checksum ) ) {
|
||||
|
||||
// Only send callable if the non sorted checksum also does not match.
|
||||
if ( $this->should_send_callable( $callable_checksums, $name, $this->get_check_sum( $value, false ) ) ) {
|
||||
|
||||
/**
|
||||
* Tells the client to sync a callable (aka function) to the server
|
||||
*
|
||||
* @param string The name of the callable
|
||||
* @param mixed The value of the callable
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_callable', $name, $value );
|
||||
}
|
||||
|
||||
$callable_checksums[ $name ] = $checksum;
|
||||
$has_changed = true;
|
||||
} else {
|
||||
$callable_checksums[ $name ] = $checksum;
|
||||
}
|
||||
}
|
||||
if ( $has_changed ) {
|
||||
\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
|
||||
}
|
||||
|
||||
if ( $this->force_send_callables_on_next_tick ) {
|
||||
$this->unlock_sync_callable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the callables that should always be sent, e.g. on cron.
|
||||
*
|
||||
* @return array Callables that should always be sent
|
||||
*/
|
||||
protected function get_always_sent_callables() {
|
||||
$callables = $this->get_all_callables();
|
||||
$cron_callables = array();
|
||||
foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option_name ) {
|
||||
if ( array_key_exists( $option_name, $callables ) ) {
|
||||
$cron_callables[ $option_name ] = $callables[ $option_name ];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the Callable name/key for the option, if different from option name.
|
||||
if ( array_key_exists( $option_name, self::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
|
||||
$callable_name = self::OPTION_NAMES_TO_CALLABLE_NAMES[ $option_name ];
|
||||
if ( array_key_exists( $callable_name, $callables ) ) {
|
||||
$cron_callables[ $callable_name ] = $callables[ $callable_name ];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $cron_callables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the callables within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_callables( $args ) {
|
||||
if ( $args[0] ) {
|
||||
$callables = $this->get_all_callables();
|
||||
$callables_checksums = array();
|
||||
foreach ( $callables as $name => $value ) {
|
||||
$callables_checksums[ $name ] = $this->get_check_sum( $value );
|
||||
}
|
||||
\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callables_checksums );
|
||||
return $callables;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return count( $this->get_callable_whitelist() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of callables by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
if ( empty( $ids ) || empty( $object_type ) || 'callable' !== $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = array();
|
||||
foreach ( (array) $ids as $id ) {
|
||||
$object = $this->get_object_by_id( $object_type, $id );
|
||||
|
||||
if ( 'CALLABLE-DOES-NOT-EXIST' !== $object ) {
|
||||
if ( 'all' === $id ) {
|
||||
// If all was requested it contains all options and can simply be returned.
|
||||
return $object;
|
||||
}
|
||||
$objects[ $id ] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a callable by its name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param string $id ID of the sync object.
|
||||
* @return mixed Value of Callable.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'callable' === $object_type ) {
|
||||
|
||||
// Only whitelisted options can be returned.
|
||||
if ( array_key_exists( $id, $this->get_callable_whitelist() ) ) {
|
||||
// requires master user to be in context.
|
||||
$current_user_id = get_current_user_id();
|
||||
wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
|
||||
$callable = $this->get_callable( $this->callable_whitelist[ $id ] );
|
||||
wp_set_current_user( $current_user_id );
|
||||
return $callable;
|
||||
} elseif ( 'all' === $id ) {
|
||||
return $this->get_all_callables();
|
||||
}
|
||||
}
|
||||
|
||||
return 'CALLABLE-DOES-NOT-EXIST';
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,495 @@
|
||||
<?php
|
||||
/**
|
||||
* Comments sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Modules;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for comments.
|
||||
*/
|
||||
class Comments extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'comments';
|
||||
}
|
||||
|
||||
/**
|
||||
* The id field in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id_field() {
|
||||
return 'comment_ID';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return 'comments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a comment by its ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param int $id ID of the sync object.
|
||||
* @return \WP_Comment|bool Filtered \WP_Comment object, or false if the object is not a comment.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
$comment_id = (int) $id;
|
||||
if ( 'comment' === $object_type ) {
|
||||
$comment = get_comment( $comment_id );
|
||||
if ( $comment ) {
|
||||
return $this->filter_comment( $comment );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize comments action listeners.
|
||||
* Also responsible for initializing comment meta listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'wp_insert_comment', $callable, 10, 2 );
|
||||
add_action( 'deleted_comment', $callable );
|
||||
add_action( 'trashed_comment', $callable );
|
||||
add_action( 'spammed_comment', $callable );
|
||||
add_action( 'trashed_post_comments', $callable, 10, 2 );
|
||||
add_action( 'untrash_post_comments', $callable );
|
||||
add_action( 'comment_approved_to_unapproved', $callable );
|
||||
add_action( 'comment_unapproved_to_approved', $callable );
|
||||
add_action( 'jetpack_modified_comment_contents', $callable, 10, 2 );
|
||||
add_action( 'untrashed_comment', $callable, 10, 2 );
|
||||
add_action( 'unspammed_comment', $callable, 10, 2 );
|
||||
add_filter( 'wp_update_comment_data', array( $this, 'handle_comment_contents_modification' ), 10, 3 );
|
||||
|
||||
// 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_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' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_spammed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_unspammed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
|
||||
|
||||
// comment status transitions.
|
||||
add_filter( 'jetpack_sync_before_enqueue_comment_approved_to_unapproved', array( $this, 'only_allow_white_listed_comment_type_transitions' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_comment_unapproved_to_approved', array( $this, 'only_allow_white_listed_comment_type_transitions' ) );
|
||||
|
||||
// Post Actions.
|
||||
add_filter( 'jetpack_sync_before_enqueue_trashed_post_comments', array( $this, 'filter_blacklisted_post_types' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_untrash_post_comments', array( $this, 'filter_blacklisted_post_types' ) );
|
||||
|
||||
/**
|
||||
* Even though it's messy, we implement these hooks because
|
||||
* the edit_comment hook doesn't include the data
|
||||
* so this saves us a DB read for every comment event.
|
||||
*/
|
||||
foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
|
||||
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
|
||||
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
|
||||
add_action( $comment_action_name, $callable, 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for meta changes.
|
||||
$this->init_listeners_for_meta_type( 'comment', $callable );
|
||||
$this->init_meta_whitelist_handler( 'comment', array( $this, 'filter_meta' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for any comment content updates.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $new_comment The new, processed comment data.
|
||||
* @param array $old_comment The old, unslashed comment data.
|
||||
* @param array $new_comment_with_slashes The new, raw comment data.
|
||||
* @return array The new, processed comment data.
|
||||
*/
|
||||
public function handle_comment_contents_modification( $new_comment, $old_comment, $new_comment_with_slashes ) {
|
||||
$changes = array();
|
||||
$content_fields = array(
|
||||
'comment_author',
|
||||
'comment_author_email',
|
||||
'comment_author_url',
|
||||
'comment_content',
|
||||
);
|
||||
foreach ( $content_fields as $field ) {
|
||||
if ( $new_comment_with_slashes[ $field ] !== $old_comment[ $field ] ) {
|
||||
$changes[ $field ] = array( $new_comment[ $field ], $old_comment[ $field ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $changes ) ) {
|
||||
/**
|
||||
* Signals to the sync listener that this comment's contents were modified and a sync action
|
||||
* reflecting the change(s) to the content should be sent
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param int $new_comment['comment_ID'] ID of comment whose content was modified
|
||||
* @param mixed $changes Array of changed comment fields with before and after values
|
||||
*/
|
||||
do_action( 'jetpack_modified_comment_contents', $new_comment['comment_ID'], $changes );
|
||||
}
|
||||
return $new_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize comments action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_comments', $callable ); // Also send comments meta.
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a filtered list of comment types that sync can hook into.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Defaults to [ '', 'trackback', 'pingback' ].
|
||||
*/
|
||||
public function get_whitelisted_comment_types() {
|
||||
/**
|
||||
* Comment types present in this list will sync their status changes to WordPress.com.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.6.0
|
||||
*
|
||||
* @param array A list of comment types.
|
||||
*/
|
||||
return apply_filters(
|
||||
'jetpack_sync_whitelisted_comment_types',
|
||||
array( '', 'comment', 'trackback', 'pingback', 'review' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
|
||||
*
|
||||
* @param array $args Arguments passed to wp_insert_comment, deleted_comment, spammed_comment, etc.
|
||||
*
|
||||
* @return bool or array $args Arguments passed to wp_insert_comment, deleted_comment, spammed_comment, etc.
|
||||
*/
|
||||
public function only_allow_white_listed_comment_types( $args ) {
|
||||
$comment = false;
|
||||
|
||||
if ( isset( $args[1] ) ) {
|
||||
// comment object is available.
|
||||
$comment = $args[1];
|
||||
} elseif ( is_numeric( $args[0] ) ) {
|
||||
// comment_id is available.
|
||||
$comment = get_comment( $args[0] );
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $comment->comment_type )
|
||||
&& ! in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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_id = $args[0];
|
||||
$posts_module = Modules::get_module( 'posts' );
|
||||
|
||||
if ( false !== $posts_module && ! $posts_module->is_post_type_allowed( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
|
||||
*
|
||||
* @param array $args Arguments passed to wp_{old_status}_to_{new_status}.
|
||||
*
|
||||
* @return bool or array $args Arguments passed to wp_{old_status}_to_{new_status}
|
||||
*/
|
||||
public function only_allow_white_listed_comment_type_transitions( $args ) {
|
||||
$comment = $args[0];
|
||||
|
||||
if ( ! in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a comment type is allowed.
|
||||
* A comment type is allowed if it's present in the comment type whitelist.
|
||||
*
|
||||
* @param int $comment_id ID of the comment.
|
||||
* @return boolean Whether the comment type is allowed.
|
||||
*/
|
||||
public function is_comment_type_allowed( $comment_id ) {
|
||||
$comment = get_comment( $comment_id );
|
||||
|
||||
if ( isset( $comment->comment_type ) ) {
|
||||
return in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_wp_insert_comment', array( $this, 'expand_wp_insert_comment' ) );
|
||||
|
||||
foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
|
||||
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
|
||||
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
|
||||
add_filter(
|
||||
'jetpack_sync_before_send_' . $comment_action_name,
|
||||
array(
|
||||
$this,
|
||||
'expand_wp_insert_comment',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the comments actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
global $wpdb;
|
||||
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return int Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT count(*) FROM $wpdb->comments";
|
||||
|
||||
$where_sql = $this->get_where_sql( $config );
|
||||
if ( $where_sql ) {
|
||||
$query .= ' WHERE ' . $where_sql;
|
||||
}
|
||||
|
||||
// TODO: Call $wpdb->prepare on the following query.
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
|
||||
*/
|
||||
public function get_where_sql( $config ) {
|
||||
if ( is_array( $config ) ) {
|
||||
return 'comment_ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
|
||||
}
|
||||
|
||||
return '1=1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_comments' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all the actions that are going to be sent.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $action_names Names of all the actions that will be sent.
|
||||
* @return int Number of actions.
|
||||
*/
|
||||
public function count_full_sync_actions( $action_names ) {
|
||||
return $this->count_actions( $action_names, array( 'jetpack_full_sync_comments' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the comment status change before the data is serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
* @todo This is not used currently - let's implement it.
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array The expanded hook parameters.
|
||||
*/
|
||||
public function expand_wp_comment_status_change( $args ) {
|
||||
return array( $args[0], $this->filter_comment( $args[1] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the comment creation before the data is serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array The expanded hook parameters.
|
||||
*/
|
||||
public function expand_wp_insert_comment( $args ) {
|
||||
return array( $args[0], $this->filter_comment( $args[1] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a comment object to the fields we need.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Comment $comment The unfiltered comment object.
|
||||
* @return \WP_Comment Filtered comment object.
|
||||
*/
|
||||
public function filter_comment( $comment ) {
|
||||
/**
|
||||
* Filters whether to prevent sending comment data to .com
|
||||
*
|
||||
* Passing true to the filter will prevent the comment data from being sent
|
||||
* to the WordPress.com.
|
||||
* Instead we pass data that will still enable us to do a checksum against the
|
||||
* Jetpacks data but will prevent us from displaying the data on in the API as well as
|
||||
* other services.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean false prevent post data from bing synced to WordPress.com
|
||||
* @param mixed $comment WP_COMMENT object
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_sync_prevent_sending_comment_data', false, $comment ) ) {
|
||||
$blocked_comment = new \stdClass();
|
||||
$blocked_comment->comment_ID = $comment->comment_ID;
|
||||
$blocked_comment->comment_date = $comment->comment_date;
|
||||
$blocked_comment->comment_date_gmt = $comment->comment_date_gmt;
|
||||
$blocked_comment->comment_approved = 'jetpack_sync_blocked';
|
||||
return $blocked_comment;
|
||||
}
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a certain comment meta key is whitelisted for sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $meta_key Comment meta key.
|
||||
* @return boolean Whether the meta key is whitelisted.
|
||||
*/
|
||||
public function is_whitelisted_comment_meta( $meta_key ) {
|
||||
return in_array( $meta_key, Settings::get_setting( 'comment_meta_whitelist' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for filtering out non-whitelisted comment meta.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Hook args.
|
||||
* @return array|boolean False if not whitelisted, the original hook args otherwise.
|
||||
*/
|
||||
public function filter_meta( $args ) {
|
||||
if ( $this->is_comment_type_allowed( $args[1] ) && $this->is_whitelisted_comment_meta( $args[2] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the comment IDs to comment objects and meta before being serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array The expanded hook parameters.
|
||||
*/
|
||||
public function expand_comment_ids( $args ) {
|
||||
list( $comment_ids, $previous_interval_end ) = $args;
|
||||
$comments = get_comments(
|
||||
array(
|
||||
'include_unapproved' => true,
|
||||
'comment__in' => $comment_ids,
|
||||
'orderby' => 'comment_ID',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
return array(
|
||||
$comments,
|
||||
$this->get_metadata( $comment_ids, 'comment', Settings::get_setting( 'comment_meta_whitelist' ) ),
|
||||
$previous_interval_end,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
<?php
|
||||
/**
|
||||
* Constants sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
|
||||
/**
|
||||
* Class to handle sync for constants.
|
||||
*/
|
||||
class Constants extends Module {
|
||||
/**
|
||||
* Name of the constants checksum option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CONSTANTS_CHECKSUM_OPTION_NAME = 'jetpack_constants_sync_checksum';
|
||||
|
||||
/**
|
||||
* Name of the transient for locking constants.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CONSTANTS_AWAIT_TRANSIENT_NAME = 'jetpack_sync_constants_await';
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'constants';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize constants action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'jetpack_sync_constant', $callable, 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize constants action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_constants', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_constants' ) );
|
||||
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_constants', array( $this, 'expand_constants' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform module cleanup.
|
||||
* Deletes any transients and options that this module uses.
|
||||
* Usually triggered when uninstalling the plugin.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
delete_option( self::CONSTANTS_CHECKSUM_OPTION_NAME );
|
||||
delete_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constants whitelist.
|
||||
*
|
||||
* @access public
|
||||
* @todo We don't seem to use this one. Should we remove it?
|
||||
*
|
||||
* @param array $constants The new constants whitelist.
|
||||
*/
|
||||
public function set_constants_whitelist( $constants ) {
|
||||
$this->constants_whitelist = $constants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constants whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array The constants whitelist.
|
||||
*/
|
||||
public function get_constants_whitelist() {
|
||||
return Defaults::get_constants_whitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the constants actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
*
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all constants to the server
|
||||
*
|
||||
* @param boolean Whether to expand constants (should always be true)
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_constants', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the constants actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $state This module Full Sync status.
|
||||
*
|
||||
* @return array This module Full Sync status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_constants', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
*
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_constants' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the constants if we're supposed to.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function maybe_sync_constants() {
|
||||
if ( get_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_constants_wait_time );
|
||||
|
||||
$constants = $this->get_all_constants();
|
||||
if ( empty( $constants ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$constants_checksums = (array) get_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, array() );
|
||||
|
||||
foreach ( $constants as $name => $value ) {
|
||||
$checksum = $this->get_check_sum( $value );
|
||||
// Explicitly not using Identical comparison as get_option returns a string.
|
||||
if ( ! $this->still_valid_checksum( $constants_checksums, $name, $checksum ) && $value !== null ) {
|
||||
/**
|
||||
* Tells the client to sync a constant to the server
|
||||
*
|
||||
* @param string The name of the constant
|
||||
* @param mixed The value of the constant
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_constant', $name, $value );
|
||||
$constants_checksums[ $name ] = $checksum;
|
||||
} else {
|
||||
$constants_checksums[ $name ] = $checksum;
|
||||
}
|
||||
}
|
||||
update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, $constants_checksums );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all constants as per the current constants whitelist.
|
||||
* Public so that we don't have to store an option for each constant.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array All constants.
|
||||
*/
|
||||
public function get_all_constants() {
|
||||
$constants_whitelist = $this->get_constants_whitelist();
|
||||
|
||||
return array_combine(
|
||||
$constants_whitelist,
|
||||
array_map( array( $this, 'get_constant' ), $constants_whitelist )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of a constant.
|
||||
* Used as a wrapper to standartize access to constants.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $constant Constant name.
|
||||
*
|
||||
* @return mixed Return value of the constant.
|
||||
*/
|
||||
private function get_constant( $constant ) {
|
||||
return ( defined( $constant ) ) ?
|
||||
constant( $constant )
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the constants within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
*
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_constants( $args ) {
|
||||
if ( $args[0] ) {
|
||||
$constants = $this->get_all_constants();
|
||||
$constants_checksums = array();
|
||||
foreach ( $constants as $name => $value ) {
|
||||
$constants_checksums[ $name ] = $this->get_check_sum( $value );
|
||||
}
|
||||
update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, $constants_checksums );
|
||||
|
||||
return $constants;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return count( $this->get_constants_whitelist() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of constants by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
if ( empty( $ids ) || empty( $object_type ) || 'constant' !== $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = array();
|
||||
foreach ( (array) $ids as $id ) {
|
||||
$object = $this->get_object_by_id( $object_type, $id );
|
||||
|
||||
if ( 'all' === $id ) {
|
||||
// If all was requested it contains all options and can simply be returned.
|
||||
return $object;
|
||||
}
|
||||
$objects[ $id ] = $object;
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a constant by its name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param string $id ID of the sync object.
|
||||
* @return mixed Value of Constant.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'constant' === $object_type ) {
|
||||
|
||||
// Only whitelisted constants can be returned.
|
||||
if ( in_array( $id, $this->get_constants_whitelist(), true ) ) {
|
||||
return $this->get_constant( $id );
|
||||
} elseif ( 'all' === $id ) {
|
||||
return $this->get_all_constants();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
/**
|
||||
* Full sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Actions;
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
use Automattic\Jetpack\Sync\Lock;
|
||||
use Automattic\Jetpack\Sync\Modules;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* This class does a full resync of the database by
|
||||
* sending an outbound action for every single object
|
||||
* that we care about.
|
||||
*/
|
||||
class Full_Sync_Immediately extends Module {
|
||||
/**
|
||||
* Prefix of the full sync status option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_OPTION = 'jetpack_sync_full_status';
|
||||
|
||||
/**
|
||||
* Sync Lock name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOCK_NAME = 'full_sync';
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'full-sync';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $full_sync_config Full sync configuration.
|
||||
*
|
||||
* @return bool Always returns true at success.
|
||||
*/
|
||||
public function start( $full_sync_config = null ) {
|
||||
// There was a full sync in progress.
|
||||
if ( $this->is_started() && ! $this->is_finished() ) {
|
||||
/**
|
||||
* Fires when a full sync is cancelled.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_cancelled' );
|
||||
$this->send_action( 'jetpack_full_sync_cancelled' );
|
||||
}
|
||||
|
||||
// Remove all evidence of previous full sync items and status.
|
||||
$this->reset_data();
|
||||
|
||||
if ( ! is_array( $full_sync_config ) ) {
|
||||
$full_sync_config = Defaults::$default_full_sync_config;
|
||||
if ( is_multisite() ) {
|
||||
$full_sync_config['network_options'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $full_sync_config['users'] ) && 'initial' === $full_sync_config['users'] ) {
|
||||
$full_sync_config['users'] = Modules::get_module( 'users' )->get_initial_sync_user_config();
|
||||
}
|
||||
|
||||
$this->update_status(
|
||||
array(
|
||||
'started' => time(),
|
||||
'config' => $full_sync_config,
|
||||
'progress' => $this->get_initial_progress( $full_sync_config ),
|
||||
)
|
||||
);
|
||||
|
||||
$range = $this->get_content_range( $full_sync_config );
|
||||
/**
|
||||
* Fires when a full sync begins. This action is serialized
|
||||
* and sent to the server so that it knows a full sync is coming.
|
||||
*
|
||||
* @param array $full_sync_config Sync configuration for all sync modules.
|
||||
* @param array $range Range of the sync items, containing min and max IDs for some item types.
|
||||
* @param array $empty The modules with no items to sync during a full sync.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
* @since-jetpack 7.3.0 Added $range arg.
|
||||
* @since-jetpack 7.4.0 Added $empty arg.
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_start', $full_sync_config, $range );
|
||||
$this->send_action( 'jetpack_full_sync_start', array( $full_sync_config, $range ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether full sync has started.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_started() {
|
||||
return (bool) $this->get_status()['started'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the status of the current full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync status.
|
||||
*/
|
||||
public function get_status() {
|
||||
$default = array(
|
||||
'started' => false,
|
||||
'finished' => false,
|
||||
'progress' => array(),
|
||||
'config' => array(),
|
||||
);
|
||||
|
||||
return wp_parse_args( \Jetpack_Options::get_raw_option( self::STATUS_OPTION ), $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the progress percentage of a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_sync_progress_percentage() {
|
||||
if ( ! $this->is_started() || $this->is_finished() ) {
|
||||
return null;
|
||||
}
|
||||
$status = $this->get_status();
|
||||
if ( empty( $status['progress'] ) ) {
|
||||
return null;
|
||||
}
|
||||
$total_items = array_reduce(
|
||||
array_values( $status['progress'] ),
|
||||
function ( $sum, $sync_item ) {
|
||||
return isset( $sync_item['total'] ) ? ( $sum + (int) $sync_item['total'] ) : $sum;
|
||||
},
|
||||
0
|
||||
);
|
||||
$total_sent = array_reduce(
|
||||
array_values( $status['progress'] ),
|
||||
function ( $sum, $sync_item ) {
|
||||
return isset( $sync_item['sent'] ) ? ( $sum + (int) $sync_item['sent'] ) : $sum;
|
||||
},
|
||||
0
|
||||
);
|
||||
return floor( ( $total_sent / $total_items ) * 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether full sync has finished.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_finished() {
|
||||
return (bool) $this->get_status()['finished'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the full sync data.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
$this->clear_status();
|
||||
( new Lock() )->remove( self::LOCK_NAME, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the full sync status options.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function clear_status() {
|
||||
\Jetpack_Options::delete_raw_option( self::STATUS_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of the current full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $values New values to set.
|
||||
*
|
||||
* @return bool True if success.
|
||||
*/
|
||||
public function update_status( $values ) {
|
||||
return $this->set_status( wp_parse_args( $values, $this->get_status() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the status of the current full sync.
|
||||
*
|
||||
* @param array $values New values to set.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean Full sync status.
|
||||
*/
|
||||
public function set_status( $values ) {
|
||||
return \Jetpack_Options::update_raw_option( self::STATUS_OPTION, $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an initial Full Sync configuration get the initial status.
|
||||
*
|
||||
* @param array $full_sync_config Full sync configuration.
|
||||
*
|
||||
* @return array Initial Sent status.
|
||||
*/
|
||||
public function get_initial_progress( $full_sync_config ) {
|
||||
// Set default configuration, calculate totals, and save configuration if totals > 0.
|
||||
$status = array();
|
||||
foreach ( $full_sync_config as $name => $config ) {
|
||||
$module = Modules::get_module( $name );
|
||||
if ( ! $module ) {
|
||||
continue;
|
||||
}
|
||||
$status[ $name ] = array(
|
||||
'total' => $module->total( $config ),
|
||||
'sent' => 0,
|
||||
'finished' => false,
|
||||
);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for content (posts and comments) to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return array Array of range (min ID, max ID, total items) for all content types.
|
||||
*/
|
||||
private function get_content_range() {
|
||||
$range = array();
|
||||
$config = $this->get_status()['config'];
|
||||
// Add range only when syncing all objects.
|
||||
if ( true === isset( $config['posts'] ) && $config['posts'] ) {
|
||||
$range['posts'] = $this->get_range( 'posts' );
|
||||
}
|
||||
|
||||
if ( true === isset( $config['comments'] ) && $config['comments'] ) {
|
||||
$range['comments'] = $this->get_range( 'comments' );
|
||||
}
|
||||
|
||||
return $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range (min ID, max ID and total items) of items to sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Type of sync item to get the range for.
|
||||
*
|
||||
* @return array Array of min ID, max ID and total items in the range.
|
||||
*/
|
||||
public function get_range( $type ) {
|
||||
global $wpdb;
|
||||
if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case 'posts':
|
||||
$table = $wpdb->posts;
|
||||
$id = 'ID';
|
||||
$where_sql = Settings::get_blacklisted_post_types_sql();
|
||||
|
||||
break;
|
||||
case 'comments':
|
||||
$table = $wpdb->comments;
|
||||
$id = 'comment_ID';
|
||||
$where_sql = Settings::get_comments_filter_sql();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Call $wpdb->prepare on the following query.
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
|
||||
if ( isset( $results[0] ) ) {
|
||||
return $results[0];
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue sending instead of enqueueing.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function continue_enqueuing() {
|
||||
$this->continue_sending();
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue sending.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function continue_sending() {
|
||||
// Return early if Full Sync is not running.
|
||||
if ( ! $this->is_started() || $this->get_status()['finished'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return early if we've gotten a retry-after header response.
|
||||
$retry_time = get_option( Actions::RETRY_AFTER_PREFIX . 'immediate-send' );
|
||||
if ( $retry_time ) {
|
||||
// If expired delete but don't send. Send will occurr in new request to avoid race conditions.
|
||||
if ( microtime( true ) > $retry_time ) {
|
||||
update_option( Actions::RETRY_AFTER_PREFIX . 'immediate-send', false, false );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtain send Lock.
|
||||
$lock = new Lock();
|
||||
$lock_expiration = $lock->attempt( self::LOCK_NAME );
|
||||
|
||||
// Return if unable to obtain lock.
|
||||
if ( false === $lock_expiration ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send Full Sync actions.
|
||||
$success = $this->send();
|
||||
|
||||
// Remove lock.
|
||||
if ( $success ) {
|
||||
$lock->remove( self::LOCK_NAME, $lock_expiration );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send the next items to full sync.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function send() {
|
||||
$config = $this->get_status()['config'];
|
||||
|
||||
$max_duration = Settings::get_setting( 'full_sync_send_duration' );
|
||||
$send_until = microtime( true ) + $max_duration;
|
||||
|
||||
$progress = $this->get_status()['progress'];
|
||||
|
||||
foreach ( $this->get_remaining_modules_to_send() as $module ) {
|
||||
$progress[ $module->name() ] = $module->send_full_sync_actions( $config[ $module->name() ], $progress[ $module->name() ], $send_until );
|
||||
if ( isset( $progress[ $module->name() ]['error'] ) ) {
|
||||
unset( $progress[ $module->name() ]['error'] );
|
||||
$this->update_status( array( 'progress' => $progress ) );
|
||||
return false;
|
||||
} elseif ( ! $progress[ $module->name() ]['finished'] ) {
|
||||
$this->update_status( array( 'progress' => $progress ) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->send_full_sync_end();
|
||||
$this->update_status( array( 'progress' => $progress ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Modules that are configured to Full Sync and haven't finished sending
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_remaining_modules_to_send() {
|
||||
$status = $this->get_status();
|
||||
|
||||
return array_filter(
|
||||
Modules::get_modules(),
|
||||
/**
|
||||
* Select configured and not finished modules.
|
||||
*
|
||||
* @return bool
|
||||
* @var $module Module
|
||||
*/
|
||||
function ( $module ) use ( $status ) {
|
||||
// Skip module if not configured for this sync or module is done.
|
||||
if ( ! isset( $status['config'][ $module->name() ] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! $status['config'][ $module->name() ] ) {
|
||||
return false;
|
||||
}
|
||||
if ( isset( $status['progress'][ $module->name() ]['finished'] ) ) {
|
||||
if ( true === $status['progress'][ $module->name() ]['finished'] ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send 'jetpack_full_sync_end' and update 'finished' status.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function send_full_sync_end() {
|
||||
$range = $this->get_content_range();
|
||||
|
||||
/**
|
||||
* Fires when a full sync ends. This action is serialized
|
||||
* and sent to the server.
|
||||
*
|
||||
* @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
|
||||
* @param array $range Range of the sync items, containing min and max IDs for some item types.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
* @since-jetpack 7.3.0 Added $range arg.
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_end', '', $range );
|
||||
$this->send_action( 'jetpack_full_sync_end', array( '', $range ) );
|
||||
|
||||
// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
|
||||
$this->update_status( array( 'finished' => time() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty Function as we don't close buffers on Immediate Full Sync.
|
||||
*
|
||||
* @param array $actions an array of actions, ignored for queueless sync.
|
||||
*/
|
||||
public function update_sent_progress_action( $actions ) { } // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
|
||||
}
|
@ -0,0 +1,730 @@
|
||||
<?php
|
||||
/**
|
||||
* Full sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Listener;
|
||||
use Automattic\Jetpack\Sync\Lock;
|
||||
use Automattic\Jetpack\Sync\Modules;
|
||||
use Automattic\Jetpack\Sync\Queue;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* This class does a full resync of the database by
|
||||
* enqueuing an outbound action for every single object
|
||||
* that we care about.
|
||||
*
|
||||
* This class, and its related class Jetpack_Sync_Module, contain a few non-obvious optimisations that should be explained:
|
||||
* - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
|
||||
* - for each object type, we page through the object IDs and enqueue them by firing some monitored actions
|
||||
* - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
|
||||
* - we fire a trigger for the entire array which the Automattic\Jetpack\Sync\Listener then serializes and queues.
|
||||
*/
|
||||
class Full_Sync extends Module {
|
||||
/**
|
||||
* Prefix of the full sync status option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
|
||||
|
||||
/**
|
||||
* Enqueue Lock name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENQUEUE_LOCK_NAME = 'full_sync_enqueue';
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'full-sync';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
// Synthetic actions for full sync.
|
||||
add_action( 'jetpack_full_sync_start', $callable, 10, 3 );
|
||||
add_action( 'jetpack_full_sync_end', $callable, 10, 2 );
|
||||
add_action( 'jetpack_full_sync_cancelled', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// This is triggered after actions have been processed on the server.
|
||||
add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $module_configs Full sync configuration for all sync modules.
|
||||
* @return bool Always returns true at success.
|
||||
*/
|
||||
public function start( $module_configs = null ) {
|
||||
$was_already_running = $this->is_started() && ! $this->is_finished();
|
||||
|
||||
// Remove all evidence of previous full sync items and status.
|
||||
$this->reset_data();
|
||||
|
||||
if ( $was_already_running ) {
|
||||
/**
|
||||
* Fires when a full sync is cancelled.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_cancelled' );
|
||||
}
|
||||
|
||||
$this->update_status_option( 'started', time() );
|
||||
$this->update_status_option( 'params', $module_configs );
|
||||
|
||||
$enqueue_status = array();
|
||||
$full_sync_config = array();
|
||||
$include_empty = false;
|
||||
$empty = array();
|
||||
|
||||
// Default value is full sync.
|
||||
if ( ! is_array( $module_configs ) ) {
|
||||
$module_configs = array();
|
||||
$include_empty = true;
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module_configs[ $module->name() ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set default configuration, calculate totals, and save configuration if totals > 0.
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module_name = $module->name();
|
||||
$module_config = isset( $module_configs[ $module_name ] ) ? $module_configs[ $module_name ] : false;
|
||||
|
||||
if ( ! $module_config ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'users' === $module_name && 'initial' === $module_config ) {
|
||||
$module_config = $module->get_initial_sync_user_config();
|
||||
}
|
||||
|
||||
$enqueue_status[ $module_name ] = false;
|
||||
|
||||
$total_items = $module->estimate_full_sync_actions( $module_config );
|
||||
|
||||
// If there's information to process, configure this module.
|
||||
if ( $total_items !== null && $total_items > 0 ) {
|
||||
$full_sync_config[ $module_name ] = $module_config;
|
||||
$enqueue_status[ $module_name ] = array(
|
||||
$total_items, // Total.
|
||||
0, // Queued.
|
||||
false, // Current state.
|
||||
);
|
||||
} elseif ( $include_empty && 0 === $total_items ) {
|
||||
$empty[ $module_name ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->set_config( $full_sync_config );
|
||||
$this->set_enqueue_status( $enqueue_status );
|
||||
|
||||
$range = $this->get_content_range( $full_sync_config );
|
||||
/**
|
||||
* Fires when a full sync begins. This action is serialized
|
||||
* and sent to the server so that it knows a full sync is coming.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
* @since-jetpack 7.3.0 Added $range arg.
|
||||
* @since-jetpack 7.4.0 Added $empty arg.
|
||||
*
|
||||
* @param array $full_sync_config Sync configuration for all sync modules.
|
||||
* @param array $range Range of the sync items, containing min and max IDs for some item types.
|
||||
* @param array $empty The modules with no items to sync during a full sync.
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_start', $full_sync_config, $range, $empty );
|
||||
|
||||
$this->continue_enqueuing( $full_sync_config );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the next items to sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $configs Full sync configuration for all sync modules.
|
||||
*/
|
||||
public function continue_enqueuing( $configs = null ) {
|
||||
// Return early if not in progress.
|
||||
if ( ! $this->get_status_option( 'started' ) || $this->get_status_option( 'queue_finished' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to obtain lock.
|
||||
$lock = new Lock();
|
||||
$lock_expiration = $lock->attempt( self::ENQUEUE_LOCK_NAME );
|
||||
|
||||
// Return if unable to obtain lock.
|
||||
if ( false === $lock_expiration ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// enqueue full sync actions.
|
||||
$this->enqueue( $configs );
|
||||
|
||||
// Remove lock.
|
||||
$lock->remove( self::ENQUEUE_LOCK_NAME, $lock_expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Modules that are configured to Full Sync and haven't finished enqueuing
|
||||
*
|
||||
* @param array $configs Full sync configuration for all sync modules.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_remaining_modules_to_enqueue( $configs ) {
|
||||
$enqueue_status = $this->get_enqueue_status();
|
||||
return array_filter(
|
||||
Modules::get_modules(),
|
||||
/**
|
||||
* Select configured and not finished modules.
|
||||
*
|
||||
* @var $module Module
|
||||
* @return bool
|
||||
*/
|
||||
function ( $module ) use ( $configs, $enqueue_status ) {
|
||||
// Skip module if not configured for this sync or module is done.
|
||||
if ( ! isset( $configs[ $module->name() ] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! $configs[ $module->name() ] ) {
|
||||
return false;
|
||||
}
|
||||
if ( isset( $enqueue_status[ $module->name() ][2] ) ) {
|
||||
if ( true === $enqueue_status[ $module->name() ][2] ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the next items to sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $configs Full sync configuration for all sync modules.
|
||||
*/
|
||||
public function enqueue( $configs = null ) {
|
||||
if ( ! $configs ) {
|
||||
$configs = $this->get_config();
|
||||
}
|
||||
|
||||
$enqueue_status = $this->get_enqueue_status();
|
||||
$full_sync_queue = new Queue( 'full_sync' );
|
||||
$available_queue_slots = Settings::get_setting( 'max_queue_size_full_sync' ) - $full_sync_queue->size();
|
||||
|
||||
if ( $available_queue_slots <= 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remaining_items_to_enqueue = min( Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
|
||||
|
||||
/**
|
||||
* If a module exits early (e.g. because it ran out of full sync queue slots, or we ran out of request time)
|
||||
* then it should exit early
|
||||
*/
|
||||
foreach ( $this->get_remaining_modules_to_enqueue( $configs ) as $module ) {
|
||||
list( $items_enqueued, $next_enqueue_state ) = $module->enqueue_full_sync_actions( $configs[ $module->name() ], $remaining_items_to_enqueue, $enqueue_status[ $module->name() ][2] );
|
||||
|
||||
$enqueue_status[ $module->name() ][2] = $next_enqueue_state;
|
||||
|
||||
// If items were processed, subtract them from the limit.
|
||||
if ( $items_enqueued !== null && $items_enqueued > 0 ) {
|
||||
$enqueue_status[ $module->name() ][1] += $items_enqueued;
|
||||
$remaining_items_to_enqueue -= $items_enqueued;
|
||||
}
|
||||
|
||||
if ( 0 >= $remaining_items_to_enqueue || true !== $next_enqueue_state ) {
|
||||
$this->set_enqueue_status( $enqueue_status );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->queue_full_sync_end( $configs );
|
||||
$this->set_enqueue_status( $enqueue_status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue 'jetpack_full_sync_end' and update 'queue_finished' status.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $configs Full sync configuration for all sync modules.
|
||||
*/
|
||||
public function queue_full_sync_end( $configs ) {
|
||||
$range = $this->get_content_range( $configs );
|
||||
|
||||
/**
|
||||
* Fires when a full sync ends. This action is serialized
|
||||
* and sent to the server.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
* @since-jetpack 7.3.0 Added $range arg.
|
||||
*
|
||||
* @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
|
||||
* @param array $range Range of the sync items, containing min and max IDs for some item types.
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_end', '', $range );
|
||||
|
||||
// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
|
||||
$this->update_status_option( 'queue_finished', time(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range (min ID, max ID and total items) of items to sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Type of sync item to get the range for.
|
||||
* @return array Array of min ID, max ID and total items in the range.
|
||||
*/
|
||||
public function get_range( $type ) {
|
||||
global $wpdb;
|
||||
if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case 'posts':
|
||||
$table = $wpdb->posts;
|
||||
$id = 'ID';
|
||||
$where_sql = Settings::get_blacklisted_post_types_sql();
|
||||
|
||||
break;
|
||||
case 'comments':
|
||||
$table = $wpdb->comments;
|
||||
$id = 'comment_ID';
|
||||
$where_sql = Settings::get_comments_filter_sql();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Call $wpdb->prepare on the following query.
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
|
||||
if ( isset( $results[0] ) ) {
|
||||
return $results[0];
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for content (posts and comments) to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $config Full sync configuration for this all sync modules.
|
||||
* @return array Array of range (min ID, max ID, total items) for all content types.
|
||||
*/
|
||||
private function get_content_range( $config ) {
|
||||
$range = array();
|
||||
// Only when we are sending the whole range do we want to send also the range.
|
||||
if ( true === isset( $config['posts'] ) && $config['posts'] ) {
|
||||
$range['posts'] = $this->get_range( 'posts' );
|
||||
}
|
||||
|
||||
if ( true === isset( $config['comments'] ) && $config['comments'] ) {
|
||||
$range['comments'] = $this->get_range( 'comments' );
|
||||
}
|
||||
return $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress after sync modules actions have been processed on the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $actions Actions that have been processed on the server.
|
||||
*/
|
||||
public function update_sent_progress_action( $actions ) {
|
||||
// Quick way to map to first items with an array of arrays.
|
||||
$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
|
||||
|
||||
// Total item counts for each action.
|
||||
$actions_with_total_counts = $this->get_actions_totals( $actions );
|
||||
|
||||
if ( ! $this->is_started() || $this->is_finished() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
|
||||
$this->update_status_option( 'send_started', time() );
|
||||
}
|
||||
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$module_actions = $module->get_full_sync_actions();
|
||||
$status_option_name = "{$module->name()}_sent";
|
||||
$total_option_name = "{$status_option_name}_total";
|
||||
$items_sent = $this->get_status_option( $status_option_name, 0 );
|
||||
$items_sent_total = $this->get_status_option( $total_option_name, 0 );
|
||||
|
||||
foreach ( $module_actions as $module_action ) {
|
||||
if ( isset( $actions_with_counts[ $module_action ] ) ) {
|
||||
$items_sent += $actions_with_counts[ $module_action ];
|
||||
}
|
||||
|
||||
if ( ! empty( $actions_with_total_counts[ $module_action ] ) ) {
|
||||
$items_sent_total += $actions_with_total_counts[ $module_action ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $items_sent > 0 ) {
|
||||
$this->update_status_option( $status_option_name, $items_sent );
|
||||
}
|
||||
|
||||
if ( 0 !== $items_sent_total ) {
|
||||
$this->update_status_option( $total_option_name, $items_sent_total );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
|
||||
$this->update_status_option( 'finished', time() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the progress percentage of a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_sync_progress_percentage() {
|
||||
if ( ! $this->is_started() || $this->is_finished() ) {
|
||||
return null;
|
||||
}
|
||||
$status = $this->get_status();
|
||||
if ( ! $status['queue'] || ! $status['sent'] || ! $status['total'] ) {
|
||||
return null;
|
||||
}
|
||||
$queued_multiplier = 0.1;
|
||||
$sent_multiplier = 0.9;
|
||||
$count_queued = array_reduce(
|
||||
$status['queue'],
|
||||
function ( $sum, $value ) {
|
||||
return $sum + $value;
|
||||
},
|
||||
0
|
||||
);
|
||||
$count_sent = array_reduce(
|
||||
$status['sent'],
|
||||
function ( $sum, $value ) {
|
||||
return $sum + $value;
|
||||
},
|
||||
0
|
||||
);
|
||||
$count_total = array_reduce(
|
||||
$status['total'],
|
||||
function ( $sum, $value ) {
|
||||
return $sum + $value;
|
||||
},
|
||||
0
|
||||
);
|
||||
$percent_queued = ( $count_queued / $count_total ) * $queued_multiplier * 100;
|
||||
$percent_sent = ( $count_sent / $count_total ) * $sent_multiplier * 100;
|
||||
return ceil( $percent_queued + $percent_sent );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the action for an item in the sync queue.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $queue_item Item of the sync queue.
|
||||
* @return string|boolean Name of the action, false if queue item is invalid.
|
||||
*/
|
||||
public function get_action_name( $queue_item ) {
|
||||
if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
|
||||
return $queue_item[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the total number of items we're syncing in a particular queue item (action).
|
||||
* `$queue_item[1]` is expected to contain chunks of items, and `$queue_item[1][0]`
|
||||
* represents the first (and only) chunk of items to sync in that action.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $queue_item Item of the sync queue that corresponds to a particular action.
|
||||
* @return int Total number of items in the action.
|
||||
*/
|
||||
public function get_action_totals( $queue_item ) {
|
||||
if ( is_array( $queue_item ) && isset( $queue_item[1][0] ) ) {
|
||||
if ( is_array( $queue_item[1][0] ) ) {
|
||||
// Let's count the items we sync in this action.
|
||||
return count( $queue_item[1][0] );
|
||||
}
|
||||
// -1 indicates that this action syncs all items by design.
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the total number of items for a set of actions, grouped by action name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $actions An array of actions.
|
||||
* @return array An array, representing the total number of items, grouped per action.
|
||||
*/
|
||||
public function get_actions_totals( $actions ) {
|
||||
$totals = array();
|
||||
|
||||
foreach ( $actions as $action ) {
|
||||
$name = $this->get_action_name( $action );
|
||||
$action_totals = $this->get_action_totals( $action );
|
||||
if ( ! isset( $totals[ $name ] ) ) {
|
||||
$totals[ $name ] = 0;
|
||||
}
|
||||
$totals[ $name ] += $action_totals;
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether full sync has started.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_started() {
|
||||
return (bool) $this->get_status_option( 'started' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether full sync has finished.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_finished() {
|
||||
return (bool) $this->get_status_option( 'finished' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the status of the current full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync status.
|
||||
*/
|
||||
public function get_status() {
|
||||
$status = array(
|
||||
'started' => $this->get_status_option( 'started' ),
|
||||
'queue_finished' => $this->get_status_option( 'queue_finished' ),
|
||||
'send_started' => $this->get_status_option( 'send_started' ),
|
||||
'finished' => $this->get_status_option( 'finished' ),
|
||||
'sent' => array(),
|
||||
'sent_total' => array(),
|
||||
'queue' => array(),
|
||||
'config' => $this->get_status_option( 'params' ),
|
||||
'total' => array(),
|
||||
);
|
||||
|
||||
$enqueue_status = $this->get_enqueue_status();
|
||||
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
$name = $module->name();
|
||||
|
||||
if ( ! isset( $enqueue_status[ $name ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $total, $queued ) = $enqueue_status[ $name ];
|
||||
|
||||
if ( $total ) {
|
||||
$status['total'][ $name ] = $total;
|
||||
}
|
||||
|
||||
if ( $queued ) {
|
||||
$status['queue'][ $name ] = $queued;
|
||||
}
|
||||
|
||||
$sent = $this->get_status_option( "{$name}_sent" );
|
||||
if ( $sent ) {
|
||||
$status['sent'][ $name ] = $sent;
|
||||
}
|
||||
|
||||
$sent_total = $this->get_status_option( "{$name}_sent_total" );
|
||||
if ( $sent_total ) {
|
||||
$status['sent_total'][ $name ] = $sent_total;
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the full sync status options.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function clear_status() {
|
||||
$prefix = self::STATUS_OPTION_PREFIX;
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_started" );
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_params" );
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_queue_finished" );
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_send_started" );
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_finished" );
|
||||
|
||||
$this->delete_enqueue_status();
|
||||
|
||||
foreach ( Modules::get_modules() as $module ) {
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent" );
|
||||
\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent_total" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the full sync data.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
$this->clear_status();
|
||||
$this->delete_config();
|
||||
( new Lock() )->remove( self::ENQUEUE_LOCK_NAME, true );
|
||||
|
||||
$listener = Listener::get_instance();
|
||||
$listener->get_full_sync_queue()->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a full sync status option.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $name Name of the option.
|
||||
* @param mixed $default Default value of the option.
|
||||
* @return mixed Option value.
|
||||
*/
|
||||
private function get_status_option( $name, $default = null ) {
|
||||
$value = \Jetpack_Options::get_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $default );
|
||||
|
||||
return is_numeric( $value ) ? (int) $value : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value of a full sync status option.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $name Name of the option.
|
||||
* @param mixed $value Value of the option.
|
||||
* @param boolean $autoload Whether the option should be autoloaded at the beginning of the request.
|
||||
*/
|
||||
private function update_status_option( $name, $value, $autoload = false ) {
|
||||
\Jetpack_Options::update_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $value, $autoload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the full sync enqueue status.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $new_status The new full sync enqueue status.
|
||||
*/
|
||||
private function set_enqueue_status( $new_status ) {
|
||||
\Jetpack_Options::update_raw_option( 'jetpack_sync_full_enqueue_status', $new_status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete full sync enqueue status.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return boolean Whether the status was deleted.
|
||||
*/
|
||||
private function delete_enqueue_status() {
|
||||
return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_enqueue_status' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current full sync enqueue status.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return array Full sync enqueue status.
|
||||
*/
|
||||
public function get_enqueue_status() {
|
||||
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_enqueue_status' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the full sync enqueue configuration.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $config The new full sync enqueue configuration.
|
||||
*/
|
||||
private function set_config( $config ) {
|
||||
\Jetpack_Options::update_raw_option( 'jetpack_sync_full_config', $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete full sync configuration.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return boolean Whether the configuration was deleted.
|
||||
*/
|
||||
private function delete_config() {
|
||||
return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_config' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current full sync enqueue config.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return array Full sync enqueue config.
|
||||
*/
|
||||
private function get_config() {
|
||||
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Import sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for imports.
|
||||
*/
|
||||
class Import extends Module {
|
||||
|
||||
/**
|
||||
* Tracks which actions have already been synced for the import
|
||||
* to prevent the same event from being triggered a second time.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $synced_actions = array();
|
||||
|
||||
/**
|
||||
* A mapping of action types to sync action name.
|
||||
* Keys are the name of the import action.
|
||||
* Values are the resulting sync action.
|
||||
*
|
||||
* Note: import_done and import_end both intentionally map to
|
||||
* jetpack_sync_import_end, as they both track the same type of action,
|
||||
* the successful completion of an import. Different import plugins use
|
||||
* differently named actions, and this is an attempt to consolidate.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $import_sync_action_map = array(
|
||||
'import_start' => 'jetpack_sync_import_start',
|
||||
'import_done' => 'jetpack_sync_import_end',
|
||||
'import_end' => 'jetpack_sync_import_end',
|
||||
);
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'import';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize imports action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'export_wp', $callable );
|
||||
add_action( 'jetpack_sync_import_start', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_import_end', $callable, 10, 2 );
|
||||
|
||||
// WordPress.
|
||||
add_action( 'import_start', array( $this, 'sync_import_action' ) );
|
||||
|
||||
// Movable type, RSS, Livejournal.
|
||||
add_action( 'import_done', array( $this, 'sync_import_action' ) );
|
||||
|
||||
// WordPress, Blogger, Livejournal, woo tax rate.
|
||||
add_action( 'import_end', array( $this, 'sync_import_action' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
* Define an empty list of synced actions for us to fill later.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->synced_actions = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic handler for import actions.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
|
||||
*/
|
||||
public function sync_import_action( $importer ) {
|
||||
$import_action = current_filter();
|
||||
// Map action to event name.
|
||||
$sync_action = self::$import_sync_action_map[ $import_action ];
|
||||
|
||||
// Only sync each action once per import.
|
||||
if ( array_key_exists( $sync_action, $this->synced_actions ) && $this->synced_actions[ $sync_action ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this action as synced.
|
||||
$this->synced_actions[ $sync_action ] = true;
|
||||
|
||||
// Prefer self-reported $importer value.
|
||||
if ( ! $importer ) {
|
||||
// Fall back to inferring by calling class name.
|
||||
$importer = self::get_calling_importer_class();
|
||||
}
|
||||
|
||||
// Get $importer from known_importers.
|
||||
$known_importers = Settings::get_setting( 'known_importers' );
|
||||
if ( is_string( $importer ) && isset( $known_importers[ $importer ] ) ) {
|
||||
$importer = $known_importers[ $importer ];
|
||||
}
|
||||
|
||||
$importer_name = $this->get_importer_name( $importer );
|
||||
|
||||
switch ( $sync_action ) {
|
||||
case 'jetpack_sync_import_start':
|
||||
/**
|
||||
* Used for syncing the start of an import
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.3.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
|
||||
* @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
|
||||
*/
|
||||
do_action( 'jetpack_sync_import_start', $importer, $importer_name );
|
||||
break;
|
||||
|
||||
case 'jetpack_sync_import_end':
|
||||
/**
|
||||
* Used for syncing the end of an import
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.3.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
|
||||
* @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
|
||||
*/
|
||||
do_action( 'jetpack_sync_import_end', $importer, $importer_name );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the importer.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
|
||||
* @return string Name of the importer, or "Unknown Importer" if importer is unknown.
|
||||
*/
|
||||
private function get_importer_name( $importer ) {
|
||||
$importers = get_importers();
|
||||
return isset( $importers[ $importer ] ) ? $importers[ $importer ][0] : 'Unknown Importer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class that extends `WP_Importer` which is responsible for
|
||||
* the current action. Designed to be used within an action handler.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @return string The name of the calling class, or 'unknown'.
|
||||
*/
|
||||
private static function get_calling_importer_class() {
|
||||
// If WP_Importer doesn't exist, neither will any importer that extends it.
|
||||
if ( ! class_exists( 'WP_Importer', false ) ) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
$action = current_filter();
|
||||
$backtrace = debug_backtrace( false ); //phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.debug_backtrace_optionsFound,WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
|
||||
$do_action_pos = -1;
|
||||
$backtrace_len = count( $backtrace );
|
||||
for ( $i = 0; $i < $backtrace_len; $i++ ) {
|
||||
// Find the location in the stack of the calling action.
|
||||
if ( 'do_action' === $backtrace[ $i ]['function'] && $action === $backtrace[ $i ]['args'][0] ) {
|
||||
$do_action_pos = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the action wasn't called, the calling class is unknown.
|
||||
if ( -1 === $do_action_pos ) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Continue iterating the stack looking for a caller that extends WP_Importer.
|
||||
for ( $i = $do_action_pos + 1; $i < $backtrace_len; $i++ ) {
|
||||
// If there is no class on the trace, continue.
|
||||
if ( ! isset( $backtrace[ $i ]['class'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class_name = $backtrace[ $i ]['class'];
|
||||
|
||||
// Check if the class extends WP_Importer.
|
||||
if ( class_exists( $class_name, false ) ) {
|
||||
$parents = class_parents( $class_name, false );
|
||||
if ( $parents && in_array( 'WP_Importer', $parents, true ) ) {
|
||||
return $class_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've exhausted the stack without a match, the calling class is unknown.
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Menus sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
/**
|
||||
* Class to handle sync for menus.
|
||||
*/
|
||||
class Menus extends Module {
|
||||
/**
|
||||
* Navigation menu items that were added but not synced yet.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $nav_items_just_added = array();
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'menus';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize menus action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'wp_create_nav_menu', $callable, 10, 2 );
|
||||
add_action( 'wp_update_nav_menu', array( $this, 'update_nav_menu' ), 10, 2 );
|
||||
add_action( 'wp_add_nav_menu_item', array( $this, 'update_nav_menu_add_item' ), 10, 3 );
|
||||
add_action( 'wp_update_nav_menu_item', array( $this, 'update_nav_menu_update_item' ), 10, 3 );
|
||||
add_action( 'post_updated', array( $this, 'remove_just_added_menu_item' ), 10, 2 );
|
||||
|
||||
add_action( 'jetpack_sync_updated_nav_menu', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_updated_nav_menu_add_item', $callable, 10, 4 );
|
||||
add_action( 'jetpack_sync_updated_nav_menu_update_item', $callable, 10, 4 );
|
||||
add_action( 'delete_nav_menu', $callable, 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Nav menu update handler.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param array $menu_data An array of menu data.
|
||||
*/
|
||||
public function update_nav_menu( $menu_id, $menu_data = array() ) {
|
||||
if ( empty( $menu_data ) ) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Helps sync log that a nav menu was updated.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param array $menu_data An array of menu data.
|
||||
*/
|
||||
do_action( 'jetpack_sync_updated_nav_menu', $menu_id, $menu_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Nav menu item addition handler.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param int $nav_item_id ID of the new menu item.
|
||||
* @param array $nav_item_args Arguments used to add the menu item.
|
||||
*/
|
||||
public function update_nav_menu_add_item( $menu_id, $nav_item_id, $nav_item_args ) {
|
||||
$menu_data = wp_get_nav_menu_object( $menu_id );
|
||||
$this->nav_items_just_added[] = $nav_item_id;
|
||||
/**
|
||||
* Helps sync log that a new menu item was added.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param array $menu_data An array of menu data.
|
||||
* @param int $nav_item_id ID of the new menu item.
|
||||
* @param array $nav_item_args Arguments used to add the menu item.
|
||||
*/
|
||||
do_action( 'jetpack_sync_updated_nav_menu_add_item', $menu_id, $menu_data, $nav_item_id, $nav_item_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Nav menu item update handler.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param int $nav_item_id ID of the new menu item.
|
||||
* @param array $nav_item_args Arguments used to update the menu item.
|
||||
*/
|
||||
public function update_nav_menu_update_item( $menu_id, $nav_item_id, $nav_item_args ) {
|
||||
if ( in_array( $nav_item_id, $this->nav_items_just_added, true ) ) {
|
||||
return;
|
||||
}
|
||||
$menu_data = wp_get_nav_menu_object( $menu_id );
|
||||
/**
|
||||
* Helps sync log that an update to the menu item happened.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param int $menu_id ID of the menu.
|
||||
* @param array $menu_data An array of menu data.
|
||||
* @param int $nav_item_id ID of the new menu item.
|
||||
* @param array $nav_item_args Arguments used to update the menu item.
|
||||
*/
|
||||
do_action( 'jetpack_sync_updated_nav_menu_update_item', $menu_id, $menu_data, $nav_item_id, $nav_item_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove menu items that have already been saved from the "just added" list.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $nav_item_id ID of the new menu item.
|
||||
* @param \WP_Post $post_after Nav menu item post object after the update.
|
||||
*/
|
||||
public function remove_just_added_menu_item( $nav_item_id, $post_after ) {
|
||||
if ( 'nav_menu_item' !== $post_after->post_type ) {
|
||||
return;
|
||||
}
|
||||
$this->nav_items_just_added = array_diff( $this->nav_items_just_added, array( $nav_item_id ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Meta sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
/**
|
||||
* Class to handle sync for meta.
|
||||
*/
|
||||
class Meta extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation of get_objects_by_id() is a bit hacky since we're not passing in an array of meta IDs,
|
||||
* but instead an array of post or comment IDs for which to retrieve meta for. On top of that,
|
||||
* we also pass in an associative array where we expect there to be 'meta_key' and 'ids' keys present.
|
||||
*
|
||||
* This seemed to be required since if we have missing meta on WP.com and need to fetch it, we don't know what
|
||||
* the meta key is, but we do know that we have missing meta for a given post or comment.
|
||||
*
|
||||
* @todo Refactor the $wpdb->prepare call to use placeholders.
|
||||
*
|
||||
* @param string $object_type The type of object for which we retrieve meta. Either 'post' or 'comment'.
|
||||
* @param array $config Must include 'meta_key' and 'ids' keys.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $config ) {
|
||||
$table = _get_meta_table( $object_type );
|
||||
|
||||
if ( ! $table ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( ! is_array( $config ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$meta_objects = array();
|
||||
foreach ( $config as $item ) {
|
||||
$meta = null;
|
||||
if ( isset( $item['id'] ) && isset( $item['meta_key'] ) ) {
|
||||
$meta = $this->get_object_by_id( $object_type, (int) $item['id'], (string) $item['meta_key'] );
|
||||
}
|
||||
$meta_objects[ $item['id'] . '-' . $item['meta_key'] ] = $meta;
|
||||
}
|
||||
|
||||
return $meta_objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single Meta Result.
|
||||
*
|
||||
* @param string $object_type post, comment, term, user.
|
||||
* @param null $id Object ID.
|
||||
* @param null $meta_key Meta Key.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id = null, $meta_key = null ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! is_int( $id ) || ! is_string( $meta_key ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$table = _get_meta_table( $object_type );
|
||||
$object_id_column = $object_type . '_id';
|
||||
|
||||
// Sanitize so that the array only has integer values.
|
||||
$meta = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT * FROM {$table} WHERE {$object_id_column} = %d AND meta_key = %s",
|
||||
$id,
|
||||
$meta_key
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$meta_objects = null;
|
||||
|
||||
if ( ! is_wp_error( $meta ) && ! empty( $meta ) ) {
|
||||
foreach ( $meta as $meta_entry ) {
|
||||
if ( 'post' === $object_type && strlen( $meta_entry['meta_value'] ) >= Posts::MAX_POST_META_LENGTH ) {
|
||||
$meta_entry['meta_value'] = '';
|
||||
}
|
||||
$meta_objects[] = array(
|
||||
'meta_type' => $object_type,
|
||||
'meta_id' => $meta_entry['meta_id'],
|
||||
'meta_key' => $meta_key,
|
||||
'meta_value' => $meta_entry['meta_value'],
|
||||
'object_id' => $meta_entry[ $object_id_column ],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $meta_objects;
|
||||
}
|
||||
}
|
@ -0,0 +1,604 @@
|
||||
<?php
|
||||
/**
|
||||
* A base abstraction of a sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Functions;
|
||||
use Automattic\Jetpack\Sync\Listener;
|
||||
use Automattic\Jetpack\Sync\Replicastore;
|
||||
use Automattic\Jetpack\Sync\Sender;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Basic methods implemented by Jetpack Sync extensions.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Module {
|
||||
/**
|
||||
* Number of items per chunk when grouping objects for performance reasons.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const ARRAY_CHUNK_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function name();
|
||||
|
||||
/**
|
||||
* The id field in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id_field() {
|
||||
return 'ID';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function table_name() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
|
||||
/**
|
||||
* Retrieve a sync object by its ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param int $id ID of the sync object.
|
||||
* @return mixed Object, or false if the object is invalid.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize callables action listeners.
|
||||
* Override these to set up listeners and set/reset data/defaults.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize module action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform module cleanup.
|
||||
* Usually triggered when uninstalling the plugin.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the module actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
// In subclasses, return the number of actions enqueued, and next module state (true == done).
|
||||
return array( null, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
// In subclasses, return the number of items yet to be enqueued.
|
||||
return null;
|
||||
}
|
||||
|
||||
// phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of actions that we care about.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param array $action_names Action names we're interested in.
|
||||
* @param array $actions_to_count Unfiltered list of actions we want to count.
|
||||
* @return array Number of actions that we're interested in.
|
||||
*/
|
||||
protected function count_actions( $action_names, $actions_to_count ) {
|
||||
return count( array_intersect( $action_names, $actions_to_count ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the checksum of one or more values.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param mixed $values Values to calculate checksum for.
|
||||
* @param bool $sort If $values should have ksort called on it.
|
||||
* @return int The checksum.
|
||||
*/
|
||||
protected function get_check_sum( $values, $sort = true ) {
|
||||
// Associative array order changes the generated checksum value.
|
||||
if ( $sort && is_array( $values ) ) {
|
||||
$this->recursive_ksort( $values );
|
||||
}
|
||||
return crc32( wp_json_encode( Functions::json_wrap( $values ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively call ksort on an Array
|
||||
*
|
||||
* @param array $values Array.
|
||||
*/
|
||||
private function recursive_ksort( &$values ) {
|
||||
ksort( $values );
|
||||
foreach ( $values as &$value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$this->recursive_ksort( $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a particular checksum in a set of checksums is valid.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param array $sums_to_check Array of checksums.
|
||||
* @param string $name Name of the checksum.
|
||||
* @param int $new_sum Checksum to compare against.
|
||||
* @return boolean Whether the checksum is valid.
|
||||
*/
|
||||
protected function still_valid_checksum( $sums_to_check, $name, $new_sum ) {
|
||||
if ( isset( $sums_to_check[ $name ] ) && $sums_to_check[ $name ] === $new_sum ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue all items of a sync type as an action.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $action_name Name of the action.
|
||||
* @param string $table_name Name of the database table.
|
||||
* @param string $id_field Name of the ID field in the database.
|
||||
* @param string $where_sql The SQL WHERE clause to filter to the desired items.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue in the same time.
|
||||
* @param boolean $state Whether enqueueing has finished.
|
||||
* @return array Array, containing the number of chunks and TRUE, indicating enqueueing has finished.
|
||||
*/
|
||||
protected function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql, $max_items_to_enqueue, $state ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $where_sql ) {
|
||||
$where_sql = '1 = 1';
|
||||
}
|
||||
|
||||
$items_per_page = 1000;
|
||||
$page = 1;
|
||||
$chunk_count = 0;
|
||||
$previous_interval_end = $state ? $state : '~0';
|
||||
$listener = Listener::get_instance();
|
||||
|
||||
// Count down from max_id to min_id so we get newest posts/comments/etc first.
|
||||
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
while ( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} AND {$id_field} < {$previous_interval_end} ORDER BY {$id_field} DESC LIMIT {$items_per_page}" ) ) {
|
||||
// Request posts in groups of N for efficiency.
|
||||
$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
|
||||
|
||||
// If we hit our row limit, process and return.
|
||||
if ( $chunk_count + count( $chunked_ids ) >= $max_items_to_enqueue ) {
|
||||
$remaining_items_count = $max_items_to_enqueue - $chunk_count;
|
||||
$remaining_items = array_slice( $chunked_ids, 0, $remaining_items_count );
|
||||
$remaining_items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $remaining_items, $previous_interval_end );
|
||||
$listener->bulk_enqueue_full_sync_actions( $action_name, $remaining_items_with_previous_interval_end );
|
||||
|
||||
$last_chunk = end( $remaining_items );
|
||||
return array( $remaining_items_count + $chunk_count, end( $last_chunk ) );
|
||||
}
|
||||
$chunked_ids_with_previous_end = $this->get_chunks_with_preceding_end( $chunked_ids, $previous_interval_end );
|
||||
|
||||
$listener->bulk_enqueue_full_sync_actions( $action_name, $chunked_ids_with_previous_end );
|
||||
|
||||
$chunk_count += count( $chunked_ids );
|
||||
$page++;
|
||||
// The $ids are ordered in descending order.
|
||||
$previous_interval_end = end( $ids );
|
||||
}
|
||||
|
||||
if ( $wpdb->last_error ) {
|
||||
// return the values that were passed in so all these chunks get retried.
|
||||
return array( $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
return array( $chunk_count, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the Module Full Sync Configuration and Status return the next chunk of items to send.
|
||||
*
|
||||
* @param array $config This module Full Sync configuration.
|
||||
* @param array $status This module Full Sync status.
|
||||
* @param int $chunk_size Chunk size.
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function get_next_chunk( $config, $status, $chunk_size ) {
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
global $wpdb;
|
||||
return $wpdb->get_col(
|
||||
<<<SQL
|
||||
SELECT {$this->id_field()}
|
||||
FROM {$wpdb->{$this->table_name()}}
|
||||
WHERE {$this->get_where_sql( $config )}
|
||||
AND {$this->id_field()} < {$status['last_sent']}
|
||||
ORDER BY {$this->id_field()}
|
||||
DESC LIMIT {$chunk_size}
|
||||
SQL
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial last sent object.
|
||||
*
|
||||
* @return string|array initial status.
|
||||
*/
|
||||
public function get_initial_last_sent() {
|
||||
return '~0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send all items of a sync type as an action.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param string $config Full sync configuration for this module.
|
||||
* @param array $status the current module full sync status.
|
||||
* @param float $send_until timestamp until we want this request to send full sync events.
|
||||
*
|
||||
* @return array Status, the module full sync status updated.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $status, $send_until ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $status['last_sent'] ) ) {
|
||||
$status['last_sent'] = $this->get_initial_last_sent();
|
||||
}
|
||||
|
||||
$limits = Settings::get_setting( 'full_sync_limits' )[ $this->name() ];
|
||||
|
||||
$chunks_sent = 0;
|
||||
// phpcs:ignore WordPress.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;
|
||||
}
|
||||
|
||||
$result = $this->send_action( 'jetpack_full_sync_' . $this->name(), array( $objects, $status['last_sent'] ) );
|
||||
|
||||
if ( is_wp_error( $result ) || $wpdb->last_error ) {
|
||||
$status['error'] = true;
|
||||
return $status;
|
||||
}
|
||||
// The $ids are ordered in descending order.
|
||||
$status['last_sent'] = end( $objects );
|
||||
$status['sent'] += count( $objects );
|
||||
}
|
||||
|
||||
if ( ! $wpdb->last_error ) {
|
||||
$status['finished'] = true;
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately sends a single item without firing or enqueuing it
|
||||
*
|
||||
* @param string $action_name The action.
|
||||
* @param array $data The data associated with the action.
|
||||
*/
|
||||
public function send_action( $action_name, $data = null ) {
|
||||
$sender = Sender::get_instance();
|
||||
return $sender->send_action( $action_name, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve chunk IDs with previous interval end.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param array $chunks All remaining items.
|
||||
* @param int $previous_interval_end The last item from the previous interval.
|
||||
* @return array Chunk IDs with the previous interval end.
|
||||
*/
|
||||
protected function get_chunks_with_preceding_end( $chunks, $previous_interval_end ) {
|
||||
$chunks_with_ends = array();
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$chunks_with_ends[] = array(
|
||||
'ids' => $chunk,
|
||||
'previous_end' => $previous_interval_end,
|
||||
);
|
||||
// Chunks are ordered in descending order.
|
||||
$previous_interval_end = end( $chunk );
|
||||
}
|
||||
return $chunks_with_ends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata of a particular object type within the designated meta key whitelist.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @todo Refactor to use $wpdb->prepare() on the SQL query.
|
||||
*
|
||||
* @param array $ids Object IDs.
|
||||
* @param string $meta_type Meta type.
|
||||
* @param array $meta_key_whitelist Meta key whitelist.
|
||||
* @return array Unserialized meta values.
|
||||
*/
|
||||
protected function get_metadata( $ids, $meta_type, $meta_key_whitelist ) {
|
||||
global $wpdb;
|
||||
$table = _get_meta_table( $meta_type );
|
||||
$id = $meta_type . '_id';
|
||||
if ( ! $table ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$private_meta_whitelist_sql = "'" . implode( "','", array_map( 'esc_sql', $meta_key_whitelist ) ) . "'";
|
||||
|
||||
return array_map(
|
||||
array( $this, 'unserialize_meta' ),
|
||||
$wpdb->get_results(
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
||||
"SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )' .
|
||||
" AND meta_key IN ( $private_meta_whitelist_sql ) ",
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
||||
OBJECT
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize listeners for the particular meta type.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $meta_type Meta type.
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners_for_meta_type( $meta_type, $callable ) {
|
||||
add_action( "added_{$meta_type}_meta", $callable, 10, 4 );
|
||||
add_action( "updated_{$meta_type}_meta", $callable, 10, 4 );
|
||||
add_action( "deleted_{$meta_type}_meta", $callable, 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize meta whitelist handler for the particular meta type.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $meta_type Meta type.
|
||||
* @param callable $whitelist_handler Action handler callable.
|
||||
*/
|
||||
public function init_meta_whitelist_handler( $meta_type, $whitelist_handler ) {
|
||||
add_filter( "jetpack_sync_before_enqueue_added_{$meta_type}_meta", $whitelist_handler );
|
||||
add_filter( "jetpack_sync_before_enqueue_updated_{$meta_type}_meta", $whitelist_handler );
|
||||
add_filter( "jetpack_sync_before_enqueue_deleted_{$meta_type}_meta", $whitelist_handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the term relationships for the specified object IDs.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @todo This feels too specific to be in the abstract sync Module class. Move it?
|
||||
*
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Term relationships - object ID and term taxonomy ID pairs.
|
||||
*/
|
||||
protected function get_term_relationships( $ids ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )', OBJECT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the value of a meta object, if necessary.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param object $meta Meta object.
|
||||
* @return object Meta object with possibly unserialized value.
|
||||
*/
|
||||
public function unserialize_meta( $meta ) {
|
||||
$meta->meta_value = maybe_unserialize( $meta->meta_value );
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of objects by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
if ( empty( $ids ) || empty( $object_type ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = array();
|
||||
foreach ( (array) $ids as $id ) {
|
||||
$object = $this->get_object_by_id( $object_type, $id );
|
||||
|
||||
// Only add object if we have the object.
|
||||
if ( $object ) {
|
||||
$objects[ $id ] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of minimum and maximum object ids for each batch based on the given batch size.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $batch_size The batch size for objects.
|
||||
* @param string|bool $where_sql The sql where clause minus 'WHERE', or false if no where clause is needed.
|
||||
*
|
||||
* @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
|
||||
*/
|
||||
public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $this->table_name() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$results = array();
|
||||
$table = $wpdb->{$this->table_name()};
|
||||
$current_max = 0;
|
||||
$current_min = 1;
|
||||
$id_field = $this->id_field();
|
||||
$replicastore = new Replicastore();
|
||||
|
||||
$total = $replicastore->get_min_max_object_id(
|
||||
$id_field,
|
||||
$table,
|
||||
$where_sql,
|
||||
false
|
||||
);
|
||||
|
||||
while ( $total->max > $current_max ) {
|
||||
$where = $where_sql ?
|
||||
$where_sql . " AND $id_field > $current_max" :
|
||||
"$id_field > $current_max";
|
||||
$result = $replicastore->get_min_max_object_id(
|
||||
$id_field,
|
||||
$table,
|
||||
$where,
|
||||
$batch_size
|
||||
);
|
||||
if ( empty( $result->min ) && empty( $result->max ) ) {
|
||||
// Our query produced no min and max. We can assume the min from the previous query,
|
||||
// and the total max we found in the initial query.
|
||||
$current_max = (int) $total->max;
|
||||
$result = (object) array(
|
||||
'min' => $current_min,
|
||||
'max' => $current_max,
|
||||
);
|
||||
} else {
|
||||
$current_min = (int) $result->min;
|
||||
$current_max = (int) $result->max;
|
||||
}
|
||||
$results[] = $result;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->{$this->table_name()};
|
||||
$where = $this->get_where_sql( $config );
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
return $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE $where" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
|
||||
*/
|
||||
public function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return '1=1';
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* Network Options sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
|
||||
/**
|
||||
* Class to handle sync for network options.
|
||||
*/
|
||||
class Network_Options extends Module {
|
||||
/**
|
||||
* Whitelist for network options we want to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $network_options_whitelist;
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'network_options';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize network options action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
// Multi site network options.
|
||||
add_action( 'add_site_option', $callable, 10, 2 );
|
||||
add_action( 'update_site_option', $callable, 10, 3 );
|
||||
add_action( 'delete_site_option', $callable, 10, 1 );
|
||||
|
||||
$whitelist_network_option_handler = array( $this, 'whitelist_network_options' );
|
||||
add_filter( 'jetpack_sync_before_enqueue_delete_site_option', $whitelist_network_option_handler );
|
||||
add_filter( 'jetpack_sync_before_enqueue_add_site_option', $whitelist_network_option_handler );
|
||||
add_filter( 'jetpack_sync_before_enqueue_update_site_option', $whitelist_network_option_handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize network options action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_network_options', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// Full sync.
|
||||
add_filter(
|
||||
'jetpack_sync_before_send_jetpack_full_sync_network_options',
|
||||
array(
|
||||
$this,
|
||||
'expand_network_options',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
* Define the network options whitelist based on the default one.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->network_options_whitelist = Defaults::$default_network_options_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the network options actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all options to the server
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean Whether to expand options (should always be true)
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_network_options', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the network options actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $state This module Full Sync status.
|
||||
*
|
||||
* @return array This module Full Sync status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_network_options', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_network_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all network options as per the current network options whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array All network options.
|
||||
*/
|
||||
public function get_all_network_options() {
|
||||
$options = array();
|
||||
foreach ( $this->network_options_whitelist as $option ) {
|
||||
$options[ $option ] = get_site_option( $option );
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network options whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $options The new network options whitelist.
|
||||
*/
|
||||
public function set_network_options_whitelist( $options ) {
|
||||
$this->network_options_whitelist = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the network options whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array The network options whitelist.
|
||||
*/
|
||||
public function get_network_options_whitelist() {
|
||||
return $this->network_options_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject non-whitelisted network options.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array|false $args The hook parameters, false if not a whitelisted network option.
|
||||
*/
|
||||
public function whitelist_network_options( $args ) {
|
||||
if ( ! $this->is_whitelisted_network_option( $args[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the option is a whitelisted network option.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return boolean True if this is a whitelisted network option.
|
||||
*/
|
||||
public function is_whitelisted_network_option( $option ) {
|
||||
return in_array( $option, $this->network_options_whitelist, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the network options within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_network_options( $args ) {
|
||||
if ( $args[0] ) {
|
||||
return $this->get_all_network_options();
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return count( $this->network_options_whitelist );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,481 @@
|
||||
<?php
|
||||
/**
|
||||
* Options sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for options.
|
||||
*/
|
||||
class Options extends Module {
|
||||
/**
|
||||
* Whitelist for options we want to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $options_whitelist;
|
||||
|
||||
/**
|
||||
* Contentless options we want to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $options_contentless;
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'options';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize options action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
// Options.
|
||||
add_action( 'added_option', $callable, 10, 2 );
|
||||
add_action( 'updated_option', $callable, 10, 3 );
|
||||
add_action( 'deleted_option', $callable, 10, 1 );
|
||||
|
||||
// Sync Core Icon: Detect changes in Core's Site Icon and make it syncable.
|
||||
add_action( 'add_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
|
||||
add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
|
||||
add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
|
||||
|
||||
// Handle deprecated options.
|
||||
add_filter( 'jetpack_options_whitelist', array( $this, 'add_deprecated_options' ) );
|
||||
|
||||
$whitelist_option_handler = array( $this, 'whitelist_options' );
|
||||
add_filter( 'jetpack_sync_before_enqueue_deleted_option', $whitelist_option_handler );
|
||||
add_filter( 'jetpack_sync_before_enqueue_added_option', $whitelist_option_handler );
|
||||
add_filter( 'jetpack_sync_before_enqueue_updated_option', $whitelist_option_handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize options action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_options', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
* Define the options whitelist and contentless options.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->update_options_whitelist();
|
||||
$this->update_options_contentless();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module defaults at a later time.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_late_default() {
|
||||
/** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */
|
||||
$late_options = apply_filters( 'jetpack_options_whitelist', array() );
|
||||
if ( ! empty( $late_options ) && is_array( $late_options ) ) {
|
||||
$this->options_whitelist = array_merge( $this->options_whitelist, $late_options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add old deprecated options to the list of options to keep in sync.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $options The default list of site options.
|
||||
*/
|
||||
public function add_deprecated_options( $options ) {
|
||||
global $wp_version;
|
||||
|
||||
$deprecated_options = array(
|
||||
'blacklist_keys' => '5.5-alpha', // Replaced by disallowed_keys.
|
||||
'comment_whitelist' => '5.5-alpha', // Replaced by comment_previously_approved.
|
||||
);
|
||||
|
||||
foreach ( $deprecated_options as $option => $version ) {
|
||||
if ( version_compare( $wp_version, $version, '<=' ) ) {
|
||||
$options[] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the options actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all options to the server
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean Whether to expand options (should always be true)
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_options', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the options actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $state This module Full Sync status.
|
||||
*
|
||||
* @return array This module Full Sync status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_options', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return int Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all options as per the current options whitelist.
|
||||
* Public so that we don't have to store so much data all the options twice.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array All options.
|
||||
*/
|
||||
public function get_all_options() {
|
||||
$options = array();
|
||||
$random_string = wp_generate_password();
|
||||
foreach ( $this->options_whitelist as $option ) {
|
||||
if ( 0 === strpos( $option, Settings::SETTINGS_OPTION_PREFIX ) ) {
|
||||
$option_value = Settings::get_setting( str_replace( Settings::SETTINGS_OPTION_PREFIX, '', $option ) );
|
||||
$options[ $option ] = $option_value;
|
||||
} else {
|
||||
$option_value = get_option( $option, $random_string );
|
||||
if ( $option_value !== $random_string ) {
|
||||
$options[ $option ] = $option_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add theme mods.
|
||||
$theme_mods_option = 'theme_mods_' . get_option( 'stylesheet' );
|
||||
$theme_mods_value = get_option( $theme_mods_option, $random_string );
|
||||
if ( $theme_mods_value === $random_string ) {
|
||||
return $options;
|
||||
}
|
||||
$this->filter_theme_mods( $theme_mods_value );
|
||||
$options[ $theme_mods_option ] = $theme_mods_value;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the options whitelist to the default one.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update_options_whitelist() {
|
||||
$this->options_whitelist = Defaults::get_options_whitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $options The new options whitelist.
|
||||
*/
|
||||
public function set_options_whitelist( $options ) {
|
||||
$this->options_whitelist = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options whitelist.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array The options whitelist.
|
||||
*/
|
||||
public function get_options_whitelist() {
|
||||
return $this->options_whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the contentless options to the defaults.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update_options_contentless() {
|
||||
$this->options_contentless = Defaults::get_options_contentless();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contentless options.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Array of the contentless options.
|
||||
*/
|
||||
public function get_options_contentless() {
|
||||
return $this->options_contentless;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject any options that aren't whitelisted or contentless.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function whitelist_options( $args ) {
|
||||
// Reject non-whitelisted options.
|
||||
if ( ! $this->is_whitelisted_option( $args[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter our weird array( false ) value for theme_mods_*.
|
||||
if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) {
|
||||
$this->filter_theme_mods( $args[1] );
|
||||
if ( isset( $args[2] ) ) {
|
||||
$this->filter_theme_mods( $args[2] );
|
||||
}
|
||||
}
|
||||
|
||||
// Set value(s) of contentless option to empty string(s).
|
||||
if ( $this->is_contentless_option( $args[0] ) ) {
|
||||
// Create a new array matching length of $args, containing empty strings.
|
||||
$empty = array_fill( 0, count( $args ), '' );
|
||||
$empty[0] = $args[0];
|
||||
return $empty;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a certain option is whitelisted for sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return boolean Whether the option is whitelisted.
|
||||
*/
|
||||
public function is_whitelisted_option( $option ) {
|
||||
return in_array( $option, $this->options_whitelist, true ) || 'theme_mods_' === substr( $option, 0, 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a certain option is a contentless one.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @return boolean Whether the option is contentless.
|
||||
*/
|
||||
private function is_contentless_option( $option ) {
|
||||
return in_array( $option, $this->options_contentless, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out falsy values from theme mod options.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $value Option value.
|
||||
*/
|
||||
private function filter_theme_mods( &$value ) {
|
||||
if ( is_array( $value ) && isset( $value[0] ) ) {
|
||||
unset( $value[0] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle changes in the core site icon and sync them.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function jetpack_sync_core_icon() {
|
||||
$url = get_site_icon_url();
|
||||
|
||||
$jetpack_url = \Jetpack_Options::get_option( 'site_icon_url' );
|
||||
if ( defined( 'JETPACK__PLUGIN_DIR' ) ) {
|
||||
if ( ! function_exists( 'jetpack_site_icon_url' ) ) {
|
||||
require_once JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php';
|
||||
}
|
||||
$jetpack_url = jetpack_site_icon_url();
|
||||
}
|
||||
|
||||
// If there's a core icon, maybe update the option. If not, fall back to Jetpack's.
|
||||
if ( ! empty( $url ) && $jetpack_url !== $url ) {
|
||||
// This is the option that is synced with dotcom.
|
||||
\Jetpack_Options::update_option( 'site_icon_url', $url );
|
||||
} elseif ( empty( $url ) ) {
|
||||
\Jetpack_Options::delete_option( 'site_icon_url' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all options within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_options( $args ) {
|
||||
if ( $args[0] ) {
|
||||
return $this->get_all_options();
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return count( Defaults::get_options_whitelist() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of options by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
if ( empty( $ids ) || empty( $object_type ) || 'option' !== $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = array();
|
||||
foreach ( (array) $ids as $id ) {
|
||||
$object = $this->get_object_by_id( $object_type, $id );
|
||||
|
||||
// Only add object if we have the object.
|
||||
if ( 'OPTION-DOES-NOT-EXIST' !== $object ) {
|
||||
if ( 'all' === $id ) {
|
||||
// If all was requested it contains all options and can simply be returned.
|
||||
return $object;
|
||||
}
|
||||
$objects[ $id ] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an option by its name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param string $id ID of the sync object.
|
||||
* @return mixed Value of Option or 'OPTION-DOES-NOT-EXIST' if not found.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'option' === $object_type ) {
|
||||
// Utilize Random string as default value to distinguish between false and not exist.
|
||||
$random_string = wp_generate_password();
|
||||
// Only whitelisted options can be returned.
|
||||
if ( in_array( $id, $this->options_whitelist, true ) ) {
|
||||
if ( 0 === strpos( $id, Settings::SETTINGS_OPTION_PREFIX ) ) {
|
||||
$option_value = Settings::get_setting( str_replace( Settings::SETTINGS_OPTION_PREFIX, '', $id ) );
|
||||
return $option_value;
|
||||
} else {
|
||||
$option_value = get_option( $id, $random_string );
|
||||
if ( $option_value !== $random_string ) {
|
||||
return $option_value;
|
||||
}
|
||||
}
|
||||
} elseif ( 'all' === $id ) {
|
||||
return $this->get_all_options();
|
||||
}
|
||||
}
|
||||
|
||||
return 'OPTION-DOES-NOT-EXIST';
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,420 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugins sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
|
||||
/**
|
||||
* Class to handle sync for plugins.
|
||||
*/
|
||||
class Plugins extends Module {
|
||||
/**
|
||||
* Action handler callable.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $action_handler;
|
||||
|
||||
/**
|
||||
* Information about plugins we store temporarily.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $plugin_info = array();
|
||||
|
||||
/**
|
||||
* List of all plugins in the installation.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $plugins = array();
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'plugins';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugins action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
$this->action_handler = $callable;
|
||||
|
||||
add_action( 'deleted_plugin', array( $this, 'deleted_plugin' ), 10, 2 );
|
||||
add_action( 'activated_plugin', $callable, 10, 2 );
|
||||
add_action( 'deactivated_plugin', $callable, 10, 2 );
|
||||
add_action( 'delete_plugin', array( $this, 'delete_plugin' ) );
|
||||
add_filter( 'upgrader_pre_install', array( $this, 'populate_plugins' ), 10, 1 );
|
||||
add_action( 'upgrader_process_complete', array( $this, 'on_upgrader_completion' ), 10, 2 );
|
||||
add_action( 'jetpack_plugin_installed', $callable, 10, 1 );
|
||||
add_action( 'jetpack_plugin_update_failed', $callable, 10, 4 );
|
||||
add_action( 'jetpack_plugins_updated', $callable, 10, 2 );
|
||||
add_action( 'admin_action_update', array( $this, 'check_plugin_edit' ) );
|
||||
add_action( 'jetpack_edited_plugin', $callable, 10, 2 );
|
||||
add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'plugin_edit_ajax' ), 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_activated_plugin', array( $this, 'expand_plugin_data' ) );
|
||||
add_filter( 'jetpack_sync_before_send_deactivated_plugin', array( $this, 'expand_plugin_data' ) );
|
||||
// Note that we don't simply 'expand_plugin_data' on the 'delete_plugin' action here because the plugin file is deleted when that action finishes.
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and populate all current plugins before upgrader installation.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param bool|WP_Error $response Install response, true if successful, WP_Error if not.
|
||||
*/
|
||||
public function populate_plugins( $response ) {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$this->plugins = get_plugins();
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the upgrader success finishes.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Upgrader $upgrader Upgrader instance.
|
||||
* @param array $details Array of bulk item update data.
|
||||
*/
|
||||
public function on_upgrader_completion( $upgrader, $details ) {
|
||||
if ( ! isset( $details['type'] ) ) {
|
||||
return;
|
||||
}
|
||||
if ( 'plugin' !== $details['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $details['action'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugins = ( isset( $details['plugins'] ) ? $details['plugins'] : null );
|
||||
if ( empty( $plugins ) ) {
|
||||
$plugins = ( isset( $details['plugin'] ) ? array( $details['plugin'] ) : null );
|
||||
}
|
||||
|
||||
// For plugin installer.
|
||||
if ( empty( $plugins ) && method_exists( $upgrader, 'plugin_info' ) ) {
|
||||
$plugins = array( $upgrader->plugin_info() );
|
||||
}
|
||||
|
||||
if ( empty( $plugins ) ) {
|
||||
return; // We shouldn't be here.
|
||||
}
|
||||
|
||||
switch ( $details['action'] ) {
|
||||
case 'update':
|
||||
$state = array(
|
||||
'is_autoupdate' => Jetpack_Constants::is_true( 'JETPACK_PLUGIN_AUTOUPDATE' ),
|
||||
);
|
||||
$errors = $this->get_errors( $upgrader->skin );
|
||||
if ( $errors ) {
|
||||
foreach ( $plugins as $slug ) {
|
||||
/**
|
||||
* Sync that a plugin update failed
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.8.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param string $plugin , Plugin slug
|
||||
* @param string Error code
|
||||
* @param string Error message
|
||||
*/
|
||||
do_action( 'jetpack_plugin_update_failed', $this->get_plugin_info( $slug ), $errors['code'], $errors['message'], $state );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Sync that a plugin update
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.8.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param array () $plugin, Plugin Data
|
||||
*/
|
||||
do_action( 'jetpack_plugins_updated', array_map( array( $this, 'get_plugin_info' ), $plugins ), $state );
|
||||
break;
|
||||
case 'install':
|
||||
}
|
||||
|
||||
if ( 'install' === $details['action'] ) {
|
||||
/**
|
||||
* Signals to the sync listener that a plugin was installed and a sync action
|
||||
* reflecting the installation and the plugin info should be sent
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.8.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param array () $plugin, Plugin Data
|
||||
*/
|
||||
do_action( 'jetpack_plugin_installed', array_map( array( $this, 'get_plugin_info' ), $plugins ) );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin information by a plugin slug.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
* @return array Plugin information.
|
||||
*/
|
||||
private function get_plugin_info( $slug ) {
|
||||
$plugins = get_plugins(); // Get the most up to date info.
|
||||
if ( isset( $plugins[ $slug ] ) ) {
|
||||
return array_merge( array( 'slug' => $slug ), $plugins[ $slug ] );
|
||||
}
|
||||
// Try grabbing the info from before the update.
|
||||
return isset( $this->plugins[ $slug ] ) ? array_merge( array( 'slug' => $slug ), $this->plugins[ $slug ] ) : array( 'slug' => $slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve upgrade errors.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param \Automatic_Upgrader_Skin|\WP_Upgrader_Skin $skin The upgrader skin being used.
|
||||
* @return array|boolean Error on error, false otherwise.
|
||||
*/
|
||||
private function get_errors( $skin ) {
|
||||
$errors = method_exists( $skin, 'get_errors' ) ? $skin->get_errors() : null;
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
$error_code = $errors->get_error_code();
|
||||
if ( ! empty( $error_code ) ) {
|
||||
return array(
|
||||
'code' => $error_code,
|
||||
'message' => $errors->get_error_message(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $skin->result ) ) {
|
||||
$errors = $skin->result;
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
return array(
|
||||
'code' => $errors->get_error_code(),
|
||||
'message' => $errors->get_error_message(),
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $skin->result ) ) {
|
||||
return array(
|
||||
'code' => 'unknown',
|
||||
'message' => __( 'Unknown Plugin Update Failure', 'jetpack-sync' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plugin edit in the administration.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo The `admin_action_update` hook is called only for logged in users, but maybe implement nonce verification?
|
||||
*/
|
||||
public function check_plugin_edit() {
|
||||
$screen = get_current_screen();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( 'plugin-editor' !== $screen->base || ! isset( $_POST['newcontent'] ) || ! isset( $_POST['plugin'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after.
|
||||
$plugin = wp_unslash( $_POST['plugin'] );
|
||||
$plugins = get_plugins();
|
||||
if ( ! isset( $plugins[ $plugin ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps Sync log that a plugin was edited
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param string $plugin, Plugin slug
|
||||
* @param mixed $plugins[ $plugin ], Array of plugin data
|
||||
*/
|
||||
do_action( 'jetpack_edited_plugin', $plugin, $plugins[ $plugin ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plugin ajax edit in the administration.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Update this method to use WP_Filesystem instead of fopen/fclose.
|
||||
*/
|
||||
public function plugin_edit_ajax() {
|
||||
// This validation is based on wp_edit_theme_plugin_file().
|
||||
$args = wp_unslash( $_POST );
|
||||
if ( empty( $args['file'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $args['file'];
|
||||
if ( 0 !== validate_file( $file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $args['newcontent'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $args['nonce'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $args['plugin'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = $args['plugin'];
|
||||
if ( ! current_user_can( 'edit_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
|
||||
return;
|
||||
}
|
||||
$plugins = get_plugins();
|
||||
if ( ! array_key_exists( $plugin, $plugins ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$real_file = WP_PLUGIN_DIR . '/' . $file;
|
||||
|
||||
if ( ! is_writeable( $real_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
|
||||
$file_pointer = fopen( $real_file, 'w+' );
|
||||
if ( false === $file_pointer ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
|
||||
fclose( $file_pointer );
|
||||
/**
|
||||
* This action is documented already in this file
|
||||
*/
|
||||
do_action( 'jetpack_edited_plugin', $plugin, $plugins[ $plugin ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plugin deletion.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $plugin_path Path to the plugin main file.
|
||||
*/
|
||||
public function delete_plugin( $plugin_path ) {
|
||||
$full_plugin_path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_path;
|
||||
|
||||
// Checking for file existence because some sync plugin module tests simulate plugin installation and deletion without putting file on disk.
|
||||
if ( file_exists( $full_plugin_path ) ) {
|
||||
$all_plugin_data = get_plugin_data( $full_plugin_path );
|
||||
$data = array(
|
||||
'name' => $all_plugin_data['Name'],
|
||||
'version' => $all_plugin_data['Version'],
|
||||
);
|
||||
} else {
|
||||
$data = array(
|
||||
'name' => $plugin_path,
|
||||
'version' => 'unknown',
|
||||
);
|
||||
}
|
||||
|
||||
$this->plugin_info[ $plugin_path ] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after plugin deletion.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $plugin_path Path to the plugin main file.
|
||||
* @param boolean $is_deleted Whether the plugin was deleted successfully.
|
||||
*/
|
||||
public function deleted_plugin( $plugin_path, $is_deleted ) {
|
||||
call_user_func( $this->action_handler, $plugin_path, $is_deleted, $this->plugin_info[ $plugin_path ] );
|
||||
unset( $this->plugin_info[ $plugin_path ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the plugins within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The expanded hook parameters.
|
||||
*/
|
||||
public function expand_plugin_data( $args ) {
|
||||
$plugin_path = $args[0];
|
||||
$plugin_data = array();
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$all_plugins = get_plugins();
|
||||
if ( isset( $all_plugins[ $plugin_path ] ) ) {
|
||||
$all_plugin_data = $all_plugins[ $plugin_path ];
|
||||
$plugin_data['name'] = $all_plugin_data['Name'];
|
||||
$plugin_data['version'] = $all_plugin_data['Version'];
|
||||
}
|
||||
|
||||
return array(
|
||||
$args[0],
|
||||
$args[1],
|
||||
$plugin_data,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,776 @@
|
||||
<?php
|
||||
/**
|
||||
* Posts sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for posts.
|
||||
*/
|
||||
class Posts extends Module {
|
||||
/**
|
||||
* The post IDs of posts that were just published but not synced yet.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $just_published = array();
|
||||
|
||||
/**
|
||||
* The previous status of posts that we use for calculating post status transitions.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $previous_status = array();
|
||||
|
||||
/**
|
||||
* Action handler callable.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $action_handler;
|
||||
|
||||
/**
|
||||
* Import end.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @todo This appears to be unused - let's remove it.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $import_end = false;
|
||||
|
||||
/**
|
||||
* Max bytes allowed for post_content => length.
|
||||
* Current Setting : 5MB.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_POST_CONTENT_LENGTH = 5000000;
|
||||
|
||||
/**
|
||||
* Max bytes allowed for post meta_value => length.
|
||||
* Current Setting : 2MB.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_POST_META_LENGTH = 2000000;
|
||||
|
||||
/**
|
||||
* Default previous post state.
|
||||
* Used for default previous post status.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_PREVIOUS_STATE = 'new';
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'posts';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return 'posts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a post by its ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param int $id ID of the sync object.
|
||||
* @return \WP_Post|bool Filtered \WP_Post object, or false if the object is not a post.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'post' === $object_type ) {
|
||||
$post = get_post( (int) $id );
|
||||
if ( $post ) {
|
||||
return $this->filter_post_content_and_add_links( $post );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize posts action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
$this->action_handler = $callable;
|
||||
|
||||
add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ), 11, 3 );
|
||||
add_action( 'wp_after_insert_post', array( $this, 'wp_after_insert_post' ), 11, 2 );
|
||||
add_action( 'jetpack_sync_save_post', $callable, 10, 4 );
|
||||
|
||||
add_action( 'deleted_post', $callable, 10 );
|
||||
add_action( 'jetpack_published_post', $callable, 10, 2 );
|
||||
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_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
|
||||
add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
|
||||
add_action( 'jetpack_post_meta_batch_delete', $callable, 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Before Akismet's daily cleanup of spam detection metadata.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $feedback_ids IDs of feedback posts.
|
||||
*/
|
||||
public function daily_akismet_meta_cleanup_before( $feedback_ids ) {
|
||||
remove_action( 'deleted_post_meta', $this->action_handler );
|
||||
|
||||
if ( ! is_array( $feedback_ids ) || count( $feedback_ids ) < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ids_chunks = array_chunk( $feedback_ids, 100, false );
|
||||
foreach ( $ids_chunks as $chunk ) {
|
||||
/**
|
||||
* Used for syncing deletion of batch post meta
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 6.1.0
|
||||
*
|
||||
* @module sync
|
||||
*
|
||||
* @param array $feedback_ids feedback post IDs
|
||||
* @param string $meta_key to be deleted
|
||||
*/
|
||||
do_action( 'jetpack_post_meta_batch_delete', $chunk, '_feedback_akismet_values' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After Akismet's daily cleanup of spam detection metadata.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $feedback_ids IDs of feedback posts.
|
||||
*/
|
||||
public function daily_akismet_meta_cleanup_after( $feedback_ids ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
add_action( 'deleted_post_meta', $this->action_handler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize posts action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_posts', $callable ); // Also sends post meta.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_sync_save_post', array( $this, 'expand_jetpack_sync_save_post' ) );
|
||||
|
||||
// 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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the posts actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
global $wpdb;
|
||||
|
||||
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Use $wpdb->prepare for the SQL query.
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
|
||||
*/
|
||||
public function get_where_sql( $config ) {
|
||||
$where_sql = Settings::get_blacklisted_post_types_sql();
|
||||
|
||||
// Config is a list of post IDs to sync.
|
||||
if ( is_array( $config ) ) {
|
||||
$where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
|
||||
}
|
||||
|
||||
return $where_sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter meta arguments so that we don't sync meta_values over MAX_POST_META_LENGTH.
|
||||
*
|
||||
* @param array $args action arguments.
|
||||
*
|
||||
* @return array filtered action arguments.
|
||||
*/
|
||||
public function trim_post_meta( $args ) {
|
||||
list( $meta_id, $object_id, $meta_key, $meta_value ) = $args;
|
||||
// Explicitly truncate meta_value when it exceeds limit.
|
||||
// Large content will cause OOM issues and break Sync.
|
||||
$serialized_value = maybe_serialize( $meta_value );
|
||||
if ( strlen( $serialized_value ) >= self::MAX_POST_META_LENGTH ) {
|
||||
$meta_value = '';
|
||||
}
|
||||
return array( $meta_id, $object_id, $meta_key, $meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process content before send.
|
||||
*
|
||||
* @param array $args Arguments of the `wp_insert_post` hook.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function expand_jetpack_sync_save_post( $args ) {
|
||||
list( $post_id, $post, $update, $previous_state ) = $args;
|
||||
return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter all blacklisted post types.
|
||||
*
|
||||
* @param array $args Hook arguments.
|
||||
* @return array|false Hook arguments, or false if the post type is a blacklisted one.
|
||||
*/
|
||||
public function filter_blacklisted_post_types_deleted( $args ) {
|
||||
|
||||
// deleted_post is called after the SQL delete but before cache cleanup.
|
||||
// There is the potential we can't detect post_type at this point.
|
||||
if ( ! $this->is_post_type_allowed( $args[0] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param array $args Hook arguments.
|
||||
* @return array|false Hook arguments, or false if meta was filtered.
|
||||
*/
|
||||
public function filter_meta( $args ) {
|
||||
if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a post meta key is whitelisted.
|
||||
*
|
||||
* @param string $meta_key Meta key.
|
||||
* @return boolean Whether the post meta key is whitelisted.
|
||||
*/
|
||||
public function is_whitelisted_post_meta( $meta_key ) {
|
||||
// The _wpas_skip_ meta key is used by Publicize.
|
||||
return in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true ) || ( 0 === strpos( $meta_key, '_wpas_skip_' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a post type is allowed.
|
||||
* A post type will be disallowed if it's present in the post type blacklist.
|
||||
*
|
||||
* @param int $post_id ID of the post.
|
||||
* @return boolean Whether the post type is allowed.
|
||||
*/
|
||||
public function is_post_type_allowed( $post_id ) {
|
||||
$post = get_post( (int) $post_id );
|
||||
|
||||
if ( isset( $post->post_type ) ) {
|
||||
return ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the embed shortcode.
|
||||
*
|
||||
* @global $wp_embed
|
||||
*/
|
||||
public function remove_embed() {
|
||||
global $wp_embed;
|
||||
remove_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
|
||||
// remove the embed shortcode since we would do the part later.
|
||||
remove_shortcode( 'embed' );
|
||||
// Attempts to embed all URLs in a post.
|
||||
remove_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the embed shortcode.
|
||||
*
|
||||
* @global $wp_embed
|
||||
*/
|
||||
public function add_embed() {
|
||||
global $wp_embed;
|
||||
add_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
|
||||
// Shortcode placeholder for strip_shortcodes().
|
||||
add_shortcode( 'embed', '__return_false' );
|
||||
// Attempts to embed all URLs in a post.
|
||||
add_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands wp_insert_post to include filtered content
|
||||
*
|
||||
* @param \WP_Post $post_object Post object.
|
||||
*/
|
||||
public function filter_post_content_and_add_links( $post_object ) {
|
||||
global $post;
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$post = $post_object;
|
||||
|
||||
// Return non existant post.
|
||||
$post_type = get_post_type_object( $post->post_type );
|
||||
if ( empty( $post_type ) || ! is_object( $post_type ) ) {
|
||||
$non_existant_post = new \stdClass();
|
||||
$non_existant_post->ID = $post->ID;
|
||||
$non_existant_post->post_modified = $post->post_modified;
|
||||
$non_existant_post->post_modified_gmt = $post->post_modified_gmt;
|
||||
$non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
|
||||
$non_existant_post->post_type = $post->post_type;
|
||||
|
||||
return $non_existant_post;
|
||||
}
|
||||
/**
|
||||
* Filters whether to prevent sending post data to .com
|
||||
*
|
||||
* Passing true to the filter will prevent the post data from being sent
|
||||
* to the WordPress.com.
|
||||
* Instead we pass data that will still enable us to do a checksum against the
|
||||
* Jetpacks data but will prevent us from displaying the data on in the API as well as
|
||||
* other services.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean false prevent post data from being synced to WordPress.com
|
||||
* @param mixed $post \WP_Post object
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_sync_prevent_sending_post_data', false, $post ) ) {
|
||||
// We only send the bare necessary object to be able to create a checksum.
|
||||
$blocked_post = new \stdClass();
|
||||
$blocked_post->ID = $post->ID;
|
||||
$blocked_post->post_modified = $post->post_modified;
|
||||
$blocked_post->post_modified_gmt = $post->post_modified_gmt;
|
||||
$blocked_post->post_status = 'jetpack_sync_blocked';
|
||||
$blocked_post->post_type = $post->post_type;
|
||||
|
||||
return $blocked_post;
|
||||
}
|
||||
|
||||
// lets not do oembed just yet.
|
||||
$this->remove_embed();
|
||||
|
||||
if ( 0 < strlen( $post->post_password ) ) {
|
||||
$post->post_password = 'auto-' . wp_generate_password( 10, false );
|
||||
}
|
||||
|
||||
// Explicitly omit post_content when it exceeds limit.
|
||||
// Large content will cause OOM issues and break Sync.
|
||||
if ( strlen( $post->post_content ) >= self::MAX_POST_CONTENT_LENGTH ) {
|
||||
$post->post_content = '';
|
||||
}
|
||||
|
||||
/** This filter is already documented in core. wp-includes/post-template.php */
|
||||
if ( Settings::get_setting( 'render_filtered_content' ) && $post_type->public ) {
|
||||
global $shortcode_tags;
|
||||
/**
|
||||
* Filter prevents some shortcodes from expanding.
|
||||
*
|
||||
* Since we can can expand some type of shortcode better on the .com side and make the
|
||||
* expansion more relevant to contexts. For example [galleries] and subscription emails
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.5.0
|
||||
*
|
||||
* @param array of shortcode tags to remove.
|
||||
*/
|
||||
$shortcodes_to_remove = apply_filters(
|
||||
'jetpack_sync_do_not_expand_shortcodes',
|
||||
array(
|
||||
'gallery',
|
||||
'slideshow',
|
||||
)
|
||||
);
|
||||
$removed_shortcode_callbacks = array();
|
||||
foreach ( $shortcodes_to_remove as $shortcode ) {
|
||||
if ( isset( $shortcode_tags[ $shortcode ] ) ) {
|
||||
$removed_shortcode_callbacks[ $shortcode ] = $shortcode_tags[ $shortcode ];
|
||||
}
|
||||
}
|
||||
|
||||
array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
|
||||
|
||||
$post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
|
||||
$post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
|
||||
|
||||
foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
|
||||
add_shortcode( $shortcode, $callback );
|
||||
}
|
||||
}
|
||||
|
||||
$this->add_embed();
|
||||
|
||||
if ( has_post_thumbnail( $post->ID ) ) {
|
||||
$image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
|
||||
if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
|
||||
$post->featured_image = $image_attributes[0];
|
||||
}
|
||||
}
|
||||
|
||||
$post->permalink = get_permalink( $post->ID );
|
||||
$post->shortlink = wp_get_shortlink( $post->ID );
|
||||
|
||||
if ( function_exists( 'amp_get_permalink' ) ) {
|
||||
$post->amp_permalink = amp_get_permalink( $post->ID );
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle transition from another post status to a published one.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
public function save_published( $new_status, $old_status, $post ) {
|
||||
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
|
||||
$this->just_published[ $post->ID ] = true;
|
||||
}
|
||||
|
||||
$this->previous_status[ $post->ID ] = $old_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* When publishing or updating a post, the Gutenberg editor sends two requests:
|
||||
* 1. sent to WP REST API endpoint `wp-json/wp/v2/posts/$id`
|
||||
* 2. sent to wp-admin/post.php `?post=$id&action=edit&classic-editor=1&meta_box=1`
|
||||
*
|
||||
* The 2nd request is to update post meta, which is not supported on WP REST API.
|
||||
* When syncing post data, we will include if this was a meta box update.
|
||||
*
|
||||
* @todo Implement nonce verification.
|
||||
*
|
||||
* @return boolean Whether this is a Gutenberg meta box update.
|
||||
*/
|
||||
public function is_gutenberg_meta_box_update() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
|
||||
return (
|
||||
isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
|
||||
'editpost' === $_POST['action'] &&
|
||||
'1' === $_GET['classic-editor'] &&
|
||||
'1' === $_GET['meta_box']
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the wp_insert_post hook.
|
||||
* Called upon creation of a new post.
|
||||
*
|
||||
* @param int $post_ID Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
* @param boolean $update Whether this is an existing post being updated or not.
|
||||
*/
|
||||
public function wp_insert_post( $post_ID, $post = null, $update = null ) {
|
||||
if ( ! is_numeric( $post_ID ) || $post === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
|
||||
if ( $post && 'shop_order' === $post->post_type ) {
|
||||
$post = get_post( $post_ID );
|
||||
}
|
||||
|
||||
$previous_status = isset( $this->previous_status[ $post_ID ] ) ? $this->previous_status[ $post_ID ] : self::DEFAULT_PREVIOUS_STATE;
|
||||
|
||||
$just_published = isset( $this->just_published[ $post_ID ] ) ? $this->just_published[ $post_ID ] : false;
|
||||
|
||||
$state = array(
|
||||
'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
|
||||
'previous_status' => $previous_status,
|
||||
'just_published' => $just_published,
|
||||
'is_gutenberg_meta_box_update' => $this->is_gutenberg_meta_box_update(),
|
||||
);
|
||||
/**
|
||||
* Filter that is used to add to the post flags ( meta data ) when a post gets published
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.8.0
|
||||
*
|
||||
* @param int $post_ID the post ID
|
||||
* @param mixed $post \WP_Post object
|
||||
* @param bool $update Whether this is an existing post being updated or not.
|
||||
* @param mixed $state state
|
||||
*
|
||||
* @module sync
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_post', $post_ID, $post, $update, $state );
|
||||
unset( $this->previous_status[ $post_ID ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the wp_after_insert_post hook.
|
||||
* Called after creation/update of a new post.
|
||||
*
|
||||
* @param int $post_ID Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
**/
|
||||
public function wp_after_insert_post( $post_ID, $post ) {
|
||||
if ( ! is_numeric( $post_ID ) || $post === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
|
||||
if ( $post && 'shop_order' === $post->post_type ) {
|
||||
$post = get_post( $post_ID );
|
||||
}
|
||||
|
||||
$this->send_published( $post_ID, $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a published post for sync.
|
||||
*
|
||||
* @param int $post_ID Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
public function send_published( $post_ID, $post ) {
|
||||
if ( ! isset( $this->just_published[ $post_ID ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Post revisions cause race conditions where this send_published add the action before the actual post gets synced.
|
||||
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_flags = array(
|
||||
'post_type' => $post->post_type,
|
||||
);
|
||||
|
||||
$author_user_object = get_user_by( 'id', $post->post_author );
|
||||
if ( $author_user_object ) {
|
||||
$roles = new Roles();
|
||||
|
||||
$post_flags['author'] = array(
|
||||
'id' => $post->post_author,
|
||||
'wpcom_user_id' => get_user_meta( $post->post_author, 'wpcom_user_id', true ),
|
||||
'display_name' => $author_user_object->display_name,
|
||||
'email' => $author_user_object->user_email,
|
||||
'translated_role' => $roles->translate_user_to_role( $author_user_object ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that is used to add to the post flags ( meta data ) when a post gets published
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @param mixed array post flags that are added to the post
|
||||
* @param mixed $post \WP_Post object
|
||||
*/
|
||||
$flags = apply_filters( 'jetpack_published_post_flags', $post_flags, $post );
|
||||
|
||||
// Only Send Pulished Post event if post_type is not blacklisted.
|
||||
if ( ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
|
||||
|
||||
// Refreshing the post in the cache site before triggering the publish event.
|
||||
// The true parameter means that it's an update action, not create action.
|
||||
$this->wp_insert_post( $post_ID, $post, true );
|
||||
|
||||
/**
|
||||
* Action that gets synced when a post type gets published.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @param int $post_ID
|
||||
* @param mixed array $flags post flags that are added to the post
|
||||
*/
|
||||
do_action( 'jetpack_published_post', $post_ID, $flags );
|
||||
}
|
||||
unset( $this->just_published[ $post_ID ] );
|
||||
|
||||
/**
|
||||
* Send additional sync action for Activity Log when post is a Customizer publish
|
||||
*/
|
||||
if ( 'customize_changeset' === $post->post_type ) {
|
||||
$post_content = json_decode( $post->post_content, true );
|
||||
foreach ( $post_content as $key => $value ) {
|
||||
// Skip if it isn't a widget.
|
||||
if ( 'widget_' !== substr( $key, 0, strlen( 'widget_' ) ) ) {
|
||||
continue;
|
||||
}
|
||||
// Change key from "widget_archives[2]" to "archives-2".
|
||||
$key = str_replace( 'widget_', '', $key );
|
||||
$key = str_replace( '[', '-', $key );
|
||||
$key = str_replace( ']', '', $key );
|
||||
|
||||
global $wp_registered_widgets;
|
||||
if ( isset( $wp_registered_widgets[ $key ] ) ) {
|
||||
$widget_data = array(
|
||||
'name' => $wp_registered_widgets[ $key ]['name'],
|
||||
'id' => $key,
|
||||
'title' => $value['value']['title'],
|
||||
);
|
||||
do_action( 'jetpack_widget_edited', $widget_data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand post IDs to post objects within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The expanded hook parameters.
|
||||
*/
|
||||
public function expand_post_ids( $args ) {
|
||||
list( $post_ids, $previous_interval_end) = $args;
|
||||
|
||||
$posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
|
||||
$posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
|
||||
$posts = array_values( $posts ); // Reindex in case posts were deleted.
|
||||
|
||||
return array(
|
||||
$posts,
|
||||
$this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) ),
|
||||
$this->get_term_relationships( $post_ids ),
|
||||
$previous_interval_end,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of minimum and maximum object ids for each batch based on the given batch size.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $batch_size The batch size for objects.
|
||||
* @param string|bool $where_sql The sql where clause minus 'WHERE', or false if no where clause is needed.
|
||||
*
|
||||
* @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
|
||||
*/
|
||||
public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
|
||||
return parent::get_min_max_object_ids_for_batches( $batch_size, $this->get_where_sql( $where_sql ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Protect sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
|
||||
/**
|
||||
* Class to handle sync for Protect.
|
||||
* Logs BruteProtect failed logins via sync.
|
||||
*/
|
||||
class Protect extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'protect';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Protect action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callback Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callback ) {
|
||||
add_action( 'jpp_log_failed_attempt', array( $this, 'maybe_log_failed_login_attempt' ) );
|
||||
add_action( 'jetpack_valid_failed_login_attempt', $callback );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe log a failed login attempt.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $failed_attempt Failed attempt data.
|
||||
*/
|
||||
public function maybe_log_failed_login_attempt( $failed_attempt ) {
|
||||
$protect = \Jetpack_Protect_Module::instance();
|
||||
if ( $protect->has_login_ability() && ! Jetpack_Constants::is_true( 'XMLRPC_REQUEST' ) ) {
|
||||
do_action( 'jetpack_valid_failed_login_attempt', $failed_attempt );
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Heartbeat;
|
||||
|
||||
/**
|
||||
* Class to handle sync for stats.
|
||||
*/
|
||||
class Stats extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'stats';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize stats action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callback Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callback ) {
|
||||
add_action( 'jetpack_heartbeat', array( $this, 'sync_site_stats' ), 20 );
|
||||
add_action( 'jetpack_sync_heartbeat_stats', $callback );
|
||||
}
|
||||
|
||||
/**
|
||||
* This namespaces the action that we sync.
|
||||
* So that we can differentiate it from future actions.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function sync_site_stats() {
|
||||
do_action( 'jetpack_sync_heartbeat_stats' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_sync_heartbeat_stats', array( $this, 'add_stats' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stats data for the site.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Stats data.
|
||||
*/
|
||||
public function add_stats() {
|
||||
return array( Heartbeat::generate_stats_array() );
|
||||
}
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* Term relationships sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Listener;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for term relationships.
|
||||
*/
|
||||
class Term_Relationships extends Module {
|
||||
|
||||
/**
|
||||
* Max terms to return in one single query
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @const int
|
||||
*/
|
||||
const QUERY_LIMIT = 1000;
|
||||
|
||||
/**
|
||||
* Max value for a signed INT in MySQL - https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @const int
|
||||
*/
|
||||
const MAX_INT = 2147483647;
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'term_relationships';
|
||||
}
|
||||
|
||||
/**
|
||||
* The id field in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id_field() {
|
||||
return 'object_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return 'term_relationships';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize term relationships action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_term_relationships', $callable, 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_term_relationships', array( $this, 'expand_term_relationships' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the term relationships actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param object $last_object_enqueued Last object enqueued.
|
||||
*
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
* @todo This method has similarities with Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action. Refactor to keep DRY.
|
||||
* @see Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $last_object_enqueued ) {
|
||||
global $wpdb;
|
||||
$term_relationships_full_sync_item_size = Settings::get_setting( 'term_relationships_full_sync_item_size' );
|
||||
$limit = min( $max_items_to_enqueue * $term_relationships_full_sync_item_size, self::QUERY_LIMIT );
|
||||
$items_enqueued_count = 0;
|
||||
$last_object_enqueued = $last_object_enqueued ? $last_object_enqueued : array(
|
||||
'object_id' => self::MAX_INT,
|
||||
'term_taxonomy_id' => self::MAX_INT,
|
||||
);
|
||||
|
||||
while ( $limit > 0 ) {
|
||||
/*
|
||||
* SELECT object_id, term_taxonomy_id
|
||||
* FROM $wpdb->term_relationships
|
||||
* WHERE ( object_id = 11 AND term_taxonomy_id < 14 ) OR ( object_id < 11 )
|
||||
* ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT 1000
|
||||
*/
|
||||
$objects = $wpdb->get_results( $wpdb->prepare( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], $limit ), ARRAY_A );
|
||||
// Request term relationships in groups of N for efficiency.
|
||||
$objects_count = count( $objects );
|
||||
if ( ! count( $objects ) ) {
|
||||
return array( $items_enqueued_count, true );
|
||||
}
|
||||
$items = array_chunk( $objects, $term_relationships_full_sync_item_size );
|
||||
$last_object_enqueued = $this->bulk_enqueue_full_sync_term_relationships( $items, $last_object_enqueued );
|
||||
$items_enqueued_count += count( $items );
|
||||
$limit = min( $limit - $objects_count, self::QUERY_LIMIT );
|
||||
}
|
||||
|
||||
// We need to do this extra check in case $max_items_to_enqueue * $term_relationships_full_sync_item_size == relationships objects left.
|
||||
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], 1 ) );
|
||||
if ( 0 === (int) $count ) {
|
||||
return array( $items_enqueued_count, true );
|
||||
}
|
||||
|
||||
return array( $items_enqueued_count, $last_object_enqueued );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial last sent object.
|
||||
*
|
||||
* @return string|array initial status.
|
||||
*/
|
||||
public function get_initial_last_sent() {
|
||||
return array(
|
||||
'object_id' => self::MAX_INT,
|
||||
'term_taxonomy_id' => self::MAX_INT,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the Module Full Sync Configuration and Status return the next chunk of items to send.
|
||||
*
|
||||
* @param array $config This module Full Sync configuration.
|
||||
* @param array $status This module Full Sync status.
|
||||
* @param int $chunk_size Chunk size.
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function get_next_chunk( $config, $status, $chunk_size ) {
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT object_id, term_taxonomy_id
|
||||
FROM $wpdb->term_relationships
|
||||
WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d )
|
||||
ORDER BY object_id DESC, term_taxonomy_id
|
||||
DESC LIMIT %d",
|
||||
$status['last_sent']['object_id'],
|
||||
$status['last_sent']['term_taxonomy_id'],
|
||||
$status['last_sent']['object_id'],
|
||||
$chunk_size
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Enqueue all $items within `jetpack_full_sync_term_relationships` actions.
|
||||
*
|
||||
* @param array $items Groups of objects to sync.
|
||||
* @param array $previous_interval_end Last item enqueued.
|
||||
*
|
||||
* @return array Last enqueued object.
|
||||
*/
|
||||
public function bulk_enqueue_full_sync_term_relationships( $items, $previous_interval_end ) {
|
||||
$listener = Listener::get_instance();
|
||||
$items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $items, $previous_interval_end );
|
||||
$listener->bulk_enqueue_full_sync_actions( 'jetpack_full_sync_term_relationships', $items_with_previous_interval_end );
|
||||
$last_item = end( $items );
|
||||
return end( $last_item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return int Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT COUNT(*) FROM $wpdb->term_relationships";
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / Settings::get_setting( 'term_relationships_full_sync_item_size' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_term_relationships' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the term relationships within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The expanded hook parameters.
|
||||
*/
|
||||
public function expand_term_relationships( $args ) {
|
||||
list( $term_relationships, $previous_end ) = $args;
|
||||
|
||||
return array(
|
||||
'term_relationships' => $term_relationships,
|
||||
'previous_end' => $previous_end,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
<?php
|
||||
/**
|
||||
* Terms sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
use Automattic\Jetpack\Sync\Settings;
|
||||
|
||||
/**
|
||||
* Class to handle sync for terms.
|
||||
*/
|
||||
class Terms extends Module {
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'terms';
|
||||
}
|
||||
|
||||
/**
|
||||
* The id field in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id_field() {
|
||||
return 'term_taxonomy_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return 'term_taxonomy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows WordPress.com servers to retrieve term-related objects via the sync API.
|
||||
*
|
||||
* @param string $object_type The type of object.
|
||||
* @param int $id The id of the object.
|
||||
*
|
||||
* @return bool|object A WP_Term object, or a row from term_taxonomy table depending on object type.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
global $wpdb;
|
||||
$object = false;
|
||||
if ( 'term' === $object_type ) {
|
||||
$object = get_term( (int) $id );
|
||||
|
||||
if ( is_wp_error( $object ) && $object->get_error_code() === 'invalid_taxonomy' ) {
|
||||
// Fetch raw term.
|
||||
$columns = implode( ', ', array_unique( array_merge( Defaults::$default_term_checksum_columns, array( 'term_group' ) ) ) );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$object = $wpdb->get_row( $wpdb->prepare( "SELECT $columns FROM $wpdb->terms WHERE term_id = %d", $id ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'term_taxonomy' === $object_type ) {
|
||||
$columns = implode( ', ', array_unique( array_merge( Defaults::$default_term_taxonomy_checksum_columns, array( 'description' ) ) ) );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$object = $wpdb->get_row( $wpdb->prepare( "SELECT $columns FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $id ) );
|
||||
}
|
||||
|
||||
if ( 'term_relationships' === $object_type ) {
|
||||
$columns = implode( ', ', Defaults::$default_term_relationships_checksum_columns );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$objects = $wpdb->get_results( $wpdb->prepare( "SELECT $columns FROM $wpdb->term_relationships WHERE object_id = %d", $id ) );
|
||||
$object = (object) array(
|
||||
'object_id' => $id,
|
||||
'relationships' => array_map( array( $this, 'expand_terms_for_relationship' ), $objects ),
|
||||
);
|
||||
}
|
||||
|
||||
return $object ? $object : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize terms action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'created_term', array( $this, 'save_term_handler' ), 10, 3 );
|
||||
add_action( 'edited_term', array( $this, 'save_term_handler' ), 10, 3 );
|
||||
add_action( 'jetpack_sync_save_term', $callable );
|
||||
add_action( 'jetpack_sync_add_term', $callable );
|
||||
add_action( 'delete_term', $callable, 10, 4 );
|
||||
add_action( 'set_object_terms', $callable, 10, 6 );
|
||||
add_action( 'deleted_term_relationships', $callable, 10, 2 );
|
||||
add_filter( 'jetpack_sync_before_enqueue_set_object_terms', array( $this, 'filter_set_object_terms_no_update' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_term', array( $this, 'filter_blacklisted_taxonomies' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_term', array( $this, 'filter_blacklisted_taxonomies' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize terms action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_terms', $callable, 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_taxonomy_id' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the terms actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
global $wpdb;
|
||||
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_terms', $wpdb->term_taxonomy, 'term_taxonomy_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
|
||||
*/
|
||||
public function get_where_sql( $config ) {
|
||||
$where_sql = Settings::get_blacklisted_taxonomies_sql();
|
||||
|
||||
if ( is_array( $config ) ) {
|
||||
$where_sql .= ' AND term_taxonomy_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
|
||||
}
|
||||
|
||||
return $where_sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return int Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT count(*) FROM $wpdb->term_taxonomy";
|
||||
|
||||
$where_sql = $this->get_where_sql( $config );
|
||||
if ( $where_sql ) {
|
||||
$query .= ' WHERE ' . $where_sql;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_terms' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for creating and updating terms.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $term_id Term ID.
|
||||
* @param int $tt_id Term taxonomy ID.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function save_term_handler( $term_id, $tt_id, $taxonomy ) {
|
||||
if ( class_exists( '\\WP_Term' ) ) {
|
||||
$term_object = \WP_Term::get_instance( $term_id, $taxonomy );
|
||||
} else {
|
||||
$term_object = get_term_by( 'id', $term_id, $taxonomy );
|
||||
}
|
||||
|
||||
$current_filter = current_filter();
|
||||
|
||||
if ( 'created_term' === $current_filter ) {
|
||||
/**
|
||||
* Fires when the client needs to add a new term
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param object the Term object
|
||||
*/
|
||||
do_action( 'jetpack_sync_add_term', $term_object );
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the client needs to update a term
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param object the Term object
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_term', $term_object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter blacklisted taxonomies.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Hook args.
|
||||
* @return array|boolean False if not whitelisted, the original hook args otherwise.
|
||||
*/
|
||||
public function filter_blacklisted_taxonomies( $args ) {
|
||||
$term = $args[0];
|
||||
|
||||
if ( in_array( $term->taxonomy, Settings::get_setting( 'taxonomies_blacklist' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out set_object_terms actions where the terms have not changed.
|
||||
*
|
||||
* @param array $args Hook args.
|
||||
* @return array|boolean False if no change in terms, the original hook args otherwise.
|
||||
*/
|
||||
public function filter_set_object_terms_no_update( $args ) {
|
||||
// There is potential for other plugins to modify args, therefore lets validate # of and types.
|
||||
// $args[2] is $tt_ids, $args[5] is $old_tt_ids see wp-includes/taxonomy.php L2740.
|
||||
if ( 6 === count( $args ) && is_array( $args[2] ) && is_array( $args[5] ) ) {
|
||||
if ( empty( array_diff( $args[2], $args[5] ) ) && empty( array_diff( $args[5], $args[2] ) ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the term taxonomy IDs to terms within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The expanded hook parameters.
|
||||
*/
|
||||
public function expand_term_taxonomy_id( $args ) {
|
||||
list( $term_taxonomy_ids, $previous_end ) = $args;
|
||||
|
||||
return array(
|
||||
'terms' => get_terms(
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
'term_taxonomy_id' => $term_taxonomy_ids,
|
||||
'orderby' => 'term_taxonomy_id',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
),
|
||||
'previous_end' => $previous_end,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a term object based on a given row from the term_relationships database table.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param object $relationship A row object from the term_relationships table.
|
||||
* @return object|bool A term object, or false if term taxonomy doesn't exist.
|
||||
*/
|
||||
public function expand_terms_for_relationship( $relationship ) {
|
||||
return get_term_by( 'term_taxonomy_id', $relationship->term_taxonomy_id );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,877 @@
|
||||
<?php
|
||||
/**
|
||||
* Themes sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
/**
|
||||
* Class to handle sync for themes.
|
||||
*/
|
||||
class Themes extends Module {
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'themes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize themes action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
|
||||
add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
|
||||
add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
|
||||
add_action( 'jetpack_installed_theme', $callable, 10, 2 );
|
||||
add_action( 'jetpack_updated_themes', $callable, 10, 2 );
|
||||
add_filter( 'wp_redirect', array( $this, 'detect_theme_edit' ) );
|
||||
add_action( 'jetpack_edited_theme', $callable, 10, 2 );
|
||||
add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'theme_edit_ajax' ), 0 );
|
||||
add_action( 'update_site_option_allowedthemes', array( $this, 'sync_network_allowed_themes_change' ), 10, 4 );
|
||||
add_action( 'jetpack_network_disabled_themes', $callable, 10, 2 );
|
||||
add_action( 'jetpack_network_enabled_themes', $callable, 10, 2 );
|
||||
|
||||
// Theme deletions.
|
||||
add_action( 'deleted_theme', array( $this, 'detect_theme_deletion' ), 10, 2 );
|
||||
add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
|
||||
|
||||
// Sidebar updates.
|
||||
add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
|
||||
|
||||
add_action( 'jetpack_widget_added', $callable, 10, 4 );
|
||||
add_action( 'jetpack_widget_removed', $callable, 10, 4 );
|
||||
add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
|
||||
add_action( 'jetpack_cleared_inactive_widgets', $callable );
|
||||
add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
|
||||
add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
|
||||
add_action( 'jetpack_widget_edited', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync handler for a widget edit.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Implement nonce verification
|
||||
*
|
||||
* @param array $instance The current widget instance's settings.
|
||||
* @param array $new_instance Array of new widget settings.
|
||||
* @param array $old_instance Array of old widget settings.
|
||||
* @param \WP_Widget $widget_object The current widget instance.
|
||||
* @return array The current widget instance's settings.
|
||||
*/
|
||||
public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
|
||||
if ( empty( $old_instance ) ) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
$widget = array(
|
||||
'name' => $widget_object->name,
|
||||
'id' => $widget_object->id,
|
||||
'title' => isset( $new_instance['title'] ) ? $new_instance['title'] : '',
|
||||
);
|
||||
/**
|
||||
* Trigger action to alert $callable sync listener that a widget was edited.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $widget_name , Name of edited widget
|
||||
*/
|
||||
do_action( 'jetpack_widget_edited', $widget );
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync handler for network allowed themes change.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $option Name of the network option.
|
||||
* @param mixed $value Current value of the network option.
|
||||
* @param mixed $old_value Old value of the network option.
|
||||
* @param int $network_id ID of the network.
|
||||
*/
|
||||
public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$all_enabled_theme_slugs = array_keys( $value );
|
||||
|
||||
if ( count( $old_value ) > count( $value ) ) {
|
||||
|
||||
// Suppress jetpack_network_disabled_themes sync action when theme is deleted.
|
||||
$delete_theme_call = $this->get_delete_theme_call();
|
||||
if ( ! empty( $delete_theme_call ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
|
||||
$newly_disabled_themes = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
|
||||
/**
|
||||
* Trigger action to alert $callable sync listener that network themes were disabled.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param mixed $newly_disabled_themes, Array of info about network disabled themes
|
||||
* @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
|
||||
*/
|
||||
do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
|
||||
return;
|
||||
}
|
||||
|
||||
$newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
|
||||
$newly_enabled_themes = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
|
||||
/**
|
||||
* Trigger action to alert $callable sync listener that network themes were enabled
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param mixed $newly_enabled_themes , Array of info about network enabled themes
|
||||
* @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
|
||||
*/
|
||||
do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve details for one or more themes by their slugs.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $theme_slugs Theme slugs.
|
||||
* @return array Details for the themes.
|
||||
*/
|
||||
private function get_theme_details_for_slugs( $theme_slugs ) {
|
||||
$theme_data = array();
|
||||
foreach ( $theme_slugs as $slug ) {
|
||||
$theme = wp_get_theme( $slug );
|
||||
$theme_data[ $slug ] = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
'slug' => $slug,
|
||||
);
|
||||
}
|
||||
return $theme_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a theme edit during a redirect.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $redirect_url Redirect URL.
|
||||
* @return string Redirect URL.
|
||||
*/
|
||||
public function detect_theme_edit( $redirect_url ) {
|
||||
$url = wp_parse_url( admin_url( $redirect_url ) );
|
||||
$theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
|
||||
|
||||
if ( $theme_editor_url['path'] !== $url['path'] ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
$query_params = array();
|
||||
wp_parse_str( $url['query'], $query_params );
|
||||
if (
|
||||
! isset( $_POST['newcontent'] ) ||
|
||||
! isset( $query_params['file'] ) ||
|
||||
! isset( $query_params['theme'] ) ||
|
||||
! isset( $query_params['updated'] )
|
||||
) {
|
||||
return $redirect_url;
|
||||
}
|
||||
$theme = wp_get_theme( $query_params['theme'] );
|
||||
$theme_data = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Trigger action to alert $callable sync listener that a theme was edited.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $query_params['theme'], Slug of edited theme
|
||||
* @param string $theme_data, Information about edited them
|
||||
*/
|
||||
do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
|
||||
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for AJAX theme editing.
|
||||
*
|
||||
* @todo Refactor to use WP_Filesystem instead of fopen()/fclose().
|
||||
*/
|
||||
public function theme_edit_ajax() {
|
||||
$args = wp_unslash( $_POST );
|
||||
|
||||
if ( empty( $args['theme'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $args['file'] ) ) {
|
||||
return;
|
||||
}
|
||||
$file = $args['file'];
|
||||
if ( 0 !== validate_file( $file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $args['newcontent'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $args['nonce'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stylesheet = $args['theme'];
|
||||
if ( 0 !== validate_file( $stylesheet ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_themes' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$theme = wp_get_theme( $stylesheet );
|
||||
if ( ! $theme->exists() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$editable_extensions = wp_get_theme_file_editable_extensions( $theme );
|
||||
|
||||
$allowed_files = array();
|
||||
foreach ( $editable_extensions as $type ) {
|
||||
switch ( $type ) {
|
||||
case 'php':
|
||||
$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
|
||||
break;
|
||||
case 'css':
|
||||
$style_files = $theme->get_files( 'css', -1 );
|
||||
$allowed_files['style.css'] = $style_files['style.css'];
|
||||
$allowed_files = array_merge( $allowed_files, $style_files );
|
||||
break;
|
||||
default:
|
||||
$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$real_file = $theme->get_stylesheet_directory() . '/' . $file;
|
||||
if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure file is real.
|
||||
if ( ! is_file( $real_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure file extension is allowed.
|
||||
$extension = null;
|
||||
if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
|
||||
$extension = strtolower( $matches[1] );
|
||||
if ( ! in_array( $extension, $editable_extensions, true ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_writeable( $real_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
|
||||
$file_pointer = fopen( $real_file, 'w+' );
|
||||
if ( false === $file_pointer ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
|
||||
fclose( $file_pointer );
|
||||
|
||||
$theme_data = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* This action is documented already in this file.
|
||||
*/
|
||||
do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a theme deletion.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $stylesheet Stylesheet of the theme to delete.
|
||||
* @param bool $deleted Whether the theme deletion was successful.
|
||||
*/
|
||||
public function detect_theme_deletion( $stylesheet, $deleted ) {
|
||||
$theme = wp_get_theme( $stylesheet );
|
||||
$theme_data = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
'slug' => $stylesheet,
|
||||
);
|
||||
|
||||
if ( $deleted ) {
|
||||
/**
|
||||
* Signals to the sync listener that a theme was deleted and a sync action
|
||||
* reflecting the deletion and theme slug should be sent
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $stylesheet Theme slug
|
||||
* @param array $theme_data Theme info Since 5.3
|
||||
*/
|
||||
do_action( 'jetpack_deleted_theme', $stylesheet, $theme_data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an upgrader completion action.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Upgrader $upgrader The upgrader instance.
|
||||
* @param array $details Array of bulk item update data.
|
||||
*/
|
||||
public function check_upgrader( $upgrader, $details ) {
|
||||
if ( ! isset( $details['type'] ) ||
|
||||
'theme' !== $details['type'] ||
|
||||
is_wp_error( $upgrader->skin->result ) ||
|
||||
! method_exists( $upgrader, 'theme_info' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'install' === $details['action'] ) {
|
||||
$theme = $upgrader->theme_info();
|
||||
if ( ! $theme instanceof \WP_Theme ) {
|
||||
return;
|
||||
}
|
||||
$theme_info = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Signals to the sync listener that a theme was installed and a sync action
|
||||
* reflecting the installation and the theme info should be sent
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param string $theme->theme_root Text domain of the theme
|
||||
* @param mixed $theme_info Array of abbreviated theme info
|
||||
*/
|
||||
do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
|
||||
}
|
||||
|
||||
if ( 'update' === $details['action'] ) {
|
||||
$themes = array();
|
||||
|
||||
if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
|
||||
$details['themes'] = array( $details['theme'] );
|
||||
}
|
||||
|
||||
foreach ( $details['themes'] as $theme_slug ) {
|
||||
$theme = wp_get_theme( $theme_slug );
|
||||
|
||||
if ( ! $theme instanceof \WP_Theme ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$themes[ $theme_slug ] = array(
|
||||
'name' => $theme->get( 'Name' ),
|
||||
'version' => $theme->get( 'Version' ),
|
||||
'uri' => $theme->get( 'ThemeURI' ),
|
||||
'stylesheet' => $theme->stylesheet,
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $themes ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to the sync listener that one or more themes was updated and a sync action
|
||||
* reflecting the update and the theme info should be sent
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 6.2.0
|
||||
*
|
||||
* @param mixed $themes Array of abbreviated theme info
|
||||
*/
|
||||
do_action( 'jetpack_updated_themes', $themes );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize themes action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_theme_data', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a theme switch.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $new_name Name of the new theme.
|
||||
* @param \WP_Theme $new_theme The new theme.
|
||||
* @param \WP_Theme $old_theme The previous theme.
|
||||
*/
|
||||
public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
|
||||
$previous_theme = $this->get_theme_info( $old_theme );
|
||||
|
||||
/**
|
||||
* Fires when the client needs to sync theme support info
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param array the theme support array
|
||||
* @param array the previous theme since Jetpack 6.5.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_current_theme_support', $this->get_theme_info(), $previous_theme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the themes actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all theme data to the server
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean Whether to expand theme data (should always be true)
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_theme_data', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the themes actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $state This module Full Sync status.
|
||||
*
|
||||
* @return array This module Full Sync status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_theme_data', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_theme_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the theme within a hook before it is serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Theme data.
|
||||
*/
|
||||
public function expand_theme_data() {
|
||||
return array( $this->get_theme_info() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the widget by the widget ID.
|
||||
*
|
||||
* @access public
|
||||
* @global $wp_registered_widgets
|
||||
*
|
||||
* @param string $widget_id Widget ID.
|
||||
* @return string Name of the widget, or null if not found.
|
||||
*/
|
||||
public function get_widget_name( $widget_id ) {
|
||||
global $wp_registered_widgets;
|
||||
return ( isset( $wp_registered_widgets[ $widget_id ] ) ? $wp_registered_widgets[ $widget_id ]['name'] : null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the sidebar by the sidebar ID.
|
||||
*
|
||||
* @access public
|
||||
* @global $wp_registered_sidebars
|
||||
*
|
||||
* @param string $sidebar_id Sidebar ID.
|
||||
* @return string Name of the sidebar, or null if not found.
|
||||
*/
|
||||
public function get_sidebar_name( $sidebar_id ) {
|
||||
global $wp_registered_sidebars;
|
||||
return ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync addition of widgets to a sidebar.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $new_widgets New widgets.
|
||||
* @param array $old_widgets Old widgets.
|
||||
* @param string $sidebar Sidebar ID.
|
||||
* @return array All widgets that have been moved to the sidebar.
|
||||
*/
|
||||
public function sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar ) {
|
||||
$added_widgets = array_diff( $new_widgets, $old_widgets );
|
||||
if ( empty( $added_widgets ) ) {
|
||||
return array();
|
||||
}
|
||||
$moved_to_sidebar = array();
|
||||
$sidebar_name = $this->get_sidebar_name( $sidebar );
|
||||
|
||||
// Don't sync jetpack_widget_added if theme was switched.
|
||||
if ( $this->is_theme_switch() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ( $added_widgets as $added_widget ) {
|
||||
$moved_to_sidebar[] = $added_widget;
|
||||
$added_widget_name = $this->get_widget_name( $added_widget );
|
||||
/**
|
||||
* Helps Sync log that a widget got added
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param string $sidebar, Sidebar id got changed
|
||||
* @param string $added_widget, Widget id got added
|
||||
* @param string $sidebar_name, Sidebar id got changed Since 5.0.0
|
||||
* @param string $added_widget_name, Widget id got added Since 5.0.0
|
||||
*/
|
||||
do_action( 'jetpack_widget_added', $sidebar, $added_widget, $sidebar_name, $added_widget_name );
|
||||
}
|
||||
return $moved_to_sidebar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync removal of widgets from a sidebar.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $new_widgets New widgets.
|
||||
* @param array $old_widgets Old widgets.
|
||||
* @param string $sidebar Sidebar ID.
|
||||
* @param array $inactive_widgets Current inactive widgets.
|
||||
* @return array All widgets that have been moved to inactive.
|
||||
*/
|
||||
public function sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $inactive_widgets ) {
|
||||
$removed_widgets = array_diff( $old_widgets, $new_widgets );
|
||||
|
||||
if ( empty( $removed_widgets ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$moved_to_inactive = array();
|
||||
$sidebar_name = $this->get_sidebar_name( $sidebar );
|
||||
|
||||
foreach ( $removed_widgets as $removed_widget ) {
|
||||
// Lets check if we didn't move the widget to in_active_widgets.
|
||||
if ( isset( $inactive_widgets ) && ! in_array( $removed_widget, $inactive_widgets, true ) ) {
|
||||
$removed_widget_name = $this->get_widget_name( $removed_widget );
|
||||
/**
|
||||
* Helps Sync log that a widgte got removed
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param string $sidebar, Sidebar id got changed
|
||||
* @param string $removed_widget, Widget id got removed
|
||||
* @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
|
||||
* @param string $removed_widget_name, Name of the widget that got removed Since 5.0.0
|
||||
*/
|
||||
do_action( 'jetpack_widget_removed', $sidebar, $removed_widget, $sidebar_name, $removed_widget_name );
|
||||
} else {
|
||||
$moved_to_inactive[] = $removed_widget;
|
||||
}
|
||||
}
|
||||
return $moved_to_inactive;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a reorder of widgets within a sidebar.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor serialize() to a json_encode().
|
||||
*
|
||||
* @param array $new_widgets New widgets.
|
||||
* @param array $old_widgets Old widgets.
|
||||
* @param string $sidebar Sidebar ID.
|
||||
*/
|
||||
public function sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar ) {
|
||||
$added_widgets = array_diff( $new_widgets, $old_widgets );
|
||||
if ( ! empty( $added_widgets ) ) {
|
||||
return;
|
||||
}
|
||||
$removed_widgets = array_diff( $old_widgets, $new_widgets );
|
||||
if ( ! empty( $removed_widgets ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
if ( serialize( $old_widgets ) !== serialize( $new_widgets ) ) {
|
||||
$sidebar_name = $this->get_sidebar_name( $sidebar );
|
||||
/**
|
||||
* Helps Sync log that a sidebar id got reordered
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param string $sidebar, Sidebar id got changed
|
||||
* @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
|
||||
*/
|
||||
do_action( 'jetpack_widget_reordered', $sidebar, $sidebar_name );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the update of the sidebars and widgets mapping option.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $old_value The old option value.
|
||||
* @param mixed $new_value The new option value.
|
||||
*/
|
||||
public function sync_sidebar_widgets_actions( $old_value, $new_value ) {
|
||||
// Don't really know how to deal with different array_values yet.
|
||||
if (
|
||||
( isset( $old_value['array_version'] ) && 3 !== $old_value['array_version'] ) ||
|
||||
( isset( $new_value['array_version'] ) && 3 !== $new_value['array_version'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$moved_to_inactive_ids = array();
|
||||
$moved_to_sidebar = array();
|
||||
|
||||
foreach ( $new_value as $sidebar => $new_widgets ) {
|
||||
if ( in_array( $sidebar, array( 'array_version', 'wp_inactive_widgets' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
$old_widgets = isset( $old_value[ $sidebar ] )
|
||||
? $old_value[ $sidebar ]
|
||||
: array();
|
||||
|
||||
if ( ! is_array( $new_widgets ) ) {
|
||||
$new_widgets = array();
|
||||
}
|
||||
|
||||
$moved_to_inactive_recently = $this->sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $new_value['wp_inactive_widgets'] );
|
||||
$moved_to_inactive_ids = array_merge( $moved_to_inactive_ids, $moved_to_inactive_recently );
|
||||
|
||||
$moved_to_sidebar_recently = $this->sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar );
|
||||
$moved_to_sidebar = array_merge( $moved_to_sidebar, $moved_to_sidebar_recently );
|
||||
|
||||
$this->sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar );
|
||||
|
||||
}
|
||||
|
||||
// Don't sync either jetpack_widget_moved_to_inactive or jetpack_cleared_inactive_widgets if theme was switched.
|
||||
if ( $this->is_theme_switch() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Treat inactive sidebar a bit differently.
|
||||
if ( ! empty( $moved_to_inactive_ids ) ) {
|
||||
$moved_to_inactive_name = array_map( array( $this, 'get_widget_name' ), $moved_to_inactive_ids );
|
||||
/**
|
||||
* Helps Sync log that a widgets IDs got moved to in active
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param array $moved_to_inactive_ids, Array of widgets id that moved to inactive id got changed
|
||||
* @param array $moved_to_inactive_names, Array of widgets names that moved to inactive id got changed Since 5.0.0
|
||||
*/
|
||||
do_action( 'jetpack_widget_moved_to_inactive', $moved_to_inactive_ids, $moved_to_inactive_name );
|
||||
} elseif ( empty( $moved_to_sidebar ) && empty( $new_value['wp_inactive_widgets'] ) && ! empty( $old_value['wp_inactive_widgets'] ) ) {
|
||||
/**
|
||||
* Helps Sync log that a got cleared from inactive.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*/
|
||||
do_action( 'jetpack_cleared_inactive_widgets' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the theme data for the current or a specific theme.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param \WP_Theme $theme Theme object. Optional, will default to the current theme.
|
||||
*
|
||||
* @return array Theme data.
|
||||
*/
|
||||
private function get_theme_info( $theme = null ) {
|
||||
$theme_support = array();
|
||||
|
||||
// We are trying to get the current theme info.
|
||||
if ( null === $theme ) {
|
||||
$theme = wp_get_theme();
|
||||
}
|
||||
|
||||
$theme_support['name'] = $theme->get( 'Name' );
|
||||
$theme_support['version'] = $theme->get( 'Version' );
|
||||
$theme_support['slug'] = $theme->get_stylesheet();
|
||||
$theme_support['uri'] = $theme->get( 'ThemeURI' );
|
||||
|
||||
return $theme_support;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we've deleted a theme in the current request.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return boolean True if this is a theme deletion request, false otherwise.
|
||||
*/
|
||||
private function get_delete_theme_call() {
|
||||
// Intentional usage of `debug_backtrace()` for production needs.
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
$backtrace = debug_backtrace();
|
||||
$delete_theme_call = null;
|
||||
foreach ( $backtrace as $call ) {
|
||||
if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
|
||||
$delete_theme_call = $call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $delete_theme_call;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we've switched to another theme in the current request.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return boolean True if this is a theme switch request, false otherwise.
|
||||
*/
|
||||
private function is_theme_switch() {
|
||||
return did_action( 'after_switch_theme' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of constants by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
if ( 'theme-info' !== $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array( $this->get_theme_info() );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,585 @@
|
||||
<?php
|
||||
/**
|
||||
* Updates sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
|
||||
/**
|
||||
* Class to handle sync for updates.
|
||||
*/
|
||||
class Updates extends Module {
|
||||
/**
|
||||
* Name of the updates checksum option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UPDATES_CHECKSUM_OPTION_NAME = 'jetpack_updates_sync_checksum';
|
||||
|
||||
/**
|
||||
* WordPress Version.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $old_wp_version = null;
|
||||
|
||||
/**
|
||||
* The current updates.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $updates = array();
|
||||
|
||||
/**
|
||||
* Set module defaults.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->updates = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'updates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize updates action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
global $wp_version;
|
||||
$this->old_wp_version = $wp_version;
|
||||
add_action( 'set_site_transient_update_plugins', array( $this, 'validate_update_change' ), 10, 3 );
|
||||
add_action( 'set_site_transient_update_themes', array( $this, 'validate_update_change' ), 10, 3 );
|
||||
add_action( 'set_site_transient_update_core', array( $this, 'validate_update_change' ), 10, 3 );
|
||||
|
||||
add_action( 'jetpack_update_plugins_change', $callable );
|
||||
add_action( 'jetpack_update_themes_change', $callable );
|
||||
add_action( 'jetpack_update_core_change', $callable );
|
||||
|
||||
add_filter(
|
||||
'jetpack_sync_before_enqueue_jetpack_update_plugins_change',
|
||||
array(
|
||||
$this,
|
||||
'filter_update_keys',
|
||||
),
|
||||
10,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'jetpack_sync_before_enqueue_upgrader_process_complete',
|
||||
array(
|
||||
$this,
|
||||
'filter_upgrader_process_complete',
|
||||
),
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_action( 'automatic_updates_complete', $callable );
|
||||
|
||||
if ( is_multisite() ) {
|
||||
add_filter( 'pre_update_site_option_wpmu_upgrade_site', array( $this, 'update_core_network_event' ), 10, 2 );
|
||||
add_action( 'jetpack_sync_core_update_network', $callable, 10, 3 );
|
||||
}
|
||||
|
||||
// Send data when update completes.
|
||||
add_action( '_core_updated_successfully', array( $this, 'update_core' ) );
|
||||
add_action( 'jetpack_sync_core_reinstalled_successfully', $callable );
|
||||
add_action( 'jetpack_sync_core_autoupdated_successfully', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_core_updated_successfully', $callable, 10, 2 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize updates action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_updates', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_updates', array( $this, 'expand_updates' ) );
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_update_themes_change', array( $this, 'expand_themes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a core network update.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $wp_db_version Current version of the WordPress database.
|
||||
* @param int $old_wp_db_version Old version of the WordPress database.
|
||||
* @return int Current version of the WordPress database.
|
||||
*/
|
||||
public function update_core_network_event( $wp_db_version, $old_wp_db_version ) {
|
||||
global $wp_version;
|
||||
/**
|
||||
* Sync event for when core wp network updates to a new db version
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param int $wp_db_version the latest wp_db_version
|
||||
* @param int $old_wp_db_version previous wp_db_version
|
||||
* @param string $wp_version the latest wp_version
|
||||
*/
|
||||
do_action( 'jetpack_sync_core_update_network', $wp_db_version, $old_wp_db_version, $wp_version );
|
||||
return $wp_db_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a core update.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Implement nonce or refactor to use `admin_post_{$action}` hooks instead.
|
||||
*
|
||||
* @param string $new_wp_version The new WP core version.
|
||||
*/
|
||||
public function update_core( $new_wp_version ) {
|
||||
global $pagenow;
|
||||
|
||||
// // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['action'] ) && 'do-core-reinstall' === $_GET['action'] ) {
|
||||
/**
|
||||
* Sync event that fires when core reinstall was successful
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $new_wp_version the updated WordPress version
|
||||
*/
|
||||
do_action( 'jetpack_sync_core_reinstalled_successfully', $new_wp_version );
|
||||
return;
|
||||
}
|
||||
|
||||
// Core was autoupdated.
|
||||
if (
|
||||
'update-core.php' !== $pagenow &&
|
||||
! Jetpack_Constants::is_true( 'REST_API_REQUEST' ) // WP.com rest api calls should never be marked as a core autoupdate.
|
||||
) {
|
||||
/**
|
||||
* Sync event that fires when core autoupdate was successful
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $new_wp_version the updated WordPress version
|
||||
* @param string $old_wp_version the previous WordPress version
|
||||
*/
|
||||
do_action( 'jetpack_sync_core_autoupdated_successfully', $new_wp_version, $this->old_wp_version );
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Sync event that fires when core update was successful
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.0.0
|
||||
*
|
||||
* @param string $new_wp_version the updated WordPress version
|
||||
* @param string $old_wp_version the previous WordPress version
|
||||
*/
|
||||
do_action( 'jetpack_sync_core_updated_successfully', $new_wp_version, $this->old_wp_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the checksum for an update.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param object $update The update object.
|
||||
* @param string $transient The transient we're retrieving a checksum for.
|
||||
* @return int The checksum.
|
||||
*/
|
||||
public function get_update_checksum( $update, $transient ) {
|
||||
$updates = array();
|
||||
$no_updated = array();
|
||||
switch ( $transient ) {
|
||||
case 'update_plugins':
|
||||
if ( ! empty( $update->response ) && is_array( $update->response ) ) {
|
||||
foreach ( $update->response as $plugin_slug => $response ) {
|
||||
if ( ! empty( $plugin_slug ) && isset( $response->new_version ) ) {
|
||||
$updates[] = array( $plugin_slug => $response->new_version );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! empty( $update->no_update ) ) {
|
||||
$no_updated = array_keys( $update->no_update );
|
||||
}
|
||||
|
||||
if ( ! isset( $no_updated['jetpack/jetpack.php'] ) && isset( $updates['jetpack/jetpack.php'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'update_themes':
|
||||
if ( ! empty( $update->response ) && is_array( $update->response ) ) {
|
||||
foreach ( $update->response as $theme_slug => $response ) {
|
||||
if ( ! empty( $theme_slug ) && isset( $response['new_version'] ) ) {
|
||||
$updates[] = array( $theme_slug => $response['new_version'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $update->checked ) ) {
|
||||
$no_updated = $update->checked;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'update_core':
|
||||
if ( ! empty( $update->updates ) && is_array( $update->updates ) ) {
|
||||
foreach ( $update->updates as $response ) {
|
||||
if ( ! empty( $response->response ) && 'latest' === $response->response ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! empty( $response->response ) && isset( $response->packages->full ) ) {
|
||||
$updates[] = array( $response->response => $response->packages->full );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $update->version_checked ) ) {
|
||||
$no_updated = $update->version_checked;
|
||||
}
|
||||
|
||||
if ( empty( $updates ) ) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
if ( empty( $updates ) && empty( $no_updated ) ) {
|
||||
return false;
|
||||
}
|
||||
return $this->get_check_sum( array( $no_updated, $updates ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a change coming from an update before sending for sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $value Site transient value.
|
||||
* @param int $expiration Time until transient expiration in seconds.
|
||||
* @param string $transient Transient name.
|
||||
*/
|
||||
public function validate_update_change( $value, $expiration, $transient ) {
|
||||
$new_checksum = $this->get_update_checksum( $value, $transient );
|
||||
|
||||
if ( false === $new_checksum ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$checksums = get_option( self::UPDATES_CHECKSUM_OPTION_NAME, array() );
|
||||
|
||||
if ( isset( $checksums[ $transient ] ) && $checksums[ $transient ] === $new_checksum ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$checksums[ $transient ] = $new_checksum;
|
||||
|
||||
update_option( self::UPDATES_CHECKSUM_OPTION_NAME, $checksums );
|
||||
if ( 'update_core' === $transient ) {
|
||||
/**
|
||||
* Trigger a change to core update that we want to sync.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.1.0
|
||||
*
|
||||
* @param array $value Contains info that tells us what needs updating.
|
||||
*/
|
||||
do_action( 'jetpack_update_core_change', $value );
|
||||
return;
|
||||
}
|
||||
if ( empty( $this->updates ) ) {
|
||||
// Lets add the shutdown method once and only when the updates move from empty to filled with something.
|
||||
add_action( 'shutdown', array( $this, 'sync_last_event' ), 9 );
|
||||
}
|
||||
if ( ! isset( $this->updates[ $transient ] ) ) {
|
||||
$this->updates[ $transient ] = array();
|
||||
}
|
||||
$this->updates[ $transient ][] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the last update only.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function sync_last_event() {
|
||||
foreach ( $this->updates as $transient => $values ) {
|
||||
$value = end( $values ); // Only send over the last value.
|
||||
/**
|
||||
* Trigger a change to a specific update that we want to sync.
|
||||
* Triggers one of the following actions:
|
||||
* - jetpack_{$transient}_change
|
||||
* - jetpack_update_plugins_change
|
||||
* - jetpack_update_themes_change
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.1.0
|
||||
*
|
||||
* @param array $value Contains info that tells us what needs updating.
|
||||
*/
|
||||
do_action( "jetpack_{$transient}_change", $value );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the updates actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
/**
|
||||
* Tells the client to sync all updates to the server
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param boolean Whether to expand updates (should always be true)
|
||||
*/
|
||||
do_action( 'jetpack_full_sync_updates', true );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 1, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the updates actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $send_until The timestamp until the current request can send.
|
||||
* @param array $state This module Full Sync status.
|
||||
*
|
||||
* @return array This module Full Sync status.
|
||||
*/
|
||||
public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// we call this instead of do_action when sending immediately.
|
||||
$this->send_action( 'jetpack_full_sync_updates', array( true ) );
|
||||
|
||||
// The number of actions enqueued, and next module state (true == done).
|
||||
return array( 'finished' => true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_updates' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all updates that we're interested in.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array All updates.
|
||||
*/
|
||||
public function get_all_updates() {
|
||||
return array(
|
||||
'core' => get_site_transient( 'update_core' ),
|
||||
'plugins' => get_site_transient( 'update_plugins' ),
|
||||
'themes' => get_site_transient( 'update_themes' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary keys from synced updates data.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Hook arguments.
|
||||
* @return array $args Hook arguments.
|
||||
*/
|
||||
public function filter_update_keys( $args ) {
|
||||
$updates = $args[0];
|
||||
|
||||
if ( isset( $updates->no_update ) ) {
|
||||
unset( $updates->no_update );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out upgrader object from the completed upgrader action args.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Hook arguments.
|
||||
* @return array $args Filtered hook arguments.
|
||||
*/
|
||||
public function filter_upgrader_process_complete( $args ) {
|
||||
array_shift( $args );
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the updates within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_updates( $args ) {
|
||||
if ( $args[0] ) {
|
||||
return $this->get_all_updates();
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the themes within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook parameters.
|
||||
* @return array $args The hook parameters.
|
||||
*/
|
||||
public function expand_themes( $args ) {
|
||||
if ( ! isset( $args[0], $args[0]->response ) ) {
|
||||
return $args;
|
||||
}
|
||||
if ( ! is_array( $args[0]->response ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
trigger_error( 'Warning: Not an Array as expected but -> ' . wp_json_encode( $args[0]->response ) . ' instead', E_USER_WARNING );
|
||||
return $args;
|
||||
}
|
||||
foreach ( $args[0]->response as $stylesheet => &$theme_data ) {
|
||||
$theme = wp_get_theme( $stylesheet );
|
||||
$theme_data['name'] = $theme->name;
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform module cleanup.
|
||||
* Deletes any transients and options that this module uses.
|
||||
* Usually triggered when uninstalling the plugin.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function reset_data() {
|
||||
delete_option( self::UPDATES_CHECKSUM_OPTION_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Total number of objects.
|
||||
*
|
||||
* @param array $config Full Sync config.
|
||||
*
|
||||
* @return int total
|
||||
*/
|
||||
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of updates by their IDs.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids Object IDs.
|
||||
* @return array Array of objects.
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
if ( empty( $ids ) || empty( $object_type ) || 'update' !== $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = array();
|
||||
foreach ( (array) $ids as $id ) {
|
||||
$object = $this->get_object_by_id( $object_type, $id );
|
||||
|
||||
if ( 'all' === $id ) {
|
||||
// If all was requested it contains all updates and can simply be returned.
|
||||
return $object;
|
||||
}
|
||||
$objects[ $id ] = $object;
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a update by its id.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param string $id ID of the sync object.
|
||||
* @return mixed Value of Update.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'update' === $object_type ) {
|
||||
|
||||
// Only whitelisted constants can be returned.
|
||||
if ( in_array( $id, array( 'core', 'plugins', 'themes' ), true ) ) {
|
||||
return get_site_transient( 'update_' . $id );
|
||||
} elseif ( 'all' === $id ) {
|
||||
return $this->get_all_updates();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,871 @@
|
||||
<?php
|
||||
/**
|
||||
* Users sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use Automattic\Jetpack\Constants as Jetpack_Constants;
|
||||
use Automattic\Jetpack\Password_Checker;
|
||||
use Automattic\Jetpack\Sync\Defaults;
|
||||
|
||||
/**
|
||||
* Class to handle sync for users.
|
||||
*/
|
||||
class Users extends Module {
|
||||
/**
|
||||
* Maximum number of users to sync initially.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_INITIAL_SYNC_USERS = 100;
|
||||
|
||||
/**
|
||||
* User flags we care about.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $flags = array();
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'users';
|
||||
}
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return 'usermeta';
|
||||
}
|
||||
|
||||
/**
|
||||
* The id field in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id_field() {
|
||||
return 'user_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a user by its ID.
|
||||
* This is here to support the backfill API.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $object_type Type of the sync object.
|
||||
* @param int $id ID of the sync object.
|
||||
* @return \WP_User|bool Filtered \WP_User object, or false if the object is not a user.
|
||||
*/
|
||||
public function get_object_by_id( $object_type, $id ) {
|
||||
if ( 'user' === $object_type ) {
|
||||
$user = get_user_by( 'id', (int) $id );
|
||||
if ( $user ) {
|
||||
return $this->sanitize_user_and_expand( $user );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize users action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
// Users.
|
||||
add_action( 'user_register', array( $this, 'user_register_handler' ) );
|
||||
add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
|
||||
|
||||
add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
|
||||
add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
|
||||
|
||||
add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
|
||||
|
||||
add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
|
||||
add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
|
||||
|
||||
add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
|
||||
add_action( 'jetpack_deleted_user', $callable, 10, 3 );
|
||||
add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
|
||||
add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
|
||||
|
||||
// User roles.
|
||||
add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
|
||||
add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
|
||||
add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
|
||||
|
||||
// User capabilities.
|
||||
add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
|
||||
add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
|
||||
add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
|
||||
|
||||
// User authentication.
|
||||
add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 );
|
||||
add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 );
|
||||
|
||||
add_action( 'jetpack_wp_login', $callable, 10, 3 );
|
||||
|
||||
add_action( 'wp_logout', $callable, 10, 0 );
|
||||
add_action( 'wp_masterbar_logout', $callable, 10, 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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize users action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_users', $callable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
|
||||
add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
|
||||
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a user by a user ID or object.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param mixed $user User object or ID.
|
||||
* @return \WP_User User object, or `null` if user invalid/not found.
|
||||
*/
|
||||
private function get_user( $user ) {
|
||||
if ( is_numeric( $user ) ) {
|
||||
$user = get_user_by( 'id', $user );
|
||||
}
|
||||
if ( $user instanceof \WP_User ) {
|
||||
return $user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a user object.
|
||||
* Removes the password from the user object because we don't want to sync it.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor `serialize`/`unserialize` to `wp_json_encode`/`wp_json_decode`.
|
||||
*
|
||||
* @param \WP_User $user User object.
|
||||
* @return \WP_User Sanitized user object.
|
||||
*/
|
||||
public function sanitize_user( $user ) {
|
||||
$user = $this->get_user( $user );
|
||||
// This creates a new user object and stops the passing of the object by reference.
|
||||
// // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
|
||||
$user = unserialize( serialize( $user ) );
|
||||
|
||||
if ( is_object( $user ) && is_object( $user->data ) ) {
|
||||
unset( $user->data->user_pass );
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a particular user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_User $user User object.
|
||||
* @return \WP_User Expanded user object.
|
||||
*/
|
||||
public function expand_user( $user ) {
|
||||
if ( ! is_object( $user ) ) {
|
||||
return null;
|
||||
}
|
||||
$user->allowed_mime_types = get_allowed_mime_types( $user );
|
||||
$user->allcaps = $this->get_real_user_capabilities( $user );
|
||||
|
||||
// Only set the user locale if it is different from the site locale.
|
||||
if ( get_locale() !== get_user_locale( $user->ID ) ) {
|
||||
$user->locale = get_user_locale( $user->ID );
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve capabilities we care about for a particular user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_User $user User object.
|
||||
* @return array User capabilities.
|
||||
*/
|
||||
public function get_real_user_capabilities( $user ) {
|
||||
$user_capabilities = array();
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user_capabilities;
|
||||
}
|
||||
foreach ( Defaults::get_capabilities_whitelist() as $capability ) {
|
||||
if ( user_can( $user, $capability ) ) {
|
||||
$user_capabilities[ $capability ] = true;
|
||||
}
|
||||
}
|
||||
return $user_capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve, expand and sanitize a user.
|
||||
* Can be directly used in the sync user action handlers.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $user User ID or user object.
|
||||
* @return \WP_User Expanded and sanitized user object.
|
||||
*/
|
||||
public function sanitize_user_and_expand( $user ) {
|
||||
$user = $this->get_user( $user );
|
||||
$user = $this->expand_user( $user );
|
||||
return $this->sanitize_user( $user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the user within a hook before it is serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @return array $args The hook arguments.
|
||||
*/
|
||||
public function expand_action( $args ) {
|
||||
// The first argument is always the user.
|
||||
list( $user ) = $args;
|
||||
if ( $user ) {
|
||||
$args[0] = $this->sanitize_user_and_expand( $user );
|
||||
return $args;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the user username at login before being sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @return array $args Expanded hook arguments.
|
||||
*/
|
||||
public function expand_login_username( $args ) {
|
||||
list( $login, $user, $flags ) = $args;
|
||||
$user = $this->sanitize_user( $user );
|
||||
|
||||
return array( $login, $user, $flags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the user username at logout before being sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @param int $user_id ID of the user.
|
||||
* @return array $args Expanded hook arguments.
|
||||
*/
|
||||
public function expand_logout_username( $args, $user_id ) {
|
||||
$user = get_userdata( $user_id );
|
||||
$user = $this->sanitize_user( $user );
|
||||
|
||||
$login = '';
|
||||
if ( is_object( $user ) && is_object( $user->data ) ) {
|
||||
$login = $user->data->user_login;
|
||||
}
|
||||
|
||||
// If we don't have a user here lets not send anything.
|
||||
if ( empty( $login ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array( $login, $user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional processing is needed for wp_login so we introduce this wrapper handler.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $user_login The user login.
|
||||
* @param \WP_User $user The user object.
|
||||
*/
|
||||
public function wp_login_handler( $user_login, $user ) {
|
||||
/**
|
||||
* Fires when a user is logged into a site.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.2.0
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @param \WP_User $user The User Object of the user that currently logged in.
|
||||
* @param array $params Any Flags that have been added during login.
|
||||
*/
|
||||
do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) );
|
||||
$this->clear_flags( $user->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook for the authenticate event that checks the password strength.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Error|\WP_User $user The user object, or an error.
|
||||
* @param string $username The username.
|
||||
* @param string $password The password used to authenticate.
|
||||
* @return \WP_Error|\WP_User the same object that was passed into the function.
|
||||
*/
|
||||
public function authenticate_handler( $user, $username, $password ) {
|
||||
// In case of cookie authentication we don't do anything here.
|
||||
if ( empty( $password ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// We are only interested in successful authentication events.
|
||||
if ( is_wp_error( $user ) || ! ( $user instanceof \WP_User ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$password_checker = new Password_Checker( $user->ID );
|
||||
|
||||
$test_results = $password_checker->test( $password, true );
|
||||
|
||||
// If the password passes tests, we don't do anything.
|
||||
if ( empty( $test_results['test_results']['failed'] ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$this->add_flags(
|
||||
$user->ID,
|
||||
array(
|
||||
'warning' => 'The password failed at least one strength test.',
|
||||
'failures' => $test_results['test_results']['failed'],
|
||||
)
|
||||
);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for after the user is deleted.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $deleted_user_id ID of the deleted user.
|
||||
* @param int $reassigned_user_id ID of the user the deleted user's posts are reassigned to (if any).
|
||||
*/
|
||||
public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
|
||||
$is_multisite = is_multisite();
|
||||
/**
|
||||
* Fires when a user is deleted on a site
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param int $deleted_user_id - ID of the deleted user.
|
||||
* @param int $reassigned_user_id - ID of the user the deleted user's posts are reassigned to (if any).
|
||||
* @param bool $is_multisite - Whether this site is a multisite installation.
|
||||
*/
|
||||
do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for user registration.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the deleted user.
|
||||
*/
|
||||
public function user_register_handler( $user_id ) {
|
||||
// Ensure we only sync users who are members of the current blog.
|
||||
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
|
||||
$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
|
||||
}
|
||||
/**
|
||||
* Fires when a new user is registered on a site
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param object The WP_User object
|
||||
*/
|
||||
do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
|
||||
$this->clear_flags( $user_id );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for user addition to the current blog.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
*/
|
||||
public function add_user_to_blog_handler( $user_id ) {
|
||||
// Ensure we only sync users who are members of the current blog.
|
||||
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
|
||||
$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when a user is added on a site
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.9.0
|
||||
*
|
||||
* @param object The WP_User object
|
||||
*/
|
||||
do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
|
||||
$this->clear_flags( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for user save.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @param \WP_User $old_user_data User object before the changes.
|
||||
*/
|
||||
public function save_user_handler( $user_id, $old_user_data = null ) {
|
||||
// Ensure we only sync users who are members of the current blog.
|
||||
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
// Older versions of WP don't pass the old_user_data in ->data.
|
||||
if ( isset( $old_user_data->data ) ) {
|
||||
$old_user = $old_user_data->data;
|
||||
} else {
|
||||
$old_user = $old_user_data;
|
||||
}
|
||||
|
||||
if ( null !== $old_user && $user->user_pass !== $old_user->user_pass ) {
|
||||
$this->flags[ $user_id ]['password_changed'] = true;
|
||||
}
|
||||
if ( null !== $old_user && $user->data->user_email !== $old_user->user_email ) {
|
||||
/**
|
||||
* The '_new_email' user meta is deleted right after the call to wp_update_user
|
||||
* that got us to this point so if it's still set then this was a user confirming
|
||||
* their new email address.
|
||||
*/
|
||||
if ( 1 === (int) get_user_meta( $user->ID, '_new_email', true ) ) {
|
||||
$this->flags[ $user_id ]['email_changed'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the client needs to sync an updated user.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 4.2.0
|
||||
*
|
||||
* @param \WP_User The WP_User object
|
||||
* @param array State - New since 5.8.0
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
|
||||
$this->clear_flags( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for user role change.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @param string $role New user role.
|
||||
* @param array $old_roles Previous user roles.
|
||||
*/
|
||||
public function save_user_role_handler( $user_id, $role, $old_roles = null ) {
|
||||
$this->add_flags(
|
||||
$user_id,
|
||||
array(
|
||||
'role_changed' => true,
|
||||
'previous_role' => $old_roles,
|
||||
)
|
||||
);
|
||||
|
||||
// The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both.
|
||||
if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* This action is documented already in this file
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
|
||||
$this->clear_flags( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve current flags for a particular user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @return array Current flags of the user.
|
||||
*/
|
||||
public function get_flags( $user_id ) {
|
||||
if ( isset( $this->flags[ $user_id ] ) ) {
|
||||
return $this->flags[ $user_id ];
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the flags of a particular user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
*/
|
||||
public function clear_flags( $user_id ) {
|
||||
if ( isset( $this->flags[ $user_id ] ) ) {
|
||||
unset( $this->flags[ $user_id ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add flags to a particular user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @param array $flags New flags to add for the user.
|
||||
*/
|
||||
public function add_flags( $user_id, $flags ) {
|
||||
$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the user meta, if we're interested in it.
|
||||
* Also uses the time to add flags for the user.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $meta_id ID of the meta object.
|
||||
* @param int $user_id ID of the user.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param mixed $value Meta value.
|
||||
*/
|
||||
public function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
if ( 'locale' === $meta_key ) {
|
||||
$this->add_flags( $user_id, array( 'locale_changed' => true ) );
|
||||
}
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
if ( isset( $user->cap_key ) && $meta_key === $user->cap_key ) {
|
||||
$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
|
||||
}
|
||||
|
||||
if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $this->flags[ $user_id ] ) ) {
|
||||
/**
|
||||
* This action is documented already in this file
|
||||
*/
|
||||
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the users actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
global $wpdb;
|
||||
|
||||
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->usermeta, 'user_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor to prepare the SQL query before executing it.
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT count(*) FROM $wpdb->usermeta";
|
||||
|
||||
$where_sql = $this->get_where_sql( $config );
|
||||
if ( $where_sql ) {
|
||||
$query .= ' WHERE ' . $where_sql;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
|
||||
*/
|
||||
public function get_where_sql( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0";
|
||||
|
||||
// The $config variable is a list of user IDs to sync.
|
||||
if ( is_array( $config ) ) {
|
||||
$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_users' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve initial sync user config.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor the SQL query to call $wpdb->prepare() before execution.
|
||||
*
|
||||
* @return array|boolean IDs of users to initially sync, or false if tbe number of users exceed the maximum.
|
||||
*/
|
||||
public function get_initial_sync_user_config() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$user_ids = $wpdb->get_col( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0 LIMIT " . ( self::MAX_INITIAL_SYNC_USERS + 1 ) );
|
||||
|
||||
if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
|
||||
return $user_ids;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the users within a hook before they are serialized and sent to the server.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @return array $args The hook arguments.
|
||||
*/
|
||||
public function expand_users( $args ) {
|
||||
list( $user_ids, $previous_end ) = $args;
|
||||
|
||||
return array(
|
||||
'users' => array_map(
|
||||
array( $this, 'sanitize_user_and_expand' ),
|
||||
get_users(
|
||||
array(
|
||||
'include' => $user_ids,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
)
|
||||
),
|
||||
'previous_end' => $previous_end,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for user removal from a particular blog.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $user_id ID of the user.
|
||||
* @param int $blog_id ID of the blog.
|
||||
*/
|
||||
public function remove_user_from_blog_handler( $user_id, $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
// User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114.
|
||||
if ( $this->is_add_new_user_to_blog() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reassigned_user_id = $this->get_reassigned_network_user_id();
|
||||
|
||||
// Note that we are in the context of the blog the user is removed from, see https://github.com/WordPress/WordPress/blob/473e1ba73bc5c18c72d7f288447503713d518790/wp-includes/ms-functions.php#L233.
|
||||
/**
|
||||
* Fires when a user is removed from a blog on a multisite installation
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 5.4.0
|
||||
*
|
||||
* @param int $user_id - ID of the removed user
|
||||
* @param int $reassigned_user_id - ID of the user the removed user's posts are reassigned to (if any).
|
||||
*/
|
||||
do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we're adding a new user to a blog in this request.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_add_new_user_to_blog() {
|
||||
return $this->is_function_in_backtrace( 'add_new_user_to_blog' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we're adding an existing user to a blog in this request.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_add_user_to_blog() {
|
||||
return $this->is_function_in_backtrace( 'add_user_to_blog' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we're removing a user from a blog in this request.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_delete_user() {
|
||||
return $this->is_function_in_backtrace( array( 'wp_delete_user', 'remove_user_from_blog' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we're creating a user or adding a new user to a blog.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_create_user() {
|
||||
$functions = array(
|
||||
'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site.
|
||||
'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site.
|
||||
'wp_insert_user', // Used to suppress jetpack_sync_save_user in save_user_cap_handler and save_user_role_handler when user registered on single site.
|
||||
);
|
||||
|
||||
return $this->is_function_in_backtrace( $functions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the user the removed user's posts are reassigned to (if any).
|
||||
*
|
||||
* @return int ID of the user that got reassigned as the author of the posts.
|
||||
*/
|
||||
protected function get_reassigned_network_user_id() {
|
||||
$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
foreach ( $backtrace as $call ) {
|
||||
if (
|
||||
'remove_user_from_blog' === $call['function'] &&
|
||||
3 === count( $call['args'] )
|
||||
) {
|
||||
return $call['args'][2];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if one or more function names is in debug_backtrace.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param array|string $names Mixed string name of function or array of string names of functions.
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_function_in_backtrace( $names ) {
|
||||
$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
if ( ! is_array( $names ) ) {
|
||||
$names = array( $names );
|
||||
}
|
||||
$names_as_keys = array_flip( $names );
|
||||
|
||||
// Do check in constant O(1) time for PHP5.5+.
|
||||
if ( function_exists( 'array_column' ) ) {
|
||||
$backtrace_functions = array_column( $backtrace, 'function' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_columnFound
|
||||
$backtrace_functions_as_keys = array_flip( $backtrace_functions );
|
||||
$intersection = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
|
||||
return ! empty( $intersection );
|
||||
}
|
||||
|
||||
// Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) ).
|
||||
foreach ( $backtrace as $call ) {
|
||||
if ( isset( $names_as_keys[ $call['function'] ] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,613 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class to handle sync for WooCommerce.
|
||||
*/
|
||||
class WooCommerce extends Module {
|
||||
/**
|
||||
* Whitelist for order item meta we are interested to sync.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $order_item_meta_whitelist = array(
|
||||
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-product-store.php#L20 .
|
||||
'_product_id',
|
||||
'_variation_id',
|
||||
'_qty',
|
||||
// Tax ones also included in below class
|
||||
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-fee-data-store.php#L20 .
|
||||
'_tax_class',
|
||||
'_tax_status',
|
||||
'_line_subtotal',
|
||||
'_line_subtotal_tax',
|
||||
'_line_total',
|
||||
'_line_tax',
|
||||
'_line_tax_data',
|
||||
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-shipping-data-store.php#L20 .
|
||||
'method_id',
|
||||
'cost',
|
||||
'total_tax',
|
||||
'taxes',
|
||||
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-tax-data-store.php#L20 .
|
||||
'rate_id',
|
||||
'label',
|
||||
'compound',
|
||||
'tax_amount',
|
||||
'shipping_tax_amount',
|
||||
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-coupon-data-store.php .
|
||||
'discount_amount',
|
||||
'discount_amount_tax',
|
||||
);
|
||||
|
||||
/**
|
||||
* Name of the order item database table.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $order_item_table_name;
|
||||
|
||||
/**
|
||||
* The table in the database.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_name() {
|
||||
return $this->order_item_table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @global $wpdb
|
||||
*
|
||||
* @todo Should we refactor this to use $this->set_defaults() instead?
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->order_item_table_name = $wpdb->prefix . 'woocommerce_order_items';
|
||||
|
||||
// Options, constants and post meta whitelists.
|
||||
add_filter( 'jetpack_sync_options_whitelist', array( $this, 'add_woocommerce_options_whitelist' ), 10 );
|
||||
add_filter( 'jetpack_sync_constants_whitelist', array( $this, 'add_woocommerce_constants_whitelist' ), 10 );
|
||||
add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'add_woocommerce_post_meta_whitelist' ), 10 );
|
||||
add_filter( 'jetpack_sync_comment_meta_whitelist', array( $this, 'add_woocommerce_comment_meta_whitelist' ), 10 );
|
||||
|
||||
add_filter( 'jetpack_sync_before_enqueue_woocommerce_new_order_item', array( $this, 'filter_order_item' ) );
|
||||
add_filter( 'jetpack_sync_before_enqueue_woocommerce_update_order_item', array( $this, 'filter_order_item' ) );
|
||||
add_filter( 'jetpack_sync_whitelisted_comment_types', array( $this, 'add_review_comment_types' ) );
|
||||
|
||||
// Blacklist Action Scheduler comment types.
|
||||
add_filter( 'jetpack_sync_prevent_sending_comment_data', array( $this, 'filter_action_scheduler_comments' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'woocommerce';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WooCommerce action listeners.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_listeners( $callable ) {
|
||||
// Attributes.
|
||||
add_action( 'woocommerce_attribute_added', $callable, 10, 2 );
|
||||
add_action( 'woocommerce_attribute_updated', $callable, 10, 3 );
|
||||
add_action( 'woocommerce_attribute_deleted', $callable, 10, 3 );
|
||||
|
||||
// Orders.
|
||||
add_action( 'woocommerce_new_order', $callable, 10, 1 );
|
||||
add_action( 'woocommerce_order_status_changed', $callable, 10, 3 );
|
||||
add_action( 'woocommerce_payment_complete', $callable, 10, 1 );
|
||||
|
||||
// Order items.
|
||||
add_action( 'woocommerce_new_order_item', $callable, 10, 4 );
|
||||
add_action( 'woocommerce_update_order_item', $callable, 10, 4 );
|
||||
add_action( 'woocommerce_delete_order_item', $callable, 10, 1 );
|
||||
$this->init_listeners_for_meta_type( 'order_item', $callable );
|
||||
|
||||
// Payment tokens.
|
||||
add_action( 'woocommerce_new_payment_token', $callable, 10, 1 );
|
||||
add_action( 'woocommerce_payment_token_deleted', $callable, 10, 2 );
|
||||
add_action( 'woocommerce_payment_token_updated', $callable, 10, 1 );
|
||||
$this->init_listeners_for_meta_type( 'payment_token', $callable );
|
||||
|
||||
// Product downloads.
|
||||
add_action( 'woocommerce_downloadable_product_download_log_insert', $callable, 10, 1 );
|
||||
add_action( 'woocommerce_grant_product_download_access', $callable, 10, 1 );
|
||||
|
||||
// Tax rates.
|
||||
add_action( 'woocommerce_tax_rate_added', $callable, 10, 2 );
|
||||
add_action( 'woocommerce_tax_rate_updated', $callable, 10, 2 );
|
||||
add_action( 'woocommerce_tax_rate_deleted', $callable, 10, 1 );
|
||||
|
||||
// Webhooks.
|
||||
add_action( 'woocommerce_new_webhook', $callable, 10, 1 );
|
||||
add_action( 'woocommerce_webhook_deleted', $callable, 10, 2 );
|
||||
add_action( 'woocommerce_webhook_updated', $callable, 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WooCommerce action listeners for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param callable $callable Action handler callable.
|
||||
*/
|
||||
public function init_full_sync_listeners( $callable ) {
|
||||
add_action( 'jetpack_full_sync_woocommerce_order_items', $callable ); // Also sends post meta.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actions that will be sent for this module during a full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array Full sync actions of this module.
|
||||
*/
|
||||
public function get_full_sync_actions() {
|
||||
return array( 'jetpack_full_sync_woocommerce_order_items' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the module in the sender.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function init_before_send() {
|
||||
// Full sync.
|
||||
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_woocommerce_order_items', array( $this, 'expand_order_item_ids' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the order items properly.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @return array $args The hook arguments.
|
||||
*/
|
||||
public function filter_order_item( $args ) {
|
||||
// Make sure we always have all the data - prior to WooCommerce 3.0 we only have the user supplied data in the second argument and not the full details.
|
||||
$args[1] = $this->build_order_item( $args[0] );
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand order item IDs to order items and their meta.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor table name to use a $wpdb->prepare placeholder.
|
||||
*
|
||||
* @param array $args The hook arguments.
|
||||
* @return array $args Expanded order items with meta.
|
||||
*/
|
||||
public function expand_order_item_ids( $args ) {
|
||||
$order_item_ids = $args[0];
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$order_item_ids_sql = implode( ', ', array_map( 'intval', $order_item_ids ) );
|
||||
|
||||
$order_items = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT * FROM $this->order_item_table_name WHERE order_item_id IN ( $order_item_ids_sql )"
|
||||
);
|
||||
|
||||
return array(
|
||||
$order_items,
|
||||
$this->get_metadata( $order_item_ids, 'order_item', static::$order_item_meta_whitelist ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the full order item from the database by its ID.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor table name to use a $wpdb->prepare placeholder.
|
||||
*
|
||||
* @param int $order_item_id Order item ID.
|
||||
* @return object Order item.
|
||||
*/
|
||||
public function build_order_item( $order_item_id ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->order_item_table_name WHERE order_item_id = %d", $order_item_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the WooCommerce actions for full sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
|
||||
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
|
||||
* @return array Number of actions enqueued, and next module state.
|
||||
*/
|
||||
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
|
||||
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_woocommerce_order_items', $this->order_item_table_name, 'order_item_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an estimated number of actions that will be enqueued.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @todo Refactor the SQL query to use $wpdb->prepare().
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return array Number of items yet to be enqueued.
|
||||
*/
|
||||
public function estimate_full_sync_actions( $config ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = "SELECT count(*) FROM $this->order_item_table_name WHERE " . $this->get_where_sql( $config );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( $query );
|
||||
|
||||
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WHERE SQL clause based on the module config.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $config Full sync configuration for this sync module.
|
||||
* @return string WHERE SQL clause.
|
||||
*/
|
||||
public function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return '1=1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WooCommerce options to the options whitelist.
|
||||
*
|
||||
* @param array $list Existing options whitelist.
|
||||
* @return array Updated options whitelist.
|
||||
*/
|
||||
public function add_woocommerce_options_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wc_options_whitelist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WooCommerce constants to the constants whitelist.
|
||||
*
|
||||
* @param array $list Existing constants whitelist.
|
||||
* @return array Updated constants whitelist.
|
||||
*/
|
||||
public function add_woocommerce_constants_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wc_constants_whitelist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WooCommerce post meta to the post meta whitelist.
|
||||
*
|
||||
* @param array $list Existing post meta whitelist.
|
||||
* @return array Updated post meta whitelist.
|
||||
*/
|
||||
public function add_woocommerce_post_meta_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wc_post_meta_whitelist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WooCommerce comment meta to the comment meta whitelist.
|
||||
*
|
||||
* @param array $list Existing comment meta whitelist.
|
||||
* @return array Updated comment meta whitelist.
|
||||
*/
|
||||
public function add_woocommerce_comment_meta_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wc_comment_meta_whitelist );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds 'revew' to the list of comment types so Sync will listen for status changes on 'reviews'.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $comment_types The list of comment types prior to this filter.
|
||||
* return array The list of comment types with 'review' added.
|
||||
*/
|
||||
public function add_review_comment_types( $comment_types ) {
|
||||
if ( is_array( $comment_types ) ) {
|
||||
$comment_types[] = 'review';
|
||||
}
|
||||
return $comment_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop comments from the Action Scheduler from being synced.
|
||||
* https://github.com/woocommerce/woocommerce/tree/e7762627c37ec1f7590e6cac4218ba0c6a20024d/includes/libraries/action-scheduler
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @since-jetpack 7.7.0
|
||||
*
|
||||
* @param boolean $can_sync Should we prevent comment data from bing synced to WordPress.com.
|
||||
* @param mixed $comment WP_COMMENT object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filter_action_scheduler_comments( $can_sync, $comment ) {
|
||||
if ( isset( $comment->comment_agent ) && 'ActionScheduler' === $comment->comment_agent ) {
|
||||
return true;
|
||||
}
|
||||
return $can_sync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist for options we are interested to sync.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $wc_options_whitelist = array(
|
||||
'woocommerce_currency',
|
||||
'woocommerce_db_version',
|
||||
'woocommerce_weight_unit',
|
||||
'woocommerce_version',
|
||||
'woocommerce_unforce_ssl_checkout',
|
||||
'woocommerce_tax_total_display',
|
||||
'woocommerce_tax_round_at_subtotal',
|
||||
'woocommerce_tax_display_shop',
|
||||
'woocommerce_tax_display_cart',
|
||||
'woocommerce_prices_include_tax',
|
||||
'woocommerce_price_thousand_sep',
|
||||
'woocommerce_price_num_decimals',
|
||||
'woocommerce_price_decimal_sep',
|
||||
'woocommerce_notify_low_stock',
|
||||
'woocommerce_notify_low_stock_amount',
|
||||
'woocommerce_notify_no_stock',
|
||||
'woocommerce_notify_no_stock_amount',
|
||||
'woocommerce_manage_stock',
|
||||
'woocommerce_force_ssl_checkout',
|
||||
'woocommerce_hide_out_of_stock_items',
|
||||
'woocommerce_file_download_method',
|
||||
'woocommerce_enable_signup_and_login_from_checkout',
|
||||
'woocommerce_enable_shipping_calc',
|
||||
'woocommerce_enable_review_rating',
|
||||
'woocommerce_enable_guest_checkout',
|
||||
'woocommerce_enable_coupons',
|
||||
'woocommerce_enable_checkout_login_reminder',
|
||||
'woocommerce_enable_ajax_add_to_cart',
|
||||
'woocommerce_dimension_unit',
|
||||
'woocommerce_default_country',
|
||||
'woocommerce_default_customer_address',
|
||||
'woocommerce_currency_pos',
|
||||
'woocommerce_api_enabled',
|
||||
'woocommerce_allow_tracking',
|
||||
'woocommerce_task_list_hidden',
|
||||
'woocommerce_onboarding_profile',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whitelist for constants we are interested to sync.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $wc_constants_whitelist = array(
|
||||
// WooCommerce constants.
|
||||
'WC_PLUGIN_FILE',
|
||||
'WC_ABSPATH',
|
||||
'WC_PLUGIN_BASENAME',
|
||||
'WC_VERSION',
|
||||
'WOOCOMMERCE_VERSION',
|
||||
'WC_ROUNDING_PRECISION',
|
||||
'WC_DISCOUNT_ROUNDING_MODE',
|
||||
'WC_TAX_ROUNDING_MODE',
|
||||
'WC_DELIMITER',
|
||||
'WC_LOG_DIR',
|
||||
'WC_SESSION_CACHE_GROUP',
|
||||
'WC_TEMPLATE_DEBUG_MODE',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whitelist for post meta we are interested to sync.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $wc_post_meta_whitelist = array(
|
||||
// WooCommerce products.
|
||||
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-product-data-store-cpt.php#L21 .
|
||||
'_visibility',
|
||||
'_sku',
|
||||
'_price',
|
||||
'_regular_price',
|
||||
'_sale_price',
|
||||
'_sale_price_dates_from',
|
||||
'_sale_price_dates_to',
|
||||
'total_sales',
|
||||
'_tax_status',
|
||||
'_tax_class',
|
||||
'_manage_stock',
|
||||
'_backorders',
|
||||
'_sold_individually',
|
||||
'_weight',
|
||||
'_length',
|
||||
'_width',
|
||||
'_height',
|
||||
'_upsell_ids',
|
||||
'_crosssell_ids',
|
||||
'_purchase_note',
|
||||
'_default_attributes',
|
||||
'_product_attributes',
|
||||
'_virtual',
|
||||
'_downloadable',
|
||||
'_download_limit',
|
||||
'_download_expiry',
|
||||
'_featured',
|
||||
'_downloadable_files',
|
||||
'_wc_rating_count',
|
||||
'_wc_average_rating',
|
||||
'_wc_review_count',
|
||||
'_variation_description',
|
||||
'_thumbnail_id',
|
||||
'_file_paths',
|
||||
'_product_image_gallery',
|
||||
'_product_version',
|
||||
'_wp_old_slug',
|
||||
|
||||
// Woocommerce orders.
|
||||
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L27 .
|
||||
'_order_key',
|
||||
'_order_currency',
|
||||
// '_billing_first_name', do not sync these as they contain personal data
|
||||
// '_billing_last_name',
|
||||
// '_billing_company',
|
||||
// '_billing_address_1',
|
||||
// '_billing_address_2',
|
||||
'_billing_city',
|
||||
'_billing_state',
|
||||
'_billing_postcode',
|
||||
'_billing_country',
|
||||
// '_billing_email', do not sync these as they contain personal data.
|
||||
// '_billing_phone',
|
||||
// '_shipping_first_name',
|
||||
// '_shipping_last_name',
|
||||
// '_shipping_company',
|
||||
// '_shipping_address_1',
|
||||
// '_shipping_address_2',
|
||||
'_shipping_city',
|
||||
'_shipping_state',
|
||||
'_shipping_postcode',
|
||||
'_shipping_country',
|
||||
'_completed_date',
|
||||
'_paid_date',
|
||||
'_cart_discount',
|
||||
'_cart_discount_tax',
|
||||
'_order_shipping',
|
||||
'_order_shipping_tax',
|
||||
'_order_tax',
|
||||
'_order_total',
|
||||
'_payment_method',
|
||||
'_payment_method_title',
|
||||
// '_transaction_id', do not sync these as they contain personal data.
|
||||
// '_customer_ip_address',
|
||||
// '_customer_user_agent',
|
||||
'_created_via',
|
||||
'_order_version',
|
||||
'_prices_include_tax',
|
||||
'_date_completed',
|
||||
'_date_paid',
|
||||
'_payment_tokens',
|
||||
'_billing_address_index',
|
||||
'_shipping_address_index',
|
||||
'_recorded_sales',
|
||||
'_recorded_coupon_usage_counts',
|
||||
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L539 .
|
||||
'_download_permissions_granted',
|
||||
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L594 .
|
||||
'_order_stock_reduced',
|
||||
|
||||
// Woocommerce order refunds.
|
||||
// See https://github.com/woocommerce/woocommerce/blob/b8a2815ae546c836467008739e7ff5150cb08e93/includes/data-stores/class-wc-order-refund-data-store-cpt.php#L20 .
|
||||
'_order_currency',
|
||||
'_refund_amount',
|
||||
'_refunded_by',
|
||||
'_refund_reason',
|
||||
'_order_shipping',
|
||||
'_order_shipping_tax',
|
||||
'_order_tax',
|
||||
'_order_total',
|
||||
'_order_version',
|
||||
'_prices_include_tax',
|
||||
'_payment_tokens',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whitelist for comment meta we are interested to sync.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $wc_comment_meta_whitelist = array(
|
||||
'rating',
|
||||
);
|
||||
|
||||
/**
|
||||
* Return a list of objects by their type and IDs
|
||||
*
|
||||
* @param string $object_type Object type.
|
||||
* @param array $ids IDs of objects to return.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array|object|WP_Error|null
|
||||
*/
|
||||
public function get_objects_by_id( $object_type, $ids ) {
|
||||
switch ( $object_type ) {
|
||||
case 'order_item':
|
||||
return $this->get_order_item_by_ids( $ids );
|
||||
}
|
||||
|
||||
return new WP_Error( 'unsupported_object_type', 'Unsupported object type' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of order_item objects by their IDs.
|
||||
*
|
||||
* @param array $ids List of order_item IDs to fetch.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function get_order_item_by_ids( $ids ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! is_array( $ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Make sure the IDs are numeric and are non-zero.
|
||||
$ids = array_filter( array_map( 'intval', $ids ) );
|
||||
|
||||
if ( empty( $ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Prepare the placeholders for the prepared query below.
|
||||
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
|
||||
|
||||
$query = "SELECT * FROM {$this->order_item_table_name} WHERE order_item_id IN ( $placeholders )";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return $wpdb->get_results( $wpdb->prepare( $query, $ids ), ARRAY_A );
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* WP_Super_Cache sync module.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Modules;
|
||||
|
||||
/**
|
||||
* Class to handle sync for WP_Super_Cache.
|
||||
*/
|
||||
class WP_Super_Cache extends Module {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @todo Should we refactor this to use $this->set_defaults() instead?
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'jetpack_sync_constants_whitelist', array( $this, 'add_wp_super_cache_constants_whitelist' ), 10 );
|
||||
add_filter( 'jetpack_sync_callable_whitelist', array( $this, 'add_wp_super_cache_callable_whitelist' ), 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist for constants we are interested to sync.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $wp_super_cache_constants = array(
|
||||
'WPLOCKDOWN',
|
||||
'WPSC_DISABLE_COMPRESSION',
|
||||
'WPSC_DISABLE_LOCKING',
|
||||
'WPSC_DISABLE_HTACCESS_UPDATE',
|
||||
'ADVANCEDCACHEPROBLEM',
|
||||
);
|
||||
|
||||
/**
|
||||
* Container for the whitelist for WP_Super_Cache callables we are interested to sync.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $wp_super_cache_callables = array(
|
||||
'wp_super_cache_globals' => array( __CLASS__, 'get_wp_super_cache_globals' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Sync module name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name() {
|
||||
return 'wp-super-cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all WP_Super_Cache callables we are interested to sync.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @global $wp_cache_mod_rewrite;
|
||||
* @global $cache_enabled;
|
||||
* @global $super_cache_enabled;
|
||||
* @global $ossdlcdn;
|
||||
* @global $cache_rebuild_files;
|
||||
* @global $wp_cache_mobile;
|
||||
* @global $wp_super_cache_late_init;
|
||||
* @global $wp_cache_anon_only;
|
||||
* @global $wp_cache_not_logged_in;
|
||||
* @global $wp_cache_clear_on_post_edit;
|
||||
* @global $wp_cache_mobile_enabled;
|
||||
* @global $wp_super_cache_debug;
|
||||
* @global $cache_max_time;
|
||||
* @global $wp_cache_refresh_single_only;
|
||||
* @global $wp_cache_mfunc_enabled;
|
||||
* @global $wp_supercache_304;
|
||||
* @global $wp_cache_no_cache_for_get;
|
||||
* @global $wp_cache_mutex_disabled;
|
||||
* @global $cache_jetpack;
|
||||
* @global $cache_domain_mapping;
|
||||
*
|
||||
* @return array All WP_Super_Cache callables.
|
||||
*/
|
||||
public static function get_wp_super_cache_globals() {
|
||||
global $wp_cache_mod_rewrite;
|
||||
global $cache_enabled;
|
||||
global $super_cache_enabled;
|
||||
global $ossdlcdn;
|
||||
global $cache_rebuild_files;
|
||||
global $wp_cache_mobile;
|
||||
global $wp_super_cache_late_init;
|
||||
global $wp_cache_anon_only;
|
||||
global $wp_cache_not_logged_in;
|
||||
global $wp_cache_clear_on_post_edit;
|
||||
global $wp_cache_mobile_enabled;
|
||||
global $wp_super_cache_debug;
|
||||
global $cache_max_time;
|
||||
global $wp_cache_refresh_single_only;
|
||||
global $wp_cache_mfunc_enabled;
|
||||
global $wp_supercache_304;
|
||||
global $wp_cache_no_cache_for_get;
|
||||
global $wp_cache_mutex_disabled;
|
||||
global $cache_jetpack;
|
||||
global $cache_domain_mapping;
|
||||
|
||||
return array(
|
||||
'wp_cache_mod_rewrite' => $wp_cache_mod_rewrite,
|
||||
'cache_enabled' => $cache_enabled,
|
||||
'super_cache_enabled' => $super_cache_enabled,
|
||||
'ossdlcdn' => $ossdlcdn,
|
||||
'cache_rebuild_files' => $cache_rebuild_files,
|
||||
'wp_cache_mobile' => $wp_cache_mobile,
|
||||
'wp_super_cache_late_init' => $wp_super_cache_late_init,
|
||||
'wp_cache_anon_only' => $wp_cache_anon_only,
|
||||
'wp_cache_not_logged_in' => $wp_cache_not_logged_in,
|
||||
'wp_cache_clear_on_post_edit' => $wp_cache_clear_on_post_edit,
|
||||
'wp_cache_mobile_enabled' => $wp_cache_mobile_enabled,
|
||||
'wp_super_cache_debug' => $wp_super_cache_debug,
|
||||
'cache_max_time' => $cache_max_time,
|
||||
'wp_cache_refresh_single_only' => $wp_cache_refresh_single_only,
|
||||
'wp_cache_mfunc_enabled' => $wp_cache_mfunc_enabled,
|
||||
'wp_supercache_304' => $wp_supercache_304,
|
||||
'wp_cache_no_cache_for_get' => $wp_cache_no_cache_for_get,
|
||||
'wp_cache_mutex_disabled' => $wp_cache_mutex_disabled,
|
||||
'cache_jetpack' => $cache_jetpack,
|
||||
'cache_domain_mapping' => $cache_domain_mapping,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WP_Super_Cache constants to the constants whitelist.
|
||||
*
|
||||
* @param array $list Existing constants whitelist.
|
||||
* @return array Updated constants whitelist.
|
||||
*/
|
||||
public function add_wp_super_cache_constants_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wp_super_cache_constants );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WP_Super_Cache callables to the callables whitelist.
|
||||
*
|
||||
* @param array $list Existing callables whitelist.
|
||||
* @return array Updated callables whitelist.
|
||||
*/
|
||||
public function add_wp_super_cache_callable_whitelist( $list ) {
|
||||
return array_merge( $list, self::$wp_super_cache_callables );
|
||||
}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* Table Checksums Class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Replicastore;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Sync;
|
||||
use Automattic\Jetpack\Sync\Modules;
|
||||
use WP_Error;
|
||||
use WP_User_Query;
|
||||
|
||||
/**
|
||||
* Class to handle Table Checksums for the User Meta table.
|
||||
*/
|
||||
class Table_Checksum_Usermeta extends Table_Checksum_Users {
|
||||
/**
|
||||
* Calculate the checksum based on provided range and filters.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param array|null $filter_values Additional filter values. Not used at the moment.
|
||||
* @param bool $granular_result If the returned result should be granular or only the checksum.
|
||||
* @param bool $simple_return_value If we want to use a simple return value for non-granular results (return only the checksum, without wrappers).
|
||||
*
|
||||
* @return array|mixed|object|WP_Error|null
|
||||
*/
|
||||
public function calculate_checksum( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false, $simple_return_value = true ) {
|
||||
|
||||
if ( ! Sync\Settings::is_checksum_enabled() ) {
|
||||
return new WP_Error( 'checksum_disabled', 'Checksums are currently disabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* First we need to fetch the user IDs for the users that we want to include in the range.
|
||||
*
|
||||
* To keep things a bit simple and avoid filtering issues, let's reuse the `build_filter_statement` that already
|
||||
* exists. Unfortunately we don't
|
||||
*/
|
||||
global $wpdb;
|
||||
|
||||
// This call depends on the `range_field` pointing to the `ID` field of the `users` table. Currently, "ID".
|
||||
$range_filter_statement = $this->build_filter_statement( $range_from, $range_to );
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
DISTINCT {$this->table}.{$this->range_field}
|
||||
FROM
|
||||
{$this->table}
|
||||
JOIN {$wpdb->usermeta} as um_table ON um_table.user_id = {$this->table}.ID
|
||||
WHERE
|
||||
{$range_filter_statement}
|
||||
AND um_table.meta_key = '{$wpdb->prefix}user_level'
|
||||
AND um_table.meta_value > 0
|
||||
";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$user_ids = $wpdb->get_col( $query );
|
||||
|
||||
// Chunk the array down to make sure we don't overload the database with queries that are too large.
|
||||
$chunked_user_ids = array_chunk( $user_ids, 500 );
|
||||
|
||||
$checksum_entries = array();
|
||||
|
||||
foreach ( $chunked_user_ids as $user_ids_chunk ) {
|
||||
$user_objects = $this->get_user_objects_by_ids( $user_ids_chunk );
|
||||
|
||||
foreach ( $user_objects as $user_object ) {
|
||||
// expand and sanitize desired meta based on WP.com logic.
|
||||
$user_object = $this->expand_and_sanitize_user_meta( $user_object );
|
||||
|
||||
// Generate checksum entry based on the serialized value if not empty.
|
||||
$checksum_entry = 0;
|
||||
if ( ! empty( $user_object->roles ) ) {
|
||||
$checksum_entry = crc32( implode( '#', array( $this->salt, 'roles', maybe_serialize( $user_object->roles ) ) ) );
|
||||
}
|
||||
|
||||
// Meta only persisted if user is connected to WP.com.
|
||||
if ( ( new Manager( 'jetpack' ) )->is_user_connected( $user_object->ID ) ) {
|
||||
if ( ! empty( $user_object->allcaps ) ) {
|
||||
$checksum_entry += crc32(
|
||||
implode(
|
||||
'#',
|
||||
array(
|
||||
$this->salt,
|
||||
'capabilities',
|
||||
maybe_serialize( $user_object->allcaps ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
// Explicitly check that locale is not same as site locale.
|
||||
if ( ! empty( $user_object->locale ) && get_locale() !== $user_object->locale ) {
|
||||
$checksum_entry += crc32(
|
||||
implode(
|
||||
'#',
|
||||
array(
|
||||
$this->salt,
|
||||
'locale',
|
||||
maybe_serialize( $user_object->locale ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( ! empty( $user_object->allowed_mime_types ) ) {
|
||||
$checksum_entry += crc32(
|
||||
implode(
|
||||
'#',
|
||||
array(
|
||||
$this->salt,
|
||||
'allowed_mime_types',
|
||||
maybe_serialize( $user_object->allowed_mime_types ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$checksum_entries[ $user_object->ID ] = '' . $checksum_entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-granular results need only to sum the different entries.
|
||||
if ( ! $granular_result ) {
|
||||
$checksum_sum = 0;
|
||||
foreach ( $checksum_entries as $entry ) {
|
||||
$checksum_sum += intval( $entry );
|
||||
}
|
||||
|
||||
if ( $simple_return_value ) {
|
||||
return '' . $checksum_sum;
|
||||
}
|
||||
|
||||
return array(
|
||||
'range' => $range_from . '-' . $range_to,
|
||||
'checksum' => '' . $checksum_sum,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Granular results.
|
||||
$response = $checksum_entries;
|
||||
|
||||
// Sort the return value for easier comparisons and code flows further down the line.
|
||||
ksort( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the User Object with additional meta santized by WP.com logic.
|
||||
*
|
||||
* @param mixed $user_object User Object from WP_User_Query.
|
||||
*
|
||||
* @return mixed $user_object expanded User Object.
|
||||
*/
|
||||
protected function expand_and_sanitize_user_meta( $user_object ) {
|
||||
$user_module = Modules::get_module( 'users' );
|
||||
// Expand User Objects based on Sync logic.
|
||||
$user_object = $user_module->expand_user( $user_object );
|
||||
|
||||
// Sanitize location.
|
||||
if ( ! empty( $user_object->locale ) ) {
|
||||
$user_object->locale = wp_strip_all_tags( $user_object->locale, true );
|
||||
}
|
||||
|
||||
// Sanitize allcaps.
|
||||
if ( ! empty( $user_object->allcaps ) ) {
|
||||
$user_object->allcaps = array_map(
|
||||
function ( $cap ) {
|
||||
return (bool) $cap;
|
||||
},
|
||||
$user_object->allcaps
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitize allowed_mime_types.
|
||||
$allowed_mime_types = $user_object->allowed_mime_types;
|
||||
foreach ( $allowed_mime_types as $allowed_mime_type_short => $allowed_mime_type_long ) {
|
||||
$allowed_mime_type_short = wp_strip_all_tags( (string) $allowed_mime_type_short, true );
|
||||
$allowed_mime_type_long = wp_strip_all_tags( (string) $allowed_mime_type_long, true );
|
||||
$allowed_mime_types[ $allowed_mime_type_short ] = $allowed_mime_type_long;
|
||||
}
|
||||
$user_object->allowed_mime_types = $allowed_mime_types;
|
||||
|
||||
// Sanitize roles.
|
||||
if ( is_array( $user_object->roles ) ) {
|
||||
$user_object->roles = array_map( 'sanitize_text_field', $user_object->roles );
|
||||
}
|
||||
return $user_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of `WP_User` objects by their IDs
|
||||
*
|
||||
* @param array $ids List of IDs to fetch.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_user_objects_by_ids( $ids ) {
|
||||
$user_query = new WP_User_Query( array( 'include' => $ids ) );
|
||||
|
||||
return $user_query->get_results();
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* Table Checksums Class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Replicastore;
|
||||
|
||||
/**
|
||||
* Class to handle Table Checksums for the Users table.
|
||||
*/
|
||||
class Table_Checksum_Users extends Table_Checksum {
|
||||
|
||||
/**
|
||||
* Returns the checksum query. All validation of fields and configurations are expected to occur prior to usage.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param array|null $filter_values Additional filter values. Not used at the moment.
|
||||
* @param bool $granular_result If the function should return a granular result.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception Throws an exception if validation fails in the internal function calls.
|
||||
*/
|
||||
protected function build_checksum_query( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false ) {
|
||||
global $wpdb;
|
||||
|
||||
// Escape the salt.
|
||||
$salt = $wpdb->prepare( '%s', $this->salt );
|
||||
|
||||
// Prepare the compound key.
|
||||
$key_fields = array();
|
||||
|
||||
// Prefix the fields with the table name, to avoid clashes in queries with sub-queries (e.g. meta tables).
|
||||
foreach ( $this->key_fields as $field ) {
|
||||
$key_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
|
||||
$key_fields = implode( ',', $key_fields );
|
||||
|
||||
// Prepare the checksum fields.
|
||||
$checksum_fields = array();
|
||||
// Prefix the fields with the table name, to avoid clashes in queries with sub-queries (e.g. meta tables).
|
||||
foreach ( $this->checksum_fields as $field ) {
|
||||
$checksum_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
// Apply latin1 conversion if enabled.
|
||||
if ( $this->perform_text_conversion ) {
|
||||
// Convert text fields to allow for encoding discrepancies as WP.com is latin1.
|
||||
foreach ( $this->checksum_text_fields as $field ) {
|
||||
$checksum_fields[] = 'CONVERT(' . $this->table . '.' . $field . ' using latin1 )';
|
||||
}
|
||||
} else {
|
||||
// Conversion disabled, default to table prefixing.
|
||||
foreach ( $this->checksum_text_fields as $field ) {
|
||||
$checksum_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
}
|
||||
|
||||
$checksum_fields_string = implode( ',', array_merge( $checksum_fields, array( $salt ) ) );
|
||||
|
||||
$additional_fields = '';
|
||||
if ( $granular_result ) {
|
||||
// TODO uniq the fields as sometimes(most) range_index is the key and there's no need to select the same field twice.
|
||||
$additional_fields = "
|
||||
{$this->table}.{$this->range_field} as range_index,
|
||||
{$key_fields},
|
||||
";
|
||||
}
|
||||
|
||||
$filter_stamenet = $this->build_filter_statement( $range_from, $range_to, $filter_values );
|
||||
|
||||
// usermeta join to limit on user_level.
|
||||
$join_statement = "JOIN {$wpdb->usermeta} as um_table ON um_table.user_id = {$this->table}.ID";
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
{$additional_fields}
|
||||
SUM(
|
||||
CRC32(
|
||||
CONCAT_WS( '#', {$salt}, {$checksum_fields_string} )
|
||||
)
|
||||
) AS checksum
|
||||
FROM
|
||||
{$this->table}
|
||||
{$join_statement}
|
||||
WHERE
|
||||
{$filter_stamenet}
|
||||
AND um_table.meta_key = '{$wpdb->prefix}user_level'
|
||||
AND um_table.meta_value > 0
|
||||
";
|
||||
|
||||
/**
|
||||
* We need the GROUP BY only for compound keys.
|
||||
*/
|
||||
if ( $granular_result ) {
|
||||
$query .= "
|
||||
GROUP BY {$key_fields}
|
||||
LIMIT 9999999
|
||||
";
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the min-max values (edges) of the range.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param int|null $limit How many values to return.
|
||||
*
|
||||
* @return array|object|void
|
||||
* @throws Exception Throws an exception if validation fails on the internal function calls.
|
||||
*/
|
||||
public function get_range_edges( $range_from = null, $range_to = null, $limit = null ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->validate_fields( array( $this->range_field ) );
|
||||
|
||||
// `trim()` to make sure we don't add the statement if it's empty.
|
||||
$filters = trim( $this->build_filter_statement( $range_from, $range_to ) );
|
||||
|
||||
$filter_statement = '';
|
||||
if ( ! empty( $filters ) ) {
|
||||
$filter_statement = "
|
||||
JOIN {$wpdb->usermeta} as um_table ON um_table.user_id = {$this->table}.ID
|
||||
WHERE
|
||||
{$filters}
|
||||
AND um_table.meta_key = '{$wpdb->prefix}user_level'
|
||||
AND um_table.meta_value > 0
|
||||
";
|
||||
}
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
MIN({$this->range_field}) as min_range,
|
||||
MAX({$this->range_field}) as max_range,
|
||||
COUNT( {$this->range_field} ) as item_count
|
||||
FROM
|
||||
";
|
||||
|
||||
/**
|
||||
* If `$limit` is not specified, we can directly use the table.
|
||||
*/
|
||||
if ( ! $limit ) {
|
||||
$query .= "
|
||||
{$this->table}
|
||||
{$filter_statement}
|
||||
";
|
||||
} else {
|
||||
/**
|
||||
* If there is `$limit` specified, we can't directly use `MIN/MAX()` as they don't work with `LIMIT`.
|
||||
* That's why we will alter the query for this case.
|
||||
*/
|
||||
$limit = intval( $limit );
|
||||
|
||||
$query .= "
|
||||
(
|
||||
SELECT
|
||||
{$this->range_field}
|
||||
FROM
|
||||
{$this->table}
|
||||
{$filter_statement}
|
||||
ORDER BY
|
||||
{$this->range_field} ASC
|
||||
LIMIT {$limit}
|
||||
) as ids_query
|
||||
";
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->get_row( $query, ARRAY_A );
|
||||
|
||||
if ( ! $result || ! is_array( $result ) ) {
|
||||
throw new Exception( 'Unable to get range edges' );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,856 @@
|
||||
<?php
|
||||
/**
|
||||
* Table Checksums Class.
|
||||
*
|
||||
* @package automattic/jetpack-sync
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Sync\Replicastore;
|
||||
|
||||
use Automattic\Jetpack\Sync;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
// TODO add rest endpoints to work with this, hopefully in the same folder.
|
||||
/**
|
||||
* Class to handle Table Checksums.
|
||||
*/
|
||||
class Table_Checksum {
|
||||
|
||||
/**
|
||||
* Table to be checksummed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $table = '';
|
||||
|
||||
/**
|
||||
* Table Checksum Configuration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $table_configuration = array();
|
||||
|
||||
/**
|
||||
* Perform Text Conversion to latin1.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $perform_text_conversion = false;
|
||||
|
||||
/**
|
||||
* Field to be used for range queries.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $range_field = '';
|
||||
|
||||
/**
|
||||
* ID Field(s) to be used.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $key_fields = array();
|
||||
|
||||
/**
|
||||
* Field(s) to be used in generating the checksum value.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $checksum_fields = array();
|
||||
|
||||
/**
|
||||
* Field(s) to be used in generating the checksum value that need latin1 conversion.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $checksum_text_fields = array();
|
||||
|
||||
/**
|
||||
* Default filter values for the table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filter_values = array();
|
||||
|
||||
/**
|
||||
* SQL Query to be used to filter results (allow/disallow).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $additional_filter_sql = '';
|
||||
|
||||
/**
|
||||
* Default Checksum Table Configurations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $default_tables = array();
|
||||
|
||||
/**
|
||||
* Salt to be used when generating checksum.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $salt = '';
|
||||
|
||||
/**
|
||||
* Tables which are allowed to be checksummed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $allowed_tables = array();
|
||||
|
||||
/**
|
||||
* If the table has a "parent" table that it's related to.
|
||||
*
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $parent_table = null;
|
||||
|
||||
/**
|
||||
* What field to use for the parent table join, if it has a "parent" table.
|
||||
*
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $parent_join_field = null;
|
||||
|
||||
/**
|
||||
* What field to use for the table join, if it has a "parent" table.
|
||||
*
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $table_join_field = null;
|
||||
|
||||
/**
|
||||
* Some tables might not exist on the remote, and we want to verify they exist, before trying to query them.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $is_table_enabled_callback = false;
|
||||
|
||||
/**
|
||||
* Table_Checksum constructor.
|
||||
*
|
||||
* @param string $table The table to calculate checksums for.
|
||||
* @param string $salt Optional salt to add to the checksum.
|
||||
* @param boolean $perform_text_conversion If text fields should be latin1 converted.
|
||||
*
|
||||
* @throws Exception Throws exception from inner functions.
|
||||
*/
|
||||
public function __construct( $table, $salt = null, $perform_text_conversion = false ) {
|
||||
|
||||
if ( ! Sync\Settings::is_checksum_enabled() ) {
|
||||
throw new Exception( 'Checksums are currently disabled.' );
|
||||
}
|
||||
|
||||
$this->salt = $salt;
|
||||
|
||||
$this->default_tables = $this->get_default_tables();
|
||||
|
||||
$this->perform_text_conversion = $perform_text_conversion;
|
||||
|
||||
// TODO change filters to allow the array format.
|
||||
// TODO add get_fields or similar method to get things out of the table.
|
||||
// TODO extract this configuration in a better way, still make it work with `$wpdb` names.
|
||||
// TODO take over the replicastore functions and move them over to this class.
|
||||
// TODO make the API work.
|
||||
|
||||
$this->allowed_tables = apply_filters( 'jetpack_sync_checksum_allowed_tables', $this->default_tables );
|
||||
|
||||
$this->table = $this->validate_table_name( $table );
|
||||
$this->table_configuration = $this->allowed_tables[ $table ];
|
||||
|
||||
$this->prepare_fields( $this->table_configuration );
|
||||
|
||||
// Run any callbacks to check if a table is enabled or not.
|
||||
if (
|
||||
is_callable( $this->is_table_enabled_callback )
|
||||
&& ! call_user_func( $this->is_table_enabled_callback, $table )
|
||||
) {
|
||||
throw new Exception( "Unable to use table name: $table" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Default Table configurations.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_tables() {
|
||||
global $wpdb;
|
||||
|
||||
return array(
|
||||
'posts' => array(
|
||||
'table' => $wpdb->posts,
|
||||
'range_field' => 'ID',
|
||||
'key_fields' => array( 'ID' ),
|
||||
'checksum_fields' => array( 'post_modified_gmt' ),
|
||||
'filter_values' => Sync\Settings::get_disallowed_post_types_structured(),
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'posts' );
|
||||
},
|
||||
),
|
||||
'postmeta' => array(
|
||||
'table' => $wpdb->postmeta,
|
||||
'range_field' => 'post_id',
|
||||
'key_fields' => array( 'post_id', 'meta_key' ),
|
||||
'checksum_text_fields' => array( 'meta_key', 'meta_value' ),
|
||||
'filter_values' => Sync\Settings::get_allowed_post_meta_structured(),
|
||||
'parent_table' => 'posts',
|
||||
'parent_join_field' => 'ID',
|
||||
'table_join_field' => 'post_id',
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'posts' );
|
||||
},
|
||||
),
|
||||
'comments' => array(
|
||||
'table' => $wpdb->comments,
|
||||
'range_field' => 'comment_ID',
|
||||
'key_fields' => array( 'comment_ID' ),
|
||||
'checksum_fields' => array( 'comment_date_gmt' ),
|
||||
'filter_values' => array(
|
||||
'comment_type' => array(
|
||||
'operator' => 'IN',
|
||||
'values' => apply_filters(
|
||||
'jetpack_sync_whitelisted_comment_types',
|
||||
array( '', 'comment', 'trackback', 'pingback', 'review' )
|
||||
),
|
||||
),
|
||||
'comment_approved' => array(
|
||||
'operator' => 'NOT IN',
|
||||
'values' => array( 'spam' ),
|
||||
),
|
||||
),
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'comments' );
|
||||
},
|
||||
),
|
||||
'commentmeta' => array(
|
||||
'table' => $wpdb->commentmeta,
|
||||
'range_field' => 'comment_id',
|
||||
'key_fields' => array( 'comment_id', 'meta_key' ),
|
||||
'checksum_text_fields' => array( 'meta_key', 'meta_value' ),
|
||||
'filter_values' => Sync\Settings::get_allowed_comment_meta_structured(),
|
||||
'parent_table' => 'comments',
|
||||
'parent_join_field' => 'comment_ID',
|
||||
'table_join_field' => 'comment_id',
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'comments' );
|
||||
},
|
||||
),
|
||||
'terms' => array(
|
||||
'table' => $wpdb->terms,
|
||||
'range_field' => 'term_id',
|
||||
'key_fields' => array( 'term_id' ),
|
||||
'checksum_fields' => array( 'term_id' ),
|
||||
'checksum_text_fields' => array( 'name', 'slug' ),
|
||||
'parent_table' => 'term_taxonomy',
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'terms' );
|
||||
},
|
||||
),
|
||||
'termmeta' => array(
|
||||
'table' => $wpdb->termmeta,
|
||||
'range_field' => 'term_id',
|
||||
'key_fields' => array( 'term_id', 'meta_key' ),
|
||||
'checksum_text_fields' => array( 'meta_key', 'meta_value' ),
|
||||
'parent_table' => 'term_taxonomy',
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'terms' );
|
||||
},
|
||||
),
|
||||
'term_relationships' => array(
|
||||
'table' => $wpdb->term_relationships,
|
||||
'range_field' => 'object_id',
|
||||
'key_fields' => array( 'object_id' ),
|
||||
'checksum_fields' => array( 'object_id', 'term_taxonomy_id' ),
|
||||
'parent_table' => 'term_taxonomy',
|
||||
'parent_join_field' => 'term_taxonomy_id',
|
||||
'table_join_field' => 'term_taxonomy_id',
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'terms' );
|
||||
},
|
||||
),
|
||||
'term_taxonomy' => array(
|
||||
'table' => $wpdb->term_taxonomy,
|
||||
'range_field' => 'term_taxonomy_id',
|
||||
'key_fields' => array( 'term_taxonomy_id' ),
|
||||
'checksum_fields' => array( 'term_taxonomy_id', 'term_id', 'parent' ),
|
||||
'checksum_text_fields' => array( 'taxonomy', 'description' ),
|
||||
'filter_values' => Sync\Settings::get_allowed_taxonomies_structured(),
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'terms' );
|
||||
},
|
||||
),
|
||||
'links' => $wpdb->links, // TODO describe in the array format or add exceptions.
|
||||
'options' => $wpdb->options, // TODO describe in the array format or add exceptions.
|
||||
'woocommerce_order_items' => array(
|
||||
'table' => "{$wpdb->prefix}woocommerce_order_items",
|
||||
'range_field' => 'order_item_id',
|
||||
'key_fields' => array( 'order_item_id' ),
|
||||
'checksum_fields' => array( 'order_id' ),
|
||||
'checksum_text_fields' => array( 'order_item_name', 'order_item_type' ),
|
||||
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
|
||||
),
|
||||
'woocommerce_order_itemmeta' => array(
|
||||
'table' => "{$wpdb->prefix}woocommerce_order_itemmeta",
|
||||
'range_field' => 'order_item_id',
|
||||
'key_fields' => array( 'order_item_id', 'meta_key' ),
|
||||
'checksum_text_fields' => array( 'meta_key', 'meta_value' ),
|
||||
'filter_values' => Sync\Settings::get_allowed_order_itemmeta_structured(),
|
||||
'parent_table' => 'woocommerce_order_items',
|
||||
'parent_join_field' => 'order_item_id',
|
||||
'table_join_field' => 'order_item_id',
|
||||
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
|
||||
),
|
||||
'users' => array(
|
||||
'table' => $wpdb->users,
|
||||
'range_field' => 'ID',
|
||||
'key_fields' => array( 'ID' ),
|
||||
'checksum_text_fields' => array( 'user_login', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_status', 'display_name' ),
|
||||
'filter_values' => array(),
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'users' );
|
||||
},
|
||||
),
|
||||
|
||||
/**
|
||||
* Usermeta is a special table, as it needs to use a custom override flow,
|
||||
* as the user roles, capabilities, locale, mime types can be filtered by plugins.
|
||||
* This prevents us from doing a direct comparison in the database.
|
||||
*/
|
||||
'usermeta' => array(
|
||||
'table' => $wpdb->users,
|
||||
/**
|
||||
* Range field points to ID, which in this case is the `WP_User` ID,
|
||||
* since we're querying the whole WP_User objects, instead of meta entries in the DB.
|
||||
*/
|
||||
'range_field' => 'ID',
|
||||
'key_fields' => array(),
|
||||
'checksum_fields' => array(),
|
||||
'is_table_enabled_callback' => function () {
|
||||
return false !== Sync\Modules::get_module( 'users' );
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare field params based off provided configuration.
|
||||
*
|
||||
* @param array $table_configuration The table configuration array.
|
||||
*/
|
||||
protected function prepare_fields( $table_configuration ) {
|
||||
$this->key_fields = $table_configuration['key_fields'];
|
||||
$this->range_field = $table_configuration['range_field'];
|
||||
$this->checksum_fields = isset( $table_configuration['checksum_fields'] ) ? $table_configuration['checksum_fields'] : array();
|
||||
$this->checksum_text_fields = isset( $table_configuration['checksum_text_fields'] ) ? $table_configuration['checksum_text_fields'] : array();
|
||||
$this->filter_values = isset( $table_configuration['filter_values'] ) ? $table_configuration['filter_values'] : null;
|
||||
$this->additional_filter_sql = ! empty( $table_configuration['filter_sql'] ) ? $table_configuration['filter_sql'] : '';
|
||||
$this->parent_table = isset( $table_configuration['parent_table'] ) ? $table_configuration['parent_table'] : null;
|
||||
$this->parent_join_field = isset( $table_configuration['parent_join_field'] ) ? $table_configuration['parent_join_field'] : $table_configuration['range_field'];
|
||||
$this->table_join_field = isset( $table_configuration['table_join_field'] ) ? $table_configuration['table_join_field'] : $table_configuration['range_field'];
|
||||
$this->is_table_enabled_callback = isset( $table_configuration['is_table_enabled_callback'] ) ? $table_configuration['is_table_enabled_callback'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify provided table name is valid for checksum processing.
|
||||
*
|
||||
* @param string $table Table name to validate.
|
||||
*
|
||||
* @return mixed|string
|
||||
* @throws Exception Throw an exception on validation failure.
|
||||
*/
|
||||
protected function validate_table_name( $table ) {
|
||||
if ( empty( $table ) ) {
|
||||
throw new Exception( 'Invalid table name: empty' );
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $table, $this->allowed_tables ) ) {
|
||||
throw new Exception( "Invalid table name: $table not allowed" );
|
||||
}
|
||||
|
||||
return $this->allowed_tables[ $table ]['table'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify provided fields are proper names.
|
||||
*
|
||||
* @param array $fields Array of field names to validate.
|
||||
*
|
||||
* @throws Exception Throw an exception on failure to validate.
|
||||
*/
|
||||
protected function validate_fields( $fields ) {
|
||||
foreach ( $fields as $field ) {
|
||||
if ( ! preg_match( '/^[0-9,a-z,A-Z$_]+$/i', $field ) ) {
|
||||
throw new Exception( "Invalid field name: $field is not allowed" );
|
||||
}
|
||||
|
||||
// TODO other verifications of the field names.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the fields exist in the table.
|
||||
*
|
||||
* @param array $fields Array of fields to validate.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception Throw an exception on failure to validate.
|
||||
*/
|
||||
protected function validate_fields_against_table( $fields ) {
|
||||
global $wpdb;
|
||||
|
||||
$valid_fields = array();
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$result = $wpdb->get_results( "SHOW COLUMNS FROM {$this->table}", ARRAY_A );
|
||||
|
||||
foreach ( $result as $result_row ) {
|
||||
$valid_fields[] = $result_row['Field'];
|
||||
}
|
||||
|
||||
// Check if the fields are actually contained in the table.
|
||||
foreach ( $fields as $field_to_check ) {
|
||||
if ( ! in_array( $field_to_check, $valid_fields, true ) ) {
|
||||
throw new Exception( "Invalid field name: field '{$field_to_check}' doesn't exist in table {$this->table}" );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the configured fields.
|
||||
*
|
||||
* @throws Exception Throw an exception on failure to validate in the internal functions.
|
||||
*/
|
||||
protected function validate_input() {
|
||||
$fields = array_merge( array( $this->range_field ), $this->key_fields, $this->checksum_fields, $this->checksum_text_fields );
|
||||
|
||||
$this->validate_fields( $fields );
|
||||
$this->validate_fields_against_table( $fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare filter values as SQL statements to be added to the other filters.
|
||||
*
|
||||
* @param array $filter_values The filter values array.
|
||||
* @param string $table_prefix If the values are going to be used in a sub-query, add a prefix with the table alias.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function prepare_filter_values_as_sql( $filter_values = array(), $table_prefix = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! is_array( $filter_values ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ( $filter_values as $field => $filter ) {
|
||||
$key = ( ! empty( $table_prefix ) ? $table_prefix : $this->table ) . '.' . $field;
|
||||
|
||||
switch ( $filter['operator'] ) {
|
||||
case 'IN':
|
||||
case 'NOT IN':
|
||||
$values_placeholders = implode( ',', array_fill( 0, count( $filter['values'] ), '%s' ) );
|
||||
$statement = "{$key} {$filter['operator']} ( $values_placeholders )";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$prepared_statement = $wpdb->prepare( $statement, $filter['values'] );
|
||||
|
||||
$result[] = $prepared_statement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the filter query baased off range fields and values and the additional sql.
|
||||
*
|
||||
* @param int|null $range_from Start of the range.
|
||||
* @param int|null $range_to End of the range.
|
||||
* @param array|null $filter_values Additional filter values. Not used at the moment.
|
||||
* @param string $table_prefix Table name to be prefixed to the columns. Used in sub-queries where columns can clash.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function build_filter_statement( $range_from = null, $range_to = null, $filter_values = null, $table_prefix = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
// If there is a field prefix that we want to use with table aliases.
|
||||
$parent_prefix = ( ! empty( $table_prefix ) ? $table_prefix : $this->table ) . '.';
|
||||
|
||||
/**
|
||||
* Prepare the ranges.
|
||||
*/
|
||||
|
||||
$filter_array = array( '1 = 1' );
|
||||
if ( null !== $range_from ) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$filter_array[] = $wpdb->prepare( "{$parent_prefix}{$this->range_field} >= %d", array( intval( $range_from ) ) );
|
||||
}
|
||||
if ( null !== $range_to ) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$filter_array[] = $wpdb->prepare( "{$parent_prefix}{$this->range_field} <= %d", array( intval( $range_to ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* End prepare the ranges.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepare data filters.
|
||||
*/
|
||||
|
||||
// Default filters.
|
||||
if ( $this->filter_values ) {
|
||||
$prepared_values_statements = $this->prepare_filter_values_as_sql( $this->filter_values, $table_prefix );
|
||||
if ( $prepared_values_statements ) {
|
||||
$filter_array = array_merge( $filter_array, $prepared_values_statements );
|
||||
}
|
||||
}
|
||||
|
||||
// Additional filters.
|
||||
if ( ! empty( $filter_values ) ) {
|
||||
// Prepare filtering.
|
||||
$prepared_values_statements = $this->prepare_filter_values_as_sql( $filter_values, $table_prefix );
|
||||
if ( $prepared_values_statements ) {
|
||||
$filter_array = array_merge( $filter_array, $prepared_values_statements );
|
||||
}
|
||||
}
|
||||
|
||||
// Add any additional filters via direct SQL statement.
|
||||
// Currently used only because we haven't converted all filtering to happen via `filter_values`.
|
||||
// This SQL is NOT prefixed and column clashes can occur when used in sub-queries.
|
||||
if ( $this->additional_filter_sql ) {
|
||||
$filter_array[] = $this->additional_filter_sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* End prepare data filters.
|
||||
*/
|
||||
return implode( ' AND ', $filter_array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checksum query. All validation of fields and configurations are expected to occur prior to usage.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param array|null $filter_values Additional filter values. Not used at the moment.
|
||||
* @param bool $granular_result If the function should return a granular result.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception Throws an exception if validation fails in the internal function calls.
|
||||
*/
|
||||
protected function build_checksum_query( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false ) {
|
||||
global $wpdb;
|
||||
|
||||
// Escape the salt.
|
||||
$salt = $wpdb->prepare( '%s', $this->salt );
|
||||
|
||||
// Prepare the compound key.
|
||||
$key_fields = array();
|
||||
|
||||
// Prefix the fields with the table name, to avoid clashes in queries with sub-queries (e.g. meta tables).
|
||||
foreach ( $this->key_fields as $field ) {
|
||||
$key_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
|
||||
$key_fields = implode( ',', $key_fields );
|
||||
|
||||
// Prepare the checksum fields.
|
||||
$checksum_fields = array();
|
||||
// Prefix the fields with the table name, to avoid clashes in queries with sub-queries (e.g. meta tables).
|
||||
foreach ( $this->checksum_fields as $field ) {
|
||||
$checksum_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
// Apply latin1 conversion if enabled.
|
||||
if ( $this->perform_text_conversion ) {
|
||||
// Convert text fields to allow for encoding discrepancies as WP.com is latin1.
|
||||
foreach ( $this->checksum_text_fields as $field ) {
|
||||
$checksum_fields[] = 'CONVERT(' . $this->table . '.' . $field . ' using latin1 )';
|
||||
}
|
||||
} else {
|
||||
// Conversion disabled, default to table prefixing.
|
||||
foreach ( $this->checksum_text_fields as $field ) {
|
||||
$checksum_fields[] = $this->table . '.' . $field;
|
||||
}
|
||||
}
|
||||
|
||||
$checksum_fields_string = implode( ',', array_merge( $checksum_fields, array( $salt ) ) );
|
||||
|
||||
$additional_fields = '';
|
||||
if ( $granular_result ) {
|
||||
// TODO uniq the fields as sometimes(most) range_index is the key and there's no need to select the same field twice.
|
||||
$additional_fields = "
|
||||
{$this->table}.{$this->range_field} as range_index,
|
||||
{$key_fields},
|
||||
";
|
||||
}
|
||||
|
||||
$filter_stamenet = $this->build_filter_statement( $range_from, $range_to, $filter_values );
|
||||
|
||||
$join_statement = '';
|
||||
if ( $this->parent_table ) {
|
||||
$parent_table_obj = new Table_Checksum( $this->parent_table );
|
||||
$parent_filter_query = $parent_table_obj->build_filter_statement( null, null, null, 'parent_table' );
|
||||
|
||||
// It is possible to have the GROUP By cause multiple rows to be returned for the same row for term_taxonomy.
|
||||
// To get distinct entries we use a correlatd subquery back on the parent table using the primary key.
|
||||
$additional_unique_clause = '';
|
||||
if ( 'term_taxonomy' === $this->parent_table ) {
|
||||
$additional_unique_clause = "
|
||||
AND parent_table.{$parent_table_obj->range_field} = (
|
||||
SELECT min( parent_table_cs.{$parent_table_obj->range_field} )
|
||||
FROM {$parent_table_obj->table} as parent_table_cs
|
||||
WHERE parent_table_cs.{$this->parent_join_field} = {$this->table}.{$this->table_join_field}
|
||||
)
|
||||
";
|
||||
}
|
||||
|
||||
$join_statement = "
|
||||
INNER JOIN {$parent_table_obj->table} as parent_table
|
||||
ON (
|
||||
{$this->table}.{$this->table_join_field} = parent_table.{$this->parent_join_field}
|
||||
AND {$parent_filter_query}
|
||||
$additional_unique_clause
|
||||
)
|
||||
";
|
||||
}
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
{$additional_fields}
|
||||
SUM(
|
||||
CRC32(
|
||||
CONCAT_WS( '#', {$salt}, {$checksum_fields_string} )
|
||||
)
|
||||
) AS checksum
|
||||
FROM
|
||||
{$this->table}
|
||||
{$join_statement}
|
||||
WHERE
|
||||
{$filter_stamenet}
|
||||
";
|
||||
|
||||
/**
|
||||
* We need the GROUP BY only for compound keys.
|
||||
*/
|
||||
if ( $granular_result ) {
|
||||
$query .= "
|
||||
GROUP BY {$key_fields}
|
||||
LIMIT 9999999
|
||||
";
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the min-max values (edges) of the range.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param int|null $limit How many values to return.
|
||||
*
|
||||
* @return array|object|void
|
||||
* @throws Exception Throws an exception if validation fails on the internal function calls.
|
||||
*/
|
||||
public function get_range_edges( $range_from = null, $range_to = null, $limit = null ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->validate_fields( array( $this->range_field ) );
|
||||
|
||||
// Performance :: When getting the postmeta range we do not want to filter by the whitelist.
|
||||
// The reason for this is that it leads to a non-performant query that can timeout.
|
||||
// Instead lets get the range based on posts regardless of meta.
|
||||
$filter_values = $this->filter_values;
|
||||
if ( 'postmeta' === $this->table ) {
|
||||
$this->filter_values = null;
|
||||
}
|
||||
|
||||
// `trim()` to make sure we don't add the statement if it's empty.
|
||||
$filters = trim( $this->build_filter_statement( $range_from, $range_to ) );
|
||||
|
||||
// Reset Post meta filter.
|
||||
if ( 'postmeta' === $this->table ) {
|
||||
$this->filter_values = $filter_values;
|
||||
}
|
||||
|
||||
$filter_statement = '';
|
||||
if ( ! empty( $filters ) ) {
|
||||
$filter_statement = "
|
||||
WHERE
|
||||
{$filters}
|
||||
";
|
||||
}
|
||||
|
||||
// Only make the distinct count when we know there can be multiple entries for the range column.
|
||||
$distinct_count = '';
|
||||
if ( count( $this->key_fields ) > 1 || $wpdb->terms === $this->table || $wpdb->term_relationships === $this->table ) {
|
||||
$distinct_count = 'DISTINCT';
|
||||
}
|
||||
|
||||
$query = "
|
||||
SELECT
|
||||
MIN({$this->range_field}) as min_range,
|
||||
MAX({$this->range_field}) as max_range,
|
||||
COUNT( {$distinct_count} {$this->range_field}) as item_count
|
||||
FROM
|
||||
";
|
||||
|
||||
/**
|
||||
* If `$limit` is not specified, we can directly use the table.
|
||||
*/
|
||||
if ( ! $limit ) {
|
||||
$query .= "
|
||||
{$this->table}
|
||||
{$filter_statement}
|
||||
";
|
||||
} else {
|
||||
/**
|
||||
* If there is `$limit` specified, we can't directly use `MIN/MAX()` as they don't work with `LIMIT`.
|
||||
* That's why we will alter the query for this case.
|
||||
*/
|
||||
$limit = intval( $limit );
|
||||
|
||||
$query .= "
|
||||
(
|
||||
SELECT
|
||||
{$distinct_count} {$this->range_field}
|
||||
FROM
|
||||
{$this->table}
|
||||
{$filter_statement}
|
||||
ORDER BY
|
||||
{$this->range_field} ASC
|
||||
LIMIT {$limit}
|
||||
) as ids_query
|
||||
";
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->get_row( $query, ARRAY_A );
|
||||
|
||||
if ( ! $result || ! is_array( $result ) ) {
|
||||
throw new Exception( 'Unable to get range edges' );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the results to have key/checksum format.
|
||||
*
|
||||
* @param array $results Prepare the results for output of granular results.
|
||||
*/
|
||||
protected function prepare_results_for_output( &$results ) {
|
||||
// get the compound key.
|
||||
// only return range and compound key for granular results.
|
||||
|
||||
$return_value = array();
|
||||
|
||||
foreach ( $results as &$result ) {
|
||||
// Working on reference to save memory here.
|
||||
|
||||
$key = array();
|
||||
foreach ( $this->key_fields as $field ) {
|
||||
$key[] = $result[ $field ];
|
||||
}
|
||||
|
||||
$return_value[ implode( '-', $key ) ] = $result['checksum'];
|
||||
}
|
||||
|
||||
return $return_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the checksum based on provided range and filters.
|
||||
*
|
||||
* @param int|null $range_from The start of the range.
|
||||
* @param int|null $range_to The end of the range.
|
||||
* @param array|null $filter_values Additional filter values. Not used at the moment.
|
||||
* @param bool $granular_result If the returned result should be granular or only the checksum.
|
||||
* @param bool $simple_return_value If we want to use a simple return value for non-granular results (return only the checksum, without wrappers).
|
||||
*
|
||||
* @return array|mixed|object|WP_Error|null
|
||||
*/
|
||||
public function calculate_checksum( $range_from = null, $range_to = null, $filter_values = null, $granular_result = false, $simple_return_value = true ) {
|
||||
|
||||
if ( ! Sync\Settings::is_checksum_enabled() ) {
|
||||
return new WP_Error( 'checksum_disabled', 'Checksums are currently disabled.' );
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validate_input();
|
||||
} catch ( Exception $ex ) {
|
||||
return new WP_Error( 'invalid_input', $ex->getMessage() );
|
||||
}
|
||||
|
||||
$query = $this->build_checksum_query( $range_from, $range_to, $filter_values, $granular_result );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $granular_result ) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->get_row( $query, ARRAY_A );
|
||||
|
||||
if ( ! is_array( $result ) ) {
|
||||
return new WP_Error( 'invalid_query', "Result wasn't an array" );
|
||||
}
|
||||
|
||||
if ( $simple_return_value ) {
|
||||
return $result['checksum'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'range' => $range_from . '-' . $range_to,
|
||||
'checksum' => $result['checksum'],
|
||||
);
|
||||
} else {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->get_results( $query, ARRAY_A );
|
||||
return $this->prepare_results_for_output( $result );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the WooCommerce tables should be enabled for Checksum/Fix.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function enable_woocommerce_tables() {
|
||||
/**
|
||||
* On WordPress.com, we can't directly check if the site has support for WooCommerce.
|
||||
* Having the option to override the functionality here helps with syncing WooCommerce tables.
|
||||
*
|
||||
* @since 10.1
|
||||
*
|
||||
* @param bool If we should we force-enable WooCommerce tables support.
|
||||
*/
|
||||
$force_woocommerce_support = apply_filters( 'jetpack_table_checksum_force_enable_woocommerce', false );
|
||||
|
||||
// If we're forcing WooCommerce tables support, there's no need to check further.
|
||||
// This is used on WordPress.com.
|
||||
if ( $force_woocommerce_support ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// No need to proceed if WooCommerce is not available.
|
||||
if ( ! class_exists( 'WooCommerce' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO more checks if needed. Probably query the DB to make sure the tables exist.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user