]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/includes/class-wp-upgrader.php
WordPress 4.2.5-scripts
[autoinstalls/wordpress.git] / wp-admin / includes / class-wp-upgrader.php
1 <?php
2 /**
3  * A File upgrader class for WordPress.
4  *
5  * This set of classes are designed to be used to upgrade/install a local set of files on the filesystem via the Filesystem Abstraction classes.
6  *
7  * @link https://core.trac.wordpress.org/ticket/7875 consolidate plugin/theme/core upgrade/install functions
8  *
9  * @package WordPress
10  * @subpackage Upgrader
11  * @since 2.8.0
12  */
13
14 require ABSPATH . 'wp-admin/includes/class-wp-upgrader-skins.php';
15
16 /**
17  * WordPress Upgrader class for Upgrading/Installing a local set of files via the Filesystem Abstraction classes from a Zip file.
18  *
19  * @package WordPress
20  * @subpackage Upgrader
21  * @since 2.8.0
22  */
23 class WP_Upgrader {
24
25         /**
26          * The error/notification strings used to update the user on the progress.
27          *
28          * @since 2.8.0
29          * @var string $strings
30          */
31         public $strings = array();
32
33         /**
34          * The upgrader skin being used.
35          *
36          * @since 2.8.0
37          * @var WP_Upgrader_Skin $skin
38          */
39         public $skin = null;
40
41         /**
42          * The result of the installation.
43          *
44          * This is set by {@see WP_Upgrader::install_package()}, only when the package is installed
45          * successfully. It will then be an array, unless a {@see WP_Error} is returned by the
46          * {@see 'upgrader_post_install'} filter. In that case, the `WP_Error` will be assigned to
47          * it.
48          *
49          * @since 2.8.0
50          * @var WP_Error|array $result {
51          *      @type string $source             The full path to the source the files were installed from.
52          *      @type string $source_files       List of all the files in the source directory.
53          *      @type string $destination        The full path to the install destination folder.
54          *      @type string $destination_name   The name of the destination folder, or empty if `$destination`
55          *                                       and `$local_destination` are the same.
56          *      @type string $local_destination  The full local path to the destination folder. This is usually
57          *                                       the same as `$destination`.
58          *      @type string $remote_destination The full remote path to the destination folder
59          *                                       (i.e., from `$wp_filesystem`).
60          *      @type bool   $clear_destination  Whether the destination folder was cleared.
61          * }
62          */
63         public $result = array();
64
65         /**
66          * The total number of updates being performed.
67          *
68          * Set by the bulk update methods.
69          *
70          * @since 3.0.0
71          * @var int $update_count
72          */
73         public $update_count = 0;
74
75         /**
76          * The current update if multiple updates are being performed.
77          *
78          * Used by the bulk update methods, and incremented for each update.
79          *
80          * @since 3.0.0
81          * @var int
82          */
83         public $update_current = 0;
84
85         /**
86          * Construct the upgrader with a skin.
87          *
88          * @since 2.8.0
89          *
90          * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a {@see WP_Upgrader_Skin}
91          *                               instance.
92          */
93         public function __construct( $skin = null ) {
94                 if ( null == $skin )
95                         $this->skin = new WP_Upgrader_Skin();
96                 else
97                         $this->skin = $skin;
98         }
99
100         /**
101          * Initialize the upgrader.
102          *
103          * This will set the relationship between the skin being used and this upgrader,
104          * and also add the generic strings to `WP_Upgrader::$strings`.
105          *
106          * @since 2.8.0
107          */
108         public function init() {
109                 $this->skin->set_upgrader($this);
110                 $this->generic_strings();
111         }
112
113         /**
114          * Add the generic strings to WP_Upgrader::$strings.
115          *
116          * @since 2.8.0
117          */
118         public function generic_strings() {
119                 $this->strings['bad_request'] = __('Invalid Data provided.');
120                 $this->strings['fs_unavailable'] = __('Could not access filesystem.');
121                 $this->strings['fs_error'] = __('Filesystem error.');
122                 $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress Root directory.');
123                 $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress Content directory (wp-content).');
124                 $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress Plugin directory.');
125                 $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress Theme directory.');
126                 /* translators: %s: directory name */
127                 $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
128
129                 $this->strings['download_failed'] = __('Download failed.');
130                 $this->strings['installing_package'] = __('Installing the latest version&#8230;');
131                 $this->strings['no_files'] = __('The package contains no files.');
132                 $this->strings['folder_exists'] = __('Destination folder already exists.');
133                 $this->strings['mkdir_failed'] = __('Could not create directory.');
134                 $this->strings['incompatible_archive'] = __('The package could not be installed.');
135
136                 $this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
137                 $this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
138         }
139
140         /**
141          * Connect to the filesystem.
142          *
143          * @since 2.8.0
144          *
145          * @param array $directories                  Optional. A list of directories. If any of these do
146          *                                            not exist, a {@see WP_Error} object will be returned.
147          *                                            Default empty array.
148          * @param bool  $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
149          *                                            Default false.
150          * @return bool|WP_Error True if able to connect, false or a {@see WP_Error} otherwise.
151          */
152         public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
153                 global $wp_filesystem;
154
155                 if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) {
156                         return false;
157                 }
158
159                 if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
160                         $error = true;
161                         if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
162                                 $error = $wp_filesystem->errors;
163                         // Failed to connect, Error and request again
164                         $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
165                         return false;
166                 }
167
168                 if ( ! is_object($wp_filesystem) )
169                         return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
170
171                 if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
172                         return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
173
174                 foreach ( (array)$directories as $dir ) {
175                         switch ( $dir ) {
176                                 case ABSPATH:
177                                         if ( ! $wp_filesystem->abspath() )
178                                                 return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
179                                         break;
180                                 case WP_CONTENT_DIR:
181                                         if ( ! $wp_filesystem->wp_content_dir() )
182                                                 return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
183                                         break;
184                                 case WP_PLUGIN_DIR:
185                                         if ( ! $wp_filesystem->wp_plugins_dir() )
186                                                 return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
187                                         break;
188                                 case get_theme_root():
189                                         if ( ! $wp_filesystem->wp_themes_dir() )
190                                                 return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
191                                         break;
192                                 default:
193                                         if ( ! $wp_filesystem->find_folder($dir) )
194                                                 return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
195                                         break;
196                         }
197                 }
198                 return true;
199         } //end fs_connect();
200
201         /**
202          * Download a package.
203          *
204          * @since 2.8.0
205          *
206          * @param string $package The URI of the package. If this is the full path to an
207          *                        existing local file, it will be returned untouched.
208          * @return string|WP_Error The full path to the downloaded package file, or a {@see WP_Error} object.
209          */
210         public function download_package( $package ) {
211
212                 /**
213                  * Filter whether to return the package.
214                  *
215                  * @since 3.7.0
216                  *
217                  * @param bool        $reply   Whether to bail without returning the package.
218                  *                             Default false.
219                  * @param string      $package The package file name.
220                  * @param WP_Upgrader $this    The WP_Upgrader instance.
221                  */
222                 $reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
223                 if ( false !== $reply )
224                         return $reply;
225
226                 if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
227                         return $package; //must be a local file..
228
229                 if ( empty($package) )
230                         return new WP_Error('no_package', $this->strings['no_package']);
231
232                 $this->skin->feedback('downloading_package', $package);
233
234                 $download_file = download_url($package);
235
236                 if ( is_wp_error($download_file) )
237                         return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
238
239                 return $download_file;
240         }
241
242         /**
243          * Unpack a compressed package file.
244          *
245          * @since 2.8.0
246          *
247          * @param string $package        Full path to the package file.
248          * @param bool   $delete_package Optional. Whether to delete the package file after attempting
249          *                               to unpack it. Default true.
250          * @return string|WP_Error The path to the unpacked contents, or a {@see WP_Error} on failure.
251          */
252         public function unpack_package( $package, $delete_package = true ) {
253                 global $wp_filesystem;
254
255                 $this->skin->feedback('unpack_package');
256
257                 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
258
259                 //Clean up contents of upgrade directory beforehand.
260                 $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
261                 if ( !empty($upgrade_files) ) {
262                         foreach ( $upgrade_files as $file )
263                                 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
264                 }
265
266                 // We need a working directory - Strip off any .tmp or .zip suffixes
267                 $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
268
269                 // Clean up working directory
270                 if ( $wp_filesystem->is_dir($working_dir) )
271                         $wp_filesystem->delete($working_dir, true);
272
273                 // Unzip package to working directory
274                 $result = unzip_file( $package, $working_dir );
275
276                 // Once extracted, delete the package if required.
277                 if ( $delete_package )
278                         unlink($package);
279
280                 if ( is_wp_error($result) ) {
281                         $wp_filesystem->delete($working_dir, true);
282                         if ( 'incompatible_archive' == $result->get_error_code() ) {
283                                 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
284                         }
285                         return $result;
286                 }
287
288                 return $working_dir;
289         }
290
291         /**
292          * Install a package.
293          *
294          * Copies the contents of a package form a source directory, and installs them in
295          * a destination directory. Optionally removes the source. It can also optionally
296          * clear out the destination folder if it already exists.
297          *
298          * @since 2.8.0
299          *
300          * @param array|string $args {
301          *     Optional. Array or string of arguments for installing a package. Default empty array.
302          *
303          *     @type string $source                      Required path to the package source. Default empty.
304          *     @type string $destination                 Required path to a folder to install the package in.
305          *                                               Default empty.
306          *     @type bool   $clear_destination           Whether to delete any files already in the destination
307          *                                               folder. Default false.
308          *     @type bool   $clear_working               Whether to delete the files form the working directory
309          *                                               after copying to the destination. Default false.
310          *     @type bool   $abort_if_destination_exists Whether to abort the installation if
311          *                                               the destination folder already exists. Default true.
312          *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
313          *                                               {@see WP_Upgrader::install_package()}. Default empty array.
314          * }
315          *
316          * @return array|WP_Error The result (also stored in `WP_Upgrader:$result`), or a {@see WP_Error} on failure.
317          */
318         public function install_package( $args = array() ) {
319                 global $wp_filesystem, $wp_theme_directories;
320
321                 $defaults = array(
322                         'source' => '', // Please always pass this
323                         'destination' => '', // and this
324                         'clear_destination' => false,
325                         'clear_working' => false,
326                         'abort_if_destination_exists' => true,
327                         'hook_extra' => array()
328                 );
329
330                 $args = wp_parse_args($args, $defaults);
331
332                 // These were previously extract()'d.
333                 $source = $args['source'];
334                 $destination = $args['destination'];
335                 $clear_destination = $args['clear_destination'];
336
337                 @set_time_limit( 300 );
338
339                 if ( empty( $source ) || empty( $destination ) ) {
340                         return new WP_Error( 'bad_request', $this->strings['bad_request'] );
341                 }
342                 $this->skin->feedback( 'installing_package' );
343
344                 /**
345                  * Filter the install response before the installation has started.
346                  *
347                  * Returning a truthy value, or one that could be evaluated as a WP_Error
348                  * will effectively short-circuit the installation, returning that value
349                  * instead.
350                  *
351                  * @since 2.8.0
352                  *
353                  * @param bool|WP_Error $response   Response.
354                  * @param array         $hook_extra Extra arguments passed to hooked filters.
355                  */
356                 $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
357                 if ( is_wp_error( $res ) ) {
358                         return $res;
359                 }
360
361                 //Retain the Original source and destinations
362                 $remote_source = $args['source'];
363                 $local_destination = $destination;
364
365                 $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) );
366                 $remote_destination = $wp_filesystem->find_folder( $local_destination );
367
368                 //Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
369                 if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents.
370                         $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
371                 } elseif ( count( $source_files ) == 0 ) {
372                         return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files?
373                 } else { //It's only a single file, the upgrader will use the foldername of this file as the destination folder. foldername is based on zip filename.
374                         $source = trailingslashit( $args['source'] );
375                 }
376
377                 /**
378                  * Filter the source file location for the upgrade package.
379                  *
380                  * @since 2.8.0
381                  *
382                  * @param string      $source        File source location.
383                  * @param string      $remote_source Remove file source location.
384                  * @param WP_Upgrader $this          WP_Upgrader instance.
385                  */
386                 $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this );
387                 if ( is_wp_error( $source ) ) {
388                         return $source;
389                 }
390
391                 // Has the source location changed? If so, we need a new source_files list.
392                 if ( $source !== $remote_source ) {
393                         $source_files = array_keys( $wp_filesystem->dirlist( $source ) );
394                 }
395                 /*
396                  * Protection against deleting files in any important base directories.
397                  * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
398                  * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
399                  * to copy the directory into the directory, whilst they pass the source
400                  * as the actual files to copy.
401                  */
402                 $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
403
404                 if ( is_array( $wp_theme_directories ) ) {
405                         $protected_directories = array_merge( $protected_directories, $wp_theme_directories );
406                 }
407                 if ( in_array( $destination, $protected_directories ) ) {
408                         $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
409                         $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
410                 }
411
412                 if ( $clear_destination ) {
413                         //We're going to clear the destination if there's something there
414                         $this->skin->feedback('remove_old');
415                         $removed = true;
416                         if ( $wp_filesystem->exists( $remote_destination ) ) {
417                                 $removed = $wp_filesystem->delete( $remote_destination, true );
418                         }
419
420                         /**
421                          * Filter whether the upgrader cleared the destination.
422                          *
423                          * @since 2.8.0
424                          *
425                          * @param bool   $removed            Whether the destination was cleared.
426                          * @param string $local_destination  The local package destination.
427                          * @param string $remote_destination The remote package destination.
428                          * @param array  $hook_extra         Extra arguments passed to hooked filters.
429                          */
430                         $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
431
432                         if ( is_wp_error($removed) ) {
433                                 return $removed;
434                         } elseif ( ! $removed ) {
435                                 return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
436                         }
437                 } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) {
438                         //If we're not clearing the destination folder and something exists there already, Bail.
439                         //But first check to see if there are actually any files in the folder.
440                         $_files = $wp_filesystem->dirlist($remote_destination);
441                         if ( ! empty($_files) ) {
442                                 $wp_filesystem->delete($remote_source, true); //Clear out the source files.
443                                 return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
444                         }
445                 }
446
447                 //Create destination if needed
448                 if ( ! $wp_filesystem->exists( $remote_destination ) ) {
449                         if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
450                                 return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
451                         }
452                 }
453                 // Copy new version of item into place.
454                 $result = copy_dir($source, $remote_destination);
455                 if ( is_wp_error($result) ) {
456                         if ( $args['clear_working'] ) {
457                                 $wp_filesystem->delete( $remote_source, true );
458                         }
459                         return $result;
460                 }
461
462                 //Clear the Working folder?
463                 if ( $args['clear_working'] ) {
464                         $wp_filesystem->delete( $remote_source, true );
465                 }
466
467                 $destination_name = basename( str_replace($local_destination, '', $destination) );
468                 if ( '.' == $destination_name ) {
469                         $destination_name = '';
470                 }
471
472                 $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
473
474                 /**
475                  * Filter the install response after the installation has finished.
476                  *
477                  * @since 2.8.0
478                  *
479                  * @param bool  $response   Install response.
480                  * @param array $hook_extra Extra arguments passed to hooked filters.
481                  * @param array $result     Installation result data.
482                  */
483                 $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
484
485                 if ( is_wp_error($res) ) {
486                         $this->result = $res;
487                         return $res;
488                 }
489
490                 //Bombard the calling function will all the info which we've just used.
491                 return $this->result;
492         }
493
494         /**
495          * Run an upgrade/install.
496          *
497          * Attempts to download the package (if it is not a local file), unpack it, and
498          * install it in the destination folder.
499          *
500          * @since 2.8.0
501          *
502          * @param array $options {
503          *     Array or string of arguments for upgrading/installing a package.
504          *
505          *     @type string $package                     The full path or URI of the package to install.
506          *                                               Default empty.
507          *     @type string $destination                 The full path to the destination folder.
508          *                                               Default empty.
509          *     @type bool   $clear_destination           Whether to delete any files already in the
510          *                                               destination folder. Default false.
511          *     @type bool   $clear_working               Whether to delete the files form the working
512          *                                               directory after copying to the destination.
513          *                                               Default false.
514          *     @type bool   $abort_if_destination_exists Whether to abort the installation if the destination
515          *                                               folder already exists. When true, `$clear_destination`
516          *                                               should be false. Default true.
517          *     @type bool   $is_multi                    Whether this run is one of multiple upgrade/install
518          *                                               actions being performed in bulk. When true, the skin
519          *                                               {@see WP_Upgrader::header()} and {@see WP_Upgrader::footer()}
520          *                                               aren't called. Default false.
521          *     @type array  $hook_extra                  Extra arguments to pass to the filter hooks called by
522          *                                               {@see WP_Upgrader::run()}.
523          * }
524          *
525          * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error,
526          *                              or false if unable to connect to the filesystem.
527          */
528         public function run( $options ) {
529
530                 $defaults = array(
531                         'package' => '', // Please always pass this.
532                         'destination' => '', // And this
533                         'clear_destination' => false,
534                         'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
535                         'clear_working' => true,
536                         'is_multi' => false,
537                         'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
538                 );
539
540                 $options = wp_parse_args( $options, $defaults );
541
542                 if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times
543                         $this->skin->header();
544                 }
545
546                 // Connect to the Filesystem first.
547                 $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
548                 // Mainly for non-connected filesystem.
549                 if ( ! $res ) {
550                         if ( ! $options['is_multi'] ) {
551                                 $this->skin->footer();
552                         }
553                         return false;
554                 }
555
556                 $this->skin->before();
557
558                 if ( is_wp_error($res) ) {
559                         $this->skin->error($res);
560                         $this->skin->after();
561                         if ( ! $options['is_multi'] ) {
562                                 $this->skin->footer();
563                         }
564                         return $res;
565                 }
566
567                 //Download the package (Note, This just returns the filename of the file if the package is a local file)
568                 $download = $this->download_package( $options['package'] );
569                 if ( is_wp_error($download) ) {
570                         $this->skin->error($download);
571                         $this->skin->after();
572                         if ( ! $options['is_multi'] ) {
573                                 $this->skin->footer();
574                         }
575                         return $download;
576                 }
577
578                 $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file
579
580                 //Unzips the file into a temporary directory
581                 $working_dir = $this->unpack_package( $download, $delete_package );
582                 if ( is_wp_error($working_dir) ) {
583                         $this->skin->error($working_dir);
584                         $this->skin->after();
585                         if ( ! $options['is_multi'] ) {
586                                 $this->skin->footer();
587                         }
588                         return $working_dir;
589                 }
590
591                 //With the given options, this installs it to the destination directory.
592                 $result = $this->install_package( array(
593                         'source' => $working_dir,
594                         'destination' => $options['destination'],
595                         'clear_destination' => $options['clear_destination'],
596                         'abort_if_destination_exists' => $options['abort_if_destination_exists'],
597                         'clear_working' => $options['clear_working'],
598                         'hook_extra' => $options['hook_extra']
599                 ) );
600
601                 $this->skin->set_result($result);
602                 if ( is_wp_error($result) ) {
603                         $this->skin->error($result);
604                         $this->skin->feedback('process_failed');
605                 } else {
606                         //Install Succeeded
607                         $this->skin->feedback('process_success');
608                 }
609
610                 $this->skin->after();
611
612                 if ( ! $options['is_multi'] ) {
613
614                         /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
615                         do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
616                         $this->skin->footer();
617                 }
618
619                 return $result;
620         }
621
622         /**
623          * Toggle maintenance mode for the site.
624          *
625          * Creates/deletes the maintenance file to enable/disable maintenance mode.
626          *
627          * @since 2.8.0
628          *
629          * @param bool $enable True to enable maintenance mode, false to disable.
630          */
631         public function maintenance_mode( $enable = false ) {
632                 global $wp_filesystem;
633                 $file = $wp_filesystem->abspath() . '.maintenance';
634                 if ( $enable ) {
635                         $this->skin->feedback('maintenance_start');
636                         // Create maintenance file to signal that we are upgrading
637                         $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
638                         $wp_filesystem->delete($file);
639                         $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
640                 } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) {
641                         $this->skin->feedback('maintenance_end');
642                         $wp_filesystem->delete($file);
643                 }
644         }
645
646 }
647
648 /**
649  * Plugin Upgrader class for WordPress Plugins, It is designed to upgrade/install plugins from a local zip, remote zip URL, or uploaded zip file.
650  *
651  * @package WordPress
652  * @subpackage Upgrader
653  * @since 2.8.0
654  */
655 class Plugin_Upgrader extends WP_Upgrader {
656
657         /**
658          * Plugin upgrade result.
659          *
660          * @since 2.8.0
661          * @var array|WP_Error $result
662          * @see WP_Upgrader::$result
663          */
664         public $result;
665
666         /**
667          * Whether a bulk upgrade/install is being performed.
668          *
669          * @since 2.9.0
670          * @var bool $bulk
671          */
672         public $bulk = false;
673
674         /**
675          * Initialize the upgrade strings.
676          *
677          * @since 2.8.0
678          */
679         public function upgrade_strings() {
680                 $this->strings['up_to_date'] = __('The plugin is at the latest version.');
681                 $this->strings['no_package'] = __('Update package not available.');
682                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
683                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
684                 $this->strings['remove_old'] = __('Removing the old version of the plugin&#8230;');
685                 $this->strings['remove_old_failed'] = __('Could not remove the old plugin.');
686                 $this->strings['process_failed'] = __('Plugin update failed.');
687                 $this->strings['process_success'] = __('Plugin updated successfully.');
688                 $this->strings['process_bulk_success'] = __('Plugins updated successfully.');
689         }
690
691         /**
692          * Initialize the install strings.
693          *
694          * @since 2.8.0
695          */
696         public function install_strings() {
697                 $this->strings['no_package'] = __('Install package not available.');
698                 $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
699                 $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
700                 $this->strings['installing_package'] = __('Installing the plugin&#8230;');
701                 $this->strings['no_files'] = __('The plugin contains no files.');
702                 $this->strings['process_failed'] = __('Plugin install failed.');
703                 $this->strings['process_success'] = __('Plugin installed successfully.');
704         }
705
706         /**
707          * Install a plugin package.
708          *
709          * @since 2.8.0
710          * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
711          *
712          * @param string $package The full local path or URI of the package.
713          * @param array  $args {
714          *     Optional. Other arguments for installing a plugin package. Default empty array.
715          *
716          *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
717          *                                    Default true.
718          * }
719          *
720          * @return bool|WP_Error True if the install was successful, false or a WP_Error otherwise.
721          */
722         public function install( $package, $args = array() ) {
723
724                 $defaults = array(
725                         'clear_update_cache' => true,
726                 );
727                 $parsed_args = wp_parse_args( $args, $defaults );
728
729                 $this->init();
730                 $this->install_strings();
731
732                 add_filter('upgrader_source_selection', array($this, 'check_package') );
733
734                 $this->run( array(
735                         'package' => $package,
736                         'destination' => WP_PLUGIN_DIR,
737                         'clear_destination' => false, // Do not overwrite files.
738                         'clear_working' => true,
739                         'hook_extra' => array(
740                                 'type' => 'plugin',
741                                 'action' => 'install',
742                         )
743                 ) );
744
745                 remove_filter('upgrader_source_selection', array($this, 'check_package') );
746
747                 if ( ! $this->result || is_wp_error($this->result) )
748                         return $this->result;
749
750                 // Force refresh of plugin update information
751                 wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
752
753                 return true;
754         }
755
756         /**
757          * Upgrade a plugin.
758          *
759          * @since 2.8.0
760          * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
761          *
762          * @param string $plugin The basename path to the main plugin file.
763          * @param array  $args {
764          *     Optional. Other arguments for upgrading a plugin package. Defualt empty array.
765          *
766          *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
767          *                                    Default true.
768          * }
769          * @return bool|WP_Error True if the upgrade was successful, false or a {@see WP_Error} object otherwise.
770          */
771         public function upgrade( $plugin, $args = array() ) {
772
773                 $defaults = array(
774                         'clear_update_cache' => true,
775                 );
776                 $parsed_args = wp_parse_args( $args, $defaults );
777
778                 $this->init();
779                 $this->upgrade_strings();
780
781                 $current = get_site_transient( 'update_plugins' );
782                 if ( !isset( $current->response[ $plugin ] ) ) {
783                         $this->skin->before();
784                         $this->skin->set_result(false);
785                         $this->skin->error('up_to_date');
786                         $this->skin->after();
787                         return false;
788                 }
789
790                 // Get the URL to the zip file
791                 $r = $current->response[ $plugin ];
792
793                 add_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'), 10, 2);
794                 add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
795                 //'source_selection' => array($this, 'source_selection'), //there's a trac ticket to move up the directory for zip's which are made a bit differently, useful for non-.org plugins.
796
797                 $this->run( array(
798                         'package' => $r->package,
799                         'destination' => WP_PLUGIN_DIR,
800                         'clear_destination' => true,
801                         'clear_working' => true,
802                         'hook_extra' => array(
803                                 'plugin' => $plugin,
804                                 'type' => 'plugin',
805                                 'action' => 'update',
806                         ),
807                 ) );
808
809                 // Cleanup our hooks, in case something else does a upgrade on this connection.
810                 remove_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'));
811                 remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
812
813                 if ( ! $this->result || is_wp_error($this->result) )
814                         return $this->result;
815
816                 // Force refresh of plugin update information
817                 wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
818
819                 return true;
820         }
821
822         /**
823          * Bulk upgrade several plugins at once.
824          *
825          * @since 2.8.0
826          * @since 3.7.0 The `$args` parameter was added, making clearing the plugin update cache optional.
827          *
828          * @param array $plugins Array of the basename paths of the plugins' main files.
829          * @param array $args {
830          *     Optional. Other arguments for upgrading several plugins at once. Default empty array.
831          *
832          *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
833          *                                    Default true.
834          * }
835          *
836          * @return array|false An array of results indexed by plugin file, or false if unable to connect to the filesystem.
837          */
838         public function bulk_upgrade( $plugins, $args = array() ) {
839
840                 $defaults = array(
841                         'clear_update_cache' => true,
842                 );
843                 $parsed_args = wp_parse_args( $args, $defaults );
844
845                 $this->init();
846                 $this->bulk = true;
847                 $this->upgrade_strings();
848
849                 $current = get_site_transient( 'update_plugins' );
850
851                 add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
852
853                 $this->skin->header();
854
855                 // Connect to the Filesystem first.
856                 $res = $this->fs_connect( array(WP_CONTENT_DIR, WP_PLUGIN_DIR) );
857                 if ( ! $res ) {
858                         $this->skin->footer();
859                         return false;
860                 }
861
862                 $this->skin->bulk_header();
863
864                 // Only start maintenance mode if:
865                 // - running Multisite and there are one or more plugins specified, OR
866                 // - a plugin with an update available is currently active.
867                 // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
868                 $maintenance = ( is_multisite() && ! empty( $plugins ) );
869                 foreach ( $plugins as $plugin )
870                         $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
871                 if ( $maintenance )
872                         $this->maintenance_mode(true);
873
874                 $results = array();
875
876                 $this->update_count = count($plugins);
877                 $this->update_current = 0;
878                 foreach ( $plugins as $plugin ) {
879                         $this->update_current++;
880                         $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
881
882                         if ( !isset( $current->response[ $plugin ] ) ) {
883                                 $this->skin->set_result('up_to_date');
884                                 $this->skin->before();
885                                 $this->skin->feedback('up_to_date');
886                                 $this->skin->after();
887                                 $results[$plugin] = true;
888                                 continue;
889                         }
890
891                         // Get the URL to the zip file
892                         $r = $current->response[ $plugin ];
893
894                         $this->skin->plugin_active = is_plugin_active($plugin);
895
896                         $result = $this->run( array(
897                                 'package' => $r->package,
898                                 'destination' => WP_PLUGIN_DIR,
899                                 'clear_destination' => true,
900                                 'clear_working' => true,
901                                 'is_multi' => true,
902                                 'hook_extra' => array(
903                                         'plugin' => $plugin
904                                 )
905                         ) );
906
907                         $results[$plugin] = $this->result;
908
909                         // Prevent credentials auth screen from displaying multiple times
910                         if ( false === $result )
911                                 break;
912                 } //end foreach $plugins
913
914                 $this->maintenance_mode(false);
915
916                 /**
917                  * Fires when the bulk upgrader process is complete.
918                  *
919                  * @since 3.6.0
920                  *
921                  * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
922                  *                              be a Theme_Upgrader or Core_Upgrade instance.
923                  * @param array           $data {
924                  *     Array of bulk item update data.
925                  *
926                  *     @type string $action   Type of action. Default 'update'.
927                  *     @type string $type     Type of update process. Accepts 'plugin', 'theme', or 'core'.
928                  *     @type bool   $bulk     Whether the update process is a bulk update. Default true.
929                  *     @type array  $packages Array of plugin, theme, or core packages to update.
930                  * }
931                  */
932                 do_action( 'upgrader_process_complete', $this, array(
933                         'action' => 'update',
934                         'type' => 'plugin',
935                         'bulk' => true,
936                         'plugins' => $plugins,
937                 ) );
938
939                 $this->skin->bulk_footer();
940
941                 $this->skin->footer();
942
943                 // Cleanup our hooks, in case something else does a upgrade on this connection.
944                 remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'));
945
946                 // Force refresh of plugin update information
947                 wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
948
949                 return $results;
950         }
951
952         /**
953          * Check a source package to be sure it contains a plugin.
954          *
955          * This function is added to the {@see 'upgrader_source_selection'} filter by
956          * {@see Plugin_Upgrader::install()}.
957          *
958          * @since 3.3.0
959          *
960          * @param string $source The path to the downloaded package source.
961          * @return string|WP_Error The source as passed, or a {@see WP_Error} object if no plugins were found.
962          */
963         public function check_package($source) {
964                 global $wp_filesystem;
965
966                 if ( is_wp_error($source) )
967                         return $source;
968
969                 $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
970                 if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
971                         return $source;
972
973                 // Check the folder contains at least 1 valid plugin.
974                 $plugins_found = false;
975                 $files = glob( $working_directory . '*.php' );
976                 if ( $files ) {
977                         foreach ( $files as $file ) {
978                                 $info = get_plugin_data( $file, false, false );
979                                 if ( ! empty( $info['Name'] ) ) {
980                                         $plugins_found = true;
981                                         break;
982                                 }
983                         }
984                 }
985
986                 if ( ! $plugins_found )
987                         return new WP_Error( 'incompatible_archive_no_plugins', $this->strings['incompatible_archive'], __( 'No valid plugins were found.' ) );
988
989                 return $source;
990         }
991
992         /**
993          * Retrieve the path to the file that contains the plugin info.
994          *
995          * This isn't used internally in the class, but is called by the skins.
996          *
997          * @since 2.8.0
998          *
999          * @return string|false The full path to the main plugin file, or false.
1000          */
1001         public function plugin_info() {
1002                 if ( ! is_array($this->result) )
1003                         return false;
1004                 if ( empty($this->result['destination_name']) )
1005                         return false;
1006
1007                 $plugin = get_plugins('/' . $this->result['destination_name']); //Ensure to pass with leading slash
1008                 if ( empty($plugin) )
1009                         return false;
1010
1011                 $pluginfiles = array_keys($plugin); //Assume the requested plugin is the first in the list
1012
1013                 return $this->result['destination_name'] . '/' . $pluginfiles[0];
1014         }
1015
1016         /**
1017          * Deactivates a plugin before it is upgraded.
1018          *
1019          * Hooked to the {@see 'upgrader_pre_install'} filter by {@see Plugin_Upgrader::upgrade()}.
1020          *
1021          * @since 2.8.0
1022          * @since 4.1.0 Added a return value.
1023          *
1024          * @param bool|WP_Error  $return Upgrade offer return.
1025          * @param array          $plugin Plugin package arguments.
1026          * @return bool|WP_Error The passed in $return param or {@see WP_Error}.
1027          */
1028         public function deactivate_plugin_before_upgrade($return, $plugin) {
1029
1030                 if ( is_wp_error($return) ) //Bypass.
1031                         return $return;
1032
1033                 // When in cron (background updates) don't deactivate the plugin, as we require a browser to reactivate it
1034                 if ( defined( 'DOING_CRON' ) && DOING_CRON )
1035                         return $return;
1036
1037                 $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
1038                 if ( empty($plugin) )
1039                         return new WP_Error('bad_request', $this->strings['bad_request']);
1040
1041                 if ( is_plugin_active($plugin) ) {
1042                         //Deactivate the plugin silently, Prevent deactivation hooks from running.
1043                         deactivate_plugins($plugin, true);
1044                 }
1045
1046                 return $return;
1047         }
1048
1049         /**
1050          * Delete the old plugin during an upgrade.
1051          *
1052          * Hooked to the {@see 'upgrader_clear_destination'} filter by
1053          * {@see Plugin_Upgrader::upgrade()} and {@see Plugin_Upgrader::bulk_upgrade()}.
1054          *
1055          * @since 2.8.0
1056          */
1057         public function delete_old_plugin($removed, $local_destination, $remote_destination, $plugin) {
1058                 global $wp_filesystem;
1059
1060                 if ( is_wp_error($removed) )
1061                         return $removed; //Pass errors through.
1062
1063                 $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
1064                 if ( empty($plugin) )
1065                         return new WP_Error('bad_request', $this->strings['bad_request']);
1066
1067                 $plugins_dir = $wp_filesystem->wp_plugins_dir();
1068                 $this_plugin_dir = trailingslashit( dirname($plugins_dir . $plugin) );
1069
1070                 if ( ! $wp_filesystem->exists($this_plugin_dir) ) //If it's already vanished.
1071                         return $removed;
1072
1073                 // If plugin is in its own directory, recursively delete the directory.
1074                 if ( strpos($plugin, '/') && $this_plugin_dir != $plugins_dir ) //base check on if plugin includes directory separator AND that it's not the root plugin folder
1075                         $deleted = $wp_filesystem->delete($this_plugin_dir, true);
1076                 else
1077                         $deleted = $wp_filesystem->delete($plugins_dir . $plugin);
1078
1079                 if ( ! $deleted )
1080                         return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
1081
1082                 return true;
1083         }
1084 }
1085
1086 /**
1087  * Theme Upgrader class for WordPress Themes, It is designed to upgrade/install themes from a local zip, remote zip URL, or uploaded zip file.
1088  *
1089  * @package WordPress
1090  * @subpackage Upgrader
1091  * @since 2.8.0
1092  */
1093 class Theme_Upgrader extends WP_Upgrader {
1094
1095         /**
1096          * Result of the theme upgrade offer.
1097          *
1098          * @since 2.8.0
1099          * @var array|WP_Erorr $result
1100          * @see WP_Upgrader::$result
1101          */
1102         public $result;
1103
1104         /**
1105          * Whether multiple plugins are being upgraded/installed in bulk.
1106          *
1107          * @since 2.9.0
1108          * @var bool $bulk
1109          */
1110         public $bulk = false;
1111
1112         /**
1113          * Initialize the upgrade strings.
1114          *
1115          * @since 2.8.0
1116          */
1117         public function upgrade_strings() {
1118                 $this->strings['up_to_date'] = __('The theme is at the latest version.');
1119                 $this->strings['no_package'] = __('Update package not available.');
1120                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
1121                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
1122                 $this->strings['remove_old'] = __('Removing the old version of the theme&#8230;');
1123                 $this->strings['remove_old_failed'] = __('Could not remove the old theme.');
1124                 $this->strings['process_failed'] = __('Theme update failed.');
1125                 $this->strings['process_success'] = __('Theme updated successfully.');
1126         }
1127
1128         /**
1129          * Initialize the install strings.
1130          *
1131          * @since 2.8.0
1132          */
1133         public function install_strings() {
1134                 $this->strings['no_package'] = __('Install package not available.');
1135                 $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
1136                 $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
1137                 $this->strings['installing_package'] = __('Installing the theme&#8230;');
1138                 $this->strings['no_files'] = __('The theme contains no files.');
1139                 $this->strings['process_failed'] = __('Theme install failed.');
1140                 $this->strings['process_success'] = __('Theme installed successfully.');
1141                 /* translators: 1: theme name, 2: version */
1142                 $this->strings['process_success_specific'] = __('Successfully installed the theme <strong>%1$s %2$s</strong>.');
1143                 $this->strings['parent_theme_search'] = __('This theme requires a parent theme. Checking if it is installed&#8230;');
1144                 /* translators: 1: theme name, 2: version */
1145                 $this->strings['parent_theme_prepare_install'] = __('Preparing to install <strong>%1$s %2$s</strong>&#8230;');
1146                 /* translators: 1: theme name, 2: version */
1147                 $this->strings['parent_theme_currently_installed'] = __('The parent theme, <strong>%1$s %2$s</strong>, is currently installed.');
1148                 /* translators: 1: theme name, 2: version */
1149                 $this->strings['parent_theme_install_success'] = __('Successfully installed the parent theme, <strong>%1$s %2$s</strong>.');
1150                 $this->strings['parent_theme_not_found'] = __('<strong>The parent theme could not be found.</strong> You will need to install the parent theme, <strong>%s</strong>, before you can use this child theme.');
1151         }
1152
1153         /**
1154          * Check if a child theme is being installed and we need to install its parent.
1155          *
1156          * Hooked to the {@see 'upgrader_post_install'} filter by {@see Theme_Upgrader::install()}.
1157          *
1158          * @since 3.4.0
1159          */
1160         public function check_parent_theme_filter( $install_result, $hook_extra, $child_result ) {
1161                 // Check to see if we need to install a parent theme
1162                 $theme_info = $this->theme_info();
1163
1164                 if ( ! $theme_info->parent() )
1165                         return $install_result;
1166
1167                 $this->skin->feedback( 'parent_theme_search' );
1168
1169                 if ( ! $theme_info->parent()->errors() ) {
1170                         $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display('Name'), $theme_info->parent()->display('Version') );
1171                         // We already have the theme, fall through.
1172                         return $install_result;
1173                 }
1174
1175                 // We don't have the parent theme, let's install it.
1176                 $api = themes_api('theme_information', array('slug' => $theme_info->get('Template'), 'fields' => array('sections' => false, 'tags' => false) ) ); //Save on a bit of bandwidth.
1177
1178                 if ( ! $api || is_wp_error($api) ) {
1179                         $this->skin->feedback( 'parent_theme_not_found', $theme_info->get('Template') );
1180                         // Don't show activate or preview actions after install
1181                         add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
1182                         return $install_result;
1183                 }
1184
1185                 // Backup required data we're going to override:
1186                 $child_api = $this->skin->api;
1187                 $child_success_message = $this->strings['process_success'];
1188
1189                 // Override them
1190                 $this->skin->api = $api;
1191                 $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version);
1192
1193                 $this->skin->feedback('parent_theme_prepare_install', $api->name, $api->version);
1194
1195                 add_filter('install_theme_complete_actions', '__return_false', 999); // Don't show any actions after installing the theme.
1196
1197                 // Install the parent theme
1198                 $parent_result = $this->run( array(
1199                         'package' => $api->download_link,
1200                         'destination' => get_theme_root(),
1201                         'clear_destination' => false, //Do not overwrite files.
1202                         'clear_working' => true
1203                 ) );
1204
1205                 if ( is_wp_error($parent_result) )
1206                         add_filter('install_theme_complete_actions', array($this, 'hide_activate_preview_actions') );
1207
1208                 // Start cleaning up after the parents installation
1209                 remove_filter('install_theme_complete_actions', '__return_false', 999);
1210
1211                 // Reset child's result and data
1212                 $this->result = $child_result;
1213                 $this->skin->api = $child_api;
1214                 $this->strings['process_success'] = $child_success_message;
1215
1216                 return $install_result;
1217         }
1218
1219         /**
1220          * Don't display the activate and preview actions to the user.
1221          *
1222          * Hooked to the {@see 'install_theme_complete_actions'} filter by
1223          * {@see Theme_Upgrader::check_parent_theme_filter()} when installing
1224          * a child theme and installing the parent theme fails.
1225          *
1226          * @since 3.4.0
1227          *
1228          * @param array $actions Preview actions.
1229          */
1230         public function hide_activate_preview_actions( $actions ) {
1231                 unset($actions['activate'], $actions['preview']);
1232                 return $actions;
1233         }
1234
1235         /**
1236          * Install a theme package.
1237          *
1238          * @since 2.8.0
1239          * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
1240          *
1241          * @param string $package The full local path or URI of the package.
1242          * @param array  $args {
1243          *     Optional. Other arguments for installing a theme package. Default empty array.
1244          *
1245          *     @type bool $clear_update_cache Whether to clear the updates cache if successful.
1246          *                                    Default true.
1247          * }
1248          *
1249          * @return bool|WP_Error True if the install was successful, false or a {@see WP_Error} object otherwise.
1250          */
1251         public function install( $package, $args = array() ) {
1252
1253                 $defaults = array(
1254                         'clear_update_cache' => true,
1255                 );
1256                 $parsed_args = wp_parse_args( $args, $defaults );
1257
1258                 $this->init();
1259                 $this->install_strings();
1260
1261                 add_filter('upgrader_source_selection', array($this, 'check_package') );
1262                 add_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'), 10, 3);
1263
1264                 $this->run( array(
1265                         'package' => $package,
1266                         'destination' => get_theme_root(),
1267                         'clear_destination' => false, //Do not overwrite files.
1268                         'clear_working' => true,
1269                         'hook_extra' => array(
1270                                 'type' => 'theme',
1271                                 'action' => 'install',
1272                         ),
1273                 ) );
1274
1275                 remove_filter('upgrader_source_selection', array($this, 'check_package') );
1276                 remove_filter('upgrader_post_install', array($this, 'check_parent_theme_filter'));
1277
1278                 if ( ! $this->result || is_wp_error($this->result) )
1279                         return $this->result;
1280
1281                 // Refresh the Theme Update information
1282                 wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
1283
1284                 return true;
1285         }
1286
1287         /**
1288          * Upgrade a theme.
1289          *
1290          * @since 2.8.0
1291          * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
1292          *
1293          * @param string $theme The theme slug.
1294          * @param array  $args {
1295          *     Optional. Other arguments for upgrading a theme. Default empty array.
1296          *
1297          *     @type bool $clear_update_cache Whether to clear the update cache if successful.
1298          *                                    Default true.
1299          * }
1300          * @return bool|WP_Error True if the upgrade was successful, false or a {@see WP_Error} object otherwise.
1301          */
1302         public function upgrade( $theme, $args = array() ) {
1303
1304                 $defaults = array(
1305                         'clear_update_cache' => true,
1306                 );
1307                 $parsed_args = wp_parse_args( $args, $defaults );
1308
1309                 $this->init();
1310                 $this->upgrade_strings();
1311
1312                 // Is an update available?
1313                 $current = get_site_transient( 'update_themes' );
1314                 if ( !isset( $current->response[ $theme ] ) ) {
1315                         $this->skin->before();
1316                         $this->skin->set_result(false);
1317                         $this->skin->error( 'up_to_date' );
1318                         $this->skin->after();
1319                         return false;
1320                 }
1321
1322                 $r = $current->response[ $theme ];
1323
1324                 add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
1325                 add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
1326                 add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
1327
1328                 $this->run( array(
1329                         'package' => $r['package'],
1330                         'destination' => get_theme_root( $theme ),
1331                         'clear_destination' => true,
1332                         'clear_working' => true,
1333                         'hook_extra' => array(
1334                                 'theme' => $theme,
1335                                 'type' => 'theme',
1336                                 'action' => 'update',
1337                         ),
1338                 ) );
1339
1340                 remove_filter('upgrader_pre_install', array($this, 'current_before'));
1341                 remove_filter('upgrader_post_install', array($this, 'current_after'));
1342                 remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
1343
1344                 if ( ! $this->result || is_wp_error($this->result) )
1345                         return $this->result;
1346
1347                 wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
1348
1349                 return true;
1350         }
1351
1352         /**
1353          * Upgrade several themes at once.
1354          *
1355          * @since 3.0.0
1356          * @since 3.7.0 The `$args` parameter was added, making clearing the update cache optional.
1357          *
1358          * @param array $themes The theme slugs.
1359          * @param array $args {
1360          *     Optional. Other arguments for upgrading several themes at once. Default empty array.
1361          *
1362          *     @type bool $clear_update_cache Whether to clear the update cache if successful.
1363          *                                    Default true.
1364          * }
1365          * @return array[]|false An array of results, or false if unable to connect to the filesystem.
1366          */
1367         public function bulk_upgrade( $themes, $args = array() ) {
1368
1369                 $defaults = array(
1370                         'clear_update_cache' => true,
1371                 );
1372                 $parsed_args = wp_parse_args( $args, $defaults );
1373
1374                 $this->init();
1375                 $this->bulk = true;
1376                 $this->upgrade_strings();
1377
1378                 $current = get_site_transient( 'update_themes' );
1379
1380                 add_filter('upgrader_pre_install', array($this, 'current_before'), 10, 2);
1381                 add_filter('upgrader_post_install', array($this, 'current_after'), 10, 2);
1382                 add_filter('upgrader_clear_destination', array($this, 'delete_old_theme'), 10, 4);
1383
1384                 $this->skin->header();
1385
1386                 // Connect to the Filesystem first.
1387                 $res = $this->fs_connect( array(WP_CONTENT_DIR) );
1388                 if ( ! $res ) {
1389                         $this->skin->footer();
1390                         return false;
1391                 }
1392
1393                 $this->skin->bulk_header();
1394
1395                 // Only start maintenance mode if:
1396                 // - running Multisite and there are one or more themes specified, OR
1397                 // - a theme with an update available is currently in use.
1398                 // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
1399                 $maintenance = ( is_multisite() && ! empty( $themes ) );
1400                 foreach ( $themes as $theme )
1401                         $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
1402                 if ( $maintenance )
1403                         $this->maintenance_mode(true);
1404
1405                 $results = array();
1406
1407                 $this->update_count = count($themes);
1408                 $this->update_current = 0;
1409                 foreach ( $themes as $theme ) {
1410                         $this->update_current++;
1411
1412                         $this->skin->theme_info = $this->theme_info($theme);
1413
1414                         if ( !isset( $current->response[ $theme ] ) ) {
1415                                 $this->skin->set_result(true);
1416                                 $this->skin->before();
1417                                 $this->skin->feedback( 'up_to_date' );
1418                                 $this->skin->after();
1419                                 $results[$theme] = true;
1420                                 continue;
1421                         }
1422
1423                         // Get the URL to the zip file
1424                         $r = $current->response[ $theme ];
1425
1426                         $result = $this->run( array(
1427                                 'package' => $r['package'],
1428                                 'destination' => get_theme_root( $theme ),
1429                                 'clear_destination' => true,
1430                                 'clear_working' => true,
1431                                 'is_multi' => true,
1432                                 'hook_extra' => array(
1433                                         'theme' => $theme
1434                                 ),
1435                         ) );
1436
1437                         $results[$theme] = $this->result;
1438
1439                         // Prevent credentials auth screen from displaying multiple times
1440                         if ( false === $result )
1441                                 break;
1442                 } //end foreach $plugins
1443
1444                 $this->maintenance_mode(false);
1445
1446                 /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
1447                 do_action( 'upgrader_process_complete', $this, array(
1448                         'action' => 'update',
1449                         'type' => 'theme',
1450                         'bulk' => true,
1451                         'themes' => $themes,
1452                 ) );
1453
1454                 $this->skin->bulk_footer();
1455
1456                 $this->skin->footer();
1457
1458                 // Cleanup our hooks, in case something else does a upgrade on this connection.
1459                 remove_filter('upgrader_pre_install', array($this, 'current_before'));
1460                 remove_filter('upgrader_post_install', array($this, 'current_after'));
1461                 remove_filter('upgrader_clear_destination', array($this, 'delete_old_theme'));
1462
1463                 // Refresh the Theme Update information
1464                 wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
1465
1466                 return $results;
1467         }
1468
1469         /**
1470          * Check that the package source contains a valid theme.
1471          *
1472          * Hooked to the {@see 'upgrader_source_selection'} filter by {@see Theme_Upgrader::install()}.
1473          * It will return an error if the theme doesn't have style.css or index.php
1474          * files.
1475          *
1476          * @since 3.3.0
1477          *
1478          * @param string $source The full path to the package source.
1479          * @return string|WP_Error The source or a WP_Error.
1480          */
1481         public function check_package( $source ) {
1482                 global $wp_filesystem;
1483
1484                 if ( is_wp_error($source) )
1485                         return $source;
1486
1487                 // Check the folder contains a valid theme
1488                 $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
1489                 if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, let's not prevent installation.
1490                         return $source;
1491
1492                 // A proper archive should have a style.css file in the single subdirectory
1493                 if ( ! file_exists( $working_directory . 'style.css' ) )
1494                         return new WP_Error( 'incompatible_archive_theme_no_style', $this->strings['incompatible_archive'], __( 'The theme is missing the <code>style.css</code> stylesheet.' ) );
1495
1496                 $info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
1497
1498                 if ( empty( $info['Name'] ) )
1499                         return new WP_Error( 'incompatible_archive_theme_no_name', $this->strings['incompatible_archive'], __( "The <code>style.css</code> stylesheet doesn't contain a valid theme header." ) );
1500
1501                 // If it's not a child theme, it must have at least an index.php to be legit.
1502                 if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) )
1503                         return new WP_Error( 'incompatible_archive_theme_no_index', $this->strings['incompatible_archive'], __( 'The theme is missing the <code>index.php</code> file.' ) );
1504
1505                 return $source;
1506         }
1507
1508         /**
1509          * Turn on maintenance mode before attempting to upgrade the current theme.
1510          *
1511          * Hooked to the {@see 'upgrader_pre_install'} filter by {@see Theme_Upgrader::upgrade()} and
1512          * {@see Theme_Upgrader::bulk_upgrade()}.
1513          *
1514          * @since 2.8.0
1515          */
1516         public function current_before($return, $theme) {
1517
1518                 if ( is_wp_error($return) )
1519                         return $return;
1520
1521                 $theme = isset($theme['theme']) ? $theme['theme'] : '';
1522
1523                 if ( $theme != get_stylesheet() ) //If not current
1524                         return $return;
1525                 //Change to maintenance mode now.
1526                 if ( ! $this->bulk )
1527                         $this->maintenance_mode(true);
1528
1529                 return $return;
1530         }
1531
1532         /**
1533          * Turn off maintenance mode after upgrading the current theme.
1534          *
1535          * Hooked to the {@see 'upgrader_post_install'} filter by {@see Theme_Upgrader::upgrade()}
1536          * and {@see Theme_Upgrader::bulk_upgrade()}.
1537          *
1538          * @since 2.8.0
1539          */
1540         public function current_after($return, $theme) {
1541                 if ( is_wp_error($return) )
1542                         return $return;
1543
1544                 $theme = isset($theme['theme']) ? $theme['theme'] : '';
1545
1546                 if ( $theme != get_stylesheet() ) // If not current
1547                         return $return;
1548
1549                 // Ensure stylesheet name hasn't changed after the upgrade:
1550                 if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) {
1551                         wp_clean_themes_cache();
1552                         $stylesheet = $this->result['destination_name'];
1553                         switch_theme( $stylesheet );
1554                 }
1555
1556                 //Time to remove maintenance mode
1557                 if ( ! $this->bulk )
1558                         $this->maintenance_mode(false);
1559                 return $return;
1560         }
1561
1562         /**
1563          * Delete the old theme during an upgrade.
1564          *
1565          * Hooked to the {@see 'upgrader_clear_destination'} filter by {@see Theme_Upgrader::upgrade()}
1566          * and {@see Theme_Upgrader::bulk_upgrade()}.
1567          *
1568          * @since 2.8.0
1569          */
1570         public function delete_old_theme( $removed, $local_destination, $remote_destination, $theme ) {
1571                 global $wp_filesystem;
1572
1573                 if ( is_wp_error( $removed ) )
1574                         return $removed; // Pass errors through.
1575
1576                 if ( ! isset( $theme['theme'] ) )
1577                         return $removed;
1578
1579                 $theme = $theme['theme'];
1580                 $themes_dir = trailingslashit( $wp_filesystem->wp_themes_dir( $theme ) );
1581                 if ( $wp_filesystem->exists( $themes_dir . $theme ) ) {
1582                         if ( ! $wp_filesystem->delete( $themes_dir . $theme, true ) )
1583                                 return false;
1584                 }
1585
1586                 return true;
1587         }
1588
1589         /**
1590          * Get the WP_Theme object for a theme.
1591          *
1592          * @since 2.8.0
1593          * @since 3.0.0 The `$theme` argument was added.
1594          *
1595          * @param string $theme The directory name of the theme. This is optional, and if not supplied,
1596          *                      the directory name from the last result will be used.
1597          * @return WP_Theme|false The theme's info object, or false `$theme` is not supplied
1598          *                        and the last result isn't set.
1599          */
1600         public function theme_info($theme = null) {
1601
1602                 if ( empty($theme) ) {
1603                         if ( !empty($this->result['destination_name']) )
1604                                 $theme = $this->result['destination_name'];
1605                         else
1606                                 return false;
1607                 }
1608                 return wp_get_theme( $theme );
1609         }
1610
1611 }
1612
1613 add_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1614
1615 /**
1616  * Language pack upgrader, for updating translations of plugins, themes, and core.
1617  *
1618  * @package WordPress
1619  * @subpackage Upgrader
1620  * @since 3.7.0
1621  */
1622 class Language_Pack_Upgrader extends WP_Upgrader {
1623
1624         /**
1625          * Result of the language pack upgrade.
1626          *
1627          * @since 3.7.0
1628          * @var array|WP_Error $result
1629          * @see WP_Upgrader::$result
1630          */
1631         public $result;
1632
1633         /**
1634          * Whether a bulk upgrade/install is being performed.
1635          *
1636          * @since 3.7.0
1637          * @var bool $bulk
1638          */
1639         public $bulk = true;
1640
1641         /**
1642          * Asynchronously upgrade language packs after other upgrades have been made.
1643          *
1644          * Hooked to the {@see 'upgrader_process_complete'} action by default.
1645          *
1646          * @since 3.7.0
1647          */
1648         public static function async_upgrade( $upgrader = false ) {
1649                 // Avoid recursion.
1650                 if ( $upgrader && $upgrader instanceof Language_Pack_Upgrader ) {
1651                         return;
1652                 }
1653
1654                 // Nothing to do?
1655                 $language_updates = wp_get_translation_updates();
1656                 if ( ! $language_updates ) {
1657                         return;
1658                 }
1659
1660                 // Avoid messing with VCS installs, at least for now.
1661                 // Noted: this is not the ideal way to accomplish this.
1662                 $check_vcs = new WP_Automatic_Updater;
1663                 if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) {
1664                         return;
1665                 }
1666
1667                 foreach ( $language_updates as $key => $language_update ) {
1668                         $update = ! empty( $language_update->autoupdate );
1669
1670                         /**
1671                          * Filter whether to asynchronously update translation for core, a plugin, or a theme.
1672                          *
1673                          * @since 4.0.0
1674                          *
1675                          * @param bool   $update          Whether to update.
1676                          * @param object $language_update The update offer.
1677                          */
1678                         $update = apply_filters( 'async_update_translation', $update, $language_update );
1679
1680                         if ( ! $update ) {
1681                                 unset( $language_updates[ $key ] );
1682                         }
1683                 }
1684
1685                 if ( empty( $language_updates ) ) {
1686                         return;
1687                 }
1688
1689                 $skin = new Language_Pack_Upgrader_Skin( array(
1690                         'skip_header_footer' => true,
1691                 ) );
1692
1693                 $lp_upgrader = new Language_Pack_Upgrader( $skin );
1694                 $lp_upgrader->bulk_upgrade( $language_updates );
1695         }
1696
1697         /**
1698          * Initialize the upgrade strings.
1699          *
1700          * @since 3.7.0
1701          */
1702         public function upgrade_strings() {
1703                 $this->strings['starting_upgrade'] = __( 'Some of your translations need updating. Sit tight for a few more seconds while we update them as well.' );
1704                 $this->strings['up_to_date'] = __( 'The translation is up to date.' ); // We need to silently skip this case
1705                 $this->strings['no_package'] = __( 'Update package not available.' );
1706                 $this->strings['downloading_package'] = __( 'Downloading translation from <span class="code">%s</span>&#8230;' );
1707                 $this->strings['unpack_package'] = __( 'Unpacking the update&#8230;' );
1708                 $this->strings['process_failed'] = __( 'Translation update failed.' );
1709                 $this->strings['process_success'] = __( 'Translation updated successfully.' );
1710         }
1711
1712         /**
1713          * Upgrade a language pack.
1714          *
1715          * @since 3.7.0
1716          *
1717          * @param string|false $update Optional. Whether an update offer is available. Default false.
1718          * @param array        $args   Optional. Other optional arguments, see
1719          *                             {@see Language_Pack_Upgrader::bulk_upgrade()}. Default empty array.
1720          * @return array|WP_Error The result of the upgrade, or a {@see wP_Error} object instead.
1721          */
1722         public function upgrade( $update = false, $args = array() ) {
1723                 if ( $update ) {
1724                         $update = array( $update );
1725                 }
1726
1727                 $results = $this->bulk_upgrade( $update, $args );
1728
1729                 if ( ! is_array( $results ) ) {
1730                         return $results;
1731                 }
1732
1733                 return $results[0];
1734         }
1735
1736         /**
1737          * Bulk upgrade language packs.
1738          *
1739          * @since 3.7.0
1740          *
1741          * @param array $language_updates Optional. Language pack updates. Default empty array.
1742          * @param array $args {
1743          *     Optional. Other arguments for upgrading multiple language packs. Default empty array
1744          *
1745          *     @type bool $clear_update_cache Whether to clear the update cache when done.
1746          *                                    Default true.
1747          * }
1748          * @return array|true|false|WP_Error Will return an array of results, or true if there are no updates,
1749          *                                   false or WP_Error for initial errors.
1750          */
1751         public function bulk_upgrade( $language_updates = array(), $args = array() ) {
1752                 global $wp_filesystem;
1753
1754                 $defaults = array(
1755                         'clear_update_cache' => true,
1756                 );
1757                 $parsed_args = wp_parse_args( $args, $defaults );
1758
1759                 $this->init();
1760                 $this->upgrade_strings();
1761
1762                 if ( ! $language_updates )
1763                         $language_updates = wp_get_translation_updates();
1764
1765                 if ( empty( $language_updates ) ) {
1766                         $this->skin->header();
1767                         $this->skin->before();
1768                         $this->skin->set_result( true );
1769                         $this->skin->feedback( 'up_to_date' );
1770                         $this->skin->after();
1771                         $this->skin->bulk_footer();
1772                         $this->skin->footer();
1773                         return true;
1774                 }
1775
1776                 if ( 'upgrader_process_complete' == current_filter() )
1777                         $this->skin->feedback( 'starting_upgrade' );
1778
1779                 // Remove any existing upgrade filters from the plugin/theme upgraders #WP29425 & #WP29230
1780                 remove_all_filters( 'upgrader_pre_install' );
1781                 remove_all_filters( 'upgrader_clear_destination' );
1782                 remove_all_filterS( 'upgrader_post_install' );
1783                 remove_all_filters( 'upgrader_source_selection' );
1784
1785                 add_filter( 'upgrader_source_selection', array( $this, 'check_package' ), 10, 2 );
1786
1787                 $this->skin->header();
1788
1789                 // Connect to the Filesystem first.
1790                 $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_LANG_DIR ) );
1791                 if ( ! $res ) {
1792                         $this->skin->footer();
1793                         return false;
1794                 }
1795
1796                 $results = array();
1797
1798                 $this->update_count = count( $language_updates );
1799                 $this->update_current = 0;
1800
1801                 /*
1802                  * The filesystem's mkdir() is not recursive. Make sure WP_LANG_DIR exists,
1803                  * as we then may need to create a /plugins or /themes directory inside of it.
1804                  */
1805                 $remote_destination = $wp_filesystem->find_folder( WP_LANG_DIR );
1806                 if ( ! $wp_filesystem->exists( $remote_destination ) )
1807                         if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) )
1808                                 return new WP_Error( 'mkdir_failed_lang_dir', $this->strings['mkdir_failed'], $remote_destination );
1809
1810                 foreach ( $language_updates as $language_update ) {
1811
1812                         $this->skin->language_update = $language_update;
1813
1814                         $destination = WP_LANG_DIR;
1815                         if ( 'plugin' == $language_update->type )
1816                                 $destination .= '/plugins';
1817                         elseif ( 'theme' == $language_update->type )
1818                                 $destination .= '/themes';
1819
1820                         $this->update_current++;
1821
1822                         $options = array(
1823                                 'package' => $language_update->package,
1824                                 'destination' => $destination,
1825                                 'clear_destination' => false,
1826                                 'abort_if_destination_exists' => false, // We expect the destination to exist.
1827                                 'clear_working' => true,
1828                                 'is_multi' => true,
1829                                 'hook_extra' => array(
1830                                         'language_update_type' => $language_update->type,
1831                                         'language_update' => $language_update,
1832                                 )
1833                         );
1834
1835                         $result = $this->run( $options );
1836
1837                         $results[] = $this->result;
1838
1839                         // Prevent credentials auth screen from displaying multiple times.
1840                         if ( false === $result )
1841                                 break;
1842                 }
1843
1844                 $this->skin->bulk_footer();
1845
1846                 $this->skin->footer();
1847
1848                 // Clean up our hooks, in case something else does an upgrade on this connection.
1849                 remove_filter( 'upgrader_source_selection', array( $this, 'check_package' ) );
1850
1851                 if ( $parsed_args['clear_update_cache'] ) {
1852                         wp_clean_update_cache();
1853                 }
1854
1855                 return $results;
1856         }
1857
1858         /**
1859          * Check the package source to make sure there are .mo and .po files.
1860          *
1861          * Hooked to the {@see 'upgrader_source_selection'} filter by
1862          * {@see Language_Pack_Upgrader::bulk_upgrade()}.
1863          *
1864          * @since 3.7.0
1865          */
1866         public function check_package( $source, $remote_source ) {
1867                 global $wp_filesystem;
1868
1869                 if ( is_wp_error( $source ) )
1870                         return $source;
1871
1872                 // Check that the folder contains a valid language.
1873                 $files = $wp_filesystem->dirlist( $remote_source );
1874
1875                 // Check to see if a .po and .mo exist in the folder.
1876                 $po = $mo = false;
1877                 foreach ( (array) $files as $file => $filedata ) {
1878                         if ( '.po' == substr( $file, -3 ) )
1879                                 $po = true;
1880                         elseif ( '.mo' == substr( $file, -3 ) )
1881                                 $mo = true;
1882                 }
1883
1884                 if ( ! $mo || ! $po )
1885                         return new WP_Error( 'incompatible_archive_pomo', $this->strings['incompatible_archive'],
1886                                 __( 'The language pack is missing either the <code>.po</code> or <code>.mo</code> files.' ) );
1887
1888                 return $source;
1889         }
1890
1891         /**
1892          * Get the name of an item being updated.
1893          *
1894          * @since 3.7.0
1895          *
1896          * @param object The data for an update.
1897          * @return string The name of the item being updated.
1898          */
1899         public function get_name_for_update( $update ) {
1900                 switch ( $update->type ) {
1901                         case 'core':
1902                                 return 'WordPress'; // Not translated
1903
1904                         case 'theme':
1905                                 $theme = wp_get_theme( $update->slug );
1906                                 if ( $theme->exists() )
1907                                         return $theme->Get( 'Name' );
1908                                 break;
1909                         case 'plugin':
1910                                 $plugin_data = get_plugins( '/' . $update->slug );
1911                                 $plugin_data = reset( $plugin_data );
1912                                 if ( $plugin_data )
1913                                         return $plugin_data['Name'];
1914                                 break;
1915                 }
1916                 return '';
1917         }
1918
1919 }
1920
1921 /**
1922  * Core Upgrader class for WordPress. It allows for WordPress to upgrade itself in combination with the wp-admin/includes/update-core.php file
1923  *
1924  * @package WordPress
1925  * @subpackage Upgrader
1926  * @since 2.8.0
1927  */
1928 class Core_Upgrader extends WP_Upgrader {
1929
1930         /**
1931          * Initialize the upgrade strings.
1932          *
1933          * @since 2.8.0
1934          */
1935         public function upgrade_strings() {
1936                 $this->strings['up_to_date'] = __('WordPress is at the latest version.');
1937                 $this->strings['no_package'] = __('Update package not available.');
1938                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
1939                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
1940                 $this->strings['copy_failed'] = __('Could not copy files.');
1941                 $this->strings['copy_failed_space'] = __('Could not copy files. You may have run out of disk space.' );
1942                 $this->strings['start_rollback'] = __( 'Attempting to roll back to previous version.' );
1943                 $this->strings['rollback_was_required'] = __( 'Due to an error during updating, WordPress has rolled back to your previous version.' );
1944         }
1945
1946         /**
1947          * Upgrade WordPress core.
1948          *
1949          * @since 2.8.0
1950          *
1951          * @param object $current Response object for whether WordPress is current.
1952          * @param array  $args {
1953          *        Optional. Arguments for upgrading WordPress core. Default empty array.
1954          *
1955          *        @type bool $pre_check_md5    Whether to check the file checksums before
1956          *                                     attempting the upgrade. Default true.
1957          *        @type bool $attempt_rollback Whether to attempt to rollback the chances if
1958          *                                     there is a problem. Default false.
1959          *        @type bool $do_rollback      Whether to perform this "upgrade" as a rollback.
1960          *                                     Default false.
1961          * }
1962          * @return null|false|WP_Error False or WP_Error on failure, null on success.
1963          */
1964         public function upgrade( $current, $args = array() ) {
1965                 global $wp_filesystem;
1966
1967                 include( ABSPATH . WPINC . '/version.php' ); // $wp_version;
1968
1969                 $start_time = time();
1970
1971                 $defaults = array(
1972                         'pre_check_md5'    => true,
1973                         'attempt_rollback' => false,
1974                         'do_rollback'      => false,
1975                         'allow_relaxed_file_ownership' => false,
1976                 );
1977                 $parsed_args = wp_parse_args( $args, $defaults );
1978
1979                 $this->init();
1980                 $this->upgrade_strings();
1981
1982                 // Is an update available?
1983                 if ( !isset( $current->response ) || $current->response == 'latest' )
1984                         return new WP_Error('up_to_date', $this->strings['up_to_date']);
1985
1986                 $res = $this->fs_connect( array( ABSPATH, WP_CONTENT_DIR ), $parsed_args['allow_relaxed_file_ownership'] );
1987                 if ( ! $res || is_wp_error( $res ) ) {
1988                         return $res;
1989                 }
1990
1991                 $wp_dir = trailingslashit($wp_filesystem->abspath());
1992
1993                 $partial = true;
1994                 if ( $parsed_args['do_rollback'] )
1995                         $partial = false;
1996                 elseif ( $parsed_args['pre_check_md5'] && ! $this->check_files() )
1997                         $partial = false;
1998
1999                 /*
2000                  * If partial update is returned from the API, use that, unless we're doing
2001                  * a reinstall. If we cross the new_bundled version number, then use
2002                  * the new_bundled zip. Don't though if the constant is set to skip bundled items.
2003                  * If the API returns a no_content zip, go with it. Finally, default to the full zip.
2004                  */
2005                 if ( $parsed_args['do_rollback'] && $current->packages->rollback )
2006                         $to_download = 'rollback';
2007                 elseif ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && $partial )
2008                         $to_download = 'partial';
2009                 elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
2010                         && ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
2011                         $to_download = 'new_bundled';
2012                 elseif ( $current->packages->no_content )
2013                         $to_download = 'no_content';
2014                 else
2015                         $to_download = 'full';
2016
2017                 $download = $this->download_package( $current->packages->$to_download );
2018                 if ( is_wp_error($download) )
2019                         return $download;
2020
2021                 $working_dir = $this->unpack_package( $download );
2022                 if ( is_wp_error($working_dir) )
2023                         return $working_dir;
2024
2025                 // Copy update-core.php from the new version into place.
2026                 if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
2027                         $wp_filesystem->delete($working_dir, true);
2028                         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' );
2029                 }
2030                 $wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
2031
2032                 require_once( ABSPATH . 'wp-admin/includes/update-core.php' );
2033
2034                 if ( ! function_exists( 'update_core' ) )
2035                         return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
2036
2037                 $result = update_core( $working_dir, $wp_dir );
2038
2039                 // In the event of an issue, we may be able to roll back.
2040                 if ( $parsed_args['attempt_rollback'] && $current->packages->rollback && ! $parsed_args['do_rollback'] ) {
2041                         $try_rollback = false;
2042                         if ( is_wp_error( $result ) ) {
2043                                 $error_code = $result->get_error_code();
2044                                 /*
2045                                  * Not all errors are equal. These codes are critical: copy_failed__copy_dir,
2046                                  * mkdir_failed__copy_dir, copy_failed__copy_dir_retry, and disk_full.
2047                                  * do_rollback allows for update_core() to trigger a rollback if needed.
2048                                  */
2049                                 if ( false !== strpos( $error_code, 'do_rollback' ) )
2050                                         $try_rollback = true;
2051                                 elseif ( false !== strpos( $error_code, '__copy_dir' ) )
2052                                         $try_rollback = true;
2053                                 elseif ( 'disk_full' === $error_code )
2054                                         $try_rollback = true;
2055                         }
2056
2057                         if ( $try_rollback ) {
2058                                 /** This filter is documented in wp-admin/includes/update-core.php */
2059                                 apply_filters( 'update_feedback', $result );
2060
2061                                 /** This filter is documented in wp-admin/includes/update-core.php */
2062                                 apply_filters( 'update_feedback', $this->strings['start_rollback'] );
2063
2064                                 $rollback_result = $this->upgrade( $current, array_merge( $parsed_args, array( 'do_rollback' => true ) ) );
2065
2066                                 $original_result = $result;
2067                                 $result = new WP_Error( 'rollback_was_required', $this->strings['rollback_was_required'], (object) array( 'update' => $original_result, 'rollback' => $rollback_result ) );
2068                         }
2069                 }
2070
2071                 /** This action is documented in wp-admin/includes/class-wp-upgrader.php */
2072                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ) );
2073
2074                 // Clear the current updates
2075                 delete_site_transient( 'update_core' );
2076
2077                 if ( ! $parsed_args['do_rollback'] ) {
2078                         $stats = array(
2079                                 'update_type'      => $current->response,
2080                                 'success'          => true,
2081                                 'fs_method'        => $wp_filesystem->method,
2082                                 'fs_method_forced' => defined( 'FS_METHOD' ) || has_filter( 'filesystem_method' ),
2083                                 'fs_method_direct' => !empty( $GLOBALS['_wp_filesystem_direct_method'] ) ? $GLOBALS['_wp_filesystem_direct_method'] : '',
2084                                 'time_taken'       => time() - $start_time,
2085                                 'reported'         => $wp_version,
2086                                 'attempted'        => $current->version,
2087                         );
2088
2089                         if ( is_wp_error( $result ) ) {
2090                                 $stats['success'] = false;
2091                                 // Did a rollback occur?
2092                                 if ( ! empty( $try_rollback ) ) {
2093                                         $stats['error_code'] = $original_result->get_error_code();
2094                                         $stats['error_data'] = $original_result->get_error_data();
2095                                         // Was the rollback successful? If not, collect its error too.
2096                                         $stats['rollback'] = ! is_wp_error( $rollback_result );
2097                                         if ( is_wp_error( $rollback_result ) ) {
2098                                                 $stats['rollback_code'] = $rollback_result->get_error_code();
2099                                                 $stats['rollback_data'] = $rollback_result->get_error_data();
2100                                         }
2101                                 } else {
2102                                         $stats['error_code'] = $result->get_error_code();
2103                                         $stats['error_data'] = $result->get_error_data();
2104                                 }
2105                         }
2106
2107                         wp_version_check( $stats );
2108                 }
2109
2110                 return $result;
2111         }
2112
2113         /**
2114          * Determines if this WordPress Core version should update to an offered version or not.
2115          *
2116          * @since 3.7.0
2117          *
2118          * @param string $offered_ver The offered version, of the format x.y.z.
2119          * @return bool True if we should update to the offered version, otherwise false.
2120          */
2121         public static function should_update_to_version( $offered_ver ) {
2122                 include( ABSPATH . WPINC . '/version.php' ); // $wp_version; // x.y.z
2123
2124                 $current_branch = implode( '.', array_slice( preg_split( '/[.-]/', $wp_version  ), 0, 2 ) ); // x.y
2125                 $new_branch     = implode( '.', array_slice( preg_split( '/[.-]/', $offered_ver ), 0, 2 ) ); // x.y
2126                 $current_is_development_version = (bool) strpos( $wp_version, '-' );
2127
2128                 // Defaults:
2129                 $upgrade_dev   = true;
2130                 $upgrade_minor = true;
2131                 $upgrade_major = false;
2132
2133                 // WP_AUTO_UPDATE_CORE = true (all), 'minor', false.
2134                 if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
2135                         if ( false === WP_AUTO_UPDATE_CORE ) {
2136                                 // Defaults to turned off, unless a filter allows it
2137                                 $upgrade_dev = $upgrade_minor = $upgrade_major = false;
2138                         } elseif ( true === WP_AUTO_UPDATE_CORE ) {
2139                                 // ALL updates for core
2140                                 $upgrade_dev = $upgrade_minor = $upgrade_major = true;
2141                         } elseif ( 'minor' === WP_AUTO_UPDATE_CORE ) {
2142                                 // Only minor updates for core
2143                                 $upgrade_dev = $upgrade_major = false;
2144                                 $upgrade_minor = true;
2145                         }
2146                 }
2147
2148                 // 1: If we're already on that version, not much point in updating?
2149                 if ( $offered_ver == $wp_version )
2150                         return false;
2151
2152                 // 2: If we're running a newer version, that's a nope
2153                 if ( version_compare( $wp_version, $offered_ver, '>' ) )
2154                         return false;
2155
2156                 $failure_data = get_site_option( 'auto_core_update_failed' );
2157                 if ( $failure_data ) {
2158                         // If this was a critical update failure, cannot update.
2159                         if ( ! empty( $failure_data['critical'] ) )
2160                                 return false;
2161
2162                         // Don't claim we can update on update-core.php if we have a non-critical failure logged.
2163                         if ( $wp_version == $failure_data['current'] && false !== strpos( $offered_ver, '.1.next.minor' ) )
2164                                 return false;
2165
2166                         // Cannot update if we're retrying the same A to B update that caused a non-critical failure.
2167                         // Some non-critical failures do allow retries, like download_failed.
2168                         // 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.
2169                         if ( empty( $failure_data['retry'] ) && $wp_version == $failure_data['current'] && $offered_ver == $failure_data['attempted'] )
2170                                 return false;
2171                 }
2172
2173                 // 3: 3.7-alpha-25000 -> 3.7-alpha-25678 -> 3.7-beta1 -> 3.7-beta2
2174                 if ( $current_is_development_version ) {
2175
2176                         /**
2177                          * Filter whether to enable automatic core updates for development versions.
2178                          *
2179                          * @since 3.7.0
2180                          *
2181                          * @param bool $upgrade_dev Whether to enable automatic updates for
2182                          *                          development versions.
2183                          */
2184                         if ( ! apply_filters( 'allow_dev_auto_core_updates', $upgrade_dev ) )
2185                                 return false;
2186                         // Else fall through to minor + major branches below.
2187                 }
2188
2189                 // 4: Minor In-branch updates (3.7.0 -> 3.7.1 -> 3.7.2 -> 3.7.4)
2190                 if ( $current_branch == $new_branch ) {
2191
2192                         /**
2193                          * Filter whether to enable minor automatic core updates.
2194                          *
2195                          * @since 3.7.0
2196                          *
2197                          * @param bool $upgrade_minor Whether to enable minor automatic core updates.
2198                          */
2199                         return apply_filters( 'allow_minor_auto_core_updates', $upgrade_minor );
2200                 }
2201
2202                 // 5: Major version updates (3.7.0 -> 3.8.0 -> 3.9.1)
2203                 if ( version_compare( $new_branch, $current_branch, '>' ) ) {
2204
2205                         /**
2206                          * Filter whether to enable major automatic core updates.
2207                          *
2208                          * @since 3.7.0
2209                          *
2210                          * @param bool $upgrade_major Whether to enable major automatic core updates.
2211                          */
2212                         return apply_filters( 'allow_major_auto_core_updates', $upgrade_major );
2213                 }
2214
2215                 // If we're not sure, we don't want it
2216                 return false;
2217         }
2218
2219         /**
2220          * Compare the disk file checksums agains the expected checksums.
2221          *
2222          * @since 3.7.0
2223          *
2224          * @return bool True if the checksums match, otherwise false.
2225          */
2226         public function check_files() {
2227                 global $wp_version, $wp_local_package;
2228
2229                 $checksums = get_core_checksums( $wp_version, isset( $wp_local_package ) ? $wp_local_package : 'en_US' );
2230
2231                 if ( ! is_array( $checksums ) )
2232                         return false;
2233
2234                 foreach ( $checksums as $file => $checksum ) {
2235                         // Skip files which get updated
2236                         if ( 'wp-content' == substr( $file, 0, 10 ) )
2237                                 continue;
2238                         if ( ! file_exists( ABSPATH . $file ) || md5_file( ABSPATH . $file ) !== $checksum )
2239                                 return false;
2240                 }
2241
2242                 return true;
2243         }
2244 }
2245
2246 /**
2247  * Upgrade Skin helper for File uploads. This class handles the upload process and passes it as if it's a local file to the Upgrade/Installer functions.
2248  *
2249  * @package WordPress
2250  * @subpackage Upgrader
2251  * @since 2.8.0
2252  */
2253 class File_Upload_Upgrader {
2254
2255         /**
2256          * The full path to the file package.
2257          *
2258          * @since 2.8.0
2259          * @var string $package
2260          */
2261         public $package;
2262
2263         /**
2264          * The name of the file.
2265          *
2266          * @since 2.8.0
2267          * @var string $filename
2268          */
2269         public $filename;
2270
2271         /**
2272          * The ID of the attachment post for this file.
2273          *
2274          * @since 3.3.0
2275          * @var int $id
2276          */
2277         public $id = 0;
2278
2279         /**
2280          * Construct the upgrader for a form.
2281          *
2282          * @since 2.8.0
2283          *
2284          * @param string $form      The name of the form the file was uploaded from.
2285          * @param string $urlholder The name of the `GET` parameter that holds the filename.
2286          */
2287         public function __construct( $form, $urlholder ) {
2288
2289                 if ( empty($_FILES[$form]['name']) && empty($_GET[$urlholder]) )
2290                         wp_die(__('Please select a file'));
2291
2292                 //Handle a newly uploaded file, Else assume it's already been uploaded
2293                 if ( ! empty($_FILES) ) {
2294                         $overrides = array( 'test_form' => false, 'test_type' => false );
2295                         $file = wp_handle_upload( $_FILES[$form], $overrides );
2296
2297                         if ( isset( $file['error'] ) )
2298                                 wp_die( $file['error'] );
2299
2300                         $this->filename = $_FILES[$form]['name'];
2301                         $this->package = $file['file'];
2302
2303                         // Construct the object array
2304                         $object = array(
2305                                 'post_title' => $this->filename,
2306                                 'post_content' => $file['url'],
2307                                 'post_mime_type' => $file['type'],
2308                                 'guid' => $file['url'],
2309                                 'context' => 'upgrader',
2310                                 'post_status' => 'private'
2311                         );
2312
2313                         // Save the data.
2314                         $this->id = wp_insert_attachment( $object, $file['file'] );
2315
2316                         // Schedule a cleanup for 2 hours from now in case of failed install.
2317                         wp_schedule_single_event( time() + 2 * HOUR_IN_SECONDS, 'upgrader_scheduled_cleanup', array( $this->id ) );
2318
2319                 } elseif ( is_numeric( $_GET[$urlholder] ) ) {
2320                         // Numeric Package = previously uploaded file, see above.
2321                         $this->id = (int) $_GET[$urlholder];
2322                         $attachment = get_post( $this->id );
2323                         if ( empty($attachment) )
2324                                 wp_die(__('Please select a file'));
2325
2326                         $this->filename = $attachment->post_title;
2327                         $this->package = get_attached_file( $attachment->ID );
2328                 } else {
2329                         // Else, It's set to something, Back compat for plugins using the old (pre-3.3) File_Uploader handler.
2330                         if ( ! ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) )
2331                                 wp_die( $uploads['error'] );
2332
2333                         $this->filename = $_GET[$urlholder];
2334                         $this->package = $uploads['basedir'] . '/' . $this->filename;
2335                 }
2336         }
2337
2338         /**
2339          * Delete the attachment/uploaded file.
2340          *
2341          * @since 3.2.2
2342          *
2343          * @return bool Whether the cleanup was successful.
2344          */
2345         public function cleanup() {
2346                 if ( $this->id )
2347                         wp_delete_attachment( $this->id );
2348
2349                 elseif ( file_exists( $this->package ) )
2350                         return @unlink( $this->package );
2351
2352                 return true;
2353         }
2354 }
2355
2356 /**
2357  * The WordPress automatic background updater.
2358  *
2359  * @package WordPress
2360  * @subpackage Upgrader
2361  * @since 3.7.0
2362  */
2363 class WP_Automatic_Updater {
2364
2365         /**
2366          * Tracks update results during processing.
2367          *
2368          * @var array
2369          */
2370         protected $update_results = array();
2371
2372         /**
2373          * Whether the entire automatic updater is disabled.
2374          *
2375          * @since 3.7.0
2376          */
2377         public function is_disabled() {
2378                 // Background updates are disabled if you don't want file changes.
2379                 if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS )
2380                         return true;
2381
2382                 if ( defined( 'WP_INSTALLING' ) )
2383                         return true;
2384
2385                 // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
2386                 $disabled = defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED;
2387
2388                 /**
2389                  * Filter whether to entirely disable background updates.
2390                  *
2391                  * There are more fine-grained filters and controls for selective disabling.
2392                  * This filter parallels the AUTOMATIC_UPDATER_DISABLED constant in name.
2393                  *
2394                  * This also disables update notification emails. That may change in the future.
2395                  *
2396                  * @since 3.7.0
2397                  *
2398                  * @param bool $disabled Whether the updater should be disabled.
2399                  */
2400                 return apply_filters( 'automatic_updater_disabled', $disabled );
2401         }
2402
2403         /**
2404          * Check for version control checkouts.
2405          *
2406          * Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
2407          * filesystem to the top of the drive, erring on the side of detecting a VCS
2408          * checkout somewhere.
2409          *
2410          * ABSPATH is always checked in addition to whatever $context is (which may be the
2411          * wp-content directory, for example). The underlying assumption is that if you are
2412          * using version control *anywhere*, then you should be making decisions for
2413          * how things get updated.
2414          *
2415          * @since 3.7.0
2416          *
2417          * @param string $context The filesystem path to check, in addition to ABSPATH.
2418          */
2419         public function is_vcs_checkout( $context ) {
2420                 $context_dirs = array( untrailingslashit( $context ) );
2421                 if ( $context !== ABSPATH )
2422                         $context_dirs[] = untrailingslashit( ABSPATH );
2423
2424                 $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' );
2425                 $check_dirs = array();
2426
2427                 foreach ( $context_dirs as $context_dir ) {
2428                         // Walk up from $context_dir to the root.
2429                         do {
2430                                 $check_dirs[] = $context_dir;
2431
2432                                 // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
2433                                 if ( $context_dir == dirname( $context_dir ) )
2434                                         break;
2435
2436                         // Continue one level at a time.
2437                         } while ( $context_dir = dirname( $context_dir ) );
2438                 }
2439
2440                 $check_dirs = array_unique( $check_dirs );
2441
2442                 // Search all directories we've found for evidence of version control.
2443                 foreach ( $vcs_dirs as $vcs_dir ) {
2444                         foreach ( $check_dirs as $check_dir ) {
2445                                 if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) )
2446                                         break 2;
2447                         }
2448                 }
2449
2450                 /**
2451                  * Filter whether the automatic updater should consider a filesystem
2452                  * location to be potentially managed by a version control system.
2453                  *
2454                  * @since 3.7.0
2455                  *
2456                  * @param bool $checkout  Whether a VCS checkout was discovered at $context
2457                  *                        or ABSPATH, or anywhere higher.
2458                  * @param string $context The filesystem context (a path) against which
2459                  *                        filesystem status should be checked.
2460                  */
2461                 return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context );
2462         }
2463
2464         /**
2465          * Tests to see if we can and should update a specific item.
2466          *
2467          * @since 3.7.0
2468          *
2469          * @param string $type    The type of update being checked: 'core', 'theme',
2470          *                        'plugin', 'translation'.
2471          * @param object $item    The update offer.
2472          * @param string $context The filesystem context (a path) against which filesystem
2473          *                        access and status should be checked.
2474          */
2475         public function should_update( $type, $item, $context ) {
2476                 // Used to see if WP_Filesystem is set up to allow unattended updates.
2477                 $skin = new Automatic_Upgrader_Skin;
2478
2479                 if ( $this->is_disabled() )
2480                         return false;
2481
2482                 // Only relax the filesystem checks when the update doesn't include new files
2483                 $allow_relaxed_file_ownership = false;
2484                 if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
2485                         $allow_relaxed_file_ownership = true;
2486                 }
2487
2488                 // If we can't do an auto core update, we may still be able to email the user.
2489                 if ( ! $skin->request_filesystem_credentials( false, $context, $allow_relaxed_file_ownership ) || $this->is_vcs_checkout( $context ) ) {
2490                         if ( 'core' == $type )
2491                                 $this->send_core_update_notification_email( $item );
2492                         return false;
2493                 }
2494
2495                 // Next up, is this an item we can update?
2496                 if ( 'core' == $type )
2497                         $update = Core_Upgrader::should_update_to_version( $item->current );
2498                 else
2499                         $update = ! empty( $item->autoupdate );
2500
2501                 /**
2502                  * Filter whether to automatically update core, a plugin, a theme, or a language.
2503                  *
2504                  * The dynamic portion of the hook name, `$type`, refers to the type of update
2505                  * being checked. Can be 'core', 'theme', 'plugin', or 'translation'.
2506                  *
2507                  * Generally speaking, plugins, themes, and major core versions are not updated
2508                  * by default, while translations and minor and development versions for core
2509                  * are updated by default.
2510                  *
2511                  * See the {@see 'allow_dev_auto_core_updates', {@see 'allow_minor_auto_core_updates'},
2512                  * and {@see 'allow_major_auto_core_updates'} filters for a more straightforward way to
2513                  * adjust core updates.
2514                  *
2515                  * @since 3.7.0
2516                  *
2517                  * @param bool   $update Whether to update.
2518                  * @param object $item   The update offer.
2519                  */
2520                 $update = apply_filters( 'auto_update_' . $type, $update, $item );
2521
2522                 if ( ! $update ) {
2523                         if ( 'core' == $type )
2524                                 $this->send_core_update_notification_email( $item );
2525                         return false;
2526                 }
2527
2528                 // If it's a core update, are we actually compatible with its requirements?
2529                 if ( 'core' == $type ) {
2530                         global $wpdb;
2531
2532                         $php_compat = version_compare( phpversion(), $item->php_version, '>=' );
2533                         if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) )
2534                                 $mysql_compat = true;
2535                         else
2536                                 $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
2537
2538                         if ( ! $php_compat || ! $mysql_compat )
2539                                 return false;
2540                 }
2541
2542                 return true;
2543         }
2544
2545         /**
2546          * Notifies an administrator of a core update.
2547          *
2548          * @since 3.7.0
2549          *
2550          * @param object $item The update offer.
2551          */
2552         protected function send_core_update_notification_email( $item ) {
2553                 $notified = get_site_option( 'auto_core_update_notified' );
2554
2555                 // Don't notify if we've already notified the same email address of the same version.
2556                 if ( $notified && $notified['email'] == get_site_option( 'admin_email' ) && $notified['version'] == $item->current )
2557                         return false;
2558
2559                 // See if we need to notify users of a core update.
2560                 $notify = ! empty( $item->notify_email );
2561
2562                 /**
2563                  * Filter whether to notify the site administrator of a new core update.
2564                  *
2565                  * By default, administrators are notified when the update offer received
2566                  * from WordPress.org sets a particular flag. This allows some discretion
2567                  * in if and when to notify.
2568                  *
2569                  * This filter is only evaluated once per release. If the same email address
2570                  * was already notified of the same new version, WordPress won't repeatedly
2571                  * email the administrator.
2572                  *
2573                  * This filter is also used on about.php to check if a plugin has disabled
2574                  * these notifications.
2575                  *
2576                  * @since 3.7.0
2577                  *
2578                  * @param bool   $notify Whether the site administrator is notified.
2579                  * @param object $item   The update offer.
2580                  */
2581                 if ( ! apply_filters( 'send_core_update_notification_email', $notify, $item ) )
2582                         return false;
2583
2584                 $this->send_email( 'manual', $item );
2585                 return true;
2586         }
2587
2588         /**
2589          * Update an item, if appropriate.
2590          *
2591          * @since 3.7.0
2592          *
2593          * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
2594          * @param object $item The update offer.
2595          */
2596         public function update( $type, $item ) {
2597                 $skin = new Automatic_Upgrader_Skin;
2598
2599                 switch ( $type ) {
2600                         case 'core':
2601                                 // The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
2602                                 add_filter( 'update_feedback', array( $skin, 'feedback' ) );
2603                                 $upgrader = new Core_Upgrader( $skin );
2604                                 $context  = ABSPATH;
2605                                 break;
2606                         case 'plugin':
2607                                 $upgrader = new Plugin_Upgrader( $skin );
2608                                 $context  = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR
2609                                 break;
2610                         case 'theme':
2611                                 $upgrader = new Theme_Upgrader( $skin );
2612                                 $context  = get_theme_root( $item->theme );
2613                                 break;
2614                         case 'translation':
2615                                 $upgrader = new Language_Pack_Upgrader( $skin );
2616                                 $context  = WP_CONTENT_DIR; // WP_LANG_DIR;
2617                                 break;
2618                 }
2619
2620                 // Determine whether we can and should perform this update.
2621                 if ( ! $this->should_update( $type, $item, $context ) )
2622                         return false;
2623
2624                 $upgrader_item = $item;
2625                 switch ( $type ) {
2626                         case 'core':
2627                                 $skin->feedback( __( 'Updating to WordPress %s' ), $item->version );
2628                                 $item_name = sprintf( __( 'WordPress %s' ), $item->version );
2629                                 break;
2630                         case 'theme':
2631                                 $upgrader_item = $item->theme;
2632                                 $theme = wp_get_theme( $upgrader_item );
2633                                 $item_name = $theme->Get( 'Name' );
2634                                 $skin->feedback( __( 'Updating theme: %s' ), $item_name );
2635                                 break;
2636                         case 'plugin':
2637                                 $upgrader_item = $item->plugin;
2638                                 $plugin_data = get_plugin_data( $context . '/' . $upgrader_item );
2639                                 $item_name = $plugin_data['Name'];
2640                                 $skin->feedback( __( 'Updating plugin: %s' ), $item_name );
2641                                 break;
2642                         case 'translation':
2643                                 $language_item_name = $upgrader->get_name_for_update( $item );
2644                                 $item_name = sprintf( __( 'Translations for %s' ), $language_item_name );
2645                                 $skin->feedback( sprintf( __( 'Updating translations for %1$s (%2$s)&#8230;' ), $language_item_name, $item->language ) );
2646                                 break;
2647                 }
2648
2649                 $allow_relaxed_file_ownership = false;
2650                 if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) {
2651                         $allow_relaxed_file_ownership = true;
2652                 }
2653
2654                 // Boom, This sites about to get a whole new splash of paint!
2655                 $upgrade_result = $upgrader->upgrade( $upgrader_item, array(
2656                         'clear_update_cache' => false,
2657                         // Always use partial builds if possible for core updates.
2658                         'pre_check_md5'      => false,
2659                         // Only available for core updates.
2660                         'attempt_rollback'   => true,
2661                         // Allow relaxed file ownership in some scenarios
2662                         'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
2663                 ) );
2664
2665                 // If the filesystem is unavailable, false is returned.
2666                 if ( false === $upgrade_result ) {
2667                         $upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
2668                 }
2669
2670                 // Core doesn't output this, so let's append it so we don't get confused.
2671                 if ( 'core' == $type ) {
2672                         if ( is_wp_error( $upgrade_result ) ) {
2673                                 $skin->error( __( 'Installation Failed' ), $upgrade_result );
2674                         } else {
2675                                 $skin->feedback( __( 'WordPress updated successfully' ) );
2676                         }
2677                 }
2678
2679                 $this->update_results[ $type ][] = (object) array(
2680                         'item'     => $item,
2681                         'result'   => $upgrade_result,
2682                         'name'     => $item_name,
2683                         'messages' => $skin->get_upgrade_messages()
2684                 );
2685
2686                 return $upgrade_result;
2687         }
2688
2689         /**
2690          * Kicks off the background update process, looping through all pending updates.
2691          *
2692          * @since 3.7.0
2693          */
2694         public function run() {
2695                 global $wpdb, $wp_version;
2696
2697                 if ( $this->is_disabled() )
2698                         return;
2699
2700                 if ( ! is_main_network() || ! is_main_site() )
2701                         return;
2702
2703                 $lock_name = 'auto_updater.lock';
2704
2705                 // Try to lock
2706                 $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
2707
2708                 if ( ! $lock_result ) {
2709                         $lock_result = get_option( $lock_name );
2710
2711                         // If we couldn't create a lock, and there isn't a lock, bail
2712                         if ( ! $lock_result )
2713                                 return;
2714
2715                         // Check to see if the lock is still valid
2716                         if ( $lock_result > ( time() - HOUR_IN_SECONDS ) )
2717                                 return;
2718                 }
2719
2720                 // Update the lock, as by this point we've definitely got a lock, just need to fire the actions
2721                 update_option( $lock_name, time() );
2722
2723                 // Don't automatically run these thins, as we'll handle it ourselves
2724                 remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
2725                 remove_action( 'upgrader_process_complete', 'wp_version_check' );
2726                 remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
2727                 remove_action( 'upgrader_process_complete', 'wp_update_themes' );
2728
2729                 // Next, Plugins
2730                 wp_update_plugins(); // Check for Plugin updates
2731                 $plugin_updates = get_site_transient( 'update_plugins' );
2732                 if ( $plugin_updates && !empty( $plugin_updates->response ) ) {
2733                         foreach ( $plugin_updates->response as $plugin ) {
2734                                 $this->update( 'plugin', $plugin );
2735                         }
2736                         // Force refresh of plugin update information
2737                         wp_clean_plugins_cache();
2738                 }
2739
2740                 // Next, those themes we all love
2741                 wp_update_themes();  // Check for Theme updates
2742                 $theme_updates = get_site_transient( 'update_themes' );
2743                 if ( $theme_updates && !empty( $theme_updates->response ) ) {
2744                         foreach ( $theme_updates->response as $theme ) {
2745                                 $this->update( 'theme', (object) $theme );
2746                         }
2747                         // Force refresh of theme update information
2748                         wp_clean_themes_cache();
2749                 }
2750
2751                 // Next, Process any core update
2752                 wp_version_check(); // Check for Core updates
2753                 $core_update = find_core_auto_update();
2754
2755                 if ( $core_update )
2756                         $this->update( 'core', $core_update );
2757
2758                 // Clean up, and check for any pending translations
2759                 // (Core_Upgrader checks for core updates)
2760                 $theme_stats = array();
2761                 if ( isset( $this->update_results['theme'] ) ) {
2762                         foreach ( $this->update_results['theme'] as $upgrade ) {
2763                                 $theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
2764                         }
2765                 }
2766                 wp_update_themes( $theme_stats );  // Check for Theme updates
2767
2768                 $plugin_stats = array();
2769                 if ( isset( $this->update_results['plugin'] ) ) {
2770                         foreach ( $this->update_results['plugin'] as $upgrade ) {
2771                                 $plugin_stats[ $upgrade->item->plugin ] = ( true === $upgrade->result );
2772                         }
2773                 }
2774                 wp_update_plugins( $plugin_stats ); // Check for Plugin updates
2775
2776                 // Finally, Process any new translations
2777                 $language_updates = wp_get_translation_updates();
2778                 if ( $language_updates ) {
2779                         foreach ( $language_updates as $update ) {
2780                                 $this->update( 'translation', $update );
2781                         }
2782
2783                         // Clear existing caches
2784                         wp_clean_update_cache();
2785
2786                         wp_version_check();  // check for Core updates
2787                         wp_update_themes();  // Check for Theme updates
2788                         wp_update_plugins(); // Check for Plugin updates
2789                 }
2790
2791                 // Send debugging email to all development installs.
2792                 if ( ! empty( $this->update_results ) ) {
2793                         $development_version = false !== strpos( $wp_version, '-' );
2794
2795                         /**
2796                          * Filter whether to send a debugging email for each automatic background update.
2797                          *
2798                          * @since 3.7.0
2799                          *
2800                          * @param bool $development_version By default, emails are sent if the
2801                          *                                  install is a development version.
2802                          *                                  Return false to avoid the email.
2803                          */
2804                         if ( apply_filters( 'automatic_updates_send_debug_email', $development_version ) )
2805                                 $this->send_debug_email();
2806
2807                         if ( ! empty( $this->update_results['core'] ) )
2808                                 $this->after_core_update( $this->update_results['core'][0] );
2809
2810                         /**
2811                          * Fires after all automatic updates have run.
2812                          *
2813                          * @since 3.8.0
2814                          *
2815                          * @param array $update_results The results of all attempted updates.
2816                          */
2817                         do_action( 'automatic_updates_complete', $this->update_results );
2818                 }
2819
2820                 // Clear the lock
2821                 delete_option( $lock_name );
2822         }
2823
2824         /**
2825          * If we tried to perform a core update, check if we should send an email,
2826          * and if we need to avoid processing future updates.
2827          *
2828          * @param object $update_result The result of the core update. Includes the update offer and result.
2829          */
2830         protected function after_core_update( $update_result ) {
2831                 global $wp_version;
2832
2833                 $core_update = $update_result->item;
2834                 $result      = $update_result->result;
2835
2836                 if ( ! is_wp_error( $result ) ) {
2837                         $this->send_email( 'success', $core_update );
2838                         return;
2839                 }
2840
2841                 $error_code = $result->get_error_code();
2842
2843                 // Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
2844                 // We should not try to perform a background update again until there is a successful one-click update performed by the user.
2845                 $critical = false;
2846                 if ( $error_code === 'disk_full' || false !== strpos( $error_code, '__copy_dir' ) ) {
2847                         $critical = true;
2848                 } elseif ( $error_code === 'rollback_was_required' && is_wp_error( $result->get_error_data()->rollback ) ) {
2849                         // A rollback is only critical if it failed too.
2850                         $critical = true;
2851                         $rollback_result = $result->get_error_data()->rollback;
2852                 } elseif ( false !== strpos( $error_code, 'do_rollback' ) ) {
2853                         $critical = true;
2854                 }
2855
2856                 if ( $critical ) {
2857                         $critical_data = array(
2858                                 'attempted'  => $core_update->current,
2859                                 'current'    => $wp_version,
2860                                 'error_code' => $error_code,
2861                                 'error_data' => $result->get_error_data(),
2862                                 'timestamp'  => time(),
2863                                 'critical'   => true,
2864                         );
2865                         if ( isset( $rollback_result ) ) {
2866                                 $critical_data['rollback_code'] = $rollback_result->get_error_code();
2867                                 $critical_data['rollback_data'] = $rollback_result->get_error_data();
2868                         }
2869                         update_site_option( 'auto_core_update_failed', $critical_data );
2870                         $this->send_email( 'critical', $core_update, $result );
2871                         return;
2872                 }
2873
2874                 /*
2875                  * Any other WP_Error code (like download_failed or files_not_writable) occurs before
2876                  * we tried to copy over core files. Thus, the failures are early and graceful.
2877                  *
2878                  * We should avoid trying to perform a background update again for the same version.
2879                  * But we can try again if another version is released.
2880                  *
2881                  * For certain 'transient' failures, like download_failed, we should allow retries.
2882                  * In fact, let's schedule a special update for an hour from now. (It's possible
2883                  * the issue could actually be on WordPress.org's side.) If that one fails, then email.
2884                  */
2885                 $send = true;
2886                 $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro' );
2887                 if ( in_array( $error_code, $transient_failures ) && ! get_site_option( 'auto_core_update_failed' ) ) {
2888                         wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
2889                         $send = false;
2890                 }
2891
2892                 $n = get_site_option( 'auto_core_update_notified' );
2893                 // Don't notify if we've already notified the same email address of the same version of the same notification type.
2894                 if ( $n && 'fail' == $n['type'] && $n['email'] == get_site_option( 'admin_email' ) && $n['version'] == $core_update->current )
2895                         $send = false;
2896
2897                 update_site_option( 'auto_core_update_failed', array(
2898                         'attempted'  => $core_update->current,
2899                         'current'    => $wp_version,
2900                         'error_code' => $error_code,
2901                         'error_data' => $result->get_error_data(),
2902                         'timestamp'  => time(),
2903                         'retry'      => in_array( $error_code, $transient_failures ),
2904                 ) );
2905
2906                 if ( $send )
2907                         $this->send_email( 'fail', $core_update, $result );
2908         }
2909
2910         /**
2911          * Sends an email upon the completion or failure of a background core update.
2912          *
2913          * @since 3.7.0
2914          *
2915          * @param string $type        The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
2916          * @param object $core_update The update offer that was attempted.
2917          * @param mixed  $result      Optional. The result for the core update. Can be WP_Error.
2918          */
2919         protected function send_email( $type, $core_update, $result = null ) {
2920                 update_site_option( 'auto_core_update_notified', array(
2921                         'type'      => $type,
2922                         'email'     => get_site_option( 'admin_email' ),
2923                         'version'   => $core_update->current,
2924                         'timestamp' => time(),
2925                 ) );
2926
2927                 $next_user_core_update = get_preferred_from_update_core();
2928                 // If the update transient is empty, use the update we just performed
2929                 if ( ! $next_user_core_update )
2930                         $next_user_core_update = $core_update;
2931                 $newer_version_available = ( 'upgrade' == $next_user_core_update->response && version_compare( $next_user_core_update->version, $core_update->version, '>' ) );
2932
2933                 /**
2934                  * Filter whether to send an email following an automatic background core update.
2935                  *
2936                  * @since 3.7.0
2937                  *
2938                  * @param bool   $send        Whether to send the email. Default true.
2939                  * @param string $type        The type of email to send. Can be one of
2940                  *                            'success', 'fail', 'critical'.
2941                  * @param object $core_update The update offer that was attempted.
2942                  * @param mixed  $result      The result for the core update. Can be WP_Error.
2943                  */
2944                 if ( 'manual' !== $type && ! apply_filters( 'auto_core_update_send_email', true, $type, $core_update, $result ) )
2945                         return;
2946
2947                 switch ( $type ) {
2948                         case 'success' : // We updated.
2949                                 /* translators: 1: Site name, 2: WordPress version number. */
2950                                 $subject = __( '[%1$s] Your site has updated to WordPress %2$s' );
2951                                 break;
2952
2953                         case 'fail' :   // We tried to update but couldn't.
2954                         case 'manual' : // We can't update (and made no attempt).
2955                                 /* translators: 1: Site name, 2: WordPress version number. */
2956                                 $subject = __( '[%1$s] WordPress %2$s is available. Please update!' );
2957                                 break;
2958
2959                         case 'critical' : // We tried to update, started to copy files, then things went wrong.
2960                                 /* translators: 1: Site name. */
2961                                 $subject = __( '[%1$s] URGENT: Your site may be down due to a failed update' );
2962                                 break;
2963
2964                         default :
2965                                 return;
2966                 }
2967
2968                 // If the auto update is not to the latest version, say that the current version of WP is available instead.
2969                 $version = 'success' === $type ? $core_update->current : $next_user_core_update->current;
2970                 $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $version );
2971
2972                 $body = '';
2973
2974                 switch ( $type ) {
2975                         case 'success' :
2976                                 $body .= sprintf( __( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ), home_url(), $core_update->current );
2977                                 $body .= "\n\n";
2978                                 if ( ! $newer_version_available )
2979                                         $body .= __( 'No further action is needed on your part.' ) . ' ';
2980
2981                                 // Can only reference the About screen if their update was successful.
2982                                 list( $about_version ) = explode( '-', $core_update->current, 2 );
2983                                 $body .= sprintf( __( "For more on version %s, see the About WordPress screen:" ), $about_version );
2984                                 $body .= "\n" . admin_url( 'about.php' );
2985
2986                                 if ( $newer_version_available ) {
2987                                         $body .= "\n\n" . sprintf( __( 'WordPress %s is also now available.' ), $next_user_core_update->current ) . ' ';
2988                                         $body .= __( 'Updating is easy and only takes a few moments:' );
2989                                         $body .= "\n" . network_admin_url( 'update-core.php' );
2990                                 }
2991
2992                                 break;
2993
2994                         case 'fail' :
2995                         case 'manual' :
2996                                 $body .= sprintf( __( 'Please update your site at %1$s to WordPress %2$s.' ), home_url(), $next_user_core_update->current );
2997
2998                                 $body .= "\n\n";
2999
3000                                 // Don't show this message if there is a newer version available.
3001                                 // Potential for confusion, and also not useful for them to know at this point.
3002                                 if ( 'fail' == $type && ! $newer_version_available )
3003                                         $body .= __( 'We tried but were unable to update your site automatically.' ) . ' ';
3004
3005                                 $body .= __( 'Updating is easy and only takes a few moments:' );
3006                                 $body .= "\n" . network_admin_url( 'update-core.php' );
3007                                 break;
3008
3009                         case 'critical' :
3010                                 if ( $newer_version_available )
3011                                         $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update WordPress to version %2$s.' ), home_url(), $core_update->current );
3012                                 else
3013                                         $body .= sprintf( __( 'Your site at %1$s experienced a critical failure while trying to update to the latest version of WordPress, %2$s.' ), home_url(), $core_update->current );
3014
3015                                 $body .= "\n\n" . __( "This means your site may be offline or broken. Don't panic; this can be fixed." );
3016
3017                                 $body .= "\n\n" . __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so:" );
3018                                 $body .= "\n" . network_admin_url( 'update-core.php' );
3019                                 break;
3020                 }
3021
3022                 $critical_support = 'critical' === $type && ! empty( $core_update->support_email );
3023                 if ( $critical_support ) {
3024                         // Support offer if available.
3025                         $body .= "\n\n" . sprintf( __( "The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working." ), $core_update->support_email );
3026                 } else {
3027                         // Add a note about the support forums.
3028                         $body .= "\n\n" . __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
3029                         $body .= "\n" . __( 'https://wordpress.org/support/' );
3030                 }
3031
3032                 // Updates are important!
3033                 if ( $type != 'success' || $newer_version_available ) {
3034                         $body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
3035                 }
3036
3037                 if ( $critical_support ) {
3038                         $body .= " " . __( "If you reach out to us, we'll also ensure you'll never have this problem again." );
3039                 }
3040
3041                 // If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
3042                 if ( $type == 'success' && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
3043                         $body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
3044                         $body .= "\n" . network_admin_url();
3045                 }
3046
3047                 $body .= "\n\n" . __( 'The WordPress Team' ) . "\n";
3048
3049                 if ( 'critical' == $type && is_wp_error( $result ) ) {
3050                         $body .= "\n***\n\n";
3051                         $body .= sprintf( __( 'Your site was running version %s.' ), $GLOBALS['wp_version'] );
3052                         $body .= ' ' . __( 'We have some data that describes the error your site encountered.' );
3053                         $body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
3054
3055                         // If we had a rollback and we're still critical, then the rollback failed too.
3056                         // Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
3057                         if ( 'rollback_was_required' == $result->get_error_code() )
3058                                 $errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
3059                         else
3060                                 $errors = array( $result );
3061
3062                         foreach ( $errors as $error ) {
3063                                 if ( ! is_wp_error( $error ) )
3064                                         continue;
3065                                 $error_code = $error->get_error_code();
3066                                 $body .= "\n\n" . sprintf( __( "Error code: %s" ), $error_code );
3067                                 if ( 'rollback_was_required' == $error_code )
3068                                         continue;
3069                                 if ( $error->get_error_message() )
3070                                         $body .= "\n" . $error->get_error_message();
3071                                 $error_data = $error->get_error_data();
3072                                 if ( $error_data )
3073                                         $body .= "\n" . implode( ', ', (array) $error_data );
3074                         }
3075                         $body .= "\n";
3076                 }
3077
3078                 $to  = get_site_option( 'admin_email' );
3079                 $headers = '';
3080
3081                 $email = compact( 'to', 'subject', 'body', 'headers' );
3082
3083                 /**
3084                  * Filter the email sent following an automatic background core update.
3085                  *
3086                  * @since 3.7.0
3087                  *
3088                  * @param array $email {
3089                  *     Array of email arguments that will be passed to wp_mail().
3090                  *
3091                  *     @type string $to      The email recipient. An array of emails
3092                  *                            can be returned, as handled by wp_mail().
3093                  *     @type string $subject The email's subject.
3094                  *     @type string $body    The email message body.
3095                  *     @type string $headers Any email headers, defaults to no headers.
3096                  * }
3097                  * @param string $type        The type of email being sent. Can be one of
3098                  *                            'success', 'fail', 'manual', 'critical'.
3099                  * @param object $core_update The update offer that was attempted.
3100                  * @param mixed  $result      The result for the core update. Can be WP_Error.
3101                  */
3102                 $email = apply_filters( 'auto_core_update_email', $email, $type, $core_update, $result );
3103
3104                 wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
3105         }
3106
3107         /**
3108          * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
3109          *
3110          * @since 3.7.0
3111          */
3112         protected function send_debug_email() {
3113                 $update_count = 0;
3114                 foreach ( $this->update_results as $type => $updates )
3115                         $update_count += count( $updates );
3116
3117                 $body = array();
3118                 $failures = 0;
3119
3120                 $body[] = sprintf( __( 'WordPress site: %s' ), network_home_url( '/' ) );
3121
3122                 // Core
3123                 if ( isset( $this->update_results['core'] ) ) {
3124                         $result = $this->update_results['core'][0];
3125                         if ( $result->result && ! is_wp_error( $result->result ) ) {
3126                                 $body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
3127                         } else {
3128                                 $body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
3129                                 $failures++;
3130                         }
3131                         $body[] = '';
3132                 }
3133
3134                 // Plugins, Themes, Translations
3135                 foreach ( array( 'plugin', 'theme', 'translation' ) as $type ) {
3136                         if ( ! isset( $this->update_results[ $type ] ) )
3137                                 continue;
3138                         $success_items = wp_list_filter( $this->update_results[ $type ], array( 'result' => true ) );
3139                         if ( $success_items ) {
3140                                 $messages = array(
3141                                         'plugin'      => __( 'The following plugins were successfully updated:' ),
3142                                         'theme'       => __( 'The following themes were successfully updated:' ),
3143                                         'translation' => __( 'The following translations were successfully updated:' ),
3144                                 );
3145
3146                                 $body[] = $messages[ $type ];
3147                                 foreach ( wp_list_pluck( $success_items, 'name' ) as $name ) {
3148                                         $body[] = ' * ' . sprintf( __( 'SUCCESS: %s' ), $name );
3149                                 }
3150                         }
3151                         if ( $success_items != $this->update_results[ $type ] ) {
3152                                 // Failed updates
3153                                 $messages = array(
3154                                         'plugin'      => __( 'The following plugins failed to update:' ),
3155                                         'theme'       => __( 'The following themes failed to update:' ),
3156                                         'translation' => __( 'The following translations failed to update:' ),
3157                                 );
3158
3159                                 $body[] = $messages[ $type ];
3160                                 foreach ( $this->update_results[ $type ] as $item ) {
3161                                         if ( ! $item->result || is_wp_error( $item->result ) ) {
3162                                                 $body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
3163                                                 $failures++;
3164                                         }
3165                                 }
3166                         }
3167                         $body[] = '';
3168                 }
3169
3170                 $site_title = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
3171                 if ( $failures ) {
3172                         $body[] = trim( __(
3173 "BETA TESTING?
3174 =============
3175
3176 This debugging email is sent when you are using a development version of WordPress.
3177
3178 If you think these failures might be due to a bug in WordPress, could you report it?
3179  * Open a thread in the support forums: https://wordpress.org/support/forum/alphabeta
3180  * Or, if you're comfortable writing a bug report: https://core.trac.wordpress.org/
3181
3182 Thanks! -- The WordPress Team" ) );
3183                         $body[] = '';
3184
3185                         $subject = sprintf( __( '[%s] There were failures during background updates' ), $site_title );
3186                 } else {
3187                         $subject = sprintf( __( '[%s] Background updates have finished' ), $site_title );
3188                 }
3189
3190                 $body[] = trim( __(
3191 'UPDATE LOG
3192 ==========' ) );
3193                 $body[] = '';
3194
3195                 foreach ( array( 'core', 'plugin', 'theme', 'translation' ) as $type ) {
3196                         if ( ! isset( $this->update_results[ $type ] ) )
3197                                 continue;
3198                         foreach ( $this->update_results[ $type ] as $update ) {
3199                                 $body[] = $update->name;
3200                                 $body[] = str_repeat( '-', strlen( $update->name ) );
3201                                 foreach ( $update->messages as $message )
3202                                         $body[] = "  " . html_entity_decode( str_replace( '&#8230;', '...', $message ) );
3203                                 if ( is_wp_error( $update->result ) ) {
3204                                         $results = array( 'update' => $update->result );
3205                                         // If we rolled back, we want to know an error that occurred then too.
3206                                         if ( 'rollback_was_required' === $update->result->get_error_code() )
3207                                                 $results = (array) $update->result->get_error_data();
3208                                         foreach ( $results as $result_type => $result ) {
3209                                                 if ( ! is_wp_error( $result ) )
3210                                                         continue;
3211
3212                                                 if ( 'rollback' === $result_type ) {
3213                                                         /* translators: 1: Error code, 2: Error message. */
3214                                                         $body[] = '  ' . sprintf( __( 'Rollback Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
3215                                                 } else {
3216                                                         /* translators: 1: Error code, 2: Error message. */
3217                                                         $body[] = '  ' . sprintf( __( 'Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
3218                                                 }
3219
3220                                                 if ( $result->get_error_data() )
3221                                                         $body[] = '         ' . implode( ', ', (array) $result->get_error_data() );
3222                                         }
3223                                 }
3224                                 $body[] = '';
3225                         }
3226                 }
3227
3228                 $email = array(
3229                         'to'      => get_site_option( 'admin_email' ),
3230                         'subject' => $subject,
3231                         'body'    => implode( "\n", $body ),
3232                         'headers' => ''
3233                 );
3234
3235                 /**
3236                  * Filter the debug email that can be sent following an automatic
3237                  * background core update.
3238                  *
3239                  * @since 3.8.0
3240                  *
3241                  * @param array $email {
3242                  *     Array of email arguments that will be passed to wp_mail().
3243                  *
3244                  *     @type string $to      The email recipient. An array of emails
3245                  *                           can be returned, as handled by wp_mail().
3246                  *     @type string $subject Email subject.
3247                  *     @type string $body    Email message body.
3248                  *     @type string $headers Any email headers. Default empty.
3249                  * }
3250                  * @param int   $failures The number of failures encountered while upgrading.
3251                  * @param mixed $results  The results of all attempted updates.
3252                  */
3253                 $email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
3254
3255                 wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
3256         }
3257 }