← All changes
|
jetpack_vendor/automattic/jetpack-connection/src/class-package-version-tracker.php
+201
-0
16.2-beta
→
16.3
View file →
| @@ -1,0 +1,201 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * The Package_Version_Tracker class. | |
| 4 | + * | |
| 5 | + * @package automattic/jetpack-connection | |
| 6 | + */ | |
| 7 | + | |
| 8 | +namespace Automattic\Jetpack\Connection; | |
| 9 | + | |
| 10 | +use Jetpack_Options; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * The Package_Version_Tracker class. | |
| 14 | + */ | |
| 15 | +class Package_Version_Tracker { | |
| 16 | + | |
| 17 | + const PACKAGE_VERSION_OPTION = 'jetpack_package_versions'; | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * The cache key for storing a failed request to update remote package versions. | |
| 21 | + * The caching logic is that when a failed request occurs, we cache it temporarily | |
| 22 | + * with a set expiration time. | |
| 23 | + * Only after the key has expired, we'll be able to repeat a remote request. | |
| 24 | + * This also implies that the cached value is redundant, however we chose the datetime | |
| 25 | + * of the failed request to avoid using booleans. | |
| 26 | + */ | |
| 27 | + const CACHED_FAILED_REQUEST_KEY = 'jetpack_failed_update_remote_package_versions'; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * The min time difference in seconds for attempting to | |
| 31 | + * update remote tracked package versions after a failed remote request. | |
| 32 | + */ | |
| 33 | + const CACHED_FAILED_REQUEST_EXPIRATION = 1 * HOUR_IN_SECONDS; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Transient key for rate limiting the package version requests; | |
| 37 | + */ | |
| 38 | + const RATE_LIMITER_KEY = 'jetpack_update_remote_package_last_query'; | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * Only allow one versions check (and request) per minute. | |
| 42 | + */ | |
| 43 | + const RATE_LIMITER_TIMEOUT = MINUTE_IN_SECONDS; | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * Runs the package version check on the `shutdown` hook. | |
| 47 | + * | |
| 48 | + * Static so the callback can be unregistered, and so the tracker is only | |
| 49 | + * constructed when the hook fires rather than during configuration. | |
| 50 | + */ | |
| 51 | + public static function update_on_shutdown() { | |
| 52 | + ( new self() )->maybe_update_package_versions(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * Uses the jetpack_package_versions filter to obtain the package versions from packages that need | |
| 57 | + * version tracking. If the package versions have changed, updates the option and notifies WPCOM. | |
| 58 | + */ | |
| 59 | + public function maybe_update_package_versions() { | |
| 60 | + // Do not run too early or all the modules may not be loaded. | |
| 61 | + if ( ! did_action( 'init' ) ) { | |
| 62 | + return; | |
| 63 | + } | |
| 64 | + | |
| 65 | + // Only attempt to update the option on POST requests. | |
| 66 | + // This will prevent the option from being updated multiple times due to concurrent requests. | |
| 67 | + if ( ! ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) ) { | |
| 68 | + return; | |
| 69 | + } | |
| 70 | + | |
| 71 | + // The version check is being rate limited. | |
| 72 | + if ( $this->is_rate_limiting() ) { | |
| 73 | + return; | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Obtains the package versions. | |
| 78 | + * | |
| 79 | + * @since 1.30.2 | |
| 80 | + * | |
| 81 | + * @param array An associative array of Jetpack package slugs and their corresponding versions as key/value pairs. | |
| 82 | + */ | |
| 83 | + $filter_versions = apply_filters( 'jetpack_package_versions', array() ); | |
| 84 | + | |
| 85 | + if ( ! is_array( $filter_versions ) ) { | |
| 86 | + return; | |
| 87 | + } | |
| 88 | + | |
| 89 | + $option_versions = get_option( self::PACKAGE_VERSION_OPTION, array() ); | |
| 90 | + | |
| 91 | + foreach ( $filter_versions as $package => $version ) { | |
| 92 | + if ( ! is_string( $package ) || ! is_string( $version ) ) { | |
| 93 | + unset( $filter_versions[ $package ] ); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + | |
| 97 | + if ( ! is_array( $option_versions ) | |
| 98 | + || count( array_diff_assoc( $filter_versions, $option_versions ) ) | |
| 99 | + || count( array_diff_assoc( $option_versions, $filter_versions ) ) | |
| 100 | + ) { | |
| 101 | + $this->update_package_versions_option( $filter_versions ); | |
| 102 | + } | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** | |
| 106 | + * Updates the package versions option. | |
| 107 | + * | |
| 108 | + * @param array $package_versions The package versions. | |
| 109 | + */ | |
| 110 | + protected function update_package_versions_option( $package_versions ) { | |
| 111 | + if ( ! $this->is_sync_enabled() ) { | |
| 112 | + $this->update_package_versions_via_remote_request( $package_versions ); | |
| 113 | + // Remove the checksum for package versions, so it gets recalculated when sync gets activated. | |
| 114 | + $jetpack_callables_sync_checksum = Jetpack_Options::get_raw_option( 'jetpack_callables_sync_checksum' ); | |
| 115 | + if ( isset( $jetpack_callables_sync_checksum['jetpack_package_versions'] ) ) { | |
| 116 | + unset( $jetpack_callables_sync_checksum['jetpack_package_versions'] ); | |
| 117 | + Jetpack_Options::update_raw_option( 'jetpack_callables_sync_checksum', $jetpack_callables_sync_checksum ); | |
| 118 | + } | |
| 119 | + return; | |
| 120 | + } | |
| 121 | + | |
| 122 | + update_option( self::PACKAGE_VERSION_OPTION, $package_versions ); | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * Whether Jetpack Sync is enabled. | |
| 127 | + * | |
| 128 | + * @return boolean true if Sync is present and enabled, false otherwise | |
| 129 | + */ | |
| 130 | + protected function is_sync_enabled() { | |
| 131 | + if ( class_exists( 'Automattic\Jetpack\Sync\Settings' ) && \Automattic\Jetpack\Sync\Settings::is_sync_enabled() ) { | |
| 132 | + | |
| 133 | + return true; | |
| 134 | + } | |
| 135 | + | |
| 136 | + return false; | |
| 137 | + } | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * Fallback for updating the package versions via a remote request when Sync is not present. | |
| 141 | + * | |
| 142 | + * Updates the package versions as follows: | |
| 143 | + * - Sends the updated package versions to wpcom. | |
| 144 | + * - Updates the 'jetpack_package_versions' option. | |
| 145 | + * | |
| 146 | + * @param array $package_versions The package versions. | |
| 147 | + */ | |
| 148 | + protected function update_package_versions_via_remote_request( $package_versions ) { | |
| 149 | + $connection = new Manager(); | |
| 150 | + if ( ! $connection->is_connected() ) { | |
| 151 | + return; | |
| 152 | + } | |
| 153 | + | |
| 154 | + $site_id = \Jetpack_Options::get_option( 'id' ); | |
| 155 | + | |
| 156 | + $last_failed_attempt_within_hour = get_transient( self::CACHED_FAILED_REQUEST_KEY ); | |
| 157 | + | |
| 158 | + if ( $last_failed_attempt_within_hour ) { | |
| 159 | + return; | |
| 160 | + } | |
| 161 | + | |
| 162 | + $body = wp_json_encode( | |
| 163 | + array( | |
| 164 | + 'package_versions' => $package_versions, | |
| 165 | + ), | |
| 166 | + JSON_UNESCAPED_SLASHES | |
| 167 | + ); | |
| 168 | + | |
| 169 | + $response = Client::wpcom_json_api_request_as_blog( | |
| 170 | + sprintf( '/sites/%d/jetpack-package-versions', $site_id ), | |
| 171 | + '2', | |
| 172 | + array( | |
| 173 | + 'headers' => array( 'content-type' => 'application/json' ), | |
| 174 | + 'method' => 'POST', | |
| 175 | + ), | |
| 176 | + $body, | |
| 177 | + 'wpcom' | |
| 178 | + ); | |
| 179 | + | |
| 180 | + if ( 200 === wp_remote_retrieve_response_code( $response ) ) { | |
| 181 | + update_option( self::PACKAGE_VERSION_OPTION, $package_versions ); | |
| 182 | + } else { | |
| 183 | + set_transient( self::CACHED_FAILED_REQUEST_KEY, time(), self::CACHED_FAILED_REQUEST_EXPIRATION ); | |
| 184 | + } | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * Check if version check is being rate limited, and update the rate limiting transient if needed. | |
| 189 | + * | |
| 190 | + * @return bool | |
| 191 | + */ | |
| 192 | + private function is_rate_limiting() { | |
| 193 | + if ( get_transient( static::RATE_LIMITER_KEY ) ) { | |
| 194 | + return true; | |
| 195 | + } | |
| 196 | + | |
| 197 | + set_transient( static::RATE_LIMITER_KEY, time(), static::RATE_LIMITER_TIMEOUT ); | |
| 198 | + | |
| 199 | + return false; | |
| 200 | + } | |
| 201 | +} | |