class-core-upgrader.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. <?php
  2. /**
  3. * Upgrade API: Core_Upgrader class
  4. *
  5. * @package WordPress
  6. * @subpackage Upgrader
  7. * @since 4.6.0
  8. */
  9. /**
  10. * Core class used for updating core.
  11. *
  12. * It allows for WordPress to upgrade itself in combination with
  13. * the wp-admin/includes/update-core.php file.
  14. *
  15. * @since 2.8.0
  16. * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
  17. *
  18. * @see WP_Upgrader
  19. */
  20. class Core_Upgrader extends WP_Upgrader {
  21. /**
  22. * Initialize the upgrade strings.
  23. *
  24. * @since 2.8.0
  25. */
  26. public function upgrade_strings() {
  27. $this->strings['up_to_date'] = __( 'WordPress is at the latest version.' );
  28. $this->strings['locked'] = __( 'Another update is currently in progress.' );
  29. $this->strings['no_package'] = __( 'Update package not available.' );
  30. /* translators: %s: Package URL. */
  31. $this->strings['downloading_package'] = sprintf( __( 'Downloading update from %s&#8230;' ), '<span class="code">%s</span>' );
  32. $this->strings['unpack_package'] = __( 'Unpacking the update&#8230;' );
  33. $this->strings['copy_failed'] = __( 'Could not copy files.' );
  34. $this->strings['copy_failed_space'] = __( 'Could not copy files. You may have run out of disk space.' );
  35. $this->strings['start_rollback'] = __( 'Attempting to roll back to previous version.' );
  36. $this->strings['rollback_was_required'] = __( 'Due to an error during updating, WordPress has rolled back to your previous version.' );
  37. }
  38. /**
  39. * Upgrade WordPress core.
  40. *
  41. * @since 2.8.0
  42. *
  43. * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  44. * @global callable $_wp_filesystem_direct_method
  45. *
  46. * @param object $current Response object for whether WordPress is current.
  47. * @param array $args {
  48. * Optional. Arguments for upgrading WordPress core. Default empty array.
  49. *
  50. * @type bool $pre_check_md5 Whether to check the file checksums before
  51. * attempting the upgrade. Default true.
  52. * @type bool $attempt_rollback Whether to attempt to rollback the chances if
  53. * there is a problem. Default false.
  54. * @type bool $do_rollback Whether to perform this "upgrade" as a rollback.
  55. * Default false.
  56. * }
  57. * @return null|false|WP_Error False or WP_Error on failure, null on success.
  58. */
  59. public function upgrade( $current, $args = array() ) {
  60. global $wp_filesystem;
  61. include( ABSPATH . WPINC . '/version.php' ); // $wp_version;
  62. $start_time = time();
  63. $defaults = array(
  64. 'pre_check_md5' => true,
  65. 'attempt_rollback' => false,
  66. 'do_rollback' => false,
  67. 'allow_relaxed_file_ownership' => false,
  68. );
  69. $parsed_args = wp_parse_args( $args, $defaults );
  70. $this->init();
  71. $this->upgrade_strings();
  72. // Is an update available?
  73. if ( ! isset( $current->response ) || $current->response == 'latest' ) {
  74. return new WP_Error( 'up_to_date', $this->strings['up_to_date'] );
  75. }
  76. $res = $this->fs_connect( array( ABSPATH, WP_CONTENT_DIR ), $parsed_args['allow_relaxed_file_ownership'] );
  77. if ( ! $res || is_wp_error( $res ) ) {
  78. return $res;
  79. }
  80. $wp_dir = trailingslashit( $wp_filesystem->abspath() );
  81. $partial = true;
  82. if ( $parsed_args['do_rollback'] ) {
  83. $partial = false;
  84. } elseif ( $parsed_args['pre_check_md5'] && ! $this->check_files() ) {
  85. $partial = false;
  86. }
  87. /*
  88. * If partial update is returned from the API, use that, unless we're doing
  89. * a reinstallation. If we cross the new_bundled version number, then use
  90. * the new_bundled zip. Don't though if the constant is set to skip bundled items.
  91. * If the API returns a no_content zip, go with it. Finally, default to the full zip.
  92. */
  93. if ( $parsed_args['do_rollback'] && $current->packages->rollback ) {
  94. $to_download = 'rollback';
  95. } elseif ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && $partial ) {
  96. $to_download = 'partial';
  97. } elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
  98. && ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) ) {
  99. $to_download = 'new_bundled';
  100. } elseif ( $current->packages->no_content ) {
  101. $to_download = 'no_content';
  102. } else {
  103. $to_download = 'full';
  104. }
  105. // Lock to prevent multiple Core Updates occurring
  106. $lock = WP_Upgrader::create_lock( 'core_updater', 15 * MINUTE_IN_SECONDS );
  107. if ( ! $lock ) {
  108. return new WP_Error( 'locked', $this->strings['locked'] );
  109. }
  110. $download = $this->download_package( $current->packages->$to_download, true );
  111. // Allow for signature soft-fail.
  112. // WARNING: This may be removed in the future.
  113. if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) {
  114. // Outout the failure error as a normal feedback, and not as an error:
  115. apply_filters( 'update_feedback', $download->get_error_message() );
  116. // Report this failure back to WordPress.org for debugging purposes.
  117. wp_version_check(
  118. array(
  119. 'signature_failure_code' => $download->get_error_code(),
  120. 'signature_failure_data' => $download->get_error_data(),
  121. )
  122. );
  123. // Pretend this error didn't happen.
  124. $download = $download->get_error_data( 'softfail-filename' );
  125. }
  126. if ( is_wp_error( $download ) ) {
  127. WP_Upgrader::release_lock( 'core_updater' );
  128. return $download;
  129. }
  130. $working_dir = $this->unpack_package( $download );
  131. if ( is_wp_error( $working_dir ) ) {
  132. WP_Upgrader::release_lock( 'core_updater' );
  133. return $working_dir;
  134. }
  135. // Copy update-core.php from the new version into place.
  136. if ( ! $wp_filesystem->copy( $working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true ) ) {
  137. $wp_filesystem->delete( $working_dir, true );
  138. WP_Upgrader::release_lock( 'core_updater' );
  139. return new WP_Error( 'copy_failed_for_update_core_file', __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ), 'wp-admin/includes/update-core.php' );
  140. }
  141. $wp_filesystem->chmod( $wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE );
  142. require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
  143. if ( ! function_exists( 'update_core' ) ) {
  144. WP_Upgrader::release_lock( 'core_updater' );
  145. return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
  146. }
  147. $result = update_core( $working_dir, $wp_dir );
  148. // In the event of an issue, we may be able to roll back.
  149. if ( $parsed_args['attempt_rollback'] && $current->packages->rollback && ! $parsed_args['do_rollback'] ) {
  150. $try_rollback = false;
  151. if ( is_wp_error( $result ) ) {
  152. $error_code = $result->get_error_code();
  153. /*
  154. * Not all errors are equal. These codes are critical: copy_failed__copy_dir,
  155. * mkdir_failed__copy_dir, copy_failed__copy_dir_retry, and disk_full.
  156. * do_rollback allows for update_core() to trigger a rollback if needed.
  157. */
  158. if ( false !== strpos( $error_code, 'do_rollback' ) ) {
  159. $try_rollback = true;
  160. } elseif ( false !== strpos( $error_code, '__copy_dir' ) ) {
  161. $try_rollback = true;
  162. } elseif ( 'disk_full' === $error_code ) {
  163. $try_rollback = true;
  164. }
  165. }
  166. if ( $try_rollback ) {
  167. /** This filter is documented in wp-admin/includes/update-core.php */
  168. apply_filters( 'update_feedback', $result );
  169. /** This filter is documented in wp-admin/includes/update-core.php */
  170. apply_filters( 'update_feedback', $this->strings['start_rollback'] );
  171. $rollback_result = $this->upgrade( $current, array_merge( $parsed_args, array( 'do_rollback' => true ) ) );
  172. $original_result = $result;
  173. $result = new WP_Error(
  174. 'rollback_was_required',
  175. $this->strings['rollback_was_required'],
  176. (object) array(
  177. 'update' => $original_result,
  178. 'rollback' => $rollback_result,
  179. )
  180. );
  181. }
  182. }
  183. /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
  184. do_action(
  185. 'upgrader_process_complete',
  186. $this,
  187. array(
  188. 'action' => 'update',
  189. 'type' => 'core',
  190. )
  191. );
  192. // Clear the current updates
  193. delete_site_transient( 'update_core' );
  194. if ( ! $parsed_args['do_rollback'] ) {
  195. $stats = array(
  196. 'update_type' => $current->response,
  197. 'success' => true,
  198. 'fs_method' => $wp_filesystem->method,
  199. 'fs_method_forced' => defined( 'FS_METHOD' ) || has_filter( 'filesystem_method' ),
  200. 'fs_method_direct' => ! empty( $GLOBALS['_wp_filesystem_direct_method'] ) ? $GLOBALS['_wp_filesystem_direct_method'] : '',
  201. 'time_taken' => time() - $start_time,
  202. 'reported' => $wp_version,
  203. 'attempted' => $current->version,
  204. );
  205. if ( is_wp_error( $result ) ) {
  206. $stats['success'] = false;
  207. // Did a rollback occur?
  208. if ( ! empty( $try_rollback ) ) {
  209. $stats['error_code'] = $original_result->get_error_code();
  210. $stats['error_data'] = $original_result->get_error_data();
  211. // Was the rollback successful? If not, collect its error too.
  212. $stats['rollback'] = ! is_wp_error( $rollback_result );
  213. if ( is_wp_error( $rollback_result ) ) {
  214. $stats['rollback_code'] = $rollback_result->get_error_code();
  215. $stats['rollback_data'] = $rollback_result->get_error_data();
  216. }
  217. } else {
  218. $stats['error_code'] = $result->get_error_code();
  219. $stats['error_data'] = $result->get_error_data();
  220. }
  221. }
  222. wp_version_check( $stats );
  223. }
  224. WP_Upgrader::release_lock( 'core_updater' );
  225. return $result;
  226. }
  227. /**
  228. * Determines if this WordPress Core version should update to an offered version or not.
  229. *
  230. * @since 3.7.0
  231. *
  232. * @param string $offered_ver The offered version, of the format x.y.z.
  233. * @return bool True if we should update to the offered version, otherwise false.
  234. */
  235. public static function should_update_to_version( $offered_ver ) {
  236. include( ABSPATH . WPINC . '/version.php' ); // $wp_version; // x.y.z
  237. $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version ), 0, 2 ) ); // x.y
  238. $new_branch = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
  239. $current_is_development_version = (bool) strpos( $wp_version, '-' );
  240. // Defaults:
  241. $upgrade_dev = true;
  242. $upgrade_minor = true;
  243. $upgrade_major = false;
  244. // WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
  245. if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
  246. if ( false === WP_AUTO_UPDATE_CORE ) {
  247. // Defaults to turned off, unless a filter allows it
  248. $upgrade_dev = false;
  249. $upgrade_minor = false;
  250. $upgrade_major = false;
  251. } elseif ( true === WP_AUTO_UPDATE_CORE ) {
  252. // ALL updates for core
  253. $upgrade_dev = true;
  254. $upgrade_minor = true;
  255. $upgrade_major = true;
  256. } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
  257. // Only minor updates for core
  258. $upgrade_dev = false;
  259. $upgrade_minor = true;
  260. $upgrade_major = false;
  261. }
  262. }
  263. // 1: If we're already on that version, not much point in updating?
  264. if ( $offered_ver == $wp_version ) {
  265. return false;
  266. }
  267. // 2: If we're running a newer version, that's a nope
  268. if ( version_compare( $wp_version, $offered_ver, '>' ) ) {
  269. return false;
  270. }
  271. $failure_data = get_site_option( 'auto_core_update_failed' );
  272. if ( $failure_data ) {
  273. // If this was a critical update failure, cannot update.
  274. if ( ! empty( $failure_data['critical'] ) ) {
  275. return false;
  276. }
  277. // Don't claim we can update on update-core.php if we have a non-critical failure logged.
  278. if ( $wp_version == $failure_data['current'] && false !== strpos( $offered_ver, '.1.next.minor' ) ) {
  279. return false;
  280. }
  281. // Cannot update if we're retrying the same A to B update that caused a non-critical failure.
  282. // Some non-critical failures do allow retries, like download_failed.
  283. // 3.7.1 => 3.7.2 resulted in files_not_writable, if we are still on 3.7.1 and still trying to update to 3.7.2.
  284. if ( empty( $failure_data['retry'] ) && $wp_version == $failure_data['current'] && $offered_ver == $failure_data['attempted'] ) {
  285. return false;
  286. }
  287. }
  288. // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
  289. if ( $current_is_development_version ) {
  290. /**
  291. * Filters whether to enable automatic core updates for development versions.
  292. *
  293. * @since 3.7.0
  294. *
  295. * @param bool $upgrade_dev Whether to enable automatic updates for
  296. * development versions.
  297. */
  298. if ( ! apply_filters( 'allow_dev_auto_core_updates', $upgrade_dev ) ) {
  299. return false;
  300. }
  301. // Else fall through to minor + major branches below.
  302. }
  303. // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
  304. if ( $current_branch == $new_branch ) {
  305. /**
  306. * Filters whether to enable minor automatic core updates.
  307. *
  308. * @since 3.7.0
  309. *
  310. * @param bool $upgrade_minor Whether to enable minor automatic core updates.
  311. */
  312. return apply_filters( 'allow_minor_auto_core_updates', $upgrade_minor );
  313. }
  314. // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
  315. if ( version_compare( $new_branch, $current_branch, '>' ) ) {
  316. /**
  317. * Filters whether to enable major automatic core updates.
  318. *
  319. * @since 3.7.0
  320. *
  321. * @param bool $upgrade_major Whether to enable major automatic core updates.
  322. */
  323. return apply_filters( 'allow_major_auto_core_updates', $upgrade_major );
  324. }
  325. // If we're not sure, we don't want it
  326. return false;
  327. }
  328. /**
  329. * Compare the disk file checksums against the expected checksums.
  330. *
  331. * @since 3.7.0
  332. *
  333. * @global string $wp_version
  334. * @global string $wp_local_package
  335. *
  336. * @return bool True if the checksums match, otherwise false.
  337. */
  338. public function check_files() {
  339. global $wp_version, $wp_local_package;
  340. $checksums = get_core_checksums( $wp_version, isset( $wp_local_package ) ? $wp_local_package : 'en_US' );
  341. if ( ! is_array( $checksums ) ) {
  342. return false;
  343. }
  344. foreach ( $checksums as $file => $checksum ) {
  345. // Skip files which get updated
  346. if ( 'wp-content' == substr( $file, 0, 10 ) ) {
  347. continue;
  348. }
  349. if ( ! file_exists( ABSPATH . $file ) || md5_file( ABSPATH . $file ) !== $checksum ) {
  350. return false;
  351. }
  352. }
  353. return true;
  354. }
  355. }