. */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Main WP Rollback Class * * @since 1.0 */ if ( ! class_exists( 'WP_Rollback' ) ) : /** * Class WP_Rollback */ final class WP_Rollback { /** * WP_Rollback instance * * @var WP_Rollback The one and only * @since 1.0 */ private static $instance; /** * WP_Rollback Settings Object * * @var object * @since 1.0 */ public $wpr_settings; /** * Plugins API url. * * @var string */ public $plugins_api = 'https://api.wordpress.org/plugins'; /** * Themes repo url. * * @var string */ public $themes_repo = 'https://themes.svn.wordpress.org'; /** * Plugin file. * * @var string */ public $plugin_file; /** * Plugin slug. * * @var string */ public $plugin_slug; /** * Versions. * * @var array */ public $versions = array(); /** * Current version. * * @var string */ public $current_version; /** * Main WP_Rollback Instance * * Insures that only one instance of WP Rollback exists in memory at any one * time. Also prevents needing to define globals all over the place. * * @since 1.0 * @static * @staticvar array $instance * @uses WP_Rollback::setup_constants() Setup the constants needed * @uses WP_Rollback::includes() Include the required files * @uses WP_Rollback::load_textdomain() load the language files * @see WP_Rollback() * @return WP_Rollback */ public static function instance() { if ( ! isset( self::$instance ) && ! ( self::$instance instanceof WP_Rollback ) && is_admin() ) { self::$instance = new WP_Rollback(); self::$instance->setup_constants(); // Only setup plugin rollback on specific page if ( isset( $_GET['plugin_file'] ) && $_GET['page'] == 'wp-rollback' ) { self::$instance->setup_plugin_vars(); } self::$instance->hooks(); self::$instance->includes(); } return self::$instance; } /** * Throw error on object clone * * The whole idea of the singleton design pattern is that there is a single * object therefore, we don't want the object to be cloned. * * @since 1.0 * @access protected * @return void */ public function __clone() { // Cloning instances of the class is forbidden _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'wp-rollback' ), '1.0' ); } /** * Disable unserializing of the class * * @since 1.0 * @access protected * @return void */ public function __wakeup() { // Unserializing instances of the class is forbidden _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'wp-rollback' ), '1.0' ); } /** * Setup plugin constants * * @access private * @since 1.0 * @return void */ private function setup_constants() { // Plugin Folder Path if ( ! defined( 'WP_ROLLBACK_PLUGIN_DIR' ) ) { define( 'WP_ROLLBACK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); } // Plugin Folder URL if ( ! defined( 'WP_ROLLBACK_PLUGIN_URL' ) ) { define( 'WP_ROLLBACK_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); } // Plugin Root File if ( ! defined( 'WP_ROLLBACK_PLUGIN_FILE' ) ) { define( 'WP_ROLLBACK_PLUGIN_FILE', __FILE__ ); } } /** * Setup Variables * * @access private * @description: */ private function setup_plugin_vars() { $this->set_plugin_slug(); $svn_tags = $this->get_svn_tags( 'plugin', $this->plugin_slug ); $this->set_svn_versions_data( $svn_tags ); } /** * Plugin hooks. * * @access private * @since 1.5 * @return void */ private function hooks() { // i18n add_action( 'plugins_loaded', array( self::$instance, 'load_textdomain' ) ); // Admin add_action( 'admin_enqueue_scripts', array( self::$instance, 'scripts' ) ); add_action( 'admin_menu', array( self::$instance, 'admin_menu' ), 20 ); add_action( 'pre_current_active_plugins', array( self::$instance, 'pre_current_active_plugins', ), 20, 1 ); add_action( 'wp_ajax_is_wordpress_theme', array( self::$instance, 'is_wordpress_theme' ) ); add_action( 'set_site_transient_update_themes', array( self::$instance, 'wpr_theme_updates_list' ) ); add_filter( 'wp_prepare_themes_for_js', array( self::$instance, 'wpr_prepare_themes_js' ) ); add_filter( 'plugin_action_links', array( self::$instance, 'plugin_action_links' ), 20, 4 ); add_action( 'network_admin_menu', array( self::$instance, 'admin_menu' ), 20 ); add_filter( 'network_admin_plugin_action_links', array( self::$instance, 'plugin_action_links' ), 20, 4 ); add_filter( 'theme_action_links', array( self::$instance, 'theme_action_links' ), 20, 4 ); add_filter( 'wp_ajax_wpr_check_changelog', array( self::$instance, 'get_plugin_changelog' ) ); } /** * Include required files * * @access private * @since 1.0 * @return void */ private function includes() { } /** * Enqueue Admin Scripts * * @access private * @since 1.0 * * @param $hook * * @return void */ public function scripts( $hook ) { if ( 'themes.php' === $hook ) { wp_enqueue_script( 'wp_rollback_themes_script', plugin_dir_url( __FILE__ ) . 'assets/js/themes-wp-rollback.js', array( 'jquery' ), false, true ); // Localize for i18n wp_localize_script( 'wp_rollback_themes_script', 'wpr_vars', array( 'ajaxurl' => admin_url(), 'ajax_loader' => admin_url( 'images/spinner.gif' ), 'nonce' => wp_create_nonce( 'wpr_rollback_nonce' ), 'text_rollback_label' => __( 'Rollback', 'wp-rollback' ), 'text_not_rollbackable' => __( 'No Rollback Available: This is a non-WordPress.org theme.', 'wp-rollback' ), 'text_loading_rollback' => __( 'Loading...', 'wp-rollback' ), ) ); } if ( ! in_array( $hook, array( 'index_page_wp-rollback', 'dashboard_page_wp-rollback' ) ) ) { return; } $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; // CSS wp_register_style( 'wp_rollback_css', plugin_dir_url( __FILE__ ) . 'assets/css/wp-rollback.css' ); wp_enqueue_style( 'wp_rollback_css' ); wp_register_style( 'wp_rollback_modal_css', plugin_dir_url( __FILE__ ) . 'assets/css/magnific-popup.css' ); wp_enqueue_style( 'wp_rollback_modal_css' ); // JS wp_register_script( 'wp_rollback_modal', plugin_dir_url( __FILE__ ) . 'assets/js/jquery.magnific-popup' . $suffix . '.js', array( 'jquery' ) ); wp_enqueue_script( 'wp_rollback_modal' ); wp_register_script( 'wp_rollback_script', plugin_dir_url( __FILE__ ) . 'assets/js/wp-rollback.js', array( 'jquery' ) ); wp_enqueue_script( 'wp_rollback_script' ); wp_enqueue_script( 'updates' ); // Localize for i18n. wp_localize_script( 'wp_rollback_script', 'wpr_vars', array( 'ajaxurl' => admin_url(), 'text_no_changelog_found' => isset( $_GET['plugin_slug'] ) ? sprintf( __( 'Sorry, we couldn\'t find a changelog entry found for this version. Try checking the developer log on WP.org.', 'wp-rollback' ), 'https://wordpress.org/plugins/' . $_GET['plugin_slug'] . '/#developers' ) : '', 'version_missing' => __( 'Please select a version number to perform a rollback.', 'wp-rollback' ), ) ); } /** * Loads the plugin language files * * @access public * @since 1.0 * @return void */ public function load_textdomain() { // Set filter for plugin's languages directory $wpr_lang_dir = dirname( plugin_basename( WP_ROLLBACK_PLUGIN_FILE ) ) . '/languages/'; $wpr_lang_dir = apply_filters( 'wpr_languages_directory', $wpr_lang_dir ); // Traditional WordPress plugin locale filter $locale = apply_filters( 'plugin_locale', get_locale(), 'wp-rollback' ); $mofile = sprintf( '%1$s-%2$s.mo', 'wp-rollback', $locale ); // Setup paths to current locale file $mofile_local = $wpr_lang_dir . $mofile; $mofile_global = WP_LANG_DIR . '/wp-rollback/' . $mofile; if ( file_exists( $mofile_global ) ) { // Look in global /wp-content/languages/wpr folder load_textdomain( 'wp-rollback', $mofile_global ); } elseif ( file_exists( $mofile_local ) ) { // Look in local /wp-content/plugins/wpr/languages/ folder load_textdomain( 'wp-rollback', $mofile_local ); } else { // Load the default language files load_plugin_textdomain( 'wp-rollback', false, $wpr_lang_dir ); } } /** * HTML */ public function html() { // Permissions check if ( ! current_user_can( 'update_plugins' ) ) { wp_die( __( 'You do not have sufficient permissions to perform rollbacks for this site.', 'wp-rollback' ) ); } // Get the necessary class include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; $defaults = apply_filters( 'wpr_rollback_html_args', array( 'page' => 'wp-rollback', 'plugin_file' => '', 'action' => '', 'plugin_version' => '', 'plugin' => '', ) ); $args = wp_parse_args( $_GET, $defaults ); if ( ! empty( $args['plugin_version'] ) ) { // Plugin: rolling back. check_admin_referer( 'wpr_rollback_nonce' ); include WP_ROLLBACK_PLUGIN_DIR . '/includes/class-rollback-plugin-upgrader.php'; include WP_ROLLBACK_PLUGIN_DIR . '/includes/rollback-action.php'; } elseif ( ! empty( $args['theme_version'] ) ) { // Theme: rolling back. check_admin_referer( 'wpr_rollback_nonce' ); include WP_ROLLBACK_PLUGIN_DIR . '/includes/class-rollback-theme-upgrader.php'; include WP_ROLLBACK_PLUGIN_DIR . '/includes/rollback-action.php'; } else { // This is the menu. check_admin_referer( 'wpr_rollback_nonce' ); include WP_ROLLBACK_PLUGIN_DIR . '/includes/rollback-menu.php'; } } /** * Get Plugin Changelog * * Uses WP.org API to get a plugin's * * @return null|string */ public function get_plugin_changelog() { // Need slug to continue. if ( ! isset( $_POST['slug'] ) || empty( $_POST['slug'] ) ) { return false; } $url = 'https://api.wordpress.org/plugins/info/1.0/' . $_POST['slug']; $response = wp_remote_get( $url ); // Do we have an error? if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { return null; } $response = maybe_unserialize( wp_remote_retrieve_body( $response ) ); // Nope: Return that bad boy echo $response->sections['changelog']; wp_die(); } /** * Get Subversion Tags * * cURLs wp.org repo to get the proper tags * * @param $type * @param $slug * * @return null|string */ public function get_svn_tags( $type, $slug ) { if ( 'plugin' === $type ) { $url = $this->plugins_api . '/info/1.0/' . $this->plugin_slug . '.json'; } elseif ( 'theme' === $type ) { $url = $this->themes_repo . '/' . $slug; } $response = wp_remote_get( $url ); // Do we have an error? if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { return null; } // Nope: Return that bad boy return wp_remote_retrieve_body( $response ); } /** * Set SVN Version Data * * @param $html * * @return array|bool */ public function set_svn_versions_data( $html ) { if ( ! $html ) { return false; } if ( ( $json = json_decode( $html ) ) && ( $html != $json ) ) { $versions = array_keys( (array) $json->versions ); } else { $DOM = new DOMDocument(); $DOM->loadHTML( $html ); $versions = array(); $items = $DOM->getElementsByTagName( 'a' ); foreach ( $items as $item ) { $href = str_replace( '/', '', $item->getAttribute( 'href' ) ); // Remove trailing slash if ( strpos( $href, 'http' ) === false && '..' !== $href ) { $versions[] = $href; } } } $this->versions = array_reverse( $versions ); return $versions; } /** * Versions Select * * Outputs the version radio buttons to select a rollback; types = 'plugin' or 'theme'. * * @param $type * * @return bool|string */ public function versions_select( $type ) { if ( empty( $this->versions ) ) { $versions_html = '
' . sprintf( __( 'It appears there are no version to select. This is likely due to the %s author not using tags for their versions and only committing new releases to the repository trunk.', 'wp-rollback' ), $type ) . '