/**
  * @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;
     }
 }
 /**
  * Download an extension
  *
  * global $wp_filesystem; must me initialized
  *
  * @param string $extension_name
  * @param array $data Extension data from the "available extensions" array
  * @return string|WP_Error WP Filesystem path to the downloaded directory
  */
 private function download($extension_name, $data)
 {
     $wp_error_id = 'fw_extension_download';
     if (empty($data['download'])) {
         return new WP_Error($wp_error_id, sprintf(__('Extension "%s" has no download sources.', 'fw'), $this->get_extension_title($extension_name)));
     }
     /** @var WP_Filesystem_Base $wp_filesystem */
     global $wp_filesystem;
     $wp_fs_tmp_dir = FW_WP_Filesystem::real_path_to_filesystem_path($this->get_tmp_dir());
     if ($wp_filesystem->exists($wp_fs_tmp_dir)) {
         // just in case it already exists, clear everything, it may contain old files
         if (!$wp_filesystem->rmdir($wp_fs_tmp_dir, true)) {
             return new WP_Error($wp_error_id, sprintf(__('Cannot remove temporary directory: %s', 'fw'), $wp_fs_tmp_dir));
         }
     }
     if (!FW_WP_Filesystem::mkdir_recursive($wp_fs_tmp_dir)) {
         return new WP_Error($wp_error_id, sprintf(__('Cannot create temporary directory: %s', 'fw'), $wp_fs_tmp_dir));
     }
     foreach ($data['download'] as $source => $source_data) {
         switch ($source) {
             case 'github':
                 if (empty($source_data['user_repo'])) {
                     return new WP_Error($wp_error_id, sprintf(__('"%s" extension github source "user_repo" parameter is required', 'fw'), $this->get_extension_title($extension_name)));
                 }
                 $transient_name = 'fw_ext_mngr_gh_dl';
                 $transient_ttl = HOUR_IN_SECONDS;
                 $cache = get_site_transient($transient_name);
                 if ($cache === false) {
                     $cache = array();
                 }
                 if (isset($cache[$source_data['user_repo']])) {
                     $download_link = $cache[$source_data['user_repo']]['zipball_url'];
                 } else {
                     $http = new WP_Http();
                     $response = $http->get(apply_filters('fw_github_api_url', 'https://api.github.com') . '/repos/' . $source_data['user_repo'] . '/releases/latest');
                     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($wp_error_id, __('Github error:', 'fw') . ' ' . $json_response['message']);
                             }
                         } elseif ($response_code) {
                             return new WP_Error($wp_error_id, sprintf(__('Failed to access Github repository "%s" releases. (Response code: %d)', 'fw'), $source_data['user_repo'], $response_code));
                         } elseif (is_wp_error($response)) {
                             return new WP_Error($wp_error_id, sprintf(__('Failed to access Github repository "%s" releases. (%s)', 'fw'), $source_data['user_repo'], $response->get_error_message()));
                         } else {
                             return new WP_Error($wp_error_id, sprintf(__('Failed to access Github repository "%s" releases.', 'fw'), $source_data['user_repo']));
                         }
                     }
                     $release = json_decode($response['body'], true);
                     unset($response);
                     if (empty($release)) {
                         return new WP_Error($wp_error_id, sprintf(__('"%s" extension github repository "%s" has no releases.', 'fw'), $this->get_extension_title($extension_name), $source_data['user_repo']));
                     }
                     $cache[$source_data['user_repo']] = array('zipball_url' => 'https://github.com/' . $source_data['user_repo'] . '/archive/' . $release['tag_name'] . '.zip', 'tag_name' => $release['tag_name']);
                     set_site_transient($transient_name, $cache, $transient_ttl);
                     $download_link = $cache[$source_data['user_repo']]['zipball_url'];
                     unset($release);
                 }
                 $http = new WP_Http();
                 $response = $http->request($download_link, array('timeout' => $this->download_timeout));
                 unset($http);
                 if (($response_code = intval(wp_remote_retrieve_response_code($response))) !== 200) {
                     if ($response_code) {
                         return new WP_Error($wp_error_id, sprintf(__('Cannot download the "%s" extension zip. (Response code: %d)', 'fw'), $this->get_extension_title($extension_name), $response_code));
                     } elseif (is_wp_error($response)) {
                         return new WP_Error($wp_error_id, sprintf(__('Cannot download the "%s" extension zip. %s', 'fw'), $this->get_extension_title($extension_name), $response->get_error_message()));
                     } else {
                         return new WP_Error($wp_error_id, sprintf(__('Cannot download the "%s" extension zip.', 'fw'), $this->get_extension_title($extension_name)));
                     }
                 }
                 $zip_path = $wp_fs_tmp_dir . '/temp.zip';
                 // save zip to file
                 if (!$wp_filesystem->put_contents($zip_path, $response['body'])) {
                     return new WP_Error($wp_error_id, sprintf(__('Cannot save the "%s" extension zip.', 'fw'), $this->get_extension_title($extension_name)));
                 }
                 unset($response);
                 $unzip_result = unzip_file(FW_WP_Filesystem::filesystem_path_to_real_path($zip_path), $wp_fs_tmp_dir);
                 if (is_wp_error($unzip_result)) {
                     return $unzip_result;
                 }
                 // remove zip file
                 if (!$wp_filesystem->delete($zip_path, false, 'f')) {
                     return new WP_Error($wp_error_id, sprintf(__('Cannot remove the "%s" extension downloaded zip.', 'fw'), $this->get_extension_title($extension_name)));
                 }
                 $unzipped_dir_files = $wp_filesystem->dirlist($wp_fs_tmp_dir);
                 if (!$unzipped_dir_files) {
                     return new WP_Error($wp_error_id, __('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_fs_tmp_dir . '/' . $file['name'];
                     }
                 }
                 return new WP_Error($wp_error_id, sprintf(__('The unzipped "%s" extension directory not found.', 'fw'), $this->get_extension_title($extension_name)));
                 break;
             default:
                 return new WP_Error($wp_error_id, sprintf(__('Unknown "%s" extension download source "%s"', 'fw'), $this->get_extension_title($extension_name), $source));
         }
     }
 }
 /**
  * @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';
 }
 /**
  * @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 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);
 }