+ $contents = $z->getFromIndex($i);
+ if ( false === $contents )
+ return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
+
+ if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE) )
+ return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
+ }
+
+ $z->close();
+
+ return true;
+}
+
+/**
+ * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the PclZip library.
+ * Assumes that WP_Filesystem() has already been called and set up.
+ *
+ * @since 3.0.0
+ * @see unzip_file
+ * @access private
+ *
+ * @param string $file Full path and filename of zip archive
+ * @param string $to Full path on the filesystem to extract archive to
+ * @param array $needed_dirs A partial list of required folders needed to be created.
+ * @return mixed WP_Error on failure, True on success
+ */
+function _unzip_file_pclzip($file, $to, $needed_dirs = array()) {
+ global $wp_filesystem;
+
+ mbstring_binary_safe_encoding();
+
+ require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
+
+ $archive = new PclZip($file);
+
+ $archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING);
+
+ reset_mbstring_encoding();
+
+ // Is the archive valid?
+ if ( !is_array($archive_files) )
+ return new WP_Error('incompatible_archive', __('Incompatible Archive.'), $archive->errorInfo(true));
+
+ if ( 0 == count($archive_files) )
+ return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
+
+ $uncompressed_size = 0;
+
+ // Determine any children directories needed (From within the archive)
+ foreach ( $archive_files as $file ) {
+ if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Skip the OS X-created __MACOSX directory
+ continue;
+
+ $uncompressed_size += $file['size'];
+
+ $needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname($file['filename']) );
+ }
+
+ /*
+ * disk_free_space() could return false. Assume that any falsey value is an error.
+ * A disk that has zero free bytes has bigger problems.
+ * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
+ */
+ if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
+ $available_space = @disk_free_space( WP_CONTENT_DIR );
+ if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
+ return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
+ }
+
+ $needed_dirs = array_unique($needed_dirs);
+ foreach ( $needed_dirs as $dir ) {
+ // Check the parent folders of the folders all exist within the creation array.
+ if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
+ continue;
+ if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
+ continue;
+
+ $parent_folder = dirname($dir);
+ while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
+ $needed_dirs[] = $parent_folder;
+ $parent_folder = dirname($parent_folder);
+ }
+ }
+ asort($needed_dirs);
+
+ // Create those directories if need be:
+ foreach ( $needed_dirs as $_dir ) {
+ // Only check to see if the dir exists upon creation failure. Less I/O this way.
+ if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) )
+ return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
+ }
+ unset($needed_dirs);
+
+ // Extract the files from the zip
+ foreach ( $archive_files as $file ) {
+ if ( $file['folder'] )
+ continue;
+
+ if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
+ continue;
+
+ if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE) )
+ return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
+ }
+ return true;
+}
+
+/**
+ * Copies a directory from one location to another via the WordPress Filesystem Abstraction.
+ * Assumes that WP_Filesystem() has already been called and setup.
+ *
+ * @since 2.5.0
+ *
+ * @param string $from source directory
+ * @param string $to destination directory
+ * @param array $skip_list a list of files/folders to skip copying
+ * @return mixed WP_Error on failure, True on success.
+ */
+function copy_dir($from, $to, $skip_list = array() ) {
+ global $wp_filesystem;
+
+ $dirlist = $wp_filesystem->dirlist($from);
+
+ $from = trailingslashit($from);
+ $to = trailingslashit($to);
+
+ foreach ( (array) $dirlist as $filename => $fileinfo ) {
+ if ( in_array( $filename, $skip_list ) )
+ continue;
+
+ if ( 'f' == $fileinfo['type'] ) {
+ if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
+ // If copy failed, chmod file to 0644 and try again.
+ $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
+ if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) )
+ return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
+ }
+ } elseif ( 'd' == $fileinfo['type'] ) {
+ if ( !$wp_filesystem->is_dir($to . $filename) ) {
+ if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
+ return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
+ }
+
+ // generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
+ $sub_skip_list = array();
+ foreach ( $skip_list as $skip_item ) {
+ if ( 0 === strpos( $skip_item, $filename . '/' ) )
+ $sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
+ }
+
+ $result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
+ if ( is_wp_error($result) )
+ return $result;
+ }
+ }
+ return true;
+}
+
+/**
+ * Initialises and connects the WordPress Filesystem Abstraction classes.
+ * This function will include the chosen transport and attempt connecting.
+ *
+ * Plugins may add extra transports, And force WordPress to use them by returning
+ * the filename via the {@see 'filesystem_method_file'} filter.
+ *
+ * @since 2.5.0
+ *
+ * @param array $args Optional. Connection args, These are passed directly to
+ * the `WP_Filesystem_*()` classes. Default false.
+ * @param string $context Optional. Context for {@see get_filesystem_method()}.
+ * Default false.
+ * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
+ * Default false.
+ * @return null|boolean false on failure, true on success.
+ */
+function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
+ global $wp_filesystem;
+
+ require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
+
+ $method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
+
+ if ( ! $method )
+ return false;
+
+ if ( ! class_exists("WP_Filesystem_$method") ) {
+
+ /**
+ * Filter the path for a specific filesystem method class file.
+ *
+ * @since 2.6.0
+ *
+ * @see get_filesystem_method()
+ *
+ * @param string $path Path to the specific filesystem method class file.
+ * @param string $method The filesystem method to use.
+ */
+ $abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
+
+ if ( ! file_exists($abstraction_file) )
+ return;
+
+ require_once($abstraction_file);
+ }
+ $method = "WP_Filesystem_$method";
+
+ $wp_filesystem = new $method($args);
+
+ //Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
+ if ( ! defined('FS_CONNECT_TIMEOUT') )
+ define('FS_CONNECT_TIMEOUT', 30);
+ if ( ! defined('FS_TIMEOUT') )
+ define('FS_TIMEOUT', 30);
+
+ if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
+ return false;
+
+ if ( !$wp_filesystem->connect() )
+ return false; //There was an error connecting to the server.
+
+ // Set the permission constants if not already set.
+ if ( ! defined('FS_CHMOD_DIR') )
+ define('FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
+ if ( ! defined('FS_CHMOD_FILE') )
+ define('FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
+
+ return true;
+}
+
+/**
+ * Determines which method to use for reading, writing, modifying, or deleting
+ * files on the filesystem.
+ *
+ * The priority of the transports are: Direct, SSH2, FTP PHP Extension, FTP Sockets
+ * (Via Sockets class, or `fsockopen()`). Valid values for these are: 'direct', 'ssh2',
+ * 'ftpext' or 'ftpsockets'.
+ *
+ * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
+ * or filtering via {@see 'filesystem_method'}.
+ *
+ * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants
+ *
+ * Plugins may define a custom transport handler, See WP_Filesystem().
+ *
+ * @since 2.5.0
+ *
+ * @param array $args Optional. Connection details. Default empty array.
+ * @param string $context Optional. Full path to the directory that is tested
+ * for being writable. Default false.
+ * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
+ * Default false.
+ * @return string The transport to use, see description for valid return values.
+ */
+function get_filesystem_method( $args = array(), $context = false, $allow_relaxed_file_ownership = false ) {
+ $method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
+
+ if ( ! $context ) {
+ $context = WP_CONTENT_DIR;
+ }
+
+ // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
+ if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) {
+ $context = dirname( $context );
+ }
+
+ $context = trailingslashit( $context );
+
+ if ( ! $method ) {
+
+ $temp_file_name = $context . 'temp-write-test-' . time();
+ $temp_handle = @fopen($temp_file_name, 'w');
+ if ( $temp_handle ) {
+
+ // Attempt to determine the file owner of the WordPress files, and that of newly created files
+ $wp_file_owner = $temp_file_owner = false;
+ if ( function_exists('fileowner') ) {
+ $wp_file_owner = @fileowner( __FILE__ );
+ $temp_file_owner = @fileowner( $temp_file_name );
+ }
+
+ if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
+ // WordPress is creating files as the same owner as the WordPress files,
+ // this means it's safe to modify & create new files via PHP.
+ $method = 'direct';
+ $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
+ } elseif ( $allow_relaxed_file_ownership ) {
+ // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
+ // safely in this directory. This mode doesn't create new files, only alter existing ones.
+ $method = 'direct';
+ $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
+ }
+
+ @fclose($temp_handle);
+ @unlink($temp_file_name);
+ }
+ }
+
+ if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
+ if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
+ if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
+
+ /**
+ * Filter the filesystem method to use.
+ *
+ * @since 2.6.0
+ *
+ * @param string $method Filesystem method to return.
+ * @param array $args An array of connection details for the method.
+ * @param string $context Full path to the directory that is tested for being writable.
+ * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
+ */
+ return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );