1 | <?php |
---|
2 | /** |
---|
3 | * Upgrade API: WP_Upgrader class |
---|
4 | * |
---|
5 | * Requires skin classes and WP_Upgrader subclasses for backward compatibility. |
---|
6 | * |
---|
7 | * @package WordPress |
---|
8 | * @subpackage Upgrader |
---|
9 | * @since 2.8.0 |
---|
10 | */ |
---|
11 | |
---|
12 | /** WP_Upgrader_Skin class */ |
---|
13 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php'; |
---|
14 | |
---|
15 | /** Plugin_Upgrader_Skin class */ |
---|
16 | require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader-skin.php'; |
---|
17 | |
---|
18 | /** Theme_Upgrader_Skin class */ |
---|
19 | require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader-skin.php'; |
---|
20 | |
---|
21 | /** Bulk_Upgrader_Skin class */ |
---|
22 | require_once ABSPATH . 'wp-admin/includes/class-bulk-upgrader-skin.php'; |
---|
23 | |
---|
24 | /** Bulk_Plugin_Upgrader_Skin class */ |
---|
25 | require_once ABSPATH . 'wp-admin/includes/class-bulk-plugin-upgrader-skin.php'; |
---|
26 | |
---|
27 | /** Bulk_Theme_Upgrader_Skin class */ |
---|
28 | require_once ABSPATH . 'wp-admin/includes/class-bulk-theme-upgrader-skin.php'; |
---|
29 | |
---|
30 | /** Plugin_Installer_Skin class */ |
---|
31 | require_once ABSPATH . 'wp-admin/includes/class-plugin-installer-skin.php'; |
---|
32 | |
---|
33 | /** Theme_Installer_Skin class */ |
---|
34 | require_once ABSPATH . 'wp-admin/includes/class-theme-installer-skin.php'; |
---|
35 | |
---|
36 | /** Language_Pack_Upgrader_Skin class */ |
---|
37 | require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader-skin.php'; |
---|
38 | |
---|
39 | /** Automatic_Upgrader_Skin class */ |
---|
40 | require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php'; |
---|
41 | |
---|
42 | /** WP_Ajax_Upgrader_Skin class */ |
---|
43 | require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; |
---|
44 | |
---|
45 | /** |
---|
46 | * Core class used for upgrading/installing a local set of files via |
---|
47 | * the Filesystem Abstraction classes from a Zip file. |
---|
48 | * |
---|
49 | * @since 2.8.0 |
---|
50 | */ |
---|
51 | class WP_Upgrader { |
---|
52 | |
---|
53 | /** |
---|
54 | * The error/notification strings used to update the user on the progress. |
---|
55 | * |
---|
56 | * @since 2.8.0 |
---|
57 | * @access public |
---|
58 | * @var array $strings |
---|
59 | */ |
---|
60 | public $strings = array(); |
---|
61 | |
---|
62 | /** |
---|
63 | * The upgrader skin being used. |
---|
64 | * |
---|
65 | * @since 2.8.0 |
---|
66 | * @access public |
---|
67 | * @var Automatic_Upgrader_Skin|WP_Upgrader_Skin $skin |
---|
68 | */ |
---|
69 | public $skin = null; |
---|
70 | |
---|
71 | /** |
---|
72 | * The result of the installation. |
---|
73 | * |
---|
74 | * This is set by WP_Upgrader::install_package(), only when the package is installed |
---|
75 | * successfully. It will then be an array, unless a WP_Error is returned by the |
---|
76 | * {@see 'upgrader_post_install'} filter. In that case, the WP_Error will be assigned to |
---|
77 | * it. |
---|
78 | * |
---|
79 | * @since 2.8.0 |
---|
80 | * @access public |
---|
81 | * |
---|
82 | * @var WP_Error|array $result { |
---|
83 | * @type string $source The full path to the source the files were installed from. |
---|
84 | * @type string $source_files List of all the files in the source directory. |
---|
85 | * @type string $destination The full path to the install destination folder. |
---|
86 | * @type string $destination_name The name of the destination folder, or empty if `$destination` |
---|
87 | * and `$local_destination` are the same. |
---|
88 | * @type string $local_destination The full local path to the destination folder. This is usually |
---|
89 | * the same as `$destination`. |
---|
90 | * @type string $remote_destination The full remote path to the destination folder |
---|
91 | * (i.e., from `$wp_filesystem`). |
---|
92 | * @type bool $clear_destination Whether the destination folder was cleared. |
---|
93 | * } |
---|
94 | */ |
---|
95 | public $result = array(); |
---|
96 | |
---|
97 | /** |
---|
98 | * The total number of updates being performed. |
---|
99 | * |
---|
100 | * Set by the bulk update methods. |
---|
101 | * |
---|
102 | * @since 3.0.0 |
---|
103 | * @access public |
---|
104 | * @var int $update_count |
---|
105 | */ |
---|
106 | public $update_count = 0; |
---|
107 | |
---|
108 | /** |
---|
109 | * The current update if multiple updates are being performed. |
---|
110 | * |
---|
111 | * Used by the bulk update methods, and incremented for each update. |
---|
112 | * |
---|
113 | * @since 3.0.0 |
---|
114 | * @access public |
---|
115 | * @var int |
---|
116 | */ |
---|
117 | public $update_current = 0; |
---|
118 | |
---|
119 | /** |
---|
120 | * Construct the upgrader with a skin. |
---|
121 | * |
---|
122 | * @since 2.8.0 |
---|
123 | * @access public |
---|
124 | * |
---|
125 | * @param WP_Upgrader_Skin $skin The upgrader skin to use. Default is a WP_Upgrader_Skin. |
---|
126 | * instance. |
---|
127 | */ |
---|
128 | public function __construct( $skin = null ) { |
---|
129 | if ( null == $skin ) |
---|
130 | $this->skin = new WP_Upgrader_Skin(); |
---|
131 | else |
---|
132 | $this->skin = $skin; |
---|
133 | } |
---|
134 | |
---|
135 | /** |
---|
136 | * Initialize the upgrader. |
---|
137 | * |
---|
138 | * This will set the relationship between the skin being used and this upgrader, |
---|
139 | * and also add the generic strings to `WP_Upgrader::$strings`. |
---|
140 | * |
---|
141 | * @since 2.8.0 |
---|
142 | * @access public |
---|
143 | */ |
---|
144 | public function init() { |
---|
145 | $this->skin->set_upgrader($this); |
---|
146 | $this->generic_strings(); |
---|
147 | } |
---|
148 | |
---|
149 | /** |
---|
150 | * Add the generic strings to WP_Upgrader::$strings. |
---|
151 | * |
---|
152 | * @since 2.8.0 |
---|
153 | * @access public |
---|
154 | */ |
---|
155 | public function generic_strings() { |
---|
156 | $this->strings['bad_request'] = __('Invalid data provided.'); |
---|
157 | $this->strings['fs_unavailable'] = __('Could not access filesystem.'); |
---|
158 | $this->strings['fs_error'] = __('Filesystem error.'); |
---|
159 | $this->strings['fs_no_root_dir'] = __('Unable to locate WordPress root directory.'); |
---|
160 | $this->strings['fs_no_content_dir'] = __('Unable to locate WordPress content directory (wp-content).'); |
---|
161 | $this->strings['fs_no_plugins_dir'] = __('Unable to locate WordPress plugin directory.'); |
---|
162 | $this->strings['fs_no_themes_dir'] = __('Unable to locate WordPress theme directory.'); |
---|
163 | /* translators: %s: directory name */ |
---|
164 | $this->strings['fs_no_folder'] = __('Unable to locate needed folder (%s).'); |
---|
165 | |
---|
166 | $this->strings['download_failed'] = __('Download failed.'); |
---|
167 | $this->strings['installing_package'] = __('Installing the latest version…'); |
---|
168 | $this->strings['no_files'] = __('The package contains no files.'); |
---|
169 | $this->strings['folder_exists'] = __('Destination folder already exists.'); |
---|
170 | $this->strings['mkdir_failed'] = __('Could not create directory.'); |
---|
171 | $this->strings['incompatible_archive'] = __('The package could not be installed.'); |
---|
172 | $this->strings['files_not_writable'] = __( 'The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.' ); |
---|
173 | |
---|
174 | $this->strings['maintenance_start'] = __('Enabling Maintenance mode…'); |
---|
175 | $this->strings['maintenance_end'] = __('Disabling Maintenance mode…'); |
---|
176 | } |
---|
177 | |
---|
178 | /** |
---|
179 | * Connect to the filesystem. |
---|
180 | * |
---|
181 | * @since 2.8.0 |
---|
182 | * @access public |
---|
183 | * |
---|
184 | * @global WP_Filesystem_Base $wp_filesystem Subclass |
---|
185 | * |
---|
186 | * @param array $directories Optional. A list of directories. If any of these do |
---|
187 | * not exist, a WP_Error object will be returned. |
---|
188 | * Default empty array. |
---|
189 | * @param bool $allow_relaxed_file_ownership Whether to allow relaxed file ownership. |
---|
190 | * Default false. |
---|
191 | * @return bool|WP_Error True if able to connect, false or a WP_Error otherwise. |
---|
192 | */ |
---|
193 | public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) { |
---|
194 | global $wp_filesystem; |
---|
195 | |
---|
196 | if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) { |
---|
197 | return false; |
---|
198 | } |
---|
199 | |
---|
200 | if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) { |
---|
201 | $error = true; |
---|
202 | if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() ) |
---|
203 | $error = $wp_filesystem->errors; |
---|
204 | // Failed to connect, Error and request again |
---|
205 | $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership ); |
---|
206 | return false; |
---|
207 | } |
---|
208 | |
---|
209 | if ( ! is_object($wp_filesystem) ) |
---|
210 | return new WP_Error('fs_unavailable', $this->strings['fs_unavailable'] ); |
---|
211 | |
---|
212 | if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() ) |
---|
213 | return new WP_Error('fs_error', $this->strings['fs_error'], $wp_filesystem->errors); |
---|
214 | |
---|
215 | foreach ( (array)$directories as $dir ) { |
---|
216 | switch ( $dir ) { |
---|
217 | case ABSPATH: |
---|
218 | if ( ! $wp_filesystem->abspath() ) |
---|
219 | return new WP_Error('fs_no_root_dir', $this->strings['fs_no_root_dir']); |
---|
220 | break; |
---|
221 | case WP_CONTENT_DIR: |
---|
222 | if ( ! $wp_filesystem->wp_content_dir() ) |
---|
223 | return new WP_Error('fs_no_content_dir', $this->strings['fs_no_content_dir']); |
---|
224 | break; |
---|
225 | case WP_PLUGIN_DIR: |
---|
226 | if ( ! $wp_filesystem->wp_plugins_dir() ) |
---|
227 | return new WP_Error('fs_no_plugins_dir', $this->strings['fs_no_plugins_dir']); |
---|
228 | break; |
---|
229 | case get_theme_root(): |
---|
230 | if ( ! $wp_filesystem->wp_themes_dir() ) |
---|
231 | return new WP_Error('fs_no_themes_dir', $this->strings['fs_no_themes_dir']); |
---|
232 | break; |
---|
233 | default: |
---|
234 | if ( ! $wp_filesystem->find_folder($dir) ) |
---|
235 | return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) ); |
---|
236 | break; |
---|
237 | } |
---|
238 | } |
---|
239 | return true; |
---|
240 | } //end fs_connect(); |
---|
241 | |
---|
242 | /** |
---|
243 | * Download a package. |
---|
244 | * |
---|
245 | * @since 2.8.0 |
---|
246 | * @access public |
---|
247 | * |
---|
248 | * @param string $package The URI of the package. If this is the full path to an |
---|
249 | * existing local file, it will be returned untouched. |
---|
250 | * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object. |
---|
251 | */ |
---|
252 | public function download_package( $package ) { |
---|
253 | |
---|
254 | /** |
---|
255 | * Filters whether to return the package. |
---|
256 | * |
---|
257 | * @since 3.7.0 |
---|
258 | * @access public |
---|
259 | * |
---|
260 | * @param bool $reply Whether to bail without returning the package. |
---|
261 | * Default false. |
---|
262 | * @param string $package The package file name. |
---|
263 | * @param WP_Upgrader $this The WP_Upgrader instance. |
---|
264 | */ |
---|
265 | $reply = apply_filters( 'upgrader_pre_download', false, $package, $this ); |
---|
266 | if ( false !== $reply ) |
---|
267 | return $reply; |
---|
268 | |
---|
269 | if ( ! preg_match('!^(http|https|ftp)://!i', $package) && file_exists($package) ) //Local file or remote? |
---|
270 | return $package; //must be a local file.. |
---|
271 | |
---|
272 | if ( empty($package) ) |
---|
273 | return new WP_Error('no_package', $this->strings['no_package']); |
---|
274 | |
---|
275 | $this->skin->feedback('downloading_package', $package); |
---|
276 | |
---|
277 | $download_file = download_url($package); |
---|
278 | |
---|
279 | if ( is_wp_error($download_file) ) |
---|
280 | return new WP_Error('download_failed', $this->strings['download_failed'], $download_file->get_error_message()); |
---|
281 | |
---|
282 | return $download_file; |
---|
283 | } |
---|
284 | |
---|
285 | /** |
---|
286 | * Unpack a compressed package file. |
---|
287 | * |
---|
288 | * @since 2.8.0 |
---|
289 | * @access public |
---|
290 | * |
---|
291 | * @global WP_Filesystem_Base $wp_filesystem Subclass |
---|
292 | * |
---|
293 | * @param string $package Full path to the package file. |
---|
294 | * @param bool $delete_package Optional. Whether to delete the package file after attempting |
---|
295 | * to unpack it. Default true. |
---|
296 | * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure. |
---|
297 | */ |
---|
298 | public function unpack_package( $package, $delete_package = true ) { |
---|
299 | global $wp_filesystem; |
---|
300 | |
---|
301 | $this->skin->feedback('unpack_package'); |
---|
302 | |
---|
303 | $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/'; |
---|
304 | |
---|
305 | //Clean up contents of upgrade directory beforehand. |
---|
306 | $upgrade_files = $wp_filesystem->dirlist($upgrade_folder); |
---|
307 | if ( !empty($upgrade_files) ) { |
---|
308 | foreach ( $upgrade_files as $file ) |
---|
309 | $wp_filesystem->delete($upgrade_folder . $file['name'], true); |
---|
310 | } |
---|
311 | |
---|
312 | // We need a working directory - Strip off any .tmp or .zip suffixes |
---|
313 | $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' ); |
---|
314 | |
---|
315 | // Clean up working directory |
---|
316 | if ( $wp_filesystem->is_dir($working_dir) ) |
---|
317 | $wp_filesystem->delete($working_dir, true); |
---|
318 | |
---|
319 | // Unzip package to working directory |
---|
320 | $result = unzip_file( $package, $working_dir ); |
---|
321 | |
---|
322 | // Once extracted, delete the package if required. |
---|
323 | if ( $delete_package ) |
---|
324 | unlink($package); |
---|
325 | |
---|
326 | if ( is_wp_error($result) ) { |
---|
327 | $wp_filesystem->delete($working_dir, true); |
---|
328 | if ( 'incompatible_archive' == $result->get_error_code() ) { |
---|
329 | return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() ); |
---|
330 | } |
---|
331 | return $result; |
---|
332 | } |
---|
333 | |
---|
334 | return $working_dir; |
---|
335 | } |
---|
336 | |
---|
337 | /** |
---|
338 | * Clears the directory where this item is going to be installed into. |
---|
339 | * |
---|
340 | * @since 4.3.0 |
---|
341 | * @access public |
---|
342 | * |
---|
343 | * @global WP_Filesystem_Base $wp_filesystem Subclass |
---|
344 | * |
---|
345 | * @param string $remote_destination The location on the remote filesystem to be cleared |
---|
346 | * @return bool|WP_Error True upon success, WP_Error on failure. |
---|
347 | */ |
---|
348 | public function clear_destination( $remote_destination ) { |
---|
349 | global $wp_filesystem; |
---|
350 | |
---|
351 | if ( ! $wp_filesystem->exists( $remote_destination ) ) { |
---|
352 | return true; |
---|
353 | } |
---|
354 | |
---|
355 | // Check all files are writable before attempting to clear the destination. |
---|
356 | $unwritable_files = array(); |
---|
357 | |
---|
358 | $_files = $wp_filesystem->dirlist( $remote_destination, true, true ); |
---|
359 | |
---|
360 | // Flatten the resulting array, iterate using each as we append to the array during iteration. |
---|
361 | while ( $f = each( $_files ) ) { |
---|
362 | $file = $f['value']; |
---|
363 | $name = $f['key']; |
---|
364 | |
---|
365 | if ( ! isset( $file['files'] ) ) { |
---|
366 | continue; |
---|
367 | } |
---|
368 | |
---|
369 | foreach ( $file['files'] as $filename => $details ) { |
---|
370 | $_files[ $name . '/' . $filename ] = $details; |
---|
371 | } |
---|
372 | } |
---|
373 | |
---|
374 | // Check writability. |
---|
375 | foreach ( $_files as $filename => $file_details ) { |
---|
376 | if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) { |
---|
377 | |
---|
378 | // Attempt to alter permissions to allow writes and try again. |
---|
379 | $wp_filesystem->chmod( $remote_destination . $filename, ( 'd' == $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) ); |
---|
380 | if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) { |
---|
381 | $unwritable_files[] = $filename; |
---|
382 | } |
---|
383 | } |
---|
384 | } |
---|
385 | |
---|
386 | if ( ! empty( $unwritable_files ) ) { |
---|
387 | return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) ); |
---|
388 | } |
---|
389 | |
---|
390 | if ( ! $wp_filesystem->delete( $remote_destination, true ) ) { |
---|
391 | return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] ); |
---|
392 | } |
---|
393 | |
---|
394 | return true; |
---|
395 | } |
---|
396 | |
---|
397 | /** |
---|
398 | * Install a package. |
---|
399 | * |
---|
400 | * Copies the contents of a package form a source directory, and installs them in |
---|
401 | * a destination directory. Optionally removes the source. It can also optionally |
---|
402 | * clear out the destination folder if it already exists. |
---|
403 | * |
---|
404 | * @since 2.8.0 |
---|
405 | * @access public |
---|
406 | * |
---|
407 | * @global WP_Filesystem_Base $wp_filesystem Subclass |
---|
408 | * @global array $wp_theme_directories |
---|
409 | * |
---|
410 | * @param array|string $args { |
---|
411 | * Optional. Array or string of arguments for installing a package. Default empty array. |
---|
412 | * |
---|
413 | * @type string $source Required path to the package source. Default empty. |
---|
414 | * @type string $destination Required path to a folder to install the package in. |
---|
415 | * Default empty. |
---|
416 | * @type bool $clear_destination Whether to delete any files already in the destination |
---|
417 | * folder. Default false. |
---|
418 | * @type bool $clear_working Whether to delete the files from the working directory |
---|
419 | * after copying to the destination. Default false. |
---|
420 | * @type bool $protect_destination Whether to protect against deleting any files already |
---|
421 | * in the destination folder. Default false. |
---|
422 | * @type bool $abort_if_destination_exists Whether to abort the installation if |
---|
423 | * the destination folder already exists. Default true. |
---|
424 | * @type array $hook_extra Extra arguments to pass to the filter hooks called by |
---|
425 | * WP_Upgrader::install_package(). Default empty array. |
---|
426 | * } |
---|
427 | * |
---|
428 | * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure. |
---|
429 | */ |
---|
430 | public function install_package( $args = array() ) { |
---|
431 | global $wp_filesystem, $wp_theme_directories; |
---|
432 | |
---|
433 | $defaults = array( |
---|
434 | 'source' => '', // Please always pass this |
---|
435 | 'destination' => '', // and this |
---|
436 | 'clear_destination' => false, |
---|
437 | 'clear_working' => false, |
---|
438 | 'protect_destination' => false, |
---|
439 | 'abort_if_destination_exists' => true, |
---|
440 | 'hook_extra' => array() |
---|
441 | ); |
---|
442 | |
---|
443 | $args = wp_parse_args($args, $defaults); |
---|
444 | |
---|
445 | // These were previously extract()'d. |
---|
446 | $source = $args['source']; |
---|
447 | $destination = $args['destination']; |
---|
448 | $clear_destination = $args['clear_destination']; |
---|
449 | |
---|
450 | @set_time_limit( 300 ); |
---|
451 | |
---|
452 | if ( empty( $source ) || empty( $destination ) ) { |
---|
453 | return new WP_Error( 'bad_request', $this->strings['bad_request'] ); |
---|
454 | } |
---|
455 | $this->skin->feedback( 'installing_package' ); |
---|
456 | |
---|
457 | /** |
---|
458 | * Filters the install response before the installation has started. |
---|
459 | * |
---|
460 | * Returning a truthy value, or one that could be evaluated as a WP_Error |
---|
461 | * will effectively short-circuit the installation, returning that value |
---|
462 | * instead. |
---|
463 | * |
---|
464 | * @since 2.8.0 |
---|
465 | * |
---|
466 | * @param bool|WP_Error $response Response. |
---|
467 | * @param array $hook_extra Extra arguments passed to hooked filters. |
---|
468 | */ |
---|
469 | $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] ); |
---|
470 | |
---|
471 | if ( is_wp_error( $res ) ) { |
---|
472 | return $res; |
---|
473 | } |
---|
474 | |
---|
475 | //Retain the Original source and destinations |
---|
476 | $remote_source = $args['source']; |
---|
477 | $local_destination = $destination; |
---|
478 | |
---|
479 | $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) ); |
---|
480 | $remote_destination = $wp_filesystem->find_folder( $local_destination ); |
---|
481 | |
---|
482 | //Locate which directory to copy to the new folder, This is based on the actual folder holding the files. |
---|
483 | if ( 1 == count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { //Only one folder? Then we want its contents. |
---|
484 | $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] ); |
---|
485 | } elseif ( count( $source_files ) == 0 ) { |
---|
486 | return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files? |
---|
487 | } else { // It's only a single file, the upgrader will use the folder name of this file as the destination folder. Folder name is based on zip filename. |
---|
488 | $source = trailingslashit( $args['source'] ); |
---|
489 | } |
---|
490 | |
---|
491 | /** |
---|
492 | * Filters the source file location for the upgrade package. |
---|
493 | * |
---|
494 | * @since 2.8.0 |
---|
495 | * @since 4.4.0 The $hook_extra parameter became available. |
---|
496 | * |
---|
497 | * @param string $source File source location. |
---|
498 | * @param string $remote_source Remote file source location. |
---|
499 | * @param WP_Upgrader $this WP_Upgrader instance. |
---|
500 | * @param array $hook_extra Extra arguments passed to hooked filters. |
---|
501 | */ |
---|
502 | $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] ); |
---|
503 | |
---|
504 | if ( is_wp_error( $source ) ) { |
---|
505 | return $source; |
---|
506 | } |
---|
507 | |
---|
508 | // Has the source location changed? If so, we need a new source_files list. |
---|
509 | if ( $source !== $remote_source ) { |
---|
510 | $source_files = array_keys( $wp_filesystem->dirlist( $source ) ); |
---|
511 | } |
---|
512 | |
---|
513 | /* |
---|
514 | * Protection against deleting files in any important base directories. |
---|
515 | * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the |
---|
516 | * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending |
---|
517 | * to copy the directory into the directory, whilst they pass the source |
---|
518 | * as the actual files to copy. |
---|
519 | */ |
---|
520 | $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' ); |
---|
521 | |
---|
522 | if ( is_array( $wp_theme_directories ) ) { |
---|
523 | $protected_directories = array_merge( $protected_directories, $wp_theme_directories ); |
---|
524 | } |
---|
525 | |
---|
526 | if ( in_array( $destination, $protected_directories ) || $args['protect_destination'] ) { |
---|
527 | $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) ); |
---|
528 | $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) ); |
---|
529 | } |
---|
530 | |
---|
531 | if ( $clear_destination ) { |
---|
532 | // We're going to clear the destination if there's something there. |
---|
533 | $this->skin->feedback('remove_old'); |
---|
534 | |
---|
535 | $removed = $this->clear_destination( $remote_destination ); |
---|
536 | |
---|
537 | /** |
---|
538 | * Filters whether the upgrader cleared the destination. |
---|
539 | * |
---|
540 | * @since 2.8.0 |
---|
541 | * |
---|
542 | * @param mixed $removed Whether the destination was cleared. true on success, WP_Error on failure |
---|
543 | * @param string $local_destination The local package destination. |
---|
544 | * @param string $remote_destination The remote package destination. |
---|
545 | * @param array $hook_extra Extra arguments passed to hooked filters. |
---|
546 | */ |
---|
547 | $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] ); |
---|
548 | |
---|
549 | if ( is_wp_error( $removed ) ) { |
---|
550 | return $removed; |
---|
551 | } |
---|
552 | } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists($remote_destination) ) { |
---|
553 | //If we're not clearing the destination folder and something exists there already, Bail. |
---|
554 | //But first check to see if there are actually any files in the folder. |
---|
555 | $_files = $wp_filesystem->dirlist($remote_destination); |
---|
556 | if ( ! empty($_files) ) { |
---|
557 | $wp_filesystem->delete($remote_source, true); //Clear out the source files. |
---|
558 | return new WP_Error('folder_exists', $this->strings['folder_exists'], $remote_destination ); |
---|
559 | } |
---|
560 | } |
---|
561 | |
---|
562 | //Create destination if needed |
---|
563 | if ( ! $wp_filesystem->exists( $remote_destination ) ) { |
---|
564 | if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) { |
---|
565 | return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination ); |
---|
566 | } |
---|
567 | } |
---|
568 | // Copy new version of item into place. |
---|
569 | $result = copy_dir($source, $remote_destination); |
---|
570 | if ( is_wp_error($result) ) { |
---|
571 | if ( $args['clear_working'] ) { |
---|
572 | $wp_filesystem->delete( $remote_source, true ); |
---|
573 | } |
---|
574 | return $result; |
---|
575 | } |
---|
576 | |
---|
577 | //Clear the Working folder? |
---|
578 | if ( $args['clear_working'] ) { |
---|
579 | $wp_filesystem->delete( $remote_source, true ); |
---|
580 | } |
---|
581 | |
---|
582 | $destination_name = basename( str_replace($local_destination, '', $destination) ); |
---|
583 | if ( '.' == $destination_name ) { |
---|
584 | $destination_name = ''; |
---|
585 | } |
---|
586 | |
---|
587 | $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' ); |
---|
588 | |
---|
589 | /** |
---|
590 | * Filters the install response after the installation has finished. |
---|
591 | * |
---|
592 | * @since 2.8.0 |
---|
593 | * |
---|
594 | * @param bool $response Install response. |
---|
595 | * @param array $hook_extra Extra arguments passed to hooked filters. |
---|
596 | * @param array $result Installation result data. |
---|
597 | */ |
---|
598 | $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result ); |
---|
599 | |
---|
600 | if ( is_wp_error($res) ) { |
---|
601 | $this->result = $res; |
---|
602 | return $res; |
---|
603 | } |
---|
604 | |
---|
605 | //Bombard the calling function will all the info which we've just used. |
---|
606 | return $this->result; |
---|
607 | } |
---|
608 | |
---|
609 | /** |
---|
610 | * Run an upgrade/install. |
---|
611 | * |
---|
612 | * Attempts to download the package (if it is not a local file), unpack it, and |
---|
613 | * install it in the destination folder. |
---|
614 | * |
---|
615 | * @since 2.8.0 |
---|
616 | * @access public |
---|
617 | * |
---|
618 | * @param array $options { |
---|
619 | * Array or string of arguments for upgrading/installing a package. |
---|
620 | * |
---|
621 | * @type string $package The full path or URI of the package to install. |
---|
622 | * Default empty. |
---|
623 | * @type string $destination The full path to the destination folder. |
---|
624 | * Default empty. |
---|
625 | * @type bool $clear_destination Whether to delete any files already in the |
---|
626 | * destination folder. Default false. |
---|
627 | * @type bool $clear_working Whether to delete the files form the working |
---|
628 | * directory after copying to the destination. |
---|
629 | * Default false. |
---|
630 | * @type bool $abort_if_destination_exists Whether to abort the installation if the destination |
---|
631 | * folder already exists. When true, `$clear_destination` |
---|
632 | * should be false. Default true. |
---|
633 | * @type bool $is_multi Whether this run is one of multiple upgrade/install |
---|
634 | * actions being performed in bulk. When true, the skin |
---|
635 | * WP_Upgrader::header() and WP_Upgrader::footer() |
---|
636 | * aren't called. Default false. |
---|
637 | * @type array $hook_extra Extra arguments to pass to the filter hooks called by |
---|
638 | * WP_Upgrader::run(). |
---|
639 | * } |
---|
640 | * @return array|false|WP_error The result from self::install_package() on success, otherwise a WP_Error, |
---|
641 | * or false if unable to connect to the filesystem. |
---|
642 | */ |
---|
643 | public function run( $options ) { |
---|
644 | |
---|
645 | $defaults = array( |
---|
646 | 'package' => '', // Please always pass this. |
---|
647 | 'destination' => '', // And this |
---|
648 | 'clear_destination' => false, |
---|
649 | 'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please |
---|
650 | 'clear_working' => true, |
---|
651 | 'is_multi' => false, |
---|
652 | 'hook_extra' => array() // Pass any extra $hook_extra args here, this will be passed to any hooked filters. |
---|
653 | ); |
---|
654 | |
---|
655 | $options = wp_parse_args( $options, $defaults ); |
---|
656 | |
---|
657 | /** |
---|
658 | * Filters the package options before running an update. |
---|
659 | * |
---|
660 | * See also {@see 'upgrader_process_complete'}. |
---|
661 | * |
---|
662 | * @since 4.3.0 |
---|
663 | * |
---|
664 | * @param array $options { |
---|
665 | * Options used by the upgrader. |
---|
666 | * |
---|
667 | * @type string $package Package for update. |
---|
668 | * @type string $destination Update location. |
---|
669 | * @type bool $clear_destination Clear the destination resource. |
---|
670 | * @type bool $clear_working Clear the working resource. |
---|
671 | * @type bool $abort_if_destination_exists Abort if the Destination directory exists. |
---|
672 | * @type bool $is_multi Whether the upgrader is running multiple times. |
---|
673 | * @type array $hook_extra { |
---|
674 | * Extra hook arguments. |
---|
675 | * |
---|
676 | * @type string $action Type of action. Default 'update'. |
---|
677 | * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'. |
---|
678 | * @type bool $bulk Whether the update process is a bulk update. Default true. |
---|
679 | * @type string $plugin The base plugin path from the plugins directory. |
---|
680 | * @type string $theme The stylesheet or template name of the theme. |
---|
681 | * @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme', |
---|
682 | * or 'core'. |
---|
683 | * @type object $language_update The language pack update offer. |
---|
684 | * } |
---|
685 | * } |
---|
686 | */ |
---|
687 | $options = apply_filters( 'upgrader_package_options', $options ); |
---|
688 | |
---|
689 | if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times |
---|
690 | $this->skin->header(); |
---|
691 | } |
---|
692 | |
---|
693 | // Connect to the Filesystem first. |
---|
694 | $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) ); |
---|
695 | // Mainly for non-connected filesystem. |
---|
696 | if ( ! $res ) { |
---|
697 | if ( ! $options['is_multi'] ) { |
---|
698 | $this->skin->footer(); |
---|
699 | } |
---|
700 | return false; |
---|
701 | } |
---|
702 | |
---|
703 | $this->skin->before(); |
---|
704 | |
---|
705 | if ( is_wp_error($res) ) { |
---|
706 | $this->skin->error($res); |
---|
707 | $this->skin->after(); |
---|
708 | if ( ! $options['is_multi'] ) { |
---|
709 | $this->skin->footer(); |
---|
710 | } |
---|
711 | return $res; |
---|
712 | } |
---|
713 | |
---|
714 | /* |
---|
715 | * Download the package (Note, This just returns the filename |
---|
716 | * of the file if the package is a local file) |
---|
717 | */ |
---|
718 | $download = $this->download_package( $options['package'] ); |
---|
719 | if ( is_wp_error($download) ) { |
---|
720 | $this->skin->error($download); |
---|
721 | $this->skin->after(); |
---|
722 | if ( ! $options['is_multi'] ) { |
---|
723 | $this->skin->footer(); |
---|
724 | } |
---|
725 | return $download; |
---|
726 | } |
---|
727 | |
---|
728 | $delete_package = ( $download != $options['package'] ); // Do not delete a "local" file |
---|
729 | |
---|
730 | // Unzips the file into a temporary directory. |
---|
731 | $working_dir = $this->unpack_package( $download, $delete_package ); |
---|
732 | if ( is_wp_error($working_dir) ) { |
---|
733 | $this->skin->error($working_dir); |
---|
734 | $this->skin->after(); |
---|
735 | if ( ! $options['is_multi'] ) { |
---|
736 | $this->skin->footer(); |
---|
737 | } |
---|
738 | return $working_dir; |
---|
739 | } |
---|
740 | |
---|
741 | // With the given options, this installs it to the destination directory. |
---|
742 | $result = $this->install_package( array( |
---|
743 | 'source' => $working_dir, |
---|
744 | 'destination' => $options['destination'], |
---|
745 | 'clear_destination' => $options['clear_destination'], |
---|
746 | 'abort_if_destination_exists' => $options['abort_if_destination_exists'], |
---|
747 | 'clear_working' => $options['clear_working'], |
---|
748 | 'hook_extra' => $options['hook_extra'] |
---|
749 | ) ); |
---|
750 | |
---|
751 | $this->skin->set_result($result); |
---|
752 | if ( is_wp_error($result) ) { |
---|
753 | $this->skin->error($result); |
---|
754 | $this->skin->feedback('process_failed'); |
---|
755 | } else { |
---|
756 | // Install succeeded. |
---|
757 | $this->skin->feedback('process_success'); |
---|
758 | } |
---|
759 | |
---|
760 | $this->skin->after(); |
---|
761 | |
---|
762 | if ( ! $options['is_multi'] ) { |
---|
763 | |
---|
764 | /** |
---|
765 | * Fires when the upgrader process is complete. |
---|
766 | * |
---|
767 | * See also {@see 'upgrader_package_options'}. |
---|
768 | * |
---|
769 | * @since 3.6.0 |
---|
770 | * @since 3.7.0 Added to WP_Upgrader::run(). |
---|
771 | * @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`. |
---|
772 | * |
---|
773 | * @param WP_Upgrader $this WP_Upgrader instance. In other contexts, $this, might be a |
---|
774 | * Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance. |
---|
775 | * @param array $hook_extra { |
---|
776 | * Array of bulk item update data. |
---|
777 | * |
---|
778 | * @type string $action Type of action. Default 'update'. |
---|
779 | * @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'. |
---|
780 | * @type bool $bulk Whether the update process is a bulk update. Default true. |
---|
781 | * @type array $plugins Array of the basename paths of the plugins' main files. |
---|
782 | * @type array $themes The theme slugs. |
---|
783 | * @type array $translations { |
---|
784 | * Array of translations update data. |
---|
785 | * |
---|
786 | * @type string $language The locale the translation is for. |
---|
787 | * @type string $type Type of translation. Accepts 'plugin', 'theme', or 'core'. |
---|
788 | * @type string $slug Text domain the translation is for. The slug of a theme/plugin or |
---|
789 | * 'default' for core translations. |
---|
790 | * @type string $version The version of a theme, plugin, or core. |
---|
791 | * } |
---|
792 | * } |
---|
793 | */ |
---|
794 | do_action( 'upgrader_process_complete', $this, $options['hook_extra'] ); |
---|
795 | |
---|
796 | $this->skin->footer(); |
---|
797 | } |
---|
798 | |
---|
799 | return $result; |
---|
800 | } |
---|
801 | |
---|
802 | /** |
---|
803 | * Toggle maintenance mode for the site. |
---|
804 | * |
---|
805 | * Creates/deletes the maintenance file to enable/disable maintenance mode. |
---|
806 | * |
---|
807 | * @since 2.8.0 |
---|
808 | * @access public |
---|
809 | * |
---|
810 | * @global WP_Filesystem_Base $wp_filesystem Subclass |
---|
811 | * |
---|
812 | * @param bool $enable True to enable maintenance mode, false to disable. |
---|
813 | */ |
---|
814 | public function maintenance_mode( $enable = false ) { |
---|
815 | global $wp_filesystem; |
---|
816 | $file = $wp_filesystem->abspath() . '.maintenance'; |
---|
817 | if ( $enable ) { |
---|
818 | $this->skin->feedback('maintenance_start'); |
---|
819 | // Create maintenance file to signal that we are upgrading |
---|
820 | $maintenance_string = '<?php $upgrading = ' . time() . '; ?>'; |
---|
821 | $wp_filesystem->delete($file); |
---|
822 | $wp_filesystem->put_contents($file, $maintenance_string, FS_CHMOD_FILE); |
---|
823 | } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) { |
---|
824 | $this->skin->feedback('maintenance_end'); |
---|
825 | $wp_filesystem->delete($file); |
---|
826 | } |
---|
827 | } |
---|
828 | |
---|
829 | /** |
---|
830 | * Creates a lock using WordPress options. |
---|
831 | * |
---|
832 | * @since 4.5.0 |
---|
833 | * @access public |
---|
834 | * @static |
---|
835 | * |
---|
836 | * @param string $lock_name The name of this unique lock. |
---|
837 | * @param int $release_timeout Optional. The duration in seconds to respect an existing lock. |
---|
838 | * Default: 1 hour. |
---|
839 | * @return bool False if a lock couldn't be created or if the lock is no longer valid. True otherwise. |
---|
840 | */ |
---|
841 | public static function create_lock( $lock_name, $release_timeout = null ) { |
---|
842 | global $wpdb; |
---|
843 | if ( ! $release_timeout ) { |
---|
844 | $release_timeout = HOUR_IN_SECONDS; |
---|
845 | } |
---|
846 | $lock_option = $lock_name . '.lock'; |
---|
847 | |
---|
848 | // Try to lock. |
---|
849 | $lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_option, time() ) ); |
---|
850 | |
---|
851 | if ( ! $lock_result ) { |
---|
852 | $lock_result = get_option( $lock_option ); |
---|
853 | |
---|
854 | // If a lock couldn't be created, and there isn't a lock, bail. |
---|
855 | if ( ! $lock_result ) { |
---|
856 | return false; |
---|
857 | } |
---|
858 | |
---|
859 | // Check to see if the lock is still valid. If not, bail. |
---|
860 | if ( $lock_result > ( time() - $release_timeout ) ) { |
---|
861 | return false; |
---|
862 | } |
---|
863 | |
---|
864 | // There must exist an expired lock, clear it and re-gain it. |
---|
865 | WP_Upgrader::release_lock( $lock_name ); |
---|
866 | |
---|
867 | return WP_Upgrader::create_lock( $lock_name, $release_timeout ); |
---|
868 | } |
---|
869 | |
---|
870 | // Update the lock, as by this point we've definitely got a lock, just need to fire the actions. |
---|
871 | update_option( $lock_option, time() ); |
---|
872 | |
---|
873 | return true; |
---|
874 | } |
---|
875 | |
---|
876 | /** |
---|
877 | * Releases an upgrader lock. |
---|
878 | * |
---|
879 | * @since 4.5.0 |
---|
880 | * @access public |
---|
881 | * @static |
---|
882 | * |
---|
883 | * @see WP_Upgrader::create_lock() |
---|
884 | * |
---|
885 | * @param string $lock_name The name of this unique lock. |
---|
886 | * @return bool True if the lock was successfully released. False on failure. |
---|
887 | */ |
---|
888 | public static function release_lock( $lock_name ) { |
---|
889 | return delete_option( $lock_name . '.lock' ); |
---|
890 | } |
---|
891 | |
---|
892 | } |
---|
893 | |
---|
894 | /** Plugin_Upgrader class */ |
---|
895 | require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; |
---|
896 | |
---|
897 | /** Theme_Upgrader class */ |
---|
898 | require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php'; |
---|
899 | |
---|
900 | /** Language_Pack_Upgrader class */ |
---|
901 | require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader.php'; |
---|
902 | |
---|
903 | /** Core_Upgrader class */ |
---|
904 | require_once ABSPATH . 'wp-admin/includes/class-core-upgrader.php'; |
---|
905 | |
---|
906 | /** File_Upload_Upgrader class */ |
---|
907 | require_once ABSPATH . 'wp-admin/includes/class-file-upload-upgrader.php'; |
---|
908 | |
---|
909 | /** WP_Automatic_Updater class */ |
---|
910 | require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php'; |
---|