38217-vm/wp-content/themes/Avada/includes/lib/inc/class-fusion-patcher-apply-patch.php
2026-02-05 17:08:59 +03:00

355 lines
10 KiB
PHP

<?php
/**
* Applies a patch.
*
* @package Fusion-Library
* @subpackage Fusion-Patcher
*/
/**
* Applies patches.
*
* @since 1.0.0
*/
class Fusion_Patcher_Apply_Patch {
/**
* The patch contents.
*
* @access public
* @var bool|array
*/
public $setting = false;
/**
* Whether the file-writing was successful or not.
*
* @access public
* @var bool
*/
public static $status = true;
/**
* Constructor.
*
* @access public
* @param object $patcher The Fusion_Patcher instance.
*/
public function __construct( $patcher ) {
if ( fusion_doing_ajax() ) {
add_action( 'wp_ajax_awb_apply_patch', [ $this, 'ajax_apply_patch' ] );
return;
}
$is_patcher_page = $patcher->get_args( 'is_patcher_page' );
if ( null === $is_patcher_page || false === $is_patcher_page ) {
return;
}
// Get patches.
$patches = Fusion_Patcher_Client::get_patches( $patcher->get_args() );
// Loop our patches.
foreach ( $patches as $key => $args ) {
// Set the $setting property to false.
// Then run $this->get_setting( $key ) to update the value.
$this->setting = false;
$this->get_setting( $key );
// If $setting property is not false apply the patch.
if ( false !== $this->setting && ! empty( $this->setting ) ) {
$this->apply_patch( $key );
}
}
}
/**
* Ajax callback for applying patch.
*/
public function ajax_apply_patch() {
check_ajax_referer( 'awb-bulk-apply-patches', 'awb_patcher_nonce' );
$patch_id = absint( $_POST['patchID'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
$patches = Fusion_Patcher_Client::get_patches( [] );
// Make sure we have a unique array.
$available_patches = array_keys( $patches );
// Sort the array by value (lowest to highest) and re-index the keys.
sort( $available_patches );
// Check if patch is available.
$key = array_search( $patch_id, $available_patches ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
if ( false !== $key ) {
// Get an array of the already applied patches.
$applied_patches = get_site_option( 'fusion_applied_patches', [] );
// Get an array of patches that failed to be applied.
$failed_patches = get_site_option( 'fusion_failed_patches', [] );
// Do not allow applying the patch initially.
// We'll have to check if they can later.
$can_apply = false;
// Get the patch arguments.
$patch_args = $patches[ $patch_id ];
// Has the patch been applied?
$patch_applied = ( in_array( $patch_id, $applied_patches, true ) );
// Has the patch failed?
$patch_failed = ( in_array( $patch_id, $failed_patches, true ) );
// If there is no previous patch, we can apply it.
if ( ! isset( $available_patches[ $key - 1 ] ) ) {
$can_apply = true;
}
// If the previous patch exists and has already been applied,
// then we can apply this one.
if ( isset( $available_patches[ $key - 1 ] ) ) {
if ( in_array( $available_patches[ $key - 1 ], $applied_patches, true ) ) {
$can_apply = true;
}
}
if ( $can_apply && ! $patch_applied ) {
self::apply_patch_from_args( $patch_id, self::format_patch( $patch_args ) );
if ( self::$status ) {
$data = [
'message' => 'Patch applied',
'patch_id' => $patch_id,
];
wp_send_json_success( $data, 200 );
} else {
$data = [
'message' => 'Patch failed',
'patch_id' => $patch_id,
];
wp_send_json_error( $data, 200 );
}
} else {
$data = [
'message' => 'Skip the patch',
'patch_id' => $patch_id,
];
wp_send_json_success( $data, 200 );
}
}
die();
}
/**
* Reformats patch, so it can be passed to apply patch function.
*
* @param array $patch Patch array.
* @return array
*/
public static function format_patch( $patch ) {
global $avada_patcher;
$patches = [];
if ( ! isset( $patch['patch'] ) ) {
return;
}
foreach ( $patch['patch'] as $key => $args ) {
if ( ! isset( $args['context'] ) || ! isset( $args['path'] ) || ! isset( $args['reference'] ) ) {
continue;
}
$product = $avada_patcher->get_args( 'context' );
$bundled = $avada_patcher->get_args( 'bundled' );
array_unshift( $bundled, $product );
foreach ( $bundled as $context ) {
if ( $context === $args['context'] ) {
if ( $context === $product ) {
$v1 = Fusion_Helper::normalize_version( $avada_patcher->get_args( 'version' ) );
} else {
$v1 = Fusion_Helper::normalize_version( $avada_patcher->get_bundled_version( $context ) );
}
$v2 = Fusion_Helper::normalize_version( $args['version'] );
if ( version_compare( $v1, $v2, '==' ) ) {
$patches[ $context ][ $args['path'] ] = $args['reference'];
}
}
}
}
return $patches;
}
/**
* Get the setting from the database.
* If the setting exists, decode it and set the class's $setting property to an array.
*
* @access public
* @param int $key The patch ID.
* @return void
*/
public function get_setting( $key ) {
// Get the patch contents.
// This is created when the "apply patch" button is pressed.
$setting = get_option( 'fusion_patch_contents_' . $key, false );
// Check we have a value before proceeding.
if ( false !== $setting && ! empty( $setting ) ) {
// Decode and prepare tha patch.
$setting = (array) json_decode( fusion_decode_input( $setting ) );
// Set the $setting property of the class to the contents of our patch.
if ( is_array( $setting ) && ! empty( $setting ) ) {
$this->setting = $setting;
}
}
}
/**
* Applies the patch.
* If everything is alright, return true else false.
*
* @access public
* @param int $key The patch ID.
* @return void
*/
public function apply_patch( $key ) {
// Check that the $setting property is properly formatted as an array.
if ( is_array( $this->setting ) ) {
// Process the patch.
foreach ( $this->setting as $target => $args ) {
$args = (array) $args;
foreach ( $args as $destination => $source ) {
$apply_patch = new Fusion_Patcher_Filesystem( $target, $source, $destination, $key );
self::$status = (bool) $apply_patch->status;
}
}
// Cleanup.
$this->remove_setting( $key );
self::update_applied_patches( $key );
delete_site_transient( Fusion_Patcher_Checker::$transient_name );
$fusion_cache = new Fusion_Cache();
$fusion_cache->reset_all_caches();
}
}
/**
* Applies the patch.
*
* @access public
* @param int $key The patch ID.
* @param array $patch_args Formatted patch data.
* @return void
*/
public static function apply_patch_from_args( $key, $patch_args ) {
// Check that the $setting property is properly formatted as an array.
if ( is_array( $patch_args ) ) {
// Process the patch.
foreach ( $patch_args as $target => $args ) {
$args = (array) $args;
foreach ( $args as $destination => $source ) {
$apply_patch = new Fusion_Patcher_Filesystem( $target, $source, $destination, $key );
self::$status = (bool) $apply_patch->status;
}
}
// Cleanup.
self::update_applied_patches( (int) $key );
delete_site_transient( Fusion_Patcher_Checker::$transient_name );
$fusion_cache = new Fusion_Cache();
$fusion_cache->reset_all_caches();
}
}
/**
* Remove the setting from the database.
*
* @access public
* @param int $key The patch ID.
* @return void
*/
public function remove_setting( $key ) {
delete_option( 'fusion_patch_contents_' . $key );
}
/**
* Update the applied patches array in the db.
*
* @access public
* @param int $key The patch ID.
* @return void
*/
public static function update_applied_patches( $key ) {
// Get an array of existing patches.
$applied_patches = get_site_option( 'fusion_applied_patches', [] );
// Get an array of patches that failed to be applied.
$failed_patches = get_site_option( 'fusion_failed_patches', [] );
// Add the patch key to the array and save.
// Save on a different setting depending on whether the patch failed to be applied or not.
if ( false === self::$status ) {
// Update the failed patches setting.
if ( ! in_array( $key, $failed_patches ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
$failed_patches[] = $key;
$failed_patches = array_unique( $failed_patches );
update_site_option( 'fusion_failed_patches', $failed_patches );
}
// Update the applied patches setting.
if ( in_array( $key, $applied_patches ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
$applied_key = array_search( $key, $applied_patches ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
unset( $applied_patches[ $applied_key ] );
update_site_option( 'fusion_applied_patches', $applied_patches );
}
return;
}
// If we got this far then the patch has been applied.
if ( ! in_array( $key, $applied_patches ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
$applied_patches[] = $key;
$applied_patches = array_unique( $applied_patches );
update_site_option( 'fusion_applied_patches', $applied_patches );
// If the current patch is in the array of failed patches, remove it.
if ( in_array( $key, $failed_patches ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
$failed_key = array_search( $key, $failed_patches ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
unset( $failed_patches[ $failed_key ] );
update_site_option( 'fusion_failed_patches', $failed_patches );
}
}
// Remove messages if they exist.
$messages_option = get_site_option( Fusion_Patcher_Admin_Notices::$option_name );
$patches = Fusion_Patcher_Client::get_patches();
if ( isset( $patches[ $key ] ) ) {
foreach ( $patches[ $key ]['patch'] as $patch ) {
$message_id = 'write-permissions-' . $patch['context'];
if ( isset( $messages_option[ $message_id ] ) ) {
unset( $messages_option[ $message_id ] );
update_site_option( Fusion_Patcher_Admin_Notices::$option_name, $messages_option );
}
}
}
Fusion_Patcher_Checker::reset_cache();
}
}