WP_Upgrader

Core class used for upgrading/installing a local set of files via the Filesystem Abstraction classes from a Zip file.

Defined (1)

The class is defined in the following location(s).

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