/**
  * Merge the downloaded extension directory with the existing directory
  *
  * @param string $source_wp_fs_dir Downloaded extension directory
  * @param string $destination_wp_fs_dir
  *
  * @return null|WP_Error
  */
 private function merge_extension($source_wp_fs_dir, $destination_wp_fs_dir)
 {
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     $wp_error_id = 'fw_extensions_merge';
     $source_files = $wp_filesystem->dirlist($source_wp_fs_dir);
     if ($source_files === false) {
         return new WP_Error($wp_error_id, sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir));
     }
     if (empty($source_files)) {
         // directory is empty, nothing to move
         return;
     }
     /**
      * Prepare destination directory
      * Remove everything except the extensions/ directory
      */
     if ($wp_filesystem->exists($destination_wp_fs_dir)) {
         $destination_files = $wp_filesystem->dirlist($destination_wp_fs_dir);
         if ($destination_files === false) {
             return new WP_Error($wp_error_id, sprintf(__('Cannot read directory "%s".', 'fw'), $destination_wp_fs_dir));
         }
         if (!empty($destination_files)) {
             // the directory contains some files, delete everything
             foreach ($destination_files as $file) {
                 if ($file['name'] === 'extensions' && $file['type'] === 'd') {
                     // do not touch the extensions/ directory
                     continue;
                 }
                 if (!$wp_filesystem->delete($destination_wp_fs_dir . '/' . $file['name'], true, $file['type'])) {
                     return new WP_Error($wp_error_id, sprintf(__('Cannot delete "%s".', 'fw'), $destination_wp_fs_dir . '/' . $file['name']));
                 }
             }
             unset($destination_files);
         }
     } else {
         if (!FW_WP_Filesystem::mkdir_recursive($destination_wp_fs_dir)) {
             return new WP_Error($wp_error_id, sprintf(__('Cannot create the "%s" directory.', 'fw'), $destination_wp_fs_dir));
         }
     }
     $has_sub_extensions = false;
     foreach ($source_files as $file) {
         if ($file['name'] === 'extensions' && $file['type'] === 'd') {
             // do not touch the extensions/ directory
             $has_sub_extensions = true;
             continue;
         }
         if (!$wp_filesystem->move($source_wp_fs_dir . '/' . $file['name'], $destination_wp_fs_dir . '/' . $file['name'])) {
             return new WP_Error($wp_error_id, sprintf(__('Cannot move "%s" to "%s".', 'fw'), $source_wp_fs_dir . '/' . $file['name'], $destination_wp_fs_dir . '/' . $file['name']));
         }
     }
     unset($source_files);
     if (!$has_sub_extensions) {
         return;
     }
     $sub_extensions = $wp_filesystem->dirlist($source_wp_fs_dir . '/extensions');
     if ($sub_extensions === false) {
         return new WP_Error($wp_error_id, sprintf(__('Cannot read directory "%s".', 'fw'), $source_wp_fs_dir . '/extensions'));
     }
     if (empty($sub_extensions)) {
         // directory is empty, nothing to remove
         return;
     }
     foreach ($sub_extensions as $file) {
         if ($file['type'] !== 'd') {
             // wrong, only directories must exist in the extensions/ directory
             continue;
         }
         $merge_result = $this->merge_extension($source_wp_fs_dir . '/extensions/' . $file['name'], $destination_wp_fs_dir . '/extensions/' . $file['name']);
         if (is_wp_error($merge_result)) {
             return $merge_result;
         }
     }
 }
 /**
  * Merge two extensions/ directories
  * @param string $source_dir WP_Filesystem dir '/a/b/c/extensions'
  * @param string $destination_dir WP_Filesystem dir '/a/b/d/extensions'
  * @return bool|WP_Error
  */
 private function merge_extensions($source_dir, $destination_dir)
 {
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     $wp_error_id = 'fw_ext_update_merge_extensions';
     if (!$wp_filesystem->exists($destination_dir)) {
         // do a simple move if destination does not exist
         if (!$wp_filesystem->move($source_dir, $destination_dir)) {
             return new WP_Error($wp_error_id, sprintf(__('Cannot move "%s" to "%s"', 'fw'), $source_dir, $destination_dir));
         }
         return true;
     }
     $source_ext_dirs = $wp_filesystem->dirlist($source_dir, true);
     if ($source_ext_dirs === false) {
         return new WP_Error($wp_error_id, __('Cannot access directory: ', 'fw') . $source_dir);
     }
     foreach ($source_ext_dirs as $ext_dir) {
         if (in_array($ext_dir['name'], $this->skip_file_names)) {
             continue;
         }
         if ($ext_dir['type'] !== 'd') {
             // process only directories from the extensions/ directory
             continue;
         }
         $source_extension_dir = $source_dir . '/' . $ext_dir['name'];
         $destination_extension_dir = $destination_dir . '/' . $ext_dir['name'];
         $source_ext_files = $wp_filesystem->dirlist($source_extension_dir, true);
         if ($source_ext_files === false) {
             return new WP_Error($wp_error_id, __('Cannot access directory: ', 'fw') . $source_extension_dir);
         }
         if (empty($source_ext_files)) {
             /**
              * Source extension directory is empty, do nothing.
              * This happens when the extension is a git submodule in repository
              * but in zip it comes as an empty directory.
              */
             continue;
         }
         // create if not exists
         if (!$wp_filesystem->exists($destination_extension_dir)) {
             if (!FW_WP_Filesystem::mkdir_recursive($destination_extension_dir)) {
                 return new WP_Error($wp_error_id, __('Cannot create directory: ', 'fw') . $destination_extension_dir);
             }
         }
         $dest_ext_files = $wp_filesystem->dirlist($destination_extension_dir, true);
         if ($dest_ext_files === false) {
             return new WP_Error($wp_error_id, __('Cannot access directory: ', 'fw') . $destination_extension_dir);
         }
         $destination_has_extensions_dir = false;
         foreach ($dest_ext_files as $dest_ext_file) {
             if (in_array($dest_ext_file['name'], $this->skip_file_names)) {
                 continue;
             }
             if ($dest_ext_file['name'] === 'extensions' && $dest_ext_file['type'] === 'd') {
                 $destination_has_extensions_dir = true;
                 continue;
             }
             $dest_ext_file_path = $destination_extension_dir . '/' . $dest_ext_file['name'];
             if (!$wp_filesystem->delete($dest_ext_file_path, true, $dest_ext_file['type'])) {
                 return new WP_Error($wp_error_id, __('Cannot delete: ', 'fw') . $dest_ext_file_path);
             }
         }
         $source_has_extensions_dir = false;
         foreach ($source_ext_files as $source_ext_file) {
             if (in_array($source_ext_file['name'], $this->skip_file_names)) {
                 continue;
             }
             if ($source_ext_file['name'] === 'extensions' && $source_ext_file['type'] === 'd') {
                 $source_has_extensions_dir = true;
                 continue;
             }
             $source_ext_file_path = $source_extension_dir . '/' . $source_ext_file['name'];
             $dest_ext_file_path = $destination_extension_dir . '/' . $source_ext_file['name'];
             if (!$wp_filesystem->move($source_ext_file_path, $dest_ext_file_path)) {
                 return new WP_Error($wp_error_id, sprintf(__('Cannot move "%s" to "%s"', 'fw'), $source_ext_file_path, $dest_ext_file_path));
             }
         }
         if ($source_has_extensions_dir) {
             if ($destination_has_extensions_dir) {
                 $merge_result = $this->merge_extensions($source_extension_dir . '/extensions', $destination_extension_dir . '/extensions');
                 if ($merge_result !== true) {
                     return $merge_result;
                 }
             } else {
                 if (!$wp_filesystem->move($source_extension_dir . '/extensions', $destination_extension_dir . '/extensions')) {
                     return new WP_Error($wp_error_id, sprintf(__('Cannot move "%s" to "%s"', 'fw'), $source_extension_dir . '/extensions', $destination_extension_dir . '/extensions'));
                 }
             }
         }
     }
     return true;
 }
 /**
  * @internal
  */
 public function _action_update_framework()
 {
     $nonce_name = '_nonce_fw_ext_update_framework';
     if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
         wp_die(__('Invalid nonce.', 'fw'));
     }
     if (!class_exists('_FW_Ext_Update_Framework_Upgrader_Skin')) {
         fw_include_file_isolated($this->get_declared_path('/includes/classes/class--fw-ext-update-framework-upgrader-skin.php'));
     }
     $skin = new _FW_Ext_Update_Framework_Upgrader_Skin(array('title' => __('Update Framework', 'fw')));
     require_once ABSPATH . 'wp-admin/admin-header.php';
     $skin->header();
     $update = $this->get_framework_update(true);
     do {
         if ($update === false) {
             $skin->error(__('Failed to get framework latest version.', 'fw'));
             break;
         } elseif (is_wp_error($update)) {
             $skin->error($update);
             break;
         }
         $context = $this->context;
         if (!FW_WP_Filesystem::request_access($context, fw_current_url(), array($nonce_name))) {
             break;
         }
         $this->maintenance_mode(true);
         /** @var WP_Filesystem_Base $wp_filesystem */
         global $wp_filesystem;
         $tmp_download_dir = FW_WP_Filesystem::real_path_to_filesystem_path(FW_CACHE_DIR . '/update');
         // just in case it already exists, clear everything, it may contain broken/old files
         $wp_filesystem->rmdir($tmp_download_dir, true);
         if (!FW_WP_Filesystem::mkdir_recursive($tmp_download_dir)) {
             $skin->error(__('Cannot create directory: ' . $tmp_download_dir, 'fw'));
             break;
         }
         $skin->feedback(__('Downloading framework...', 'fw'));
         /** @var FW_Ext_Update_Service $service */
         $service = $this->get_child($update['service']);
         $downloaded_files_dir = $service->_download_framework($update['latest_version'], $tmp_download_dir);
         if (!$downloaded_files_dir) {
             $skin->error(__('Failed to download framework.', 'fw'));
             break;
         } elseif (is_wp_error($downloaded_files_dir)) {
             $skin->error($downloaded_files_dir);
             break;
         }
         $skin->feedback(__('Installing framework...', 'fw'));
         $framework_dir = FW_WP_Filesystem::real_path_to_filesystem_path(FW_DIR);
         // remove entire framework directory
         $wp_filesystem->rmdir($framework_dir, true);
         // move downloaded directory as new framework directory
         $wp_filesystem->move($downloaded_files_dir, $framework_dir);
         $skin->feedback(__('Framework updated.', 'fw'));
         $wp_filesystem->delete($tmp_download_dir, true, 'd');
         $skin->set_result(true);
         $skin->after();
     } while (false);
     $this->maintenance_mode(false);
     $skin->footer();
     require_once ABSPATH . 'wp-admin/admin-footer.php';
 }
 /**
  * Copy Theme Available Extensions to a tmp directory
  * Used before theme update
  * @since 2.6.0
  * @return null|WP_Error
  */
 public function theme_available_extensions_copy()
 {
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     if (!$wp_filesystem || is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
         return new WP_Error('fs_not_initialized', __('WP Filesystem is not initialized', 'fw'));
     }
     $wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir('/theme-ext'));
     if ($wp_filesystem->exists($wpfs_tmp_dir) && !$wp_filesystem->rmdir($wpfs_tmp_dir, true)) {
         return new WP_Error('tmp_dir_rm_fail', sprintf(__('Temporary directory cannot be removed: %s', 'fw'), $wpfs_tmp_dir));
     }
     if (!FW_WP_Filesystem::mkdir_recursive($wpfs_tmp_dir)) {
         return new WP_Error('tmp_dir_rm_fail', sprintf(__('Temporary directory cannot be created: %s', 'fw'), $wpfs_tmp_dir));
     }
     $available_extensions = $this->get_available_extensions();
     $installed_extensions = $this->get_installed_extensions(true);
     $base_dir = fw_get_template_customizations_directory('/extensions');
     foreach ($installed_extensions as $name => $ext) {
         if (!(isset($available_extensions[$name]) && isset($available_extensions[$name]['theme']) && $available_extensions[$name]['theme'])) {
             continue;
         }
         if (($rel_path = preg_replace('/^' . preg_quote($base_dir, '/') . '/', '', $ext['path'])) === $base_dir) {
             return new WP_Error('rel_path_failed', sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path']));
         }
         if (($wpfs_path = FW_WP_Filesystem::real_path_to_filesystem_path($ext['path'])) === false) {
             return new WP_Error('real_to_wpfs_filed', sprintf(__('Failed to extract relative directory from: %s', 'fw'), $ext['path']));
         }
         $wpfs_dest_dir = $wpfs_tmp_dir . $rel_path;
         if (!FW_WP_Filesystem::mkdir_recursive($wpfs_dest_dir)) {
             return new WP_Error('dest_dir_mk_fail', sprintf(__('Failed to create directory %s', 'fw'), $wpfs_dest_dir));
         }
         if (is_wp_error($copy_result = copy_dir($wpfs_path, $wpfs_dest_dir))) {
             /** @var WP_Error $copy_result */
             return new WP_Error('ext_copy_failed', sprintf(__('Failed to copy extension to %s', 'fw'), $wpfs_dest_dir));
         }
     }
 }