/wp-admin/includes/class-wp-upgrader.php

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