[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Class for testing automatic updates in the WordPress code. 4 * 5 * @package WordPress 6 * @subpackage Site_Health 7 * @since 5.2.0 8 */ 9 10 class WP_Site_Health_Auto_Updates { 11 /** 12 * WP_Site_Health_Auto_Updates constructor. 13 * 14 * @since 5.2.0 15 */ 16 public function __construct() { 17 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 18 } 19 20 21 /** 22 * Run tests to determine if auto-updates can run. 23 * 24 * @since 5.2.0 25 * 26 * @return array The test results. 27 */ 28 public function run_tests() { 29 $tests = array( 30 $this->test_constants( 'WP_AUTO_UPDATE_CORE', array( true, 'beta', 'rc', 'development', 'branch-development', 'minor' ) ), 31 $this->test_wp_version_check_attached(), 32 $this->test_filters_automatic_updater_disabled(), 33 $this->test_wp_automatic_updates_disabled(), 34 $this->test_if_failed_update(), 35 $this->test_vcs_abspath(), 36 $this->test_check_wp_filesystem_method(), 37 $this->test_all_files_writable(), 38 $this->test_accepts_dev_updates(), 39 $this->test_accepts_minor_updates(), 40 ); 41 42 $tests = array_filter( $tests ); 43 $tests = array_map( 44 static function( $test ) { 45 $test = (object) $test; 46 47 if ( empty( $test->severity ) ) { 48 $test->severity = 'warning'; 49 } 50 51 return $test; 52 }, 53 $tests 54 ); 55 56 return $tests; 57 } 58 59 /** 60 * Test if auto-updates related constants are set correctly. 61 * 62 * @since 5.2.0 63 * @since 5.5.1 The `$value` parameter can accept an array. 64 * 65 * @param string $constant The name of the constant to check. 66 * @param bool|string|array $value The value that the constant should be, if set, 67 * or an array of acceptable values. 68 * @return array The test results. 69 */ 70 public function test_constants( $constant, $value ) { 71 $acceptable_values = (array) $value; 72 73 if ( defined( $constant ) && ! in_array( constant( $constant ), $acceptable_values, true ) ) { 74 return array( 75 'description' => sprintf( 76 /* translators: %s: Name of the constant used. */ 77 __( 'The %s constant is defined and enabled.' ), 78 "<code>$constant</code>" 79 ), 80 'severity' => 'fail', 81 ); 82 } 83 } 84 85 /** 86 * Check if updates are intercepted by a filter. 87 * 88 * @since 5.2.0 89 * 90 * @return array The test results. 91 */ 92 public function test_wp_version_check_attached() { 93 if ( ( ! is_multisite() || is_main_site() && is_network_admin() ) 94 && ! has_filter( 'wp_version_check', 'wp_version_check' ) 95 ) { 96 return array( 97 'description' => sprintf( 98 /* translators: %s: Name of the filter used. */ 99 __( 'A plugin has prevented updates by disabling %s.' ), 100 '<code>wp_version_check()</code>' 101 ), 102 'severity' => 'fail', 103 ); 104 } 105 } 106 107 /** 108 * Check if automatic updates are disabled by a filter. 109 * 110 * @since 5.2.0 111 * 112 * @return array The test results. 113 */ 114 public function test_filters_automatic_updater_disabled() { 115 /** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */ 116 if ( apply_filters( 'automatic_updater_disabled', false ) ) { 117 return array( 118 'description' => sprintf( 119 /* translators: %s: Name of the filter used. */ 120 __( 'The %s filter is enabled.' ), 121 '<code>automatic_updater_disabled</code>' 122 ), 123 'severity' => 'fail', 124 ); 125 } 126 } 127 128 /** 129 * Check if automatic updates are disabled. 130 * 131 * @since 5.3.0 132 * 133 * @return array|false The test results. False if auto-updates are enabled. 134 */ 135 public function test_wp_automatic_updates_disabled() { 136 if ( ! class_exists( 'WP_Automatic_Updater' ) ) { 137 require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php'; 138 } 139 140 $auto_updates = new WP_Automatic_Updater(); 141 142 if ( ! $auto_updates->is_disabled() ) { 143 return false; 144 } 145 146 return array( 147 'description' => __( 'All automatic updates are disabled.' ), 148 'severity' => 'fail', 149 ); 150 } 151 152 /** 153 * Check if automatic updates have tried to run, but failed, previously. 154 * 155 * @since 5.2.0 156 * 157 * @return array|false The test results. False if the auto-updates failed. 158 */ 159 public function test_if_failed_update() { 160 $failed = get_site_option( 'auto_core_update_failed' ); 161 162 if ( ! $failed ) { 163 return false; 164 } 165 166 if ( ! empty( $failed['critical'] ) ) { 167 $description = __( 'A previous automatic background update ended with a critical failure, so updates are now disabled.' ); 168 $description .= ' ' . __( 'You would have received an email because of this.' ); 169 $description .= ' ' . __( "When you've been able to update using the \"Update now\" button on Dashboard > Updates, we'll clear this error for future update attempts." ); 170 $description .= ' ' . sprintf( 171 /* translators: %s: Code of error shown. */ 172 __( 'The error code was %s.' ), 173 '<code>' . $failed['error_code'] . '</code>' 174 ); 175 return array( 176 'description' => $description, 177 'severity' => 'warning', 178 ); 179 } 180 181 $description = __( 'A previous automatic background update could not occur.' ); 182 if ( empty( $failed['retry'] ) ) { 183 $description .= ' ' . __( 'You would have received an email because of this.' ); 184 } 185 186 $description .= ' ' . __( "We'll try again with the next release." ); 187 $description .= ' ' . sprintf( 188 /* translators: %s: Code of error shown. */ 189 __( 'The error code was %s.' ), 190 '<code>' . $failed['error_code'] . '</code>' 191 ); 192 return array( 193 'description' => $description, 194 'severity' => 'warning', 195 ); 196 } 197 198 /** 199 * Check if WordPress is controlled by a VCS (Git, Subversion etc). 200 * 201 * @since 5.2.0 202 * 203 * @return array The test results. 204 */ 205 public function test_vcs_abspath() { 206 $context_dirs = array( ABSPATH ); 207 $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' ); 208 $check_dirs = array(); 209 210 foreach ( $context_dirs as $context_dir ) { 211 // Walk up from $context_dir to the root. 212 do { 213 $check_dirs[] = $context_dir; 214 215 // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here. 216 if ( dirname( $context_dir ) === $context_dir ) { 217 break; 218 } 219 220 // Continue one level at a time. 221 } while ( $context_dir = dirname( $context_dir ) ); 222 } 223 224 $check_dirs = array_unique( $check_dirs ); 225 226 // Search all directories we've found for evidence of version control. 227 foreach ( $vcs_dirs as $vcs_dir ) { 228 foreach ( $check_dirs as $check_dir ) { 229 // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition,Squiz.PHP.DisallowMultipleAssignments 230 if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) ) { 231 break 2; 232 } 233 } 234 } 235 236 /** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */ 237 if ( $checkout && ! apply_filters( 'automatic_updates_is_vcs_checkout', true, ABSPATH ) ) { 238 return array( 239 'description' => sprintf( 240 /* translators: 1: Folder name. 2: Version control directory. 3: Filter name. */ 241 __( 'The folder %1$s was detected as being under version control (%2$s), but the %3$s filter is allowing updates.' ), 242 '<code>' . $check_dir . '</code>', 243 "<code>$vcs_dir</code>", 244 '<code>automatic_updates_is_vcs_checkout</code>' 245 ), 246 'severity' => 'info', 247 ); 248 } 249 250 if ( $checkout ) { 251 return array( 252 'description' => sprintf( 253 /* translators: 1: Folder name. 2: Version control directory. */ 254 __( 'The folder %1$s was detected as being under version control (%2$s).' ), 255 '<code>' . $check_dir . '</code>', 256 "<code>$vcs_dir</code>" 257 ), 258 'severity' => 'warning', 259 ); 260 } 261 262 return array( 263 'description' => __( 'No version control systems were detected.' ), 264 'severity' => 'pass', 265 ); 266 } 267 268 /** 269 * Check if we can access files without providing credentials. 270 * 271 * @since 5.2.0 272 * 273 * @return array The test results. 274 */ 275 public function test_check_wp_filesystem_method() { 276 // Make sure the `request_filesystem_credentials()` function is available during our REST API call. 277 if ( ! function_exists( 'request_filesystem_credentials' ) ) { 278 require_once ABSPATH . '/wp-admin/includes/file.php'; 279 } 280 281 $skin = new Automatic_Upgrader_Skin; 282 $success = $skin->request_filesystem_credentials( false, ABSPATH ); 283 284 if ( ! $success ) { 285 $description = __( 'Your installation of WordPress prompts for FTP credentials to perform updates.' ); 286 $description .= ' ' . __( '(Your site is performing updates over FTP due to file ownership. Talk to your hosting company.)' ); 287 288 return array( 289 'description' => $description, 290 'severity' => 'fail', 291 ); 292 } 293 294 return array( 295 'description' => __( 'Your installation of WordPress does not require FTP credentials to perform updates.' ), 296 'severity' => 'pass', 297 ); 298 } 299 300 /** 301 * Check if core files are writable by the web user/group. 302 * 303 * @since 5.2.0 304 * 305 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 306 * 307 * @return array|false The test results. False if they're not writeable. 308 */ 309 public function test_all_files_writable() { 310 global $wp_filesystem; 311 312 require ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z 313 314 $skin = new Automatic_Upgrader_Skin; 315 $success = $skin->request_filesystem_credentials( false, ABSPATH ); 316 317 if ( ! $success ) { 318 return false; 319 } 320 321 WP_Filesystem(); 322 323 if ( 'direct' !== $wp_filesystem->method ) { 324 return false; 325 } 326 327 // Make sure the `get_core_checksums()` function is available during our REST API call. 328 if ( ! function_exists( 'get_core_checksums' ) ) { 329 require_once ABSPATH . '/wp-admin/includes/update.php'; 330 } 331 332 $checksums = get_core_checksums( $wp_version, 'en_US' ); 333 $dev = ( false !== strpos( $wp_version, '-' ) ); 334 // Get the last stable version's files and test against that. 335 if ( ! $checksums && $dev ) { 336 $checksums = get_core_checksums( (float) $wp_version - 0.1, 'en_US' ); 337 } 338 339 // There aren't always checksums for development releases, so just skip the test if we still can't find any. 340 if ( ! $checksums && $dev ) { 341 return false; 342 } 343 344 if ( ! $checksums ) { 345 $description = sprintf( 346 /* translators: %s: WordPress version. */ 347 __( "Couldn't retrieve a list of the checksums for WordPress %s." ), 348 $wp_version 349 ); 350 $description .= ' ' . __( 'This could mean that connections are failing to WordPress.org.' ); 351 return array( 352 'description' => $description, 353 'severity' => 'warning', 354 ); 355 } 356 357 $unwritable_files = array(); 358 foreach ( array_keys( $checksums ) as $file ) { 359 if ( 'wp-content' === substr( $file, 0, 10 ) ) { 360 continue; 361 } 362 if ( ! file_exists( ABSPATH . $file ) ) { 363 continue; 364 } 365 if ( ! is_writable( ABSPATH . $file ) ) { 366 $unwritable_files[] = $file; 367 } 368 } 369 370 if ( $unwritable_files ) { 371 if ( count( $unwritable_files ) > 20 ) { 372 $unwritable_files = array_slice( $unwritable_files, 0, 20 ); 373 $unwritable_files[] = '...'; 374 } 375 return array( 376 'description' => __( 'Some files are not writable by WordPress:' ) . ' <ul><li>' . implode( '</li><li>', $unwritable_files ) . '</li></ul>', 377 'severity' => 'fail', 378 ); 379 } else { 380 return array( 381 'description' => __( 'All of your WordPress files are writable.' ), 382 'severity' => 'pass', 383 ); 384 } 385 } 386 387 /** 388 * Check if the install is using a development branch and can use nightly packages. 389 * 390 * @since 5.2.0 391 * 392 * @return array|false The test results. False if it isn't a development version. 393 */ 394 public function test_accepts_dev_updates() { 395 require ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z 396 // Only for dev versions. 397 if ( false === strpos( $wp_version, '-' ) ) { 398 return false; 399 } 400 401 if ( defined( 'WP_AUTO_UPDATE_CORE' ) && ( 'minor' === WP_AUTO_UPDATE_CORE || false === WP_AUTO_UPDATE_CORE ) ) { 402 return array( 403 'description' => sprintf( 404 /* translators: %s: Name of the constant used. */ 405 __( 'WordPress development updates are blocked by the %s constant.' ), 406 '<code>WP_AUTO_UPDATE_CORE</code>' 407 ), 408 'severity' => 'fail', 409 ); 410 } 411 412 /** This filter is documented in wp-admin/includes/class-core-upgrader.php */ 413 if ( ! apply_filters( 'allow_dev_auto_core_updates', $wp_version ) ) { 414 return array( 415 'description' => sprintf( 416 /* translators: %s: Name of the filter used. */ 417 __( 'WordPress development updates are blocked by the %s filter.' ), 418 '<code>allow_dev_auto_core_updates</code>' 419 ), 420 'severity' => 'fail', 421 ); 422 } 423 } 424 425 /** 426 * Check if the site supports automatic minor updates. 427 * 428 * @since 5.2.0 429 * 430 * @return array The test results. 431 */ 432 public function test_accepts_minor_updates() { 433 if ( defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE ) { 434 return array( 435 'description' => sprintf( 436 /* translators: %s: Name of the constant used. */ 437 __( 'WordPress security and maintenance releases are blocked by %s.' ), 438 "<code>define( 'WP_AUTO_UPDATE_CORE', false );</code>" 439 ), 440 'severity' => 'fail', 441 ); 442 } 443 444 /** This filter is documented in wp-admin/includes/class-core-upgrader.php */ 445 if ( ! apply_filters( 'allow_minor_auto_core_updates', true ) ) { 446 return array( 447 'description' => sprintf( 448 /* translators: %s: Name of the filter used. */ 449 __( 'WordPress security and maintenance releases are blocked by the %s filter.' ), 450 '<code>allow_minor_auto_core_updates</code>' 451 ), 452 'severity' => 'fail', 453 ); 454 } 455 } 456 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |