← All changes
|
jetpack_vendor/automattic/jetpack-sync/src/modules/class-plugins.php
+149
-97
12.0.3
→
16.3-a.1
View file →
| @@ -7,9 +7,14 @@ | ||
| 7 | 7 | |
| 8 | 8 | namespace Automattic\Jetpack\Sync\Modules; |
| 9 | 9 | |
| 10 | 10 | use Automattic\Jetpack\Constants as Jetpack_Constants; |
| 11 | +use WP_Error; | |
| 11 | 12 | |
| 13 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 14 | + exit( 0 ); | |
| 15 | +} | |
| 16 | + | |
| 12 | 17 | /** |
| 13 | 18 | * Class to handle sync for plugins. |
| 14 | 19 | */ |
| 15 | 20 | class Plugins extends Module { |
| @@ -40,8 +45,44 @@ | ||
| 40 | 45 | */ |
| 41 | 46 | private $plugins = array(); |
| 42 | 47 | |
| 43 | 48 | /** |
| 49 | + * List of all updated plugins. | |
| 50 | + * | |
| 51 | + * @access private | |
| 52 | + * | |
| 53 | + * @var array | |
| 54 | + */ | |
| 55 | + private $plugins_updated = array(); | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * List of plugins installed during this request. | |
| 59 | + * | |
| 60 | + * @access private | |
| 61 | + * | |
| 62 | + * @var array | |
| 63 | + */ | |
| 64 | + private $plugins_installed = array(); | |
| 65 | + | |
| 66 | + /** | |
| 67 | + * List of all plugin update failures during this request. | |
| 68 | + * | |
| 69 | + * @access private | |
| 70 | + * | |
| 71 | + * @var array | |
| 72 | + */ | |
| 73 | + private $plugins_update_failures = array(); | |
| 74 | + | |
| 75 | + /** | |
| 76 | + * State | |
| 77 | + * | |
| 78 | + * @access private | |
| 79 | + * | |
| 80 | + * @var array | |
| 81 | + */ | |
| 82 | + private $state = array(); | |
| 83 | + | |
| 84 | + /** | |
| 44 | 85 | * Sync module name. |
| 45 | 86 | * |
| 46 | 87 | * @access public |
| 47 | 88 | * |
| @@ -69,22 +110,14 @@ | ||
| 69 | 110 | add_action( 'upgrader_process_complete', array( $this, 'on_upgrader_completion' ), 10, 2 ); |
| 70 | 111 | add_action( 'jetpack_plugin_installed', $callable, 10, 1 ); |
| 71 | 112 | add_action( 'jetpack_plugin_update_failed', $callable, 10, 4 ); |
| 72 | 113 | add_action( 'jetpack_plugins_updated', $callable, 10, 2 ); |
| 73 | - add_action( 'admin_action_update', array( $this, 'check_plugin_edit' ) ); | |
| 74 | 114 | add_action( 'jetpack_edited_plugin', $callable, 10, 2 ); |
| 75 | 115 | add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'plugin_edit_ajax' ), 0 ); |
| 76 | - } | |
| 77 | 116 | |
| 78 | - /** | |
| 79 | - * Initialize the module in the sender. | |
| 80 | - * | |
| 81 | - * @access public | |
| 82 | - */ | |
| 83 | - public function init_before_send() { | |
| 84 | - add_filter( 'jetpack_sync_before_send_activated_plugin', array( $this, 'expand_plugin_data' ) ); | |
| 85 | - add_filter( 'jetpack_sync_before_send_deactivated_plugin', array( $this, 'expand_plugin_data' ) ); | |
| 86 | 117 | // 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. |
| 118 | + add_filter( 'jetpack_sync_before_enqueue_activated_plugin', array( $this, 'expand_plugin_data' ) ); | |
| 119 | + add_filter( 'jetpack_sync_before_enqueue_deactivated_plugin', array( $this, 'expand_plugin_data' ) ); | |
| 87 | 120 | } |
| 88 | 121 | |
| 89 | 122 | /** |
| 90 | 123 | * Fetch and populate all current plugins before upgrader installation. |
| @@ -120,9 +153,9 @@ | ||
| 120 | 153 | if ( ! isset( $details['action'] ) ) { |
| 121 | 154 | return; |
| 122 | 155 | } |
| 123 | 156 | |
| 124 | - $plugins = ( isset( $details['plugins'] ) ? $details['plugins'] : null ); | |
| 157 | + $plugins = ( $details['plugins'] ?? null ); | |
| 125 | 158 | if ( empty( $plugins ) ) { |
| 126 | 159 | $plugins = ( isset( $details['plugin'] ) ? array( $details['plugin'] ) : null ); |
| 127 | 160 | } |
| 128 | 161 | |
| @@ -127,8 +160,9 @@ | ||
| 127 | 160 | } |
| 128 | 161 | |
| 129 | 162 | // For plugin installer. |
| 130 | 163 | if ( empty( $plugins ) && method_exists( $upgrader, 'plugin_info' ) ) { |
| 164 | + // @phan-suppress-next-line PhanUndeclaredMethod -- Checked above. See also https://github.com/phan/phan/issues/1204. | |
| 131 | 165 | $plugins = array( $upgrader->plugin_info() ); |
| 132 | 166 | } |
| 133 | 167 | |
| 134 | 168 | if ( empty( $plugins ) ) { |
| @@ -136,61 +170,43 @@ | ||
| 136 | 170 | } |
| 137 | 171 | |
| 138 | 172 | switch ( $details['action'] ) { |
| 139 | 173 | case 'update': |
| 140 | - $state = array( | |
| 174 | + $this->state = array( | |
| 141 | 175 | 'is_autoupdate' => Jetpack_Constants::is_true( 'JETPACK_PLUGIN_AUTOUPDATE' ), |
| 142 | 176 | ); |
| 143 | - $errors = $this->get_errors( $upgrader->skin ); | |
| 177 | + $errors = $this->get_errors( $upgrader->skin ); | |
| 144 | 178 | if ( $errors ) { |
| 145 | - foreach ( $plugins as $slug ) { | |
| 146 | - /** | |
| 147 | - * Sync that a plugin update failed | |
| 148 | - * | |
| 149 | - * @since 1.6.3 | |
| 150 | - * @since-jetpack 5.8.0 | |
| 151 | - * | |
| 152 | - * @module sync | |
| 153 | - * | |
| 154 | - * @param string $plugin , Plugin slug | |
| 155 | - * @param string Error code | |
| 156 | - * @param string Error message | |
| 157 | - */ | |
| 158 | - do_action( 'jetpack_plugin_update_failed', $this->get_plugin_info( $slug ), $errors['code'], $errors['message'], $state ); | |
| 179 | + foreach ( $plugins as $slug ) { // Accumulate failures and defer to shutdown, to reduce request-time lag. | |
| 180 | + $this->plugins_update_failures[] = array( | |
| 181 | + 'plugin' => $this->get_plugin_info( $slug ), | |
| 182 | + 'code' => $errors['code'], | |
| 183 | + 'message' => $errors['message'], | |
| 184 | + 'state' => $this->state, | |
| 185 | + ); | |
| 159 | 186 | } |
| 187 | + if ( ! has_action( 'shutdown', array( $this, 'sync_plugins_update_failed' ) ) ) { | |
| 188 | + add_action( 'shutdown', array( $this, 'sync_plugins_update_failed' ), 9 ); | |
| 189 | + } | |
| 160 | 190 | |
| 161 | 191 | return; |
| 162 | 192 | } |
| 163 | - /** | |
| 164 | - * Sync that a plugin update | |
| 165 | - * | |
| 166 | - * @since 1.6.3 | |
| 167 | - * @since-jetpack 5.8.0 | |
| 168 | - * | |
| 169 | - * @module sync | |
| 170 | - * | |
| 171 | - * @param array () $plugin, Plugin Data | |
| 172 | - */ | |
| 173 | - do_action( 'jetpack_plugins_updated', array_map( array( $this, 'get_plugin_info' ), $plugins ), $state ); | |
| 193 | + | |
| 194 | + $this->plugins_updated = array_map( array( $this, 'get_plugin_info' ), $plugins ); | |
| 195 | + add_action( 'shutdown', array( $this, 'sync_plugins_updated' ), 9 ); | |
| 196 | + | |
| 174 | 197 | break; |
| 175 | 198 | case 'install': |
| 176 | - } | |
| 199 | + // Accumulate installs and defer to shutdown. | |
| 200 | + $this->plugins_installed = array_merge( | |
| 201 | + $this->plugins_installed, | |
| 202 | + array_map( array( $this, 'get_plugin_info' ), $plugins ) | |
| 203 | + ); | |
| 204 | + if ( ! has_action( 'shutdown', array( $this, 'sync_plugins_installed' ) ) ) { | |
| 205 | + add_action( 'shutdown', array( $this, 'sync_plugins_installed' ), 9 ); | |
| 206 | + } | |
| 177 | 207 | |
| 178 | - if ( 'install' === $details['action'] ) { | |
| 179 | - /** | |
| 180 | - * Signals to the sync listener that a plugin was installed and a sync action | |
| 181 | - * reflecting the installation and the plugin info should be sent | |
| 182 | - * | |
| 183 | - * @since 1.6.3 | |
| 184 | - * @since-jetpack 5.8.0 | |
| 185 | - * | |
| 186 | - * @module sync | |
| 187 | - * | |
| 188 | - * @param array () $plugin, Plugin Data | |
| 189 | - */ | |
| 190 | - do_action( 'jetpack_plugin_installed', array_map( array( $this, 'get_plugin_info' ), $plugins ) ); | |
| 191 | - | |
| 192 | - return; | |
| 208 | + break; | |
| 193 | 209 | } |
| 194 | 210 | } |
| 195 | 211 | |
| 196 | 212 | /** |
| @@ -218,8 +234,9 @@ | ||
| 218 | 234 | * @param \Automatic_Upgrader_Skin|\WP_Upgrader_Skin $skin The upgrader skin being used. |
| 219 | 235 | * @return array|boolean Error on error, false otherwise. |
| 220 | 236 | */ |
| 221 | 237 | private function get_errors( $skin ) { |
| 238 | + // @phan-suppress-next-line PhanUndeclaredMethod -- Checked before being called. See also https://github.com/phan/phan/issues/1204. | |
| 222 | 239 | $errors = method_exists( $skin, 'get_errors' ) ? $skin->get_errors() : null; |
| 223 | 240 | if ( is_wp_error( $errors ) ) { |
| 224 | 241 | $error_code = $errors->get_error_code(); |
| 225 | 242 | if ( ! empty( $error_code ) ) { |
| @@ -249,41 +266,8 @@ | ||
| 249 | 266 | return false; |
| 250 | 267 | } |
| 251 | 268 | |
| 252 | 269 | /** |
| 253 | - * Handle plugin edit in the administration. | |
| 254 | - * | |
| 255 | - * @access public | |
| 256 | - * | |
| 257 | - * @todo The `admin_action_update` hook is called only for logged in users, but maybe implement nonce verification? | |
| 258 | - */ | |
| 259 | - public function check_plugin_edit() { | |
| 260 | - $screen = get_current_screen(); | |
| 261 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 262 | - if ( 'plugin-editor' !== $screen->base || ! isset( $_POST['newcontent'] ) || ! isset( $_POST['plugin'] ) ) { | |
| 263 | - return; | |
| 264 | - } | |
| 265 | - | |
| 266 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after. | |
| 267 | - $plugin = wp_unslash( $_POST['plugin'] ); | |
| 268 | - $plugins = get_plugins(); | |
| 269 | - if ( ! isset( $plugins[ $plugin ] ) ) { | |
| 270 | - return; | |
| 271 | - } | |
| 272 | - | |
| 273 | - /** | |
| 274 | - * Helps Sync log that a plugin was edited | |
| 275 | - * | |
| 276 | - * @since 1.6.3 | |
| 277 | - * @since-jetpack 4.9.0 | |
| 278 | - * | |
| 279 | - * @param string $plugin, Plugin slug | |
| 280 | - * @param mixed $plugins[ $plugin ], Array of plugin data | |
| 281 | - */ | |
| 282 | - do_action( 'jetpack_edited_plugin', $plugin, $plugins[ $plugin ] ); | |
| 283 | - } | |
| 284 | - | |
| 285 | - /** | |
| 286 | 270 | * Handle plugin ajax edit in the administration. |
| 287 | 271 | * |
| 288 | 272 | * @access public |
| 289 | 273 | * |
| @@ -290,36 +274,35 @@ | ||
| 290 | 274 | * @todo Update this method to use WP_Filesystem instead of fopen/fclose. |
| 291 | 275 | */ |
| 292 | 276 | public function plugin_edit_ajax() { |
| 293 | 277 | // This validation is based on wp_edit_theme_plugin_file(). |
| 294 | - $args = wp_unslash( $_POST ); | |
| 295 | - if ( empty( $args['file'] ) ) { | |
| 278 | + if ( empty( $_POST['file'] ) ) { | |
| 296 | 279 | return; |
| 297 | 280 | } |
| 298 | 281 | |
| 299 | - $file = $args['file']; | |
| 282 | + $file = wp_unslash( $_POST['file'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after. | |
| 300 | 283 | if ( 0 !== validate_file( $file ) ) { |
| 301 | 284 | return; |
| 302 | 285 | } |
| 303 | 286 | |
| 304 | - if ( ! isset( $args['newcontent'] ) ) { | |
| 287 | + if ( ! isset( $_POST['newcontent'] ) ) { | |
| 305 | 288 | return; |
| 306 | 289 | } |
| 307 | 290 | |
| 308 | - if ( ! isset( $args['nonce'] ) ) { | |
| 291 | + if ( ! isset( $_POST['nonce'] ) ) { | |
| 309 | 292 | return; |
| 310 | 293 | } |
| 311 | 294 | |
| 312 | - if ( empty( $args['plugin'] ) ) { | |
| 295 | + if ( empty( $_POST['plugin'] ) ) { | |
| 313 | 296 | return; |
| 314 | 297 | } |
| 315 | 298 | |
| 316 | - $plugin = $args['plugin']; | |
| 299 | + $plugin = wp_unslash( $_POST['plugin'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after. | |
| 317 | 300 | if ( ! current_user_can( 'edit_plugins' ) ) { |
| 318 | 301 | return; |
| 319 | 302 | } |
| 320 | 303 | |
| 321 | - if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) { | |
| 304 | + if ( ! wp_verify_nonce( $_POST['nonce'], 'edit-plugin_' . $file ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either. | |
| 322 | 305 | return; |
| 323 | 306 | } |
| 324 | 307 | $plugins = get_plugins(); |
| 325 | 308 | if ( ! array_key_exists( $plugin, $plugins ) ) { |
| @@ -331,10 +314,10 @@ | ||
| 331 | 314 | } |
| 332 | 315 | |
| 333 | 316 | $real_file = WP_PLUGIN_DIR . '/' . $file; |
| 334 | 317 | |
| 335 | - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writeable | |
| 336 | - if ( ! is_writeable( $real_file ) ) { | |
| 318 | + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable | |
| 319 | + if ( ! is_writable( $real_file ) ) { | |
| 337 | 320 | return; |
| 338 | 321 | } |
| 339 | 322 | |
| 340 | 323 | // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen |
| @@ -416,6 +399,75 @@ | ||
| 416 | 399 | $args[0], |
| 417 | 400 | $args[1], |
| 418 | 401 | $plugin_data, |
| 419 | 402 | ); |
| 403 | + } | |
| 404 | + | |
| 405 | + /** | |
| 406 | + * Helper method for firing the 'jetpack_plugins_updated' action on shutdown. | |
| 407 | + * | |
| 408 | + * @access public | |
| 409 | + */ | |
| 410 | + public function sync_plugins_updated() { | |
| 411 | + /** | |
| 412 | + * Sync that a plugin update | |
| 413 | + * | |
| 414 | + * @since 1.6.3 | |
| 415 | + * @since-jetpack 5.8.0 | |
| 416 | + * | |
| 417 | + * @module sync | |
| 418 | + * | |
| 419 | + * @param array () $plugin, Plugin Data | |
| 420 | + */ | |
| 421 | + do_action( 'jetpack_plugins_updated', $this->plugins_updated, $this->state ); | |
| 422 | + } | |
| 423 | + | |
| 424 | + /** | |
| 425 | + * Helper method for firing the 'jetpack_plugin_installed' action on shutdown. | |
| 426 | + * | |
| 427 | + * @access public | |
| 428 | + */ | |
| 429 | + public function sync_plugins_installed() { | |
| 430 | + if ( empty( $this->plugins_installed ) ) { | |
| 431 | + return; | |
| 432 | + } | |
| 433 | + /** | |
| 434 | + * Signals to the sync listener that a plugin was installed and a sync action | |
| 435 | + * reflecting the installation and the plugin info should be sent. | |
| 436 | + * | |
| 437 | + * @since 1.6.3 | |
| 438 | + * @since-jetpack 5.8.0 | |
| 439 | + * | |
| 440 | + * @module sync | |
| 441 | + * | |
| 442 | + * @param array () $plugin, Plugin Data | |
| 443 | + */ | |
| 444 | + do_action( 'jetpack_plugin_installed', $this->plugins_installed ); | |
| 445 | + } | |
| 446 | + | |
| 447 | + /** | |
| 448 | + * Helper method for firing the 'jetpack_plugin_update_failed' actions on shutdown. | |
| 449 | + * | |
| 450 | + * @access public | |
| 451 | + */ | |
| 452 | + public function sync_plugins_update_failed() { | |
| 453 | + if ( empty( $this->plugins_update_failures ) ) { | |
| 454 | + return; | |
| 455 | + } | |
| 456 | + foreach ( $this->plugins_update_failures as $failure ) { | |
| 457 | + /** | |
| 458 | + * Sync that a plugin update failed | |
| 459 | + * | |
| 460 | + * @since 1.6.3 | |
| 461 | + * @since-jetpack 5.8.0 | |
| 462 | + * | |
| 463 | + * @module sync | |
| 464 | + * | |
| 465 | + * @param array $plugin Plugin Data | |
| 466 | + * @param string $code Error code | |
| 467 | + * @param string $message Error message | |
| 468 | + * @param array $state State data | |
| 469 | + */ | |
| 470 | + do_action( 'jetpack_plugin_update_failed', $failure['plugin'], $failure['code'], $failure['message'], $failure['state'] ); | |
| 471 | + } | |
| 420 | 472 | } |
| 421 | 473 | } |