[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-wp-site-health-auto-updates.php (source)

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


Generated : Fri Nov 13 08:20:01 2026 Cross-referenced by PHPXref