';
}
/**
* Patches html for XStore Control panel -> Patcher tab
* @param $theme_version
* @return void
*/
public function get_patches_list($theme_version = '1.0.0')
{
$global_admin_class = EthemeAdmin::get_instance();
$patches = $this->get_patches($theme_version);
if ( !$this->patcher_api_connected ) {
$this->get_patcher_heading();
echo '
'.esc_html__('We are unable to connect to the XStore API with the XStore theme. Please check your SSL certificate or white lists.', 'xstore').'
';
return;
}
if (!count($patches)) {
$this->get_patcher_heading('
'.$global_admin_class->get_loader(false, false).'
'. ' ' .
''.esc_html__('Check for updates', 'xstore').''.
'');
echo '
' . sprintf(esc_html__('Currently, you have installed the most recent version. There are no updates or patches available at this time. In the unlikely event that you encounter any bugs, please reach out to our support team through the provided %slink%s and we will work to resolve the issue promptly.', 'xstore'), '', '') . '
' .
sprintf(esc_html__('Unleash the full potential of your website with The Parcher! With its unique ability to apply small fixes between XStore releases, you can now keep your site up-to-date and running smoothly. In the unlikely event that you encounter any bugs, please reach out to our support team through the provided %slink%s and we will work to resolve the issue promptly.', 'xstore'),
'', '') .
'
';
echo '
' .
esc_html__('We recommend that you make backups of your website before making any changes. After applying patches, please remember to clear your website caches and CDN in order to see the changes take effect.', 'xstore') .
'
' .
'' . esc_html__('Applied', 'xstore') . '';
} elseif (count($patch_info['requirements'])) {
echo '' . $info_alert . esc_html__('Read details', 'xstore') .
'' . implode("\n", $patch_info['requirements']) . '';
}
else {
$files_from_previous_patches = array();
$all_patches = $this->patches_info;
$all_patches = array_reverse($all_patches);
$prev_patch_id = false;
$patch_same_files = array();
foreach ($all_patches as $patch_local_info) {
// skip patches which were already applied
if ($patch_local_info['applied']) continue;
if ($patch_local_info['patch_id'] === $patch_info['patch_id']) break;
// showing which patches should be applied first
if (count(array_intersect($patch_local_info['files_inline'], $patch_info['files_inline'])))
$patch_same_files[] = $patch_local_info['patch_id'];
}
// showing which patches should be applied first
if (!$this->patcher_test_mode && count($patch_same_files) && $prev_patch_id = $patch_same_files[array_key_first($patch_same_files)]) {
echo
(!$patch_info['active_status'] ? ''.esc_html__('Test mode', 'xstore').' ' : '') .
'' .
esc_html__('Apply', 'xstore') .
'' . sprintf(esc_html__('Apply %s patch first', 'xstore'), '#' . $prev_patch_id) . ( !$patch_info['active_status'] ? '. ' . sprintf(esc_html__(' %s patch should have active status first', 'xstore'), '#' . $prev_patch_id) : '' ) . '';
}
// check if current theme version is ok for patch's theme version
// could work without this condition also but better to check to prevent global changes in files
elseif ( !$this->patcher_test_mode && version_compare($patch_info['theme_version'], $theme_version, '>') ) {
echo '' .
esc_html__('Apply', 'xstore') .
'' . sprintf(__('This patch is available for XStore theme version %1s or later. Please update your %2s before proceeding.', 'xstore'), $patch_info['theme_version'], ''.esc_html__('XStore theme', 'xstore').'') . '';
}
else {
// data-files will be shown in alert before applied -> These files will be overwritten on your server ...
$files_info_attributes = array();
foreach ($patch_info['files'] as $files_project => $patch_project_file) {
if (!array_key_exists($files_project, $files_info_attributes))
$files_info_attributes[$files_project] = array();
$files_info_attributes[$files_project][] = $patch_project_file;
}
// strange patch with no files changed but let's better leave this condition here
// could be useful as fix for some cases when patch was added for update but no files added for this patch
if (!count($files_info_attributes))
echo '' . $info_alert . esc_html__('Read details', 'xstore') .
'' . esc_html__('This patch is currently unavailable. Please ignore it.', 'xstore') . '';
else {
$available_patches_count++;
echo (!$patch_info['active_status'] ? ''.esc_html__('Test mode', 'xstore').' ' : '') .
'';
}
}
}
?>
0 ) {
$apply_all_button =
'';
}
echo '
'; // end .etheme-patcher-list
echo str_replace('{{apply_all_patches_button}}', $apply_all_button, ob_get_clean());
}
/**
* Generate information data for each patches set from args
* @param $patches
* @return array
*/
public function get_patches_info($patches, $on_test = false)
{
$patch_index = array();
$ready_patches = array();
$test_mode = $on_test || $this->patcher_test_mode;
$all_applied_patches_keys = $this->get_applied_patches();
foreach ($patches as $patches_theme_version => $theme_version_patches_list) {
$patch_index[$patches_theme_version] = 0;
foreach ($theme_version_patches_list as $patch) {
if ( !$test_mode && $patch['status'] == 'inactive') continue;
// hide patches for the lower theme versions than current theme version, useful for cached patches
if ( !$test_mode && version_compare($patches_theme_version, ETHEME_THEME_VERSION, '<')) continue;
$patch_index[$patches_theme_version]++;
$files = $patch['files'];
$patch_id = $patch['code'];
$patch_details = array(
'theme_version' => $patches_theme_version,
'patch_id' => $patch_id,
'patch_index' => $patch_index[$patches_theme_version],
'active_status' => $patch['status'] == 'active',
'files' => array(),
'files_inline' => array(),
'description' => $patch['description'],
'last_update' => explode(' ', $patch['last_update']),
'requirements' => array(),
'applied' => false
);
if (in_array($patch_id, $all_applied_patches_keys)) {
$patch_details['applied'] = true;
}
foreach ($files as $project_key => $project_files) {
$patch_details['files'][$project_key] = $project_files;
foreach ($project_files as $project_file_id => $project_file_path) {
$patch_details['files_inline'][$project_file_id] = $project_key . '/' . $project_file_path;
}
}
$patch_details['files_inline'] = array_unique($patch_details['files_inline']);
if (isset($patch['requirements'])) {
foreach ($patch['requirements'] as $req_project_key => $project_requirements) {
if (isset($project_requirements['min_version'])) {
$project_name = false;
$project_version = false;
$project_type = 'plugin';
switch ($req_project_key) {
case 'theme':
$project_name = 'XStore';
$project_version = ETHEME_THEME_VERSION;
$project_type = 'theme';
break;
case 'et-core-plugin':
if (defined('ET_CORE_VERSION')) {
$project_name = 'XStore Core';
$project_version = ET_CORE_VERSION;
}
break;
case 'xstore-advanced-sticky-header':
if (defined('XStore_Advanced_Sticky_Header_Version')) {
$project_name = 'XStore Advanced Sticky Header';
$project_version = XStore_Advanced_Sticky_Header_Version;
}
break;
}
if ($project_name && $project_version) {
if (version_compare($project_version, $project_requirements['min_version'], '<')) {
if ($project_type == 'theme')
$patch_details['requirements'][$req_project_key] = sprintf(esc_html__('You are using %s version of %s theme but for this patch you need to have at least %s version', 'xstore'), $project_version, '' . $project_name . '', $project_requirements['min_version']);
else
$patch_details['requirements'][$req_project_key] = sprintf(esc_html__('You are using %s version of %s plugin but for this patch you need to have at least %s version', 'xstore'), $project_version, '' . $project_name . '', $project_requirements['min_version']);
}
}
}
}
}
array_unshift($ready_patches, $patch_details);
}
}
return $ready_patches;
}
/**
* Getter of patch file's content
* @param $patch_id
* @param $file_id
* @return mixed|string
*/
public function get_patch_file_from_server($patch_id, $file_id)
{
add_filter( 'http_request_args', 'et_increase_http_request_timeout', 10, 2 );
$url = add_query_arg(array(
'action' => 'file-get',
'code' => $patch_id,
'token' => $this->patcher_token,
'fid' => $file_id
), $this->patcher_api_url);
$response = wp_remote_get($url);
if (is_wp_error($response)) {
return array(
'message' => $response->get_error_message(),
'status' => 'error',
);
}
if (!isset($response['body'])) {
return array(
'message' => $response['response']['code'] . ': ' . $response['response']['message'],
'status' => 'error',
);
}
$response = json_decode(html_entity_decode($response['body']), true);
if (isset($response['status']) && $response['status'] == 'error' && isset($response['data'])) {
return array(
'message' => $response['data'],
'status' => 'error',
);
}
if (!isset($response['content'])) {
return array(
'message' => esc_html__('Error', 'xstore') . ': ' . sprintf(esc_html__('We would like to inform you that the file you have downloaded has no content. Please kindly inform us via our contact page %1s so that we can fix the issue immediately.', 'xstore'), etheme_contact_us_url()),
'status' => 'error',
);
}
return array(
'content' => $response['content'],
'status' => 'success',
);
}
/**
* Applier of the patch
* @return void
*/
public function apply_patch($patch_local_id = null, $patch_files = [], $multiple = false, $on_test = false)
{
$errors = array();
if ( !current_user_can( 'manage_options' ) || ! isset( $_POST['_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['_nonce'] ), $this->patcher_transient_key . '_apply_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, it's used only for nonce verification
$errors['server_errors'] = array();
$errors['server_errors'][] = __( 'You are not allowed to complete this task due to invalid nonce validation.', 'xstore' );
echo wp_json_encode(
array(
'success' => false,
'server_errors_text' => esc_html__('Invalid nonce', 'xstore'),
'errors' => $errors
)
);
exit;
}
$patch_id = isset($_POST['patch_id']) ? $_POST['patch_id'] : $patch_local_id;
$files = isset($_POST['files']) ? $_POST['files'] : $patch_files;
$test_mode = isset($_POST['test_mode']) ? $_POST['test_mode'] : $on_test;
if (!$patch_id && !$multiple) {
echo wp_json_encode(
array(
'success' => false,
'errors_text' => __('This patch does not exist or was removed. Please try again later.', 'xstore'),
'errors' => $errors
)
);
exit;
}
foreach ($files as $project => $project_files) {
// in some cases it returns array in array
$project_files = isset($project_files[0]) ? $project_files[0] : $project_files;
foreach ($project_files as $project_files_key => $project_file_path) {
$content = $this->get_patch_file_from_server($patch_id, $project_files_key);
if ( $content['status'] != 'success' ) {
$this->writable_files = false;
if (!array_key_exists('server_errors', $errors))
$errors['server_errors'] = array();
$errors['server_errors'][] = $content['message'];
continue;
}
if (!$content['content']) {
$this->writable_files = false;
continue;
}
if (!$this->write_file($project, $project_file_path, $content['content'])) {
if (!array_key_exists($project, $errors))
$errors[$project] = array();
$errors[$project][] = $project_file_path;
}
}
}
if (count($errors) < 1 && !$test_mode) {
$theme_version = $_POST['theme_version'];
$applied_patches = get_site_transient('xstore_patches_done', array());
$applied_patches = !$applied_patches ? array() : json_decode($applied_patches, true);
if (!array_key_exists($theme_version, $applied_patches)) {
$applied_patches[$theme_version] = array();
}
$applied_patches[$theme_version][] = $patch_id;
set_site_transient('xstore_patches_done', json_encode($applied_patches));
$this->reset_available_patches();
}
if ($multiple) {
return $errors;
} else {
echo wp_json_encode(
array(
'success' => count($errors) < 1,
'errors_text' => __('Some files were unable to be modified on your server. Please check your "System Status" page or try again later.', 'xstore'),
'server_errors_text' => __('There seems to be an issue with the server request. Please ensure that your theme has a valid purchase code.', 'xstore'),
'system_requirements_link' => add_query_arg(array('page' => 'et-panel-system-requirements'), admin_url('admin.php')),
'errors' => $errors
)
);
exit;
}
}
public function apply_all_patches()
{
if ( !current_user_can( 'manage_options' ) || ! isset( $_POST['_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['_nonce'] ), $this->patcher_transient_key . '_apply_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, it's used only for nonce verification
$errors = array();
$errors['server_errors'] = array();
$errors['server_errors'][] = __( 'You are not allowed to complete this task due to invalid nonce validation.', 'xstore' );
echo wp_json_encode(
array(
'success' => false,
'server_errors_text' => esc_html__('Invalid nonce', 'xstore'),
'errors' => $errors
)
);
exit;
}
$test_mode = $_POST['test_mode'];
$patches = $this->get_patches($_POST['theme_version'], $test_mode);
$patches_info = array_reverse($this->get_patches_info($patches, $test_mode));
$success_patches = array();
$broken_patches = array();
$broken_patches_files = array();
foreach ($patches_info as $patch_info) {
// check if current theme version is ok for patch's theme version
// could work without this condition also but better to check to prevent global changes in files
if ( !$test_mode && version_compare($patch_info['theme_version'], $_POST['theme_version'], '>') )
continue;
// there are no sense to re-apply already applied patches
if ($patch_info['applied'])
continue;
// don't apply patches with inactive status
if (!$test_mode && !$patch_info['active_status'])
continue;
// if some files from previous patches were broken then prevent next patches action to rewrite same files
if (count(array_intersect($broken_patches_files, $patch_info['files_inline']))) {
continue;
}
$test = $this->apply_patch($patch_info['patch_id'], $patch_info['files'], true, $test_mode);
if (count($test)) {
$broken_patches[] = '#' . $patch_info['patch_id'];
$broken_patches_files = array_unique(array_merge($broken_patches_files, $patch_info['files_inline']));
}
else {
$success_patches[] = $patch_info['patch_id'];
}
}
$this->reset_available_patches();
echo wp_json_encode(
array(
'success' => count($broken_patches) < 1,
'success_text' => __('Congratulations, all patches have been successfully applied.', 'xstore'),
'success_patches' => $success_patches,
'server_errors_text' => __('There is a problem with the request to get files\' content. Please ensure that your theme has a valid purchase code.', 'xstore'),
'errors_text' => __('We apologize for the inconvenience, but it seems that some patches could not be applied. Please make sure to check your "System Status" page or try again later. Thank you.', 'xstore'),
'system_requirements_link' => add_query_arg(array('page' => 'et-panel-system-requirements'), admin_url('admin.php')),
'errors' => $broken_patches
)
);
exit;
}
/**
* Write file content on server with the content from patch's file
* @param $project_type
* @param $file_path
* @param $content
* @return bool
*/
public function write_file($project_type, $file_path, $content)
{
global $wp_filesystem;
if (function_exists('WP_Filesystem')) {
WP_Filesystem();
}
$target = $project_type == 'xstore' ? get_template_directory() . wp_normalize_path('/' . $file_path) :
WP_PLUGIN_DIR . wp_normalize_path('/' . $project_type . '/' . $file_path);
// create folders if the ones didn't exist yet
if (!$wp_filesystem->exists($target)) {
$pos = strripos($target, '/');
if (!wp_mkdir_p(substr($target, 0, $pos))) {
return false;
}
}
$status_write_file = $wp_filesystem->put_contents($target, $content);
if (!$status_write_file) {
$this->successful_write_files = false;
return false;
}
return true;
}
/**
* Returns the instance.
*
* @return object
* @since 9.1.1
*/
public static function get_instance( $shortcodes = array() ) {
if ( null == self::$instance ) {
self::$instance = new self( $shortcodes );
}
return self::$instance;
}
}
$patcher = new Etheme_Patcher();