parent_file ) ) { return; } if ( 'themes.php' !== $screen->parent_file ) { return; } if ( ! $this->product->is_theme() ) { return; } $version = $this->get_rollback(); if ( empty( $version ) ) { return; } ?> get_api_versions(); $versions = apply_filters( $this->product->get_key() . '_rollbacks', $versions ); if ( empty( $versions ) ) { return $rollback; } if ( $versions ) { usort( $versions, array( $this, 'sort_rollback_array' ) ); foreach ( $versions as $version ) { if ( isset( $version['version'] ) && isset( $version['url'] ) && version_compare( $this->product->get_version(), $version['version'], '>' ) ) { $rollback = $version; break; } } } return $rollback; } /** * Get versions array from wp.org * * @return array Array of versions. */ private function get_api_versions() { $cache_key = $this->product->get_cache_key(); $cache_versions = get_transient( $cache_key ); if ( false === $cache_versions ) { $versions = $this->get_remote_versions(); set_transient( $cache_key, $versions, 5 * DAY_IN_SECONDS ); } else { $versions = is_array( $cache_versions ) ? $cache_versions : array(); } return $versions; } /** * Get remote versions zips. * * @return array Array of available versions. */ private function get_remote_versions() { $url = $this->get_versions_api_url(); if ( empty( $url ) ) { return []; } $response = function_exists( 'wp_remote_get_wp_remote_get' ) ? wp_remote_get_wp_remote_get( $url ) : wp_remote_get( $url ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get if ( is_wp_error( $response ) ) { return array(); } $response = wp_remote_retrieve_body( $response ); if ( is_serialized( $response ) ) { $response = maybe_unserialize( $response ); } else { $response = json_decode( $response ); } if ( ! is_object( $response ) ) { return array(); } if ( ! isset( $response->versions ) ) { return array(); } $versions = array(); foreach ( $response->versions as $key => $value ) { $versions[] = array( 'version' => is_object( $value ) ? $value->version : $key, 'url' => is_object( $value ) ? $value->file : $value, ); } return $versions; } /** * Return url where to check for versions. * * @return string Url where to check for versions. */ private function get_versions_api_url() { if ( $this->product->is_wordpress_available() && $this->product->is_plugin() ) { return sprintf( 'https://api.wordpress.org/plugins/info/1.0/%s', $this->product->get_slug() ); } if ( $this->product->is_wordpress_available() && $this->product->is_theme() ) { return sprintf( 'https://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=%s&request[fields][versions]=true', $this->product->get_slug() ); } $license = $this->product->get_license(); if ( $this->product->requires_license() && strlen( $license ) < 10 ) { return ''; } return sprintf( '%slicense/versions/%s/%s/%s/%s', Product::API_URL, rawurlencode( $this->product->get_name() ), $license, urlencode( get_site_url() ), $this->product->get_version() ); } /** * Show the rollback links in the plugin page. * * @param array $links Plugin links. * * @return array $links Altered links. */ public function add_rollback_link( $links ) { $version = $this->get_rollback(); if ( empty( $version ) ) { return $links; } $links[] = '' . sprintf( apply_filters( $this->product->get_key() . '_rollback_label', 'Rollback to v%s' ), $version['version'] ) . ''; return $links; } /** * Start the rollback operation. */ public function start_rollback() { if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], $this->product->get_key() . '_rollback' ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized wp_nonce_ays( '' ); } if ( $this->product->is_plugin() ) { $this->start_rollback_plugin(); return; } if ( $this->product->is_theme() ) { $this->start_rollback_theme(); return; } } /** * Start the rollback operation for the plugin. */ private function start_rollback_plugin() { $rollback = $this->get_rollback(); $plugin_transient = get_site_transient( 'update_plugins' ); $plugin_folder = $this->product->get_slug(); $plugin_file = $this->product->get_file(); $version = $rollback['version']; $temp_array = array( 'slug' => $plugin_folder, 'new_version' => $version, 'package' => $rollback['url'], ); $temp_object = (object) $temp_array; $plugin_transient->response[ $plugin_folder . '/' . $plugin_file ] = $temp_object; set_site_transient( 'update_plugins', $plugin_transient ); $transient = get_transient( $this->product->get_key() . '_warning_rollback' ); // Style fix for the api link that gets outside the content. echo ''; if ( false === $transient ) { set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 ); require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; $title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version ); $plugin = $plugin_folder . '/' . $plugin_file; $nonce = 'upgrade-plugin_' . $plugin; $url = 'update.php?action=upgrade-plugin&plugin=' . urlencode( $plugin ); $upgrader_skin = new \Plugin_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'plugin' ) ); $upgrader = new \Plugin_Upgrader( $upgrader_skin ); $upgrader->upgrade( $plugin ); delete_transient( $this->product->get_key() . '_warning_rollback' ); wp_die( '', esc_attr( $title ), array( 'response' => 200, ) ); } } /** * Start the rollback operation for the theme. */ private function start_rollback_theme() { add_filter( 'update_theme_complete_actions', array( $this, 'alter_links_theme_upgrade' ) ); $rollback = $this->get_rollback(); $transient = get_site_transient( 'update_themes' ); $folder = $this->product->get_slug(); $version = $rollback['version']; $temp_array = array( 'new_version' => $version, 'package' => $rollback['url'], ); $transient->response[ $folder . '/style.css' ] = $temp_array; set_site_transient( 'update_themes', $transient ); $transient = get_transient( $this->product->get_key() . '_warning_rollback' ); // Style fix for the api link that gets outside the content. echo ''; if ( false === $transient ) { set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 ); require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; $title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version ); $theme = $folder . '/style.css'; $nonce = 'upgrade-theme_' . $theme; $url = 'update.php?action=upgrade-theme&theme=' . urlencode( $theme ); $upgrader = new \Theme_Upgrader( new \Theme_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'theme' ) ) ); $upgrader->upgrade( $theme ); delete_transient( $this->product->get_key() . '_warning_rollback' ); wp_die( '', esc_attr( $title ), array( 'response' => 200, ) ); } } /** * Alter links and remove duplicate customize message. * * @param array $links Array of old links. * * @return mixed Array of links. */ public function alter_links_theme_upgrade( $links ) { if ( isset( $links['preview'] ) ) { $links['preview'] = str_replace( '', '', $links['preview'] ); } return $links; } /** * Loads product object. * * @param Product $product Product object. * * @return bool Should we load the module? */ public function can_load( $product ) { if ( $this->is_from_partner( $product ) ) { return false; } if ( $product->is_theme() && ! current_user_can( 'switch_themes' ) ) { return false; } if ( $product->is_plugin() && ! current_user_can( 'install_plugins' ) ) { return false; } return true; } /** * Sort the rollbacks array in descending order. * * @param mixed $a First version to compare. * @param mixed $b Second version to compare. * * @return bool Which version is greater? */ public function sort_rollback_array( $a, $b ) { return version_compare( $b['version'], $a['version'] ); } /** * Load module logic. * * @param Product $product Product object. * * @return $this Module object. */ public function load( $product ) { $this->product = $product; $this->show_link(); $this->add_hooks(); return $this; } /** * If product can be rolled back, show the link to rollback. */ private function show_link() { add_filter( 'plugin_action_links_' . plugin_basename( $this->product->get_basefile() ), array( $this, 'add_rollback_link', ) ); } /** * Fires after the option has been updated. * * @param mixed $old_value The old option value. * @param mixed $value The new option value. * @param string $option Option name. */ public function update_active_plugins_action( $old_value, $value, $option ) { delete_site_transient( 'update_plugins' ); wp_cache_delete( 'plugins', 'plugins' ); } /** * Set the rollback hook. Strangely, this does not work if placed in the ThemeIsle_SDK_Rollback class, so it is being called from there instead. */ public function add_hooks() { add_action( 'admin_post_' . $this->product->get_key() . '_rollback', array( $this, 'start_rollback' ) ); add_action( 'admin_footer', array( $this, 'add_footer' ) ); // This hook will be invoked after the plugin activation. // We use this to force an update of the cache so that Update is present immediate after a rollback. add_action( 'update_option_active_plugins', array( $this, 'update_active_plugins_action' ), 10, 3 ); } }