/**
  * @param string $source_dir path
  * @param string $destination_dir path
  * @param bool $fs Use WP_Filesystem or not
  * @param array $skip_dirs {'path': mixed}
  *
  * @return WP_Error|true
  */
 private function copy_dir($source_dir, $destination_dir, $fs, $skip_dirs)
 {
     $included_hidden_names = fw_ext('backups')->get_config('included_hidden_names');
     if ($fs) {
         global $wp_filesystem;
         /** @var WP_Filesystem_Base $wp_filesystem */
         $fs_source_dir = fw_fix_path(FW_WP_Filesystem::real_path_to_filesystem_path($source_dir));
         if (empty($fs_source_dir)) {
             return new WP_Error('dir_to_fs_failed', sprintf(__('Cannot convert Filesystem path: %s', 'fw'), $source_dir));
         } elseif (false === ($list = $wp_filesystem->dirlist($fs_source_dir, true))) {
             return new WP_Error('dir_list_failed', sprintf(__('Failed to list dir: %s', 'fw'), $source_dir));
         }
         foreach ($list as $file) {
             if ($file['name'][0] === '.' && !isset($included_hidden_names[$file['name']])) {
                 continue;
             }
             $file_path = $source_dir . '/' . $file['name'];
             $fs_file_path = $fs_source_dir . '/' . $file['name'];
             $destination_file_path = $destination_dir . '/' . $file['name'];
             $fs_destination_file_path = fw_fix_path(FW_WP_Filesystem::real_path_to_filesystem_path($destination_file_path));
             if (empty($fs_destination_file_path)) {
                 return new WP_Error('path_to_fs_failed', sprintf(__('Cannot convert Filesystem path: %s', 'fw'), $destination_file_path));
             }
             if ($file['type'] === 'd') {
                 if (isset($skip_dirs[$destination_file_path])) {
                     continue;
                 } else {
                     foreach ($skip_dirs as $skip_dir => $skip_dir_data) {
                         if (strlen(preg_replace('/^' . preg_quote($destination_file_path, '/') . '/', '', $skip_dir)) != strlen($skip_dir)) {
                             continue 2;
                             // skip dir if it's inside current dir
                         }
                     }
                 }
                 if (!$wp_filesystem->mkdir($fs_destination_file_path)) {
                     return new WP_Error('fs_mkdir_fail', sprintf(__('Failed to create dir: %s', 'fw'), $destination_file_path));
                 }
                 if (is_wp_error($result = copy_dir($fs_file_path, $fs_destination_file_path))) {
                     return $result;
                 }
             } else {
                 if (!$wp_filesystem->copy($fs_file_path, $fs_destination_file_path)) {
                     return new WP_Error('file_copy_fail', sprintf(__('Failed to copy file: %s', 'fw'), $file_path));
                 }
             }
         }
         return true;
     } else {
         $names = array_diff(($names = scandir($source_dir)) ? $names : array(), array('.', '..'));
         foreach ($names as $file_name) {
             $file_path = $source_dir . '/' . $file_name;
             $destination_file_path = $destination_dir . '/' . $file_name;
             if ($file_name[0] === '.' && !isset($included_hidden_names[$file_name])) {
                 continue;
             }
             if (is_dir($file_path)) {
                 if (isset($skip_dirs[$destination_file_path])) {
                     continue;
                 } else {
                     foreach ($skip_dirs as $skip_dir => $skip_dir_data) {
                         if (strlen(preg_replace('/^' . preg_quote($destination_file_path, '/') . '/', '', $skip_dir)) != strlen($skip_dir)) {
                             continue 2;
                             // skip dir it's inside current dir
                         }
                     }
                 }
                 if (is_dir($destination_file_path)) {
                     /**
                      * Some times empty directories are not deleted ( @see fw_ext_backups_rmdir_recursive() )
                      * even if rmdir() returns true, the directory remains (I don't know why),
                      * so a workaround is to check if it exists and do not try to created it
                      * (because create will return false)
                      */
                 } elseif (!mkdir($destination_file_path)) {
                     return new WP_Error('dir_mk_fail', sprintf(__('Failed to create dir: %s', 'fw'), $destination_file_path));
                 }
                 if (is_wp_error($result = $this->copy_dir($file_path, $destination_file_path, $fs, array()))) {
                     return $result;
                 }
             } else {
                 if (!copy($file_path, $destination_file_path)) {
                     return new WP_Error('file_copy_fail', sprintf(__('Failed to copy file: %s', 'fw'), $file_path));
                 }
             }
         }
         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';
 }
 /**
  * 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;
         }
     }
 }
 /**
  * @internal
  */
 public function _action_update_extensions()
 {
     $nonce_name = '_nonce_fw_ext_update_extensions';
     if (!isset($_POST[$nonce_name]) || !wp_verify_nonce($_POST[$nonce_name])) {
         wp_die(__('Invalid nonce.', 'fw'));
     }
     $form_input_name = 'extensions';
     $extensions_list = FW_Request::POST($form_input_name);
     if (empty($extensions_list)) {
         FW_Flash_Messages::add('fw_ext_update', __('Please check the extensions you want to update.', 'fw'), 'warning');
         wp_redirect(self_admin_url('update-core.php'));
         exit;
     }
     if (is_string($extensions_list)) {
         $extensions_list = json_decode($extensions_list);
     } else {
         $extensions_list = array_keys($extensions_list);
     }
     if (!class_exists('_FW_Ext_Update_Extensions_Upgrader_Skin')) {
         fw_include_file_isolated($this->get_declared_path('/includes/classes/class--fw-ext-update-extensions-upgrader-skin.php'));
     }
     $skin = new _FW_Ext_Update_Extensions_Upgrader_Skin(array('title' => __('Extensions Update', 'fw')));
     require_once ABSPATH . 'wp-admin/admin-header.php';
     $skin->header();
     do {
         $original_post_value = $_POST[$form_input_name];
         $_POST[$form_input_name] = wp_slash(json_encode($extensions_list));
         if (!FW_WP_Filesystem::request_access(fw_get_framework_directory('/extensions'), fw_current_url(), array($nonce_name, $form_input_name))) {
             // revert hack changes
             $_POST[$form_input_name] = $original_post_value;
             unset($original_post_value);
             break;
         }
         // revert hack changes
         $_POST[$form_input_name] = $original_post_value;
         unset($original_post_value);
         $updates = $this->get_extensions_with_updates();
         if (empty($updates)) {
             $skin->error(__('No extensions updates found.', 'fw'));
             break;
         }
         foreach ($extensions_list as $extension_name) {
             if (!($extension = fw()->extensions->get($extension_name))) {
                 $skin->error(sprintf(__('Extension "%s" does not exist or is disabled.', 'fw'), $extension_name));
                 continue;
             }
             if (!isset($updates[$extension_name])) {
                 $skin->error(sprintf(__('No update found for the "%s" extension.', 'fw'), $extension->manifest->get_name()));
                 continue;
             }
             $update = $updates[$extension_name];
             if (is_wp_error($update)) {
                 $skin->error($update);
                 continue;
             }
             /** @var FW_Ext_Update_Service $service */
             $service = $this->get_child($update['service']);
             $update_result = $this->update(array('wp_fs_destination_dir' => FW_WP_Filesystem::real_path_to_filesystem_path($extension->get_declared_path()), 'download_callback' => array($service, '_download_extension'), 'download_callback_args' => array($extension, $update['latest_version'], $this->get_wp_fs_tmp_dir()), 'skin' => $skin, 'title' => sprintf(__('%s extension', 'fw'), $extension->manifest->get_name())), true);
             if (is_wp_error($update_result)) {
                 $skin->error($update_result);
                 continue;
             }
             $skin->set_result(true);
             if (!$this->get_config('extensions_as_one_update')) {
                 $skin->decrement_extension_update_count($extension_name);
             }
         }
         if ($this->get_config('extensions_as_one_update')) {
             $skin->decrement_extension_update_count($extension_name);
         }
         $skin->after();
     } while (false);
     $skin->footer();
     require_once ABSPATH . 'wp-admin/admin-footer.php';
 }
 /**
  * Copy Theme Available Extensions from tmp directory to theme
  * Used after theme update
  * @since 2.6.0
  * @return null|WP_Error
  */
 public function theme_available_extensions_restore()
 {
     /** @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'));
     }
     if (!$wp_filesystem->exists($wpfs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir('/theme-ext')))) {
         return new WP_Error('no_tmp_dir', sprintf(__('Temporary directory does not exist: %s', 'fw'), $wpfs_tmp_dir));
     }
     /**
      * Fixes the case when the theme path before update was
      * wp-content/themes/theme-name/theme-name-parent
      * but after update it became
      * wp-content/themes/theme-name-parent
      *
      * and at this point get_template_directory() returns old theme directory
      * so fw_get_template_customizations_directory() also returns old path
      */
     $theme_dir = wp_get_theme()->get_theme_root() . '/' . wp_get_theme()->get_template();
     if (!($wpfs_base_dir = FW_WP_Filesystem::real_path_to_filesystem_path($base_dir = $theme_dir . fw_get_framework_customizations_dir_rel_path('/extensions')))) {
         return new WP_Error('base_dir_to_wpfs_fail', sprintf(__('Cannot obtain WP Filesystem dir for %s', 'fw'), $base_dir));
     }
     if (!($dirlist = $wp_filesystem->dirlist($wpfs_tmp_dir))) {
         return;
     }
     foreach ($dirlist as $filename => $fileinfo) {
         if ('d' !== $fileinfo['type']) {
             continue;
         }
         if (is_wp_error($merge_result = $this->merge_extension($wpfs_tmp_dir . '/' . $filename, $wpfs_base_dir . '/' . $filename))) {
             return $merge_result;
         }
     }
     $wp_filesystem->rmdir($wpfs_tmp_dir, true);
 }
 /**
  * @internal
  */
 public function _render_page()
 {
     echo '<div class="wrap">';
     $this->render_view('page', array('archives_html' => $this->render_view('archives', array('archives' => $this->get_archives(), 'is_busy' => (bool) $this->tasks()->get_active_task_collection()))), false);
     echo '</div>';
     echo '<div id="fw-ext-backups-filesystem-form" style="display:none;">';
     FW_WP_Filesystem::request_access(ABSPATH);
     echo '</div>';
 }
 /**
  * {@inheritdoc}
  * @internal
  */
 public function _download_framework($version, $wp_filesystem_download_directory)
 {
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     $user_slash_repo = fw()->manifest->get($this->manifest_key);
     $http = new WP_Http();
     $response = $http->get('https://api.github.com/repos/' . $user_slash_repo . '/releases');
     unset($http);
     if (wp_remote_retrieve_response_code($response) !== 200) {
         return new WP_Error('fw_ext_update_github_framework_download_releases_failed', __('Failed to access Github repository releases.', 'fw'));
     }
     $releases = json_decode($response['body'], true);
     unset($response);
     if (empty($releases)) {
         return new WP_Error('fw_ext_update_github_framework_download_no_releases', __('Github repository has no releases.', 'fw'));
     }
     $release = false;
     foreach ($releases as $_release) {
         if ($_release['tag_name'] === $version) {
             $release = $_release;
         }
     }
     if (empty($release)) {
         return new WP_Error('fw_ext_update_github_framework_download_not_existing_release', sprintf(__('Requested version (release) for download does not exists "%s".', 'fw'), $version));
     }
     $http = new WP_Http();
     $response = $http->request($release['zipball_url'], array('timeout' => $this->download_timeout));
     unset($http);
     if (wp_remote_retrieve_response_code($response) !== 200) {
         return new WP_Error('fw_ext_update_github_framework_download_failed', __('Failed to download framework zip.', 'fw'));
     }
     $zip_path = $wp_filesystem_download_directory . '/temp.zip';
     // save zip to file
     $wp_filesystem->put_contents($zip_path, $response['body']);
     unset($response);
     unzip_file(FW_WP_Filesystem::filesystem_path_to_real_path($zip_path), $wp_filesystem_download_directory);
     // remove zip file
     $wp_filesystem->delete($zip_path, false, 'f');
     $unzipped_dir = $wp_filesystem->dirlist($wp_filesystem_download_directory);
     $unzipped_dir = $wp_filesystem_download_directory . '/' . key($unzipped_dir);
     return $unzipped_dir;
 }
 /**
  * @param string $user_slash_repo Github 'user/repo'
  * @param string $version Requested version to download
  * @param string $wp_filesystem_download_directory Allocated temporary empty directory
  * @param string $title Used in messages
  *
  * @return string|WP_Error Path to the downloaded directory
  */
 private function download($user_slash_repo, $version, $wp_filesystem_download_directory, $title)
 {
     $http = new WP_Http();
     $response = $http->get($this->get_github_api_url('/repos/' . $user_slash_repo . '/releases/tags/' . $version));
     unset($http);
     $response_code = intval(wp_remote_retrieve_response_code($response));
     if ($response_code !== 200) {
         if ($response_code === 403) {
             $json_response = json_decode($response['body'], true);
             if ($json_response) {
                 return new WP_Error('fw_ext_update_github_download_releases_failed', __('Github error:', 'fw') . ' ' . $json_response['message']);
             }
         }
         if ($response_code) {
             return new WP_Error('fw_ext_update_github_download_releases_failed', sprintf(__('Failed to access Github repository "%s" releases. (Response code: %d)', 'fw'), $user_slash_repo, $response_code));
         } else {
             return new WP_Error('fw_ext_update_github_download_releases_failed', sprintf(__('Failed to access Github repository "%s" releases.', 'fw'), $user_slash_repo));
         }
     }
     $release = json_decode($response['body'], true);
     unset($response);
     if (empty($release)) {
         return new WP_Error('fw_ext_update_github_download_no_release', sprintf(__('%s github repository "%s" does not have the "%s" release.', 'fw'), $title, $user_slash_repo, $version));
     }
     $http = new WP_Http();
     $response = $http->request('https://github.com/' . $user_slash_repo . '/archive/' . $release['tag_name'] . '.zip', array('timeout' => $this->download_timeout));
     unset($http);
     if (intval(wp_remote_retrieve_response_code($response)) !== 200) {
         return new WP_Error('fw_ext_update_github_download_failed', sprintf(__('Cannot download %s zip.', 'fw'), $title));
     }
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     $zip_path = $wp_filesystem_download_directory . '/temp.zip';
     // save zip to file
     if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
         return new WP_Error('fw_ext_update_github_save_download_failed', sprintf(__('Cannot save %s zip.', 'fw'), $title));
     }
     unset($response);
     $unzip_result = unzip_file(FW_WP_Filesystem::filesystem_path_to_real_path($zip_path), $wp_filesystem_download_directory);
     if (is_wp_error($unzip_result)) {
         return $unzip_result;
     }
     // remove zip file
     if (!$wp_filesystem->delete($zip_path, false, 'f')) {
         return new WP_Error('fw_ext_update_github_remove_downloaded_zip_failed', sprintf(__('Cannot remove %s zip.', 'fw'), $title));
     }
     $unzipped_dir_files = $wp_filesystem->dirlist($wp_filesystem_download_directory);
     if (!$unzipped_dir_files) {
         return new WP_Error('fw_ext_update_github_unzipped_dir_fail', __('Cannot access the unzipped directory files.', 'fw'));
     }
     /**
      * get first found directory
      * (if everything worked well, there should be only one directory)
      */
     foreach ($unzipped_dir_files as $file) {
         if ($file['type'] == 'd') {
             return $wp_filesystem_download_directory . '/' . $file['name'];
         }
     }
     return new WP_Error('fw_ext_update_github_unzipped_dir_not_found', sprintf(__('The unzipped %s directory not found.', 'fw'), $title));
 }