Wordpress 3.6
[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 http://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 /**
15  * WordPress Upgrader class for Upgrading/Installing a local set of files via the Filesystem Abstraction classes from a Zip file.
16  *
17  * @TODO More Detailed docs, for methods as well.
18  *
19  * @package WordPress
20  * @subpackage Upgrader
21  * @since 2.8.0
22  */
23 class WP_Upgrader {
24         var $strings = array();
25         var $skin = null;
26         var $result = array();
27
28         function __construct($skin = null) {
29                 if ( null == $skin )
30                         $this->skin = new WP_Upgrader_Skin();
31                 else
32                         $this->skin = $skin;
33         }
34
35         function init() {
36                 $this->skin->set_upgrader($this);
37                 $this->generic_strings();
38         }
39
40         function generic_strings() {
41                 $this->strings['bad_request'] = __('Invalid Data provided.');
42                 $this->strings['fs_unavailable'] = __('Could not access filesystem.');
43                 $this->strings['fs_error'] = __('Filesystem error.');
44                 $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress Root directory.');
45                 $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress Content directory (wp-content).');
46                 $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress Plugin directory.');
47                 $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress Theme directory.');
48                 /* translators: %s: directory name */
49                 $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).');
50
51                 $this->strings['download_failed'] = __('Download failed.');
52                 $this->strings['installing_package'] = __('Installing the latest version&#8230;');
53                 $this->strings['no_files'] = __('The package contains no files.');
54                 $this->strings['folder_exists'] = __('Destination folder already exists.');
55                 $this->strings['mkdir_failed'] = __('Could not create directory.');
56                 $this->strings['incompatible_archive'] = __('The package could not be installed.');
57
58                 $this->strings['maintenance_start'] = __('Enabling Maintenance mode&#8230;');
59                 $this->strings['maintenance_end'] = __('Disabling Maintenance mode&#8230;');
60         }
61
62         function fs_connect( $directories = array() ) {
63                 global $wp_filesystem;
64
65                 if ( false === ($credentials = $this->skin->request_filesystem_credentials()) )
66                         return false;
67
68                 if ( ! WP_Filesystem($credentials) ) {
69                         $error = true;
70                         if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() )
71                                 $error = $wp_filesystem->errors;
72                         $this->skin->request_filesystem_credentials($error); //Failed to connect, Error and request again
73                         return false;
74                 }
75
76                 if ( ! is_object($wp_filesystem) )
77                         return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] );
78
79                 if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
80                         return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors);
81
82                 foreach ( (array)$directories as $dir ) {
83                         switch ( $dir ) {
84                                 case ABSPATH:
85                                         if ( ! $wp_filesystem->abspath() )
86                                                 return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']);
87                                         break;
88                                 case WP_CONTENT_DIR:
89                                         if ( ! $wp_filesystem->wp_content_dir() )
90                                                 return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']);
91                                         break;
92                                 case WP_PLUGIN_DIR:
93                                         if ( ! $wp_filesystem->wp_plugins_dir() )
94                                                 return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']);
95                                         break;
96                                 case WP_CONTENT_DIR . '/themes':
97                                         if ( ! $wp_filesystem->find_folder(WP_CONTENT_DIR . '/themes') )
98                                                 return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']);
99                                         break;
100                                 default:
101                                         if ( ! $wp_filesystem->find_folder($dir) )
102                                                 return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
103                                         break;
104                         }
105                 }
106                 return true;
107         } //end fs_connect();
108
109         function download_package($package) {
110
111                 if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote?
112                         return $package; //must be a local file..
113
114                 if ( empty($package) )
115                         return new WP_Error('no_package', $this->strings['no_package']);
116
117                 $this->skin->feedback('downloading_package', $package);
118
119                 $download_file = download_url($package);
120
121                 if ( is_wp_error($download_file) )
122                         return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message());
123
124                 return $download_file;
125         }
126
127         function unpack_package($package, $delete_package = true) {
128                 global $wp_filesystem;
129
130                 $this->skin->feedback('unpack_package');
131
132                 $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
133
134                 //Clean up contents of upgrade directory beforehand.
135                 $upgrade_files = $wp_filesystem->dirlist($upgrade_folder);
136                 if ( !empty($upgrade_files) ) {
137                         foreach ( $upgrade_files as $file )
138                                 $wp_filesystem->delete($upgrade_folder . $file['name'], true);
139                 }
140
141                 //We need a working directory
142                 $working_dir = $upgrade_folder . basename($package, '.zip');
143
144                 // Clean up working directory
145                 if ( $wp_filesystem->is_dir($working_dir) )
146                         $wp_filesystem->delete($working_dir, true);
147
148                 // Unzip package to working directory
149                 $result = unzip_file($package, $working_dir); //TODO optimizations, Copy when Move/Rename would suffice?
150
151                 // Once extracted, delete the package if required.
152                 if ( $delete_package )
153                         unlink($package);
154
155                 if ( is_wp_error($result) ) {
156                         $wp_filesystem->delete($working_dir, true);
157                         if ( 'incompatible_archive' == $result->get_error_code() ) {
158                                 return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
159                         }
160                         return $result;
161                 }
162
163                 return $working_dir;
164         }
165
166         function install_package($args = array()) {
167                 global $wp_filesystem;
168                 $defaults = array( 'source' => '', 'destination' => '', //Please always pass these
169                                                 'clear_destination' => false, 'clear_working' => false,
170                                                 'abort_if_destination_exists' => true,
171                                                 'hook_extra' => array());
172
173                 $args = wp_parse_args($args, $defaults);
174                 extract($args);
175
176                 @set_time_limit( 300 );
177
178                 if ( empty($source) || empty($destination) )
179                         return new WP_Error('bad_request', $this->strings['bad_request']);
180
181                 $this->skin->feedback('installing_package');
182
183                 $res = apply_filters('upgrader_pre_install', true, $hook_extra);
184                 if ( is_wp_error($res) )
185                         return $res;
186
187                 //Retain the Original source and destinations
188                 $remote_source = $source;
189                 $local_destination = $destination;
190
191                 $source_files = array_keys( $wp_filesystem->dirlist($remote_source) );
192                 $remote_destination = $wp_filesystem->find_folder($local_destination);
193
194                 //Locate which directory to copy to the new folder, This is based on the actual folder holding the files.
195                 if ( 1 == count($source_files) && $wp_filesystem->is_dir( trailingslashit($source) . $source_files[0] . '/') ) //Only one folder? Then we want its contents.
196                         $source = trailingslashit($source) . trailingslashit($source_files[0]);
197                 elseif ( count($source_files) == 0 )
198                         return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $this->strings['no_files'] ); //There are no files?
199                 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.
200                         $source = trailingslashit($source);
201
202                 //Hook ability to change the source file location..
203                 $source = apply_filters('upgrader_source_selection', $source, $remote_source, $this);
204                 if ( is_wp_error($source) )
205                         return $source;
206
207                 //Has the source location changed? If so, we need a new source_files list.
208                 if ( $source !== $remote_source )
209                         $source_files = array_keys( $wp_filesystem->dirlist($source) );
210
211                 //Protection against deleting files in any important base directories.
212                 if ( in_array( $destination, array(ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes') ) ) {
213                         $remote_destination = trailingslashit($remote_destination) . trailingslashit(basename($source));
214                         $destination = trailingslashit($destination) . trailingslashit(basename($source));
215                 }
216
217                 if ( $clear_destination ) {
218                         //We're going to clear the destination if there's something there
219                         $this->skin->feedback('remove_old');
220                         $removed = true;
221                         if ( $wp_filesystem->exists($remote_destination) )
222                                 $removed = $wp_filesystem->delete($remote_destination, true);
223                         $removed = apply_filters('upgrader_clear_destination', $removed, $local_destination, $remote_destination, $hook_extra);
224
225                         if ( is_wp_error($removed) )
226                                 return $removed;
227                         else if ( ! $removed )
228                                 return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
229                 } elseif ( $abort_if_destination_exists && $wp_filesystem->exists($remote_destination) ) {
230                         //If we're not clearing the destination folder and something exists there already, Bail.
231                         //But first check to see if there are actually any files in the folder.
232                         $_files = $wp_filesystem->dirlist($remote_destination);
233                         if ( ! empty($_files) ) {
234                                 $wp_filesystem->delete($remote_source, true); //Clear out the source files.
235                                 return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination );
236                         }
237                 }
238
239                 //Create destination if needed
240                 if ( !$wp_filesystem->exists($remote_destination) )
241                         if ( !$wp_filesystem->mkdir($remote_destination, FS_CHMOD_DIR) )
242                                 return new WP_Error('mkdir_failed', $this->strings['mkdir_failed'], $remote_destination);
243
244                 // Copy new version of item into place.
245                 $result = copy_dir($source, $remote_destination);
246                 if ( is_wp_error($result) ) {
247                         if ( $clear_working )
248                                 $wp_filesystem->delete($remote_source, true);
249                         return $result;
250                 }
251
252                 //Clear the Working folder?
253                 if ( $clear_working )
254                         $wp_filesystem->delete($remote_source, true);
255
256                 $destination_name = basename( str_replace($local_destination, '', $destination) );
257                 if ( '.' == $destination_name )
258                         $destination_name = '';
259
260                 $this->result = compact('local_source', 'source', 'source_name', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination', 'delete_source_dir');
261
262                 $res = apply_filters('upgrader_post_install', true, $hook_extra, $this->result);
263                 if ( is_wp_error($res) ) {
264                         $this->result = $res;
265                         return $res;
266                 }
267
268                 //Bombard the calling function will all the info which we've just used.
269                 return $this->result;
270         }
271
272         function run($options) {
273
274                 $defaults = array(      'package' => '', //Please always pass this.
275                                                         'destination' => '', //And this
276                                                         'clear_destination' => false,
277                                                         'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please
278                                                         'clear_working' => true,
279                                                         'is_multi' => false,
280                                                         'hook_extra' => array() //Pass any extra $hook_extra args here, this will be passed to any hooked filters.
281                                                 );
282
283                 $options = wp_parse_args($options, $defaults);
284                 extract($options);
285
286                 //Connect to the Filesystem first.
287                 $res = $this->fs_connect( array(WP_CONTENT_DIR, $destination) );
288                 if ( ! $res ) //Mainly for non-connected filesystem.
289                         return false;
290
291                 if ( is_wp_error($res) ) {
292                         $this->skin->error($res);
293                         return $res;
294                 }
295
296                 if ( !$is_multi ) // call $this->header separately if running multiple times
297                         $this->skin->header();
298
299                 $this->skin->before();
300
301                 //Download the package (Note, This just returns the filename of the file if the package is a local file)
302                 $download = $this->download_package( $package );
303                 if ( is_wp_error($download) ) {
304                         $this->skin->error($download);
305                         $this->skin->after();
306                         return $download;
307                 }
308
309                 $delete_package = ($download != $package); // Do not delete a "local" file
310
311                 //Unzips the file into a temporary directory
312                 $working_dir = $this->unpack_package( $download, $delete_package );
313                 if ( is_wp_error($working_dir) ) {
314                         $this->skin->error($working_dir);
315                         $this->skin->after();
316                         return $working_dir;
317                 }
318
319                 //With the given options, this installs it to the destination directory.
320                 $result = $this->install_package( array(
321                                                                                         'source' => $working_dir,
322                                                                                         'destination' => $destination,
323                                                                                         'clear_destination' => $clear_destination,
324                                                                                         'abort_if_destination_exists' => $abort_if_destination_exists,
325                                                                                         'clear_working' => $clear_working,
326                                                                                         'hook_extra' => $hook_extra
327                                                                                 ) );
328                 $this->skin->set_result($result);
329                 if ( is_wp_error($result) ) {
330                         $this->skin->error($result);
331                         $this->skin->feedback('process_failed');
332                 } else {
333                         //Install Succeeded
334                         $this->skin->feedback('process_success');
335                 }
336                 $this->skin->after();
337
338                 if ( !$is_multi )
339                         $this->skin->footer();
340
341                 return $result;
342         }
343
344         function maintenance_mode($enable = false) {
345                 global $wp_filesystem;
346                 $file = $wp_filesystem->abspath() . '.maintenance';
347                 if ( $enable ) {
348                         $this->skin->feedback('maintenance_start');
349                         // Create maintenance file to signal that we are upgrading
350                         $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
351                         $wp_filesystem->delete($file);
352                         $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE);
353                 } else if ( !$enable && $wp_filesystem->exists($file) ) {
354                         $this->skin->feedback('maintenance_end');
355                         $wp_filesystem->delete($file);
356                 }
357         }
358
359 }
360
361 /**
362  * Plugin Upgrader class for WordPress Plugins, It is designed to upgrade/install plugins from a local zip, remote zip URL, or uploaded zip file.
363  *
364  * @TODO More Detailed docs, for methods as well.
365  *
366  * @package WordPress
367  * @subpackage Upgrader
368  * @since 2.8.0
369  */
370 class Plugin_Upgrader extends WP_Upgrader {
371
372         var $result;
373         var $bulk = false;
374         var $show_before = '';
375
376         function upgrade_strings() {
377                 $this->strings['up_to_date'] = __('The plugin is at the latest version.');
378                 $this->strings['no_package'] = __('Update package not available.');
379                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
380                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
381                 $this->strings['remove_old'] = __('Removing the old version of the plugin&#8230;');
382                 $this->strings['remove_old_failed'] = __('Could not remove the old plugin.');
383                 $this->strings['process_failed'] = __('Plugin update failed.');
384                 $this->strings['process_success'] = __('Plugin updated successfully.');
385         }
386
387         function install_strings() {
388                 $this->strings['no_package'] = __('Install package not available.');
389                 $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
390                 $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
391                 $this->strings['installing_package'] = __('Installing the plugin&#8230;');
392                 $this->strings['no_files'] = __('The plugin contains no files.');
393                 $this->strings['process_failed'] = __('Plugin install failed.');
394                 $this->strings['process_success'] = __('Plugin installed successfully.');
395         }
396
397         function install($package) {
398
399                 $this->init();
400                 $this->install_strings();
401
402                 add_filter('upgrader_source_selection', array(&$this, 'check_package') );
403
404                 $this->run(array(
405                                         'package' => $package,
406                                         'destination' => WP_PLUGIN_DIR,
407                                         'clear_destination' => false, //Do not overwrite files.
408                                         'clear_working' => true,
409                                         'hook_extra' => array()
410                                         ));
411
412                 remove_filter('upgrader_source_selection', array(&$this, 'check_package') );
413
414                 if ( ! $this->result || is_wp_error($this->result) )
415                         return $this->result;
416
417                 // Force refresh of plugin update information
418                 delete_site_transient('update_plugins');
419                 wp_cache_delete( 'plugins', 'plugins' );
420                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'install', 'type' => 'plugin' ), $package );
421
422                 return true;
423         }
424
425         function upgrade($plugin) {
426
427                 $this->init();
428                 $this->upgrade_strings();
429
430                 $current = get_site_transient( 'update_plugins' );
431                 if ( !isset( $current->response[ $plugin ] ) ) {
432                         $this->skin->before();
433                         $this->skin->set_result(false);
434                         $this->skin->error('up_to_date');
435                         $this->skin->after();
436                         return false;
437                 }
438
439                 // Get the URL to the zip file
440                 $r = $current->response[ $plugin ];
441
442                 add_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'), 10, 2);
443                 add_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'), 10, 4);
444                 //'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.
445
446                 $this->run(array(
447                                         'package' => $r->package,
448                                         'destination' => WP_PLUGIN_DIR,
449                                         'clear_destination' => true,
450                                         'clear_working' => true,
451                                         'hook_extra' => array(
452                                                                 'plugin' => $plugin
453                                         )
454                                 ));
455
456                 // Cleanup our hooks, in case something else does a upgrade on this connection.
457                 remove_filter('upgrader_pre_install', array(&$this, 'deactivate_plugin_before_upgrade'));
458                 remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'));
459
460                 if ( ! $this->result || is_wp_error($this->result) )
461                         return $this->result;
462
463                 // Force refresh of plugin update information
464                 delete_site_transient('update_plugins');
465                 wp_cache_delete( 'plugins', 'plugins' );
466                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'plugin' ), $plugin );
467         }
468
469         function bulk_upgrade($plugins) {
470
471                 $this->init();
472                 $this->bulk = true;
473                 $this->upgrade_strings();
474
475                 $current = get_site_transient( 'update_plugins' );
476
477                 add_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'), 10, 4);
478
479                 $this->skin->header();
480
481                 // Connect to the Filesystem first.
482                 $res = $this->fs_connect( array(WP_CONTENT_DIR, WP_PLUGIN_DIR) );
483                 if ( ! $res ) {
484                         $this->skin->footer();
485                         return false;
486                 }
487
488                 $this->skin->bulk_header();
489
490                 // Only start maintenance mode if running in Multisite OR the plugin is in use
491                 $maintenance = is_multisite(); // @TODO: This should only kick in for individual sites if at all possible.
492                 foreach ( $plugins as $plugin )
493                         $maintenance = $maintenance || (is_plugin_active($plugin) && isset($current->response[ $plugin ]) ); // Only activate Maintenance mode if a plugin is active AND has an update available
494                 if ( $maintenance )
495                         $this->maintenance_mode(true);
496
497                 $results = array();
498
499                 $this->update_count = count($plugins);
500                 $this->update_current = 0;
501                 foreach ( $plugins as $plugin ) {
502                         $this->update_current++;
503                         $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
504
505                         if ( !isset( $current->response[ $plugin ] ) ) {
506                                 $this->skin->set_result(true);
507                                 $this->skin->before();
508                                 $this->skin->feedback('up_to_date');
509                                 $this->skin->after();
510                                 $results[$plugin] = true;
511                                 continue;
512                         }
513
514                         // Get the URL to the zip file
515                         $r = $current->response[ $plugin ];
516
517                         $this->skin->plugin_active = is_plugin_active($plugin);
518
519                         $result = $this->run(array(
520                                                 'package' => $r->package,
521                                                 'destination' => WP_PLUGIN_DIR,
522                                                 'clear_destination' => true,
523                                                 'clear_working' => true,
524                                                 'is_multi' => true,
525                                                 'hook_extra' => array(
526                                                                         'plugin' => $plugin
527                                                 )
528                                         ));
529
530                         $results[$plugin] = $this->result;
531
532                         // Prevent credentials auth screen from displaying multiple times
533                         if ( false === $result )
534                                 break;
535                 } //end foreach $plugins
536
537                 $this->maintenance_mode(false);
538
539                 $this->skin->bulk_footer();
540
541                 $this->skin->footer();
542
543                 // Cleanup our hooks, in case something else does a upgrade on this connection.
544                 remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_plugin'));
545
546                 // Force refresh of plugin update information
547                 delete_site_transient('update_plugins');
548                 wp_cache_delete( 'plugins', 'plugins' );
549                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'plugin', 'bulk' => true ), $plugins );
550
551                 return $results;
552         }
553
554         function check_package($source) {
555                 global $wp_filesystem;
556
557                 if ( is_wp_error($source) )
558                         return $source;
559
560                 $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
561                 if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, lets not prevent installation.
562                         return $source;
563
564                 // Check the folder contains at least 1 valid plugin.
565                 $plugins_found = false;
566                 foreach ( glob( $working_directory . '*.php' ) as $file ) {
567                         $info = get_plugin_data($file, false, false);
568                         if ( !empty( $info['Name'] ) ) {
569                                 $plugins_found = true;
570                                 break;
571                         }
572                 }
573
574                 if ( ! $plugins_found )
575                         return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('No valid plugins were found.') );
576
577                 return $source;
578         }
579
580         //return plugin info.
581         function plugin_info() {
582                 if ( ! is_array($this->result) )
583                         return false;
584                 if ( empty($this->result['destination_name']) )
585                         return false;
586
587                 $plugin = get_plugins('/' . $this->result['destination_name']); //Ensure to pass with leading slash
588                 if ( empty($plugin) )
589                         return false;
590
591                 $pluginfiles = array_keys($plugin); //Assume the requested plugin is the first in the list
592
593                 return $this->result['destination_name'] . '/' . $pluginfiles[0];
594         }
595
596         //Hooked to pre_install
597         function deactivate_plugin_before_upgrade($return, $plugin) {
598
599                 if ( is_wp_error($return) ) //Bypass.
600                         return $return;
601
602                 $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
603                 if ( empty($plugin) )
604                         return new WP_Error('bad_request', $this->strings['bad_request']);
605
606                 if ( is_plugin_active($plugin) ) {
607                         //Deactivate the plugin silently, Prevent deactivation hooks from running.
608                         deactivate_plugins($plugin, true);
609                 }
610         }
611
612         //Hooked to upgrade_clear_destination
613         function delete_old_plugin($removed, $local_destination, $remote_destination, $plugin) {
614                 global $wp_filesystem;
615
616                 if ( is_wp_error($removed) )
617                         return $removed; //Pass errors through.
618
619                 $plugin = isset($plugin['plugin']) ? $plugin['plugin'] : '';
620                 if ( empty($plugin) )
621                         return new WP_Error('bad_request', $this->strings['bad_request']);
622
623                 $plugins_dir = $wp_filesystem->wp_plugins_dir();
624                 $this_plugin_dir = trailingslashit( dirname($plugins_dir . $plugin) );
625
626                 if ( ! $wp_filesystem->exists($this_plugin_dir) ) //If it's already vanished.
627                         return $removed;
628
629                 // If plugin is in its own directory, recursively delete the directory.
630                 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
631                         $deleted = $wp_filesystem->delete($this_plugin_dir, true);
632                 else
633                         $deleted = $wp_filesystem->delete($plugins_dir . $plugin);
634
635                 if ( ! $deleted )
636                         return new WP_Error('remove_old_failed', $this->strings['remove_old_failed']);
637
638                 return true;
639         }
640 }
641
642 /**
643  * Theme Upgrader class for WordPress Themes, It is designed to upgrade/install themes from a local zip, remote zip URL, or uploaded zip file.
644  *
645  * @TODO More Detailed docs, for methods as well.
646  *
647  * @package WordPress
648  * @subpackage Upgrader
649  * @since 2.8.0
650  */
651 class Theme_Upgrader extends WP_Upgrader {
652
653         var $result;
654         var $bulk = false;
655
656         function upgrade_strings() {
657                 $this->strings['up_to_date'] = __('The theme is at the latest version.');
658                 $this->strings['no_package'] = __('Update package not available.');
659                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
660                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
661                 $this->strings['remove_old'] = __('Removing the old version of the theme&#8230;');
662                 $this->strings['remove_old_failed'] = __('Could not remove the old theme.');
663                 $this->strings['process_failed'] = __('Theme update failed.');
664                 $this->strings['process_success'] = __('Theme updated successfully.');
665         }
666
667         function install_strings() {
668                 $this->strings['no_package'] = __('Install package not available.');
669                 $this->strings['downloading_package'] = __('Downloading install package from <span class="code">%s</span>&#8230;');
670                 $this->strings['unpack_package'] = __('Unpacking the package&#8230;');
671                 $this->strings['installing_package'] = __('Installing the theme&#8230;');
672                 $this->strings['no_files'] = __('The theme contains no files.');
673                 $this->strings['process_failed'] = __('Theme install failed.');
674                 $this->strings['process_success'] = __('Theme installed successfully.');
675                 /* translators: 1: theme name, 2: version */
676                 $this->strings['process_success_specific'] = __('Successfully installed the theme <strong>%1$s %2$s</strong>.');
677                 $this->strings['parent_theme_search'] = __('This theme requires a parent theme. Checking if it is installed&#8230;');
678                 /* translators: 1: theme name, 2: version */
679                 $this->strings['parent_theme_prepare_install'] = __('Preparing to install <strong>%1$s %2$s</strong>&#8230;');
680                 /* translators: 1: theme name, 2: version */
681                 $this->strings['parent_theme_currently_installed'] = __('The parent theme, <strong>%1$s %2$s</strong>, is currently installed.');
682                 /* translators: 1: theme name, 2: version */
683                 $this->strings['parent_theme_install_success'] = __('Successfully installed the parent theme, <strong>%1$s %2$s</strong>.');
684                 $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.');
685         }
686
687         function check_parent_theme_filter($install_result, $hook_extra, $child_result) {
688                 // Check to see if we need to install a parent theme
689                 $theme_info = $this->theme_info();
690
691                 if ( ! $theme_info->parent() )
692                         return $install_result;
693
694                 $this->skin->feedback( 'parent_theme_search' );
695
696                 if ( ! $theme_info->parent()->errors() ) {
697                         $this->skin->feedback( 'parent_theme_currently_installed', $theme_info->parent()->display('Name'), $theme_info->parent()->display('Version') );
698                         // We already have the theme, fall through.
699                         return $install_result;
700                 }
701
702                 // We don't have the parent theme, lets install it
703                 $api = themes_api('theme_information', array('slug' => $theme_info->get('Template'), 'fields' => array('sections' => false, 'tags' => false) ) ); //Save on a bit of bandwidth.
704
705                 if ( ! $api || is_wp_error($api) ) {
706                         $this->skin->feedback( 'parent_theme_not_found', $theme_info->get('Template') );
707                         // Don't show activate or preview actions after install
708                         add_filter('install_theme_complete_actions', array(&$this, 'hide_activate_preview_actions') );
709                         return $install_result;
710                 }
711
712                 // Backup required data we're going to override:
713                 $child_api = $this->skin->api;
714                 $child_success_message = $this->strings['process_success'];
715
716                 // Override them
717                 $this->skin->api = $api;
718                 $this->strings['process_success_specific'] = $this->strings['parent_theme_install_success'];//, $api->name, $api->version);
719
720                 $this->skin->feedback('parent_theme_prepare_install', $api->name, $api->version);
721
722                 add_filter('install_theme_complete_actions', '__return_false', 999); // Don't show any actions after installing the theme.
723
724                 // Install the parent theme
725                 $parent_result = $this->run( array(
726                         'package' => $api->download_link,
727                         'destination' => WP_CONTENT_DIR . '/themes',
728                         'clear_destination' => false, //Do not overwrite files.
729                         'clear_working' => true
730                 ) );
731
732                 if ( is_wp_error($parent_result) )
733                         add_filter('install_theme_complete_actions', array(&$this, 'hide_activate_preview_actions') );
734
735                 // Start cleaning up after the parents installation
736                 remove_filter('install_theme_complete_actions', '__return_false', 999);
737
738                 // Reset child's result and data
739                 $this->result = $child_result;
740                 $this->skin->api = $child_api;
741                 $this->strings['process_success'] = $child_success_message;
742
743                 return $install_result;
744         }
745
746         function hide_activate_preview_actions($actions) {
747                 unset($actions['activate'], $actions['preview']);
748                 return $actions;
749         }
750
751         function install($package) {
752
753                 $this->init();
754                 $this->install_strings();
755
756                 add_filter('upgrader_source_selection', array(&$this, 'check_package') );
757                 add_filter('upgrader_post_install', array(&$this, 'check_parent_theme_filter'), 10, 3);
758
759                 $options = array(
760                                                 'package' => $package,
761                                                 'destination' => WP_CONTENT_DIR . '/themes',
762                                                 'clear_destination' => false, //Do not overwrite files.
763                                                 'clear_working' => true
764                                                 );
765
766                 $this->run($options);
767
768                 remove_filter('upgrader_source_selection', array(&$this, 'check_package') );
769                 remove_filter('upgrader_post_install', array(&$this, 'check_parent_theme_filter'));
770
771                 if ( ! $this->result || is_wp_error($this->result) )
772                         return $this->result;
773
774                 // Force refresh of theme update information
775                 wp_clean_themes_cache();
776                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'install', 'type' => 'theme' ), $package );
777
778                 return true;
779         }
780
781         function upgrade($theme) {
782
783                 $this->init();
784                 $this->upgrade_strings();
785
786                 // Is an update available?
787                 $current = get_site_transient( 'update_themes' );
788                 if ( !isset( $current->response[ $theme ] ) ) {
789                         $this->skin->before();
790                         $this->skin->set_result(false);
791                         $this->skin->error('up_to_date');
792                         $this->skin->after();
793                         return false;
794                 }
795
796                 $r = $current->response[ $theme ];
797
798                 add_filter('upgrader_pre_install', array(&$this, 'current_before'), 10, 2);
799                 add_filter('upgrader_post_install', array(&$this, 'current_after'), 10, 2);
800                 add_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'), 10, 4);
801
802                 $options = array(
803                                                 'package' => $r['package'],
804                                                 'destination' => WP_CONTENT_DIR . '/themes',
805                                                 'clear_destination' => true,
806                                                 'clear_working' => true,
807                                                 'hook_extra' => array(
808                                                                                         'theme' => $theme
809                                                                                         )
810                                                 );
811
812                 $this->run($options);
813
814                 remove_filter('upgrader_pre_install', array(&$this, 'current_before'));
815                 remove_filter('upgrader_post_install', array(&$this, 'current_after'));
816                 remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'));
817
818                 if ( ! $this->result || is_wp_error($this->result) )
819                         return $this->result;
820
821                 // Force refresh of theme update information
822                 wp_clean_themes_cache();
823                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'theme' ), $theme );
824
825                 return true;
826         }
827
828         function bulk_upgrade($themes) {
829
830                 $this->init();
831                 $this->bulk = true;
832                 $this->upgrade_strings();
833
834                 $current = get_site_transient( 'update_themes' );
835
836                 add_filter('upgrader_pre_install', array(&$this, 'current_before'), 10, 2);
837                 add_filter('upgrader_post_install', array(&$this, 'current_after'), 10, 2);
838                 add_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'), 10, 4);
839
840                 $this->skin->header();
841
842                 // Connect to the Filesystem first.
843                 $res = $this->fs_connect( array(WP_CONTENT_DIR) );
844                 if ( ! $res ) {
845                         $this->skin->footer();
846                         return false;
847                 }
848
849                 $this->skin->bulk_header();
850
851                 // Only start maintenance mode if running in Multisite OR the theme is in use
852                 $maintenance = is_multisite(); // @TODO: This should only kick in for individual sites if at all possible.
853                 foreach ( $themes as $theme )
854                         $maintenance = $maintenance || $theme == get_stylesheet() || $theme == get_template();
855                 if ( $maintenance )
856                         $this->maintenance_mode(true);
857
858                 $results = array();
859
860                 $this->update_count = count($themes);
861                 $this->update_current = 0;
862                 foreach ( $themes as $theme ) {
863                         $this->update_current++;
864
865                         $this->skin->theme_info = $this->theme_info($theme);
866
867                         if ( !isset( $current->response[ $theme ] ) ) {
868                                 $this->skin->set_result(true);
869                                 $this->skin->before();
870                                 $this->skin->feedback('up_to_date');
871                                 $this->skin->after();
872                                 $results[$theme] = true;
873                                 continue;
874                         }
875
876                         // Get the URL to the zip file
877                         $r = $current->response[ $theme ];
878
879                         $options = array(
880                                                         'package' => $r['package'],
881                                                         'destination' => WP_CONTENT_DIR . '/themes',
882                                                         'clear_destination' => true,
883                                                         'clear_working' => true,
884                                                         'hook_extra' => array(
885                                                                                                 'theme' => $theme
886                                                                                                 )
887                                                         );
888
889                         $result = $this->run($options);
890
891                         $results[$theme] = $this->result;
892
893                         // Prevent credentials auth screen from displaying multiple times
894                         if ( false === $result )
895                                 break;
896                 } //end foreach $plugins
897
898                 $this->maintenance_mode(false);
899
900                 $this->skin->bulk_footer();
901
902                 $this->skin->footer();
903
904                 // Cleanup our hooks, in case something else does a upgrade on this connection.
905                 remove_filter('upgrader_pre_install', array(&$this, 'current_before'));
906                 remove_filter('upgrader_post_install', array(&$this, 'current_after'));
907                 remove_filter('upgrader_clear_destination', array(&$this, 'delete_old_theme'));
908
909                 // Force refresh of theme update information
910                 wp_clean_themes_cache();
911                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'theme', 'bulk' => true ), $themes );
912
913                 return $results;
914         }
915
916         function check_package($source) {
917                 global $wp_filesystem;
918
919                 if ( is_wp_error($source) )
920                         return $source;
921
922                 // Check the folder contains a valid theme
923                 $working_directory = str_replace( $wp_filesystem->wp_content_dir(), trailingslashit(WP_CONTENT_DIR), $source);
924                 if ( ! is_dir($working_directory) ) // Sanity check, if the above fails, lets not prevent installation.
925                         return $source;
926
927                 // A proper archive should have a style.css file in the single subdirectory
928                 if ( ! file_exists( $working_directory . 'style.css' ) )
929                         return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('The theme is missing the <code>style.css</code> stylesheet.') );
930
931                 $info = get_file_data( $working_directory . 'style.css', array( 'Name' => 'Theme Name', 'Template' => 'Template' ) );
932
933                 if ( empty( $info['Name'] ) )
934                         return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __("The <code>style.css</code> stylesheet doesn't contain a valid theme header.") );
935
936                 // If it's not a child theme, it must have at least an index.php to be legit.
937                 if ( empty( $info['Template'] ) && ! file_exists( $working_directory . 'index.php' ) )
938                         return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], __('The theme is missing the <code>index.php</code> file.') );
939
940                 return $source;
941         }
942
943         function current_before($return, $theme) {
944
945                 if ( is_wp_error($return) )
946                         return $return;
947
948                 $theme = isset($theme['theme']) ? $theme['theme'] : '';
949
950                 if ( $theme != get_stylesheet() ) //If not current
951                         return $return;
952                 //Change to maintenance mode now.
953                 if ( ! $this->bulk )
954                         $this->maintenance_mode(true);
955
956                 return $return;
957         }
958
959         function current_after($return, $theme) {
960                 if ( is_wp_error($return) )
961                         return $return;
962
963                 $theme = isset($theme['theme']) ? $theme['theme'] : '';
964
965                 if ( $theme != get_stylesheet() ) // If not current
966                         return $return;
967
968                 // Ensure stylesheet name hasn't changed after the upgrade:
969                 if ( $theme == get_stylesheet() && $theme != $this->result['destination_name'] ) {
970                         wp_clean_themes_cache();
971                         $stylesheet = $this->result['destination_name'];
972                         switch_theme( $stylesheet );
973                 }
974
975                 //Time to remove maintenance mode
976                 if ( ! $this->bulk )
977                         $this->maintenance_mode(false);
978                 return $return;
979         }
980
981         function delete_old_theme($removed, $local_destination, $remote_destination, $theme) {
982                 global $wp_filesystem;
983
984                 $theme = isset($theme['theme']) ? $theme['theme'] : '';
985
986                 if ( is_wp_error($removed) || empty($theme) )
987                         return $removed; //Pass errors through.
988
989                 $themes_dir = $wp_filesystem->wp_themes_dir();
990                 if ( $wp_filesystem->exists( trailingslashit($themes_dir) . $theme ) )
991                         if ( ! $wp_filesystem->delete( trailingslashit($themes_dir) . $theme, true ) )
992                                 return false;
993                 return true;
994         }
995
996         function theme_info($theme = null) {
997
998                 if ( empty($theme) ) {
999                         if ( !empty($this->result['destination_name']) )
1000                                 $theme = $this->result['destination_name'];
1001                         else
1002                                 return false;
1003                 }
1004                 return wp_get_theme( $theme, WP_CONTENT_DIR . '/themes/' );
1005         }
1006
1007 }
1008
1009 /**
1010  * Core Upgrader class for WordPress. It allows for WordPress to upgrade itself in combination with the wp-admin/includes/update-core.php file
1011  *
1012  * @TODO More Detailed docs, for methods as well.
1013  *
1014  * @package WordPress
1015  * @subpackage Upgrader
1016  * @since 2.8.0
1017  */
1018 class Core_Upgrader extends WP_Upgrader {
1019
1020         function upgrade_strings() {
1021                 $this->strings['up_to_date'] = __('WordPress is at the latest version.');
1022                 $this->strings['no_package'] = __('Update package not available.');
1023                 $this->strings['downloading_package'] = __('Downloading update from <span class="code">%s</span>&#8230;');
1024                 $this->strings['unpack_package'] = __('Unpacking the update&#8230;');
1025                 $this->strings['copy_failed'] = __('Could not copy files.');
1026                 $this->strings['copy_failed_space'] = __('Could not copy files. You may have run out of disk space.' );
1027         }
1028
1029         function upgrade($current) {
1030                 global $wp_filesystem, $wp_version;
1031
1032                 $this->init();
1033                 $this->upgrade_strings();
1034
1035                 // Is an update available?
1036                 if ( !isset( $current->response ) || $current->response == 'latest' )
1037                         return new WP_Error('up_to_date', $this->strings['up_to_date']);
1038
1039                 $res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) );
1040                 if ( is_wp_error($res) )
1041                         return $res;
1042
1043                 $wp_dir = trailingslashit($wp_filesystem->abspath());
1044
1045                 // If partial update is returned from the API, use that, unless we're doing a reinstall.
1046                 // If we cross the new_bundled version number, then use the new_bundled zip.
1047                 // Don't though if the constant is set to skip bundled items.
1048                 // If the API returns a no_content zip, go with it. Finally, default to the full zip.
1049                 if ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version )
1050                         $to_download = 'partial';
1051                 elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
1052                         && ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
1053                         $to_download = 'new_bundled';
1054                 elseif ( $current->packages->no_content )
1055                         $to_download = 'no_content';
1056                 else
1057                         $to_download = 'full';
1058
1059                 $download = $this->download_package( $current->packages->$to_download );
1060                 if ( is_wp_error($download) )
1061                         return $download;
1062
1063                 $working_dir = $this->unpack_package( $download );
1064                 if ( is_wp_error($working_dir) )
1065                         return $working_dir;
1066
1067                 // Copy update-core.php from the new version into place.
1068                 if ( !$wp_filesystem->copy($working_dir . '/wordpress/wp-admin/includes/update-core.php', $wp_dir . 'wp-admin/includes/update-core.php', true) ) {
1069                         $wp_filesystem->delete($working_dir, true);
1070                         return new WP_Error('copy_failed', $this->strings['copy_failed']);
1071                 }
1072                 $wp_filesystem->chmod($wp_dir . 'wp-admin/includes/update-core.php', FS_CHMOD_FILE);
1073
1074                 require(ABSPATH . 'wp-admin/includes/update-core.php');
1075
1076                 if ( ! function_exists( 'update_core' ) )
1077                         return new WP_Error( 'copy_failed_space', $this->strings['copy_failed_space'] );
1078
1079                 $result = update_core( $working_dir, $wp_dir );
1080                 do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ), $result );
1081                 return $result;
1082         }
1083
1084 }
1085
1086 /**
1087  * Generic Skin for the WordPress Upgrader classes. This skin is designed to be extended for specific purposes.
1088  *
1089  * @TODO More Detailed docs, for methods as well.
1090  *
1091  * @package WordPress
1092  * @subpackage Upgrader
1093  * @since 2.8.0
1094  */
1095 class WP_Upgrader_Skin {
1096
1097         var $upgrader;
1098         var $done_header = false;
1099         var $result = false;
1100
1101         function __construct($args = array()) {
1102                 $defaults = array( 'url' => '', 'nonce' => '', 'title' => '', 'context' => false );
1103                 $this->options = wp_parse_args($args, $defaults);
1104         }
1105
1106         function set_upgrader(&$upgrader) {
1107                 if ( is_object($upgrader) )
1108                         $this->upgrader =& $upgrader;
1109                 $this->add_strings();
1110         }
1111
1112         function add_strings() {
1113         }
1114
1115         function set_result($result) {
1116                 $this->result = $result;
1117         }
1118
1119         function request_filesystem_credentials($error = false) {
1120                 $url = $this->options['url'];
1121                 $context = $this->options['context'];
1122                 if ( !empty($this->options['nonce']) )
1123                         $url = wp_nonce_url($url, $this->options['nonce']);
1124                 return request_filesystem_credentials($url, '', $error, $context); //Possible to bring inline, Leaving as is for now.
1125         }
1126
1127         function header() {
1128                 if ( $this->done_header )
1129                         return;
1130                 $this->done_header = true;
1131                 echo '<div class="wrap">';
1132                 screen_icon();
1133                 echo '<h2>' . $this->options['title'] . '</h2>';
1134         }
1135         function footer() {
1136                 echo '</div>';
1137         }
1138
1139         function error($errors) {
1140                 if ( ! $this->done_header )
1141                         $this->header();
1142                 if ( is_string($errors) ) {
1143                         $this->feedback($errors);
1144                 } elseif ( is_wp_error($errors) && $errors->get_error_code() ) {
1145                         foreach ( $errors->get_error_messages() as $message ) {
1146                                 if ( $errors->get_error_data() )
1147                                         $this->feedback($message . ' ' . esc_html( $errors->get_error_data() ) );
1148                                 else
1149                                         $this->feedback($message);
1150                         }
1151                 }
1152         }
1153
1154         function feedback($string) {
1155                 if ( isset( $this->upgrader->strings[$string] ) )
1156                         $string = $this->upgrader->strings[$string];
1157
1158                 if ( strpos($string, '%') !== false ) {
1159                         $args = func_get_args();
1160                         $args = array_splice($args, 1);
1161                         if ( $args ) {
1162                                 $args = array_map( 'strip_tags', $args );
1163                                 $args = array_map( 'esc_html', $args );
1164                                 $string = vsprintf($string, $args);
1165                         }
1166                 }
1167                 if ( empty($string) )
1168                         return;
1169                 show_message($string);
1170         }
1171         function before() {}
1172         function after() {}
1173
1174 }
1175
1176 /**
1177  * Plugin Upgrader Skin for WordPress Plugin Upgrades.
1178  *
1179  * @TODO More Detailed docs, for methods as well.
1180  *
1181  * @package WordPress
1182  * @subpackage Upgrader
1183  * @since 2.8.0
1184  */
1185 class Plugin_Upgrader_Skin extends WP_Upgrader_Skin {
1186         var $plugin = '';
1187         var $plugin_active = false;
1188         var $plugin_network_active = false;
1189
1190         function __construct($args = array()) {
1191                 $defaults = array( 'url' => '', 'plugin' => '', 'nonce' => '', 'title' => __('Update Plugin') );
1192                 $args = wp_parse_args($args, $defaults);
1193
1194                 $this->plugin = $args['plugin'];
1195
1196                 $this->plugin_active = is_plugin_active( $this->plugin );
1197                 $this->plugin_network_active = is_plugin_active_for_network( $this->plugin );
1198
1199                 parent::__construct($args);
1200         }
1201
1202         function after() {
1203                 $this->plugin = $this->upgrader->plugin_info();
1204                 if ( !empty($this->plugin) && !is_wp_error($this->result) && $this->plugin_active ){
1205                         echo '<iframe style="border:0;overflow:hidden" width="100%" height="170px" src="' . wp_nonce_url('update.php?action=activate-plugin&networkwide=' . $this->plugin_network_active . '&plugin=' . urlencode( $this->plugin ), 'activate-plugin_' . $this->plugin) .'"></iframe>';
1206                 }
1207
1208                 $update_actions =  array(
1209                         'activate_plugin' => '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;plugin=' . urlencode( $this->plugin ), 'activate-plugin_' . $this->plugin) . '" title="' . esc_attr__('Activate this plugin') . '" target="_parent">' . __('Activate Plugin') . '</a>',
1210                         'plugins_page' => '<a href="' . self_admin_url('plugins.php') . '" title="' . esc_attr__('Go to plugins page') . '" target="_parent">' . __('Return to Plugins page') . '</a>'
1211                 );
1212                 if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugins' ) )
1213                         unset( $update_actions['activate_plugin'] );
1214
1215                 $update_actions = apply_filters('update_plugin_complete_actions', $update_actions, $this->plugin);
1216                 if ( ! empty($update_actions) )
1217                         $this->feedback(implode(' | ', (array)$update_actions));
1218         }
1219
1220         function before() {
1221                 if ( $this->upgrader->show_before ) {
1222                         echo $this->upgrader->show_before;
1223                         $this->upgrader->show_before = '';
1224                 }
1225         }
1226 }
1227
1228 /**
1229  * Plugin Upgrader Skin for WordPress Plugin Upgrades.
1230  *
1231  * @package WordPress
1232  * @subpackage Upgrader
1233  * @since 3.0.0
1234  */
1235 class Bulk_Upgrader_Skin extends WP_Upgrader_Skin {
1236         var $in_loop = false;
1237         var $error = false;
1238
1239         function __construct($args = array()) {
1240                 $defaults = array( 'url' => '', 'nonce' => '' );
1241                 $args = wp_parse_args($args, $defaults);
1242
1243                 parent::__construct($args);
1244         }
1245
1246         function add_strings() {
1247                 $this->upgrader->strings['skin_upgrade_start'] = __('The update process is starting. This process may take a while on some hosts, so please be patient.');
1248                 $this->upgrader->strings['skin_update_failed_error'] = __('An error occurred while updating %1$s: <strong>%2$s</strong>');
1249                 $this->upgrader->strings['skin_update_failed'] = __('The update of %1$s failed.');
1250                 $this->upgrader->strings['skin_update_successful'] = __('%1$s updated successfully.').' <a onclick="%2$s" href="#" class="hide-if-no-js"><span>'.__('Show Details').'</span><span class="hidden">'.__('Hide Details').'</span>.</a>';
1251                 $this->upgrader->strings['skin_upgrade_end'] = __('All updates have been completed.');
1252         }
1253
1254         function feedback($string) {
1255                 if ( isset( $this->upgrader->strings[$string] ) )
1256                         $string = $this->upgrader->strings[$string];
1257
1258                 if ( strpos($string, '%') !== false ) {
1259                         $args = func_get_args();
1260                         $args = array_splice($args, 1);
1261                         if ( $args ) {
1262                                 $args = array_map( 'strip_tags', $args );
1263                                 $args = array_map( 'esc_html', $args );
1264                                 $string = vsprintf($string, $args);
1265                         }
1266                 }
1267                 if ( empty($string) )
1268                         return;
1269                 if ( $this->in_loop )
1270                         echo "$string<br />\n";
1271                 else
1272                         echo "<p>$string</p>\n";
1273         }
1274
1275         function header() {
1276                 // Nothing, This will be displayed within a iframe.
1277         }
1278
1279         function footer() {
1280                 // Nothing, This will be displayed within a iframe.
1281         }
1282         function error($error) {
1283                 if ( is_string($error) && isset( $this->upgrader->strings[$error] ) )
1284                         $this->error = $this->upgrader->strings[$error];
1285
1286                 if ( is_wp_error($error) ) {
1287                         foreach ( $error->get_error_messages() as $emessage ) {
1288                                 if ( $error->get_error_data() )
1289                                         $messages[] = $emessage . ' ' . esc_html( $error->get_error_data() );
1290                                 else
1291                                         $messages[] = $emessage;
1292                         }
1293                         $this->error = implode(', ', $messages);
1294                 }
1295                 echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js($this->upgrader->update_current) . '\').hide();</script>';
1296         }
1297
1298         function bulk_header() {
1299                 $this->feedback('skin_upgrade_start');
1300         }
1301
1302         function bulk_footer() {
1303                 $this->feedback('skin_upgrade_end');
1304         }
1305
1306         function before($title = '') {
1307                 $this->in_loop = true;
1308                 printf( '<h4>' . $this->upgrader->strings['skin_before_update_header'] . ' <span class="spinner waiting-' . $this->upgrader->update_current . '"></span></h4>',  $title, $this->upgrader->update_current, $this->upgrader->update_count);
1309                 echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js($this->upgrader->update_current) . '\').css("display", "inline-block");</script>';
1310                 echo '<div class="update-messages hide-if-js" id="progress-' . esc_attr($this->upgrader->update_current) . '"><p>';
1311                 $this->flush_output();
1312         }
1313
1314         function after($title = '') {
1315                 echo '</p></div>';
1316                 if ( $this->error || ! $this->result ) {
1317                         if ( $this->error )
1318                                 echo '<div class="error"><p>' . sprintf($this->upgrader->strings['skin_update_failed_error'], $title, $this->error) . '</p></div>';
1319                         else
1320                                 echo '<div class="error"><p>' . sprintf($this->upgrader->strings['skin_update_failed'], $title) . '</p></div>';
1321
1322                         echo '<script type="text/javascript">jQuery(\'#progress-' . esc_js($this->upgrader->update_current) . '\').show();</script>';
1323                 }
1324                 if ( $this->result && ! is_wp_error( $this->result ) ) {
1325                         if ( ! $this->error )
1326                                 echo '<div class="updated"><p>' . sprintf($this->upgrader->strings['skin_update_successful'], $title, 'jQuery(\'#progress-' . esc_js($this->upgrader->update_current) . '\').toggle();jQuery(\'span\', this).toggle(); return false;') . '</p></div>';
1327                         echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js($this->upgrader->update_current) . '\').hide();</script>';
1328                 }
1329
1330                 $this->reset();
1331                 $this->flush_output();
1332         }
1333
1334         function reset() {
1335                 $this->in_loop = false;
1336                 $this->error = false;
1337         }
1338
1339         function flush_output() {
1340                 wp_ob_end_flush_all();
1341                 flush();
1342         }
1343 }
1344
1345 class Bulk_Plugin_Upgrader_Skin extends Bulk_Upgrader_Skin {
1346         var $plugin_info = array(); // Plugin_Upgrader::bulk() will fill this in.
1347
1348         function __construct($args = array()) {
1349                 parent::__construct($args);
1350         }
1351
1352         function add_strings() {
1353                 parent::add_strings();
1354                 $this->upgrader->strings['skin_before_update_header'] = __('Updating Plugin %1$s (%2$d/%3$d)');
1355         }
1356
1357         function before($title = '') {
1358                 parent::before($this->plugin_info['Title']);
1359         }
1360
1361         function after($title = '') {
1362                 parent::after($this->plugin_info['Title']);
1363         }
1364         function bulk_footer() {
1365                 parent::bulk_footer();
1366                 $update_actions =  array(
1367                         'plugins_page' => '<a href="' . self_admin_url('plugins.php') . '" title="' . esc_attr__('Go to plugins page') . '" target="_parent">' . __('Return to Plugins page') . '</a>',
1368                         'updates_page' => '<a href="' . self_admin_url('update-core.php') . '" title="' . esc_attr__('Go to WordPress Updates page') . '" target="_parent">' . __('Return to WordPress Updates') . '</a>'
1369                 );
1370                 if ( ! current_user_can( 'activate_plugins' ) )
1371                         unset( $update_actions['plugins_page'] );
1372
1373                 $update_actions = apply_filters('update_bulk_plugins_complete_actions', $update_actions, $this->plugin_info);
1374                 if ( ! empty($update_actions) )
1375                         $this->feedback(implode(' | ', (array)$update_actions));
1376         }
1377 }
1378
1379 class Bulk_Theme_Upgrader_Skin extends Bulk_Upgrader_Skin {
1380         var $theme_info = array(); // Theme_Upgrader::bulk() will fill this in.
1381
1382         function __construct($args = array()) {
1383                 parent::__construct($args);
1384         }
1385
1386         function add_strings() {
1387                 parent::add_strings();
1388                 $this->upgrader->strings['skin_before_update_header'] = __('Updating Theme %1$s (%2$d/%3$d)');
1389         }
1390
1391         function before($title = '') {
1392                 parent::before( $this->theme_info->display('Name') );
1393         }
1394
1395         function after($title = '') {
1396                 parent::after( $this->theme_info->display('Name') );
1397         }
1398
1399         function bulk_footer() {
1400                 parent::bulk_footer();
1401                 $update_actions =  array(
1402                         'themes_page' => '<a href="' . self_admin_url('themes.php') . '" title="' . esc_attr__('Go to themes page') . '" target="_parent">' . __('Return to Themes page') . '</a>',
1403                         'updates_page' => '<a href="' . self_admin_url('update-core.php') . '" title="' . esc_attr__('Go to WordPress Updates page') . '" target="_parent">' . __('Return to WordPress Updates') . '</a>'
1404                 );
1405                 if ( ! current_user_can( 'switch_themes' ) && ! current_user_can( 'edit_theme_options' ) )
1406                         unset( $update_actions['themes_page'] );
1407
1408                 $update_actions = apply_filters('update_bulk_theme_complete_actions', $update_actions, $this->theme_info );
1409                 if ( ! empty($update_actions) )
1410                         $this->feedback(implode(' | ', (array)$update_actions));
1411         }
1412 }
1413
1414 /**
1415  * Plugin Installer Skin for WordPress Plugin Installer.
1416  *
1417  * @TODO More Detailed docs, for methods as well.
1418  *
1419  * @package WordPress
1420  * @subpackage Upgrader
1421  * @since 2.8.0
1422  */
1423 class Plugin_Installer_Skin extends WP_Upgrader_Skin {
1424         var $api;
1425         var $type;
1426
1427         function __construct($args = array()) {
1428                 $defaults = array( 'type' => 'web', 'url' => '', 'plugin' => '', 'nonce' => '', 'title' => '' );
1429                 $args = wp_parse_args($args, $defaults);
1430
1431                 $this->type = $args['type'];
1432                 $this->api = isset($args['api']) ? $args['api'] : array();
1433
1434                 parent::__construct($args);
1435         }
1436
1437         function before() {
1438                 if ( !empty($this->api) )
1439                         $this->upgrader->strings['process_success'] = sprintf( __('Successfully installed the plugin <strong>%s %s</strong>.'), $this->api->name, $this->api->version);
1440         }
1441
1442         function after() {
1443
1444                 $plugin_file = $this->upgrader->plugin_info();
1445
1446                 $install_actions = array();
1447
1448                 $from = isset($_GET['from']) ? wp_unslash( $_GET['from'] ) : 'plugins';
1449
1450                 if ( 'import' == $from )
1451                         $install_actions['activate_plugin'] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;from=import&amp;plugin=' . urlencode( $plugin_file ), 'activate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Activate this plugin') . '" target="_parent">' . __('Activate Plugin &amp; Run Importer') . '</a>';
1452                 else
1453                         $install_actions['activate_plugin'] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ), 'activate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Activate this plugin') . '" target="_parent">' . __('Activate Plugin') . '</a>';
1454
1455                 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
1456                         $install_actions['network_activate'] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;networkwide=1&amp;plugin=' . urlencode( $plugin_file ), 'activate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Activate this plugin for all sites in this network') . '" target="_parent">' . __('Network Activate') . '</a>';
1457                         unset( $install_actions['activate_plugin'] );
1458                 }
1459
1460                 if ( 'import' == $from )
1461                         $install_actions['importers_page'] = '<a href="' . admin_url('import.php') . '" title="' . esc_attr__('Return to Importers') . '" target="_parent">' . __('Return to Importers') . '</a>';
1462                 else if ( $this->type == 'web' )
1463                         $install_actions['plugins_page'] = '<a href="' . self_admin_url('plugin-install.php') . '" title="' . esc_attr__('Return to Plugin Installer') . '" target="_parent">' . __('Return to Plugin Installer') . '</a>';
1464                 else
1465                         $install_actions['plugins_page'] = '<a href="' . self_admin_url('plugins.php') . '" title="' . esc_attr__('Return to Plugins page') . '" target="_parent">' . __('Return to Plugins page') . '</a>';
1466
1467                 if ( ! $this->result || is_wp_error($this->result) ) {
1468                         unset( $install_actions['activate_plugin'], $install_actions['network_activate'] );
1469                 } elseif ( ! current_user_can( 'activate_plugins' ) ) {
1470                         unset( $install_actions['activate_plugin'] );
1471                 }
1472
1473                 $install_actions = apply_filters('install_plugin_complete_actions', $install_actions, $this->api, $plugin_file);
1474                 if ( ! empty($install_actions) )
1475                         $this->feedback(implode(' | ', (array)$install_actions));
1476         }
1477 }
1478
1479 /**
1480  * Theme Installer Skin for the WordPress Theme Installer.
1481  *
1482  * @TODO More Detailed docs, for methods as well.
1483  *
1484  * @package WordPress
1485  * @subpackage Upgrader
1486  * @since 2.8.0
1487  */
1488 class Theme_Installer_Skin extends WP_Upgrader_Skin {
1489         var $api;
1490         var $type;
1491
1492         function __construct($args = array()) {
1493                 $defaults = array( 'type' => 'web', 'url' => '', 'theme' => '', 'nonce' => '', 'title' => '' );
1494                 $args = wp_parse_args($args, $defaults);
1495
1496                 $this->type = $args['type'];
1497                 $this->api = isset($args['api']) ? $args['api'] : array();
1498
1499                 parent::__construct($args);
1500         }
1501
1502         function before() {
1503                 if ( !empty($this->api) )
1504                         $this->upgrader->strings['process_success'] = sprintf( $this->upgrader->strings['process_success_specific'], $this->api->name, $this->api->version);
1505         }
1506
1507         function after() {
1508                 if ( empty($this->upgrader->result['destination_name']) )
1509                         return;
1510
1511                 $theme_info = $this->upgrader->theme_info();
1512                 if ( empty( $theme_info ) )
1513                         return;
1514
1515                 $name       = $theme_info->display('Name');
1516                 $stylesheet = $this->upgrader->result['destination_name'];
1517                 $template   = $theme_info->get_template();
1518
1519                 $preview_link = add_query_arg( array(
1520                         'preview'    => 1,
1521                         'template'   => urlencode( $template ),
1522                         'stylesheet' => urlencode( $stylesheet ),
1523                 ), trailingslashit( home_url() ) );
1524
1525                 $activate_link = add_query_arg( array(
1526                         'action'     => 'activate',
1527                         'template'   => urlencode( $template ),
1528                         'stylesheet' => urlencode( $stylesheet ),
1529                 ), admin_url('themes.php') );
1530                 $activate_link = wp_nonce_url( $activate_link, 'switch-theme_' . $stylesheet );
1531
1532                 $install_actions = array();
1533                 $install_actions['preview']  = '<a href="' . esc_url( $preview_link ) . '" class="hide-if-customize" title="' . esc_attr( sprintf( __('Preview &#8220;%s&#8221;'), $name ) ) . '">' . __('Preview') . '</a>';
1534                 $install_actions['preview'] .= '<a href="' . wp_customize_url( $stylesheet ) . '" class="hide-if-no-customize load-customize" title="' . esc_attr( sprintf( __('Preview &#8220;%s&#8221;'), $name ) ) . '">' . __('Live Preview') . '</a>';
1535                 $install_actions['activate'] = '<a href="' . esc_url( $activate_link ) . '" class="activatelink" title="' . esc_attr( sprintf( __('Activate &#8220;%s&#8221;'), $name ) ) . '">' . __('Activate') . '</a>';
1536
1537                 if ( is_network_admin() && current_user_can( 'manage_network_themes' ) )
1538                         $install_actions['network_enable'] = '<a href="' . esc_url( wp_nonce_url( 'themes.php?action=enable&amp;theme=' . urlencode( $stylesheet ), 'enable-theme_' . $stylesheet ) ) . '" title="' . esc_attr__( 'Enable this theme for all sites in this network' ) . '" target="_parent">' . __( 'Network Enable' ) . '</a>';
1539
1540                 if ( $this->type == 'web' )
1541                         $install_actions['themes_page'] = '<a href="' . self_admin_url('theme-install.php') . '" title="' . esc_attr__('Return to Theme Installer') . '" target="_parent">' . __('Return to Theme Installer') . '</a>';
1542                 elseif ( current_user_can( 'switch_themes' ) || current_user_can( 'edit_theme_options' ) )
1543                         $install_actions['themes_page'] = '<a href="' . self_admin_url('themes.php') . '" title="' . esc_attr__('Themes page') . '" target="_parent">' . __('Return to Themes page') . '</a>';
1544
1545                 if ( ! $this->result || is_wp_error($this->result) || is_network_admin() || ! current_user_can( 'switch_themes' ) )
1546                         unset( $install_actions['activate'], $install_actions['preview'] );
1547
1548                 $install_actions = apply_filters('install_theme_complete_actions', $install_actions, $this->api, $stylesheet, $theme_info);
1549                 if ( ! empty($install_actions) )
1550                         $this->feedback(implode(' | ', (array)$install_actions));
1551         }
1552 }
1553
1554 /**
1555  * Theme Upgrader Skin for WordPress Theme Upgrades.
1556  *
1557  * @TODO More Detailed docs, for methods as well.
1558  *
1559  * @package WordPress
1560  * @subpackage Upgrader
1561  * @since 2.8.0
1562  */
1563 class Theme_Upgrader_Skin extends WP_Upgrader_Skin {
1564         var $theme = '';
1565
1566         function __construct($args = array()) {
1567                 $defaults = array( 'url' => '', 'theme' => '', 'nonce' => '', 'title' => __('Update Theme') );
1568                 $args = wp_parse_args($args, $defaults);
1569
1570                 $this->theme = $args['theme'];
1571
1572                 parent::__construct($args);
1573         }
1574
1575         function after() {
1576
1577                 $update_actions = array();
1578                 if ( ! empty( $this->upgrader->result['destination_name'] ) && $theme_info = $this->upgrader->theme_info() ) {
1579                         $name       = $theme_info->display('Name');
1580                         $stylesheet = $this->upgrader->result['destination_name'];
1581                         $template   = $theme_info->get_template();
1582
1583                         $preview_link = add_query_arg( array(
1584                                 'preview'    => 1,
1585                                 'template'   => urlencode( $template ),
1586                                 'stylesheet' => urlencode( $stylesheet ),
1587                         ), trailingslashit( home_url() ) );
1588
1589                         $activate_link = add_query_arg( array(
1590                                 'action'     => 'activate',
1591                                 'template'   => urlencode( $template ),
1592                                 'stylesheet' => urlencode( $stylesheet ),
1593                         ), admin_url('themes.php') );
1594                         $activate_link = wp_nonce_url( $activate_link, 'switch-theme_' . $stylesheet );
1595
1596                         if ( get_stylesheet() == $stylesheet ) {
1597                                 if ( current_user_can( 'edit_theme_options' ) )
1598                                         $update_actions['preview']  = '<a href="' . wp_customize_url( $stylesheet ) . '" class="hide-if-no-customize load-customize" title="' . esc_attr( sprintf( __('Customize &#8220;%s&#8221;'), $name ) ) . '">' . __('Customize') . '</a>';
1599                         } elseif ( current_user_can( 'switch_themes' ) ) {
1600                                 $update_actions['preview']  = '<a href="' . esc_url( $preview_link ) . '" class="hide-if-customize" title="' . esc_attr( sprintf( __('Preview &#8220;%s&#8221;'), $name ) ) . '">' . __('Preview') . '</a>';
1601                                 $update_actions['preview'] .= '<a href="' . wp_customize_url( $stylesheet ) . '" class="hide-if-no-customize load-customize" title="' . esc_attr( sprintf( __('Preview &#8220;%s&#8221;'), $name ) ) . '">' . __('Live Preview') . '</a>';
1602                                 $update_actions['activate'] = '<a href="' . esc_url( $activate_link ) . '" class="activatelink" title="' . esc_attr( sprintf( __('Activate &#8220;%s&#8221;'), $name ) ) . '">' . __('Activate') . '</a>';
1603                         }
1604
1605                         if ( ! $this->result || is_wp_error( $this->result ) || is_network_admin() )
1606                                 unset( $update_actions['preview'], $update_actions['activate'] );
1607                 }
1608
1609                 $update_actions['themes_page'] = '<a href="' . self_admin_url('themes.php') . '" title="' . esc_attr__('Return to Themes page') . '" target="_parent">' . __('Return to Themes page') . '</a>';
1610
1611                 $update_actions = apply_filters('update_theme_complete_actions', $update_actions, $this->theme);
1612                 if ( ! empty($update_actions) )
1613                         $this->feedback(implode(' | ', (array)$update_actions));
1614         }
1615 }
1616
1617 /**
1618  * 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.
1619  *
1620  * @TODO More Detailed docs, for methods as well.
1621  *
1622  * @package WordPress
1623  * @subpackage Upgrader
1624  * @since 2.8.0
1625  */
1626 class File_Upload_Upgrader {
1627         var $package;
1628         var $filename;
1629         var $id = 0;
1630
1631         function __construct($form, $urlholder) {
1632
1633                 if ( empty($_FILES[$form]['name']) && empty($_GET[$urlholder]) )
1634                         wp_die(__('Please select a file'));
1635
1636                 //Handle a newly uploaded file, Else assume it's already been uploaded
1637                 if ( ! empty($_FILES) ) {
1638                         $overrides = array( 'test_form' => false, 'test_type' => false );
1639                         $file = wp_handle_upload( $_FILES[$form], $overrides );
1640
1641                         if ( isset( $file['error'] ) )
1642                                 wp_die( $file['error'] );
1643
1644                         $this->filename = $_FILES[$form]['name'];
1645                         $this->package = $file['file'];
1646
1647                         // Construct the object array
1648                         $object = array(
1649                                 'post_title' => $this->filename,
1650                                 'post_content' => $file['url'],
1651                                 'post_mime_type' => $file['type'],
1652                                 'guid' => $file['url'],
1653                                 'context' => 'upgrader',
1654                                 'post_status' => 'private'
1655                         );
1656
1657                         // Save the data
1658                         $this->id = wp_insert_attachment( $object, $file['file'] );
1659
1660                         // schedule a cleanup for 2 hours from now in case of failed install
1661                         wp_schedule_single_event( time() + 7200, 'upgrader_scheduled_cleanup', array( $this->id ) );
1662
1663                 } elseif ( is_numeric( $_GET[$urlholder] ) ) {
1664                         // Numeric Package = previously uploaded file, see above.
1665                         $this->id = (int) $_GET[$urlholder];
1666                         $attachment = get_post( $this->id );
1667                         if ( empty($attachment) )
1668                                 wp_die(__('Please select a file'));
1669
1670                         $this->filename = $attachment->post_title;
1671                         $this->package = get_attached_file( $attachment->ID );
1672                 } else {
1673                         // Else, It's set to something, Back compat for plugins using the old (pre-3.3) File_Uploader handler.
1674                         if ( ! ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) )
1675                                 wp_die( $uploads['error'] );
1676
1677                         $this->filename = $_GET[$urlholder];
1678                         $this->package = $uploads['basedir'] . '/' . $this->filename;
1679                 }
1680         }
1681
1682         function cleanup() {
1683                 if ( $this->id )
1684                         wp_delete_attachment( $this->id );
1685
1686                 elseif ( file_exists( $this->package ) )
1687                         return @unlink( $this->package );
1688
1689                 return true;
1690         }
1691 }