645 lines
18 KiB
PHP
645 lines
18 KiB
PHP
<?php
|
|
// phpcs:disable Generic.WhiteSpace.ScopeIndent -- our preference is to not indent the whole inner function in this scenario.
|
|
if ( ! class_exists( 'ET_Core_VersionRollback' ) ) :
|
|
/**
|
|
* Handles version rollback.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @private
|
|
*
|
|
* @package ET\Core\VersionRollback
|
|
*/
|
|
class ET_Core_VersionRollback {
|
|
/**
|
|
* Product name.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $product_name = '';
|
|
|
|
/**
|
|
* Product shortname.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $product_shortname = '';
|
|
|
|
/**
|
|
* Current product version.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $product_version = '';
|
|
|
|
/**
|
|
* Is rollback service enabled.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $enabled = false;
|
|
|
|
/**
|
|
* API Username.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $api_username = '';
|
|
|
|
/**
|
|
* API Key.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $api_key = '';
|
|
|
|
/**
|
|
* ET_Core_VersionRollback constructor.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @param string $product_name Product name.
|
|
* @param string $product_shortname Product shortname.
|
|
* @param string $product_version Product version.
|
|
*/
|
|
public function __construct( $product_name, $product_shortname, $product_version ) {
|
|
$this->product_name = sanitize_text_field( $product_name );
|
|
$this->product_shortname = sanitize_text_field( $product_shortname );
|
|
$this->product_version = sanitize_text_field( $product_version );
|
|
|
|
if ( ! $options = get_site_option( 'et_automatic_updates_options' ) ) {
|
|
$options = get_option( 'et_automatic_updates_options' );
|
|
}
|
|
|
|
$this->api_username = isset( $options['username'] ) ? sanitize_text_field( $options['username'] ) : '';
|
|
$this->api_key = isset( $options['api_key'] ) ? sanitize_text_field( $options['api_key'] ) : '';
|
|
}
|
|
|
|
/**
|
|
* Enqueue assets.
|
|
*
|
|
* @since ?.? Script `et-core-version-rollback` now loads in footer.
|
|
* @since 3.10
|
|
*/
|
|
public function assets() {
|
|
wp_enqueue_style(
|
|
'et-core-version-rollback',
|
|
ET_CORE_URL . 'admin/css/version-rollback.css',
|
|
array(
|
|
'et-core-admin',
|
|
),
|
|
ET_CORE_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'et-core-version-rollback',
|
|
ET_CORE_URL . 'admin/js/version-rollback.js',
|
|
array(
|
|
'jquery',
|
|
'jquery-ui-tabs',
|
|
'jquery-form',
|
|
'et-core-admin',
|
|
),
|
|
ET_CORE_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script(
|
|
'et-core-version-rollback',
|
|
'etCoreVersionRollbackI18n',
|
|
array(
|
|
'unknownError' => esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ),
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get previous installed version, if any.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _get_previous_installed_version() {
|
|
return et_get_option( "{$this->product_shortname}_previous_installed_version", '' );
|
|
}
|
|
|
|
/**
|
|
* Set previous installed version.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @param string $version
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _set_previous_installed_version( $version ) {
|
|
et_update_option( "{$this->product_shortname}_previous_installed_version", sanitize_text_field( $version ) );
|
|
}
|
|
|
|
/**
|
|
* Get latest installed version, if any.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _get_latest_installed_version() {
|
|
return et_get_option( "{$this->product_shortname}_latest_installed_version", '' );
|
|
}
|
|
|
|
/**
|
|
* Set latest installed version.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @param string $version
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _set_latest_installed_version( $version ) {
|
|
et_update_option( "{$this->product_shortname}_latest_installed_version", sanitize_text_field( $version ) );
|
|
}
|
|
|
|
/**
|
|
* Check if the product has already been rolled back.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function _is_rolled_back() {
|
|
return version_compare( $this->_get_latest_installed_version(), $this->_get_previous_installed_version(), '<=' );
|
|
}
|
|
|
|
/**
|
|
* Get unique ajax action.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _get_ajax_action() {
|
|
return 'et_core_version_rollback';
|
|
}
|
|
|
|
/**
|
|
* Enable update rollback.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function enable() {
|
|
if ( $this->enabled ) {
|
|
return;
|
|
}
|
|
|
|
$this->enabled = true;
|
|
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) );
|
|
add_action( 'wp_ajax_' . $this->_get_ajax_action(), array( $this, 'ajax_rollback' ) );
|
|
// Update version number when theme is manually replaced.
|
|
add_action( 'admin_init', array( $this, 'store_previous_version_number' ) );
|
|
// Update version number when theme is activated.
|
|
add_action( 'after_switch_theme', array( $this, 'store_previous_version_number' ) );
|
|
// Update version number when theme is updated.
|
|
add_action( 'upgrader_process_complete', array( $this, 'store_previous_version_number' ), 10, 0 );
|
|
}
|
|
|
|
/**
|
|
* Handle REST API requests to rollback.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function ajax_rollback() {
|
|
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $this->_get_ajax_action() ) ) {
|
|
wp_send_json_error( array(
|
|
'errorCode' => 'et_unknown',
|
|
'error' => esc_html__( 'Security check failed. Please refresh and try again.', 'et-core' ),
|
|
), 400 );
|
|
}
|
|
|
|
if ( ! current_user_can( 'install_themes' ) ) {
|
|
wp_send_json_error( array(
|
|
'errorCode' => 'et_unknown',
|
|
'error' => esc_html__( 'You don\'t have sufficient permissions to access this page.', 'et-core' ),
|
|
), 400 );
|
|
}
|
|
|
|
if ( $this->_is_rolled_back() ) {
|
|
$error = '
|
|
<p>
|
|
' . et_get_safe_localization( sprintf(
|
|
__( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ),
|
|
esc_html( $this->_get_latest_installed_version() ),
|
|
esc_html( $this->_get_previous_installed_version() )
|
|
) ) . '
|
|
</p>
|
|
<p>
|
|
' . et_get_safe_localization( sprintf(
|
|
__( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
|
|
esc_html( $this->product_name ),
|
|
esc_url( $this->_get_update_documentation_url() )
|
|
) ) . '
|
|
</p>
|
|
';
|
|
|
|
wp_send_json_error( array(
|
|
'errorCode' => 'et_unknown',
|
|
'error' => $error,
|
|
), 400 );
|
|
}
|
|
|
|
$success = $this->rollback();
|
|
|
|
if ( is_wp_error( $success ) ) {
|
|
$error = $success->get_error_message();
|
|
if ( $success->get_error_code() === 'et_version_rollback_blocklisted' ) {
|
|
$error = '
|
|
<p>
|
|
' . et_get_safe_localization( sprintf(
|
|
__( 'For privacy and security reasons, you cannot rollback to <strong>Version %1$s</strong>.', 'et-core' ),
|
|
esc_html( $this->_get_previous_installed_version() )
|
|
) ) . '
|
|
</p>
|
|
<p>
|
|
<a href="' . esc_url( $this->_get_update_documentation_url() ) . '" target="_blank">
|
|
' . esc_html__( 'Learn more here.', 'et-core' ) . '
|
|
</a>
|
|
</p>
|
|
';
|
|
}
|
|
|
|
wp_send_json_error( array(
|
|
'errorIsUnrecoverable' => in_array( $success->get_error_code(), array( 'et_version_rollback_not_available', 'et_version_rollback_blocklisted' ) ),
|
|
'errorCode' => $success->get_error_code(),
|
|
'error' => $error,
|
|
), 400 );
|
|
}
|
|
|
|
wp_send_json_success();
|
|
}
|
|
|
|
/**
|
|
* Execute a version rollback.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return bool|WP_Error
|
|
*/
|
|
public function rollback() {
|
|
// Load versions before rollback so they are not affected.
|
|
$previous_version = $this->_get_previous_installed_version();
|
|
$latest_version = $this->_get_latest_installed_version();
|
|
|
|
$api = new ET_Core_API_ElegantThemes( $this->api_username, $this->api_key );
|
|
$available = $api->is_product_available( $this->product_name, $previous_version );
|
|
|
|
if ( is_wp_error( $available ) ) {
|
|
$major_minor = implode( '.', array_slice( explode( '.', $previous_version ), 0, 2 ) );
|
|
|
|
if ( $major_minor . '.0' === $previous_version ) {
|
|
// Skip the trailing 0 in the version number and retry.
|
|
$previous_version = $major_minor;
|
|
$available = $api->is_product_available( $this->product_name, $previous_version );
|
|
}
|
|
|
|
if ( is_wp_error( $available ) ) {
|
|
return $available;
|
|
}
|
|
}
|
|
|
|
$download_url = $api->get_download_url( $this->product_name, $previous_version );
|
|
|
|
// Buffer and discard output as upgrader classes still output content even if the upgrader skin is silent.
|
|
$buffer_started = ob_start();
|
|
$result = $this->_install_theme( $download_url );
|
|
if ( $buffer_started ) {
|
|
ob_end_clean();
|
|
}
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
return $result;
|
|
}
|
|
|
|
if ( true !== $result ) {
|
|
return new WP_Error( 'et_unknown', esc_html__( 'An unknown error has occurred. Please try again later.', 'et-core' ) );
|
|
}
|
|
|
|
/**
|
|
* Fires after successful product version rollback.
|
|
*
|
|
* @since 3.26
|
|
*
|
|
* @param string $product_short_name - The short name of the product rolling back.
|
|
* @param string $rollback_from_version - The product version rolling back from.
|
|
* @param string $rollback_to_version - The product version rolling back to.
|
|
*/
|
|
do_action( 'et_after_version_rollback', $this->product_shortname, $latest_version, $previous_version );
|
|
|
|
// Swap version numbers after a successful rollback.
|
|
$this->_set_previous_installed_version( $latest_version );
|
|
$this->_set_latest_installed_version( $previous_version );
|
|
}
|
|
|
|
/**
|
|
* Install a theme overwriting it if it already exists.
|
|
* Copied from Theme_Upgrader::install() due to lack of control over the clear_desination argument.
|
|
*
|
|
* @see Theme_Upgrader::install() @ WordPress 4.9.4
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @param string $package
|
|
*
|
|
* @return bool|WP_Error
|
|
*/
|
|
protected function _install_theme( $package ) {
|
|
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
|
|
$upgrader = new Theme_Upgrader( new ET_Core_LIB_SilentThemeUpgraderSkin() );
|
|
|
|
$defaults = array(
|
|
'clear_update_cache' => true,
|
|
);
|
|
$parsed_args = wp_parse_args( array(), $defaults );
|
|
|
|
$upgrader->init();
|
|
$upgrader->install_strings();
|
|
|
|
add_filter('upgrader_source_selection', array( $upgrader, 'check_package' ) );
|
|
add_filter('upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ), 10, 3 );
|
|
if ( $parsed_args['clear_update_cache'] ) {
|
|
// Clear cache so wp_update_themes() knows about the new theme.
|
|
add_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9, 0 );
|
|
}
|
|
|
|
$upgrader->run( array(
|
|
'package' => $package,
|
|
'destination' => get_theme_root(),
|
|
'clear_destination' => true, // Overwrite theme.
|
|
'clear_working' => true,
|
|
'hook_extra' => array(
|
|
'type' => 'theme',
|
|
'action' => 'install',
|
|
),
|
|
) );
|
|
|
|
remove_action( 'upgrader_process_complete', 'wp_clean_themes_cache', 9 );
|
|
remove_filter( 'upgrader_source_selection', array( $upgrader, 'check_package' ) );
|
|
remove_filter( 'upgrader_post_install', array( $upgrader, 'check_parent_theme_filter' ) );
|
|
|
|
if ( ! $upgrader->result || is_wp_error( $upgrader->result ) ) {
|
|
return $upgrader->result;
|
|
}
|
|
|
|
// Refresh the Theme Update information.
|
|
wp_clean_themes_cache( $parsed_args['clear_update_cache'] );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get update documentation url for the product.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _get_update_documentation_url() {
|
|
return "https://www.elegantthemes.com/documentation/{$this->product_shortname}/update-{$this->product_shortname}/";
|
|
}
|
|
|
|
/**
|
|
* Return ePanel option.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_epanel_option() {
|
|
return array(
|
|
'name' => esc_html__( 'Version Rollback', 'et-core' ),
|
|
'id' => 'et_version_rollback',
|
|
'type' => 'callback_function',
|
|
'function_name' => array( $this, 'render_epanel_option' ),
|
|
'desc' => et_get_safe_localization( __( 'If you recently updated to a new version and are experiencing problems, you can easily roll back to the previously-installed version. We always recommend using the latest version and testing updates on a staging site. However, if you run into problems after updating you always have the option to roll back.', 'et-core' ) ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render ePanel option.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_epanel_option() {
|
|
$previous = $this->_get_previous_installed_version();
|
|
$modal_renderer = array( $this, 'render_epanel_no_previous_version_modal' );
|
|
|
|
if ( ! empty( $previous ) ) {
|
|
$modal_renderer = array( $this, 'render_epanel_confirm_rollback_modal' );
|
|
|
|
if ( $this->_is_rolled_back() ) {
|
|
$modal_renderer = array( $this, 'render_epanel_already_rolled_back_modal' );
|
|
}
|
|
}
|
|
|
|
add_action( 'admin_footer', $modal_renderer );
|
|
?>
|
|
<button type="button" class="et-button et-button--simple" data-et-core-modal=".et-core-version-rollback-modal">
|
|
<?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?>
|
|
</button>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render ePanel warning modal when no previous supported version has been used.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_epanel_no_previous_version_modal() {
|
|
?>
|
|
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal et-core-modal-actionless">
|
|
<div class="et-core-modal">
|
|
<div class="et-core-modal-header">
|
|
<h3 class="et-core-modal-title">
|
|
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
|
|
</h3>
|
|
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
|
|
</div>
|
|
<div id="et-core-version-rollback-modal-content">
|
|
<div class="et-core-modal-content">
|
|
<p>
|
|
<?php
|
|
printf(
|
|
esc_html__( 'The previously used version of %1$s does not support version rollback.', 'et-core' ),
|
|
esc_html( $this->product_name )
|
|
);
|
|
?>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render ePanel confirmation modal for rollback.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_epanel_confirm_rollback_modal() {
|
|
$action = $this->_get_ajax_action();
|
|
$url = add_query_arg( array(
|
|
'action' => $action,
|
|
'nonce' => wp_create_nonce( $action ),
|
|
), admin_url( 'admin-ajax.php' ) );
|
|
?>
|
|
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal">
|
|
<div class="et-core-modal">
|
|
<div class="et-core-modal-header">
|
|
<h3 class="et-core-modal-title">
|
|
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
|
|
</h3>
|
|
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
|
|
</div>
|
|
<div id="et-core-version-rollback-modal-content">
|
|
<div class="et-core-modal-content">
|
|
<p>
|
|
<?php
|
|
echo et_get_safe_localization( sprintf(
|
|
__( 'You\'ll be rolled back to <strong>Version %1$s</strong> from the current <strong>Version %2$s</strong>.', 'et-core' ),
|
|
esc_html( $this->_get_previous_installed_version() ),
|
|
esc_html( $this->_get_latest_installed_version() )
|
|
) );
|
|
?>
|
|
</p>
|
|
<p>
|
|
<?php
|
|
echo et_get_safe_localization( sprintf(
|
|
__( 'Rolling back will reinstall the previous version of %1$s. You will be able to update to the latest version at any time. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
|
|
esc_html( $this->product_name ),
|
|
esc_url( $this->_get_update_documentation_url() )
|
|
) );
|
|
?>
|
|
</p>
|
|
<p>
|
|
<strong>
|
|
<?php esc_html_e( 'Make sure you have a full site backup before proceeding.', 'et-core' ); ?>
|
|
</strong>
|
|
</p>
|
|
</div>
|
|
<a class="et-core-modal-action et-core-version-rollback-confirm" href="<?php echo esc_url( $url ); ?>">
|
|
<?php esc_html_e( 'Rollback to the previous version', 'et-core' ); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render ePanel warning modal when a rollback has already been done.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_epanel_already_rolled_back_modal() {
|
|
?>
|
|
<div class="et-core-modal-overlay et-core-form et-core-version-rollback-modal">
|
|
<div class="et-core-modal">
|
|
<div class="et-core-modal-header">
|
|
<h3 class="et-core-modal-title">
|
|
<?php esc_html_e( 'Version Rollback', 'et-core' ); ?>
|
|
</h3>
|
|
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
|
|
</div>
|
|
<div id="et-core-version-rollback-modal-content">
|
|
<div class="et-core-modal-content">
|
|
<p>
|
|
<?php
|
|
echo et_get_safe_localization( sprintf(
|
|
__( 'You\'re currently rolled back to <strong>Version %1$s</strong> from <strong>Version %2$s</strong>.', 'et-core' ),
|
|
esc_html( $this->_get_latest_installed_version() ),
|
|
esc_html( $this->_get_previous_installed_version() )
|
|
) );
|
|
?>
|
|
</p>
|
|
<p>
|
|
<?php
|
|
echo et_get_safe_localization( sprintf(
|
|
__( 'Update to the latest version to unlock the full power of %1$s. <a href="%2$s" target="_blank">Learn more here</a>.', 'et-core' ),
|
|
esc_html( $this->product_name ),
|
|
esc_url( $this->_get_update_documentation_url() )
|
|
) );
|
|
?>
|
|
</p>
|
|
</div>
|
|
<a class="et-core-modal-action" href="<?php echo esc_url( admin_url( 'update-core.php' ) ); ?>">
|
|
<?php esc_html_e( 'Update to the Latest Version', 'et-core' ); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Store latest and previous installed version.
|
|
*
|
|
* @since 3.10
|
|
*
|
|
* @return void;
|
|
*/
|
|
public function store_previous_version_number() {
|
|
$previous_installed_version = $this->_get_previous_installed_version();
|
|
$latest_installed_version = $this->_get_latest_installed_version();
|
|
|
|
// Get the theme version since the files may have changed but
|
|
// we are still executing old code from memory.
|
|
$theme_version = et_get_theme_version();
|
|
|
|
if ( $latest_installed_version === $theme_version ) {
|
|
return;
|
|
}
|
|
|
|
if ( empty( $latest_installed_version ) ) {
|
|
$latest_installed_version = $theme_version;
|
|
}
|
|
|
|
if ( version_compare( $theme_version, $latest_installed_version, '!=') ) {
|
|
$previous_installed_version = $latest_installed_version;
|
|
$latest_installed_version = $theme_version;
|
|
}
|
|
|
|
/**
|
|
* Fires after new version number is updated.
|
|
*
|
|
* @since 4.10.0
|
|
*/
|
|
do_action( 'et_store_before_new_version_update' );
|
|
|
|
$this->_set_previous_installed_version( $previous_installed_version );
|
|
$this->_set_latest_installed_version( $latest_installed_version );
|
|
|
|
/**
|
|
* Fires after new version number is updated.
|
|
*
|
|
* @since 4.10.0
|
|
*/
|
|
do_action( 'et_store_after_new_version_update' );
|
|
}
|
|
|
|
}
|
|
endif;
|