: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( ! class_exists( 'ET_Core_VersionRollback' ) ):
* Handles version rollback.
* @package ET\Core\VersionRollback
class ET_Core_VersionRollback {
protected $product_name = '';
protected $product_shortname = '';
* Current product version.
protected $product_version = '';
* Is rollback service enabled.
protected $enabled = false;
protected $api_username = '';
* ET_Core_VersionRollback constructor.
* @param string $product_name
* @param string $product_shortname
* @param string $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'] ) : '';
public function assets() {
wp_enqueue_style( 'et-core-version-rollback', ET_CORE_URL . 'admin/css/version-rollback.css', array(
wp_enqueue_script( 'et-core-version-rollback', ET_CORE_URL . 'admin/js/version-rollback.js', array(
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.
protected function _get_previous_installed_version() {
return et_get_option( "{$this->product_shortname}_previous_installed_version", '' );
* Set previous installed version.
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.
protected function _get_latest_installed_version() {
return et_get_option( "{$this->product_shortname}_latest_installed_version", '' );
* Set latest installed version.
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.
protected function _is_rolled_back() {
return version_compare( $this->_get_latest_installed_version(), $this->_get_previous_installed_version(), '<=' );
* Get unique ajax action.
protected function _get_ajax_action() {
return 'et_core_version_rollback';
* Enable update rollback.
public function enable() {
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.
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' ),
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' ),
if ( $this->_is_rolled_back() ) {
' . 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() )
' . 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() )
wp_send_json_error( array(
'errorCode' => 'et_unknown',
$success = $this->rollback();
if ( is_wp_error( $success ) ) {
$error = $success->get_error_message();
if ( $success->get_error_code() === 'et_version_rollback_blocklisted' ) {
' . 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() )
<a href="' . esc_url( $this->_get_update_documentation_url() ) . '" target="_blank">
' . esc_html__( 'Learn more here.', 'et-core' ) . '
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(),
* Execute a version rollback.
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 ) ) {
$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 ( is_wp_error( $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.
* @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
protected function _install_theme( $package ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$upgrader = new Theme_Upgrader( new ET_Core_LIB_SilentThemeUpgraderSkin() );
'clear_update_cache' => true,
$parsed_args = wp_parse_args( array(), $defaults );
$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 );
'destination' => get_theme_root(),
'clear_destination' => true, // Overwrite theme.
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'] );
* Get update documentation url for the product.
protected function _get_update_documentation_url() {
return "https://www.elegantthemes.com/documentation/{$this->product_shortname}/update-{$this->product_shortname}/";
public function get_epanel_option() {
'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( __( '<em>Before you can receive product updates, you must first authenticate your Elegant Themes subscription. To do this, you need to enter both your Elegant Themes Username and your Elegant Themes API Key into the Updates Tab in your theme and plugin settings. To locate your API Key, <a href="https://www.elegantthemes.com/members-area/api/" target="_blank">log in</a> to your Elegant Themes account and navigate to the <strong>Account > API Key</strong> page. <a href="http://www.elegantthemes.com/gallery/divi/documentation/update/" target="_blank">Learn more here</a></em>. If you still get this message, please make sure that your Username and API Key have been entered correctly', 'et-core' ) ),
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' ); ?>
* Render ePanel warning modal when no previous supported version has been used.
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' ); ?>
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
<div id="et-core-version-rollback-modal-content">
<div class="et-core-modal-content">
esc_html__( 'The previously used version of %1$s does not support version rollback.', 'et-core' ),
esc_html( $this->product_name )
* Render ePanel confirmation modal for rollback.
public function render_epanel_confirm_rollback_modal() {
$action = $this->_get_ajax_action();
$url = add_query_arg( array(
'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' ); ?>
<a href="#" class="et-core-modal-close" data-et-core-modal="close"></a>
<div id="et-core-version-rollback-modal-content">
<div class="et-core-modal-content">