*/ public static function known() { return array( 'accessibe' => array( 'name' => 'accessiBe', 'plugins' => array( 'accessibe' ), 'hosts' => array( 'acsbapp.com', 'acsbap.com' ), ), 'userway' => array( 'name' => 'UserWay', 'plugins' => array( 'userway-accessibility-widget' ), 'hosts' => array( 'cdn.userway.org' ), ), 'audioeye' => array( 'name' => 'AudioEye', 'plugins' => array( 'accessibility-by-audioeye' ), 'hosts' => array( 'audioeye.com', 'audioeye-services.com' ), ), 'ally' => array( 'name' => 'Web Accessibility by Elementor (Ally)', 'plugins' => array( 'pojo-accessibility' ), 'hosts' => array( 'cdn.elementor.com/a11y' ), ), ); } /** * Hooks. */ public static function init() { add_action( Background::DAILY_HOOK, array( __CLASS__, 'check_home' ), 30 ); if ( is_admin() ) { add_action( 'admin_post_fixlight_overlay_deactivate', array( __CLASS__, 'action_deactivate' ) ); add_action( 'admin_post_fixlight_overlay_check', array( __CLASS__, 'action_check' ) ); add_action( 'admin_post_fixlight_overlay_hide', array( __CLASS__, 'action_hide' ) ); } } /** * Which vendors' scripts appear in an HTML page. * * @param string $html Page HTML. * @return string[] Vendor ids. */ public static function scripts_in( $html ) { $found = array(); foreach ( self::known() as $id => $vendor ) { foreach ( $vendor['hosts'] as $host ) { if ( preg_match( '#]+src=["\'][^"\']*' . preg_quote( $host, '#' ) . '#i', (string) $html ) || preg_match( '#["\']https?://[^"\']*' . preg_quote( $host, '#' ) . '[^"\']*\.js#i', (string) $html ) ) { $found[] = $id; break; } } } return $found; } /** * Reads the home page and remembers which overlay scripts it loads (daily, and when the owner asks). */ public static function check_home() { $response = wp_remote_get( home_url( '/' ), array( 'timeout' => 15, 'redirection' => 2, ) ); $html = is_wp_error( $response ) ? '' : (string) wp_remote_retrieve_body( $response ); update_option( self::FOUND, array( 'checked' => time(), 'ok' => '' !== $html, 'scripts' => '' !== $html ? self::scripts_in( $html ) : array(), ), false ); } /** * What was found: per vendor, its active plugin files and whether its script is on the home page. * * @return array */ public static function found() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $stored = get_option( self::FOUND, array() ); $scripts = isset( $stored['scripts'] ) ? (array) $stored['scripts'] : array(); $files = array_keys( get_plugins() ); $out = array(); foreach ( self::known() as $id => $vendor ) { $active = array(); foreach ( $files as $file ) { if ( in_array( strtok( $file, '/' ), $vendor['plugins'], true ) && is_plugin_active( $file ) ) { $active[] = $file; } } if ( $active || in_array( $id, $scripts, true ) ) { $out[ $id ] = array( 'name' => $vendor['name'], 'plugins' => $active, // A script with its plugin active is the plugin's own; only a script without one needs hand removal. 'script' => in_array( $id, $scripts, true ) && ! $active, ); } } return $out; } /** * The card on the Overview, when something was found and the owner hasn't hidden it. */ public static function render_card() { if ( ! current_user_can( 'activate_plugins' ) || get_user_meta( get_current_user_id(), self::HIDDEN, true ) ) { return; } $found = self::found(); if ( ! $found ) { return; } $post = admin_url( 'admin-post.php' ); echo '
'; echo '

' . esc_html__( 'Another accessibility widget is on your site', 'fixlight' ) . '

'; echo '

' . esc_html__( 'Fixlight fixes problems in your site\'s own HTML, so you may not need a widget as well. If you decide to switch, here is how. Turning a widget off doesn\'t cancel your subscription with its maker.', 'fixlight' ) . '

'; foreach ( $found as $id => $vendor ) { $guide = self::GUIDE . rawurlencode( $id ) . '/'; echo '

' . esc_html( $vendor['name'] ) . '

'; foreach ( $vendor['plugins'] as $file ) { $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false ); $name = ! empty( $data['Name'] ) ? $data['Name'] : $file; echo '

' . esc_html( /* translators: %s: plugin name. */ sprintf( __( 'Its WordPress plugin "%s" is active.', 'fixlight' ), $name ) ) . '

'; echo '
' . esc_html( /* translators: %s: plugin name. */ sprintf( __( 'Deactivate "%s"…', 'fixlight' ), $name ) ) . ''; echo '

' . esc_html__( 'Its widget stops showing on your site. Your account and subscription with its maker stay as they are, and you can turn the plugin back on any time under Plugins.', 'fixlight' ) . '

'; echo '
'; wp_nonce_field( 'fixlight_overlay_deactivate' ); echo '

'; } if ( $vendor['script'] ) { echo '

' . esc_html__( 'Its script is on your home page, added outside a plugin (for example in your theme or a header-code plugin). Fixlight doesn\'t edit that code; the guide shows where to find it.', 'fixlight' ) . '

'; } printf( '

%2$s %3$s

', esc_url( $guide ), esc_html( /* translators: %s: widget name. */ sprintf( __( 'How to switch from %s, step by step', 'fixlight' ), $vendor['name'] ) ), esc_html__( '(opens in a new tab)', 'fixlight' ) ); } echo '
'; wp_nonce_field( 'fixlight_overlay_check' ); echo '
'; echo '
'; wp_nonce_field( 'fixlight_overlay_hide' ); echo '
'; echo '
'; } /** * Deactivates one detected widget plugin (never any other plugin). */ public static function action_deactivate() { if ( ! current_user_can( 'activate_plugins' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'fixlight' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'fixlight_overlay_deactivate' ); $file = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : ''; $done = self::deactivatable( $file ); if ( $done ) { deactivate_plugins( $file ); self::check_home(); } wp_safe_redirect( admin_url( 'admin.php?page=' . Admin::SLUG . '&fixlight_overlay=' . ( $done ? 'deactivated' : 'not_found' ) ) ); exit; } /** * Whether a plugin file is an active widget plugin Fixlight found (the only plugins the switch may deactivate). * * @param string $file Plugin file, e.g. accessibe/accessibe.php. * @return bool */ public static function deactivatable( $file ) { foreach ( self::found() as $vendor ) { if ( in_array( (string) $file, $vendor['plugins'], true ) ) { return true; } } return false; } /** * Checks the home page again, on the owner's request. */ public static function action_check() { if ( ! current_user_can( 'activate_plugins' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'fixlight' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'fixlight_overlay_check' ); self::check_home(); wp_safe_redirect( admin_url( 'admin.php?page=' . Admin::SLUG . '&fixlight_overlay=checked' ) ); exit; } /** * Hides the card for this user. */ public static function action_hide() { if ( ! current_user_can( 'activate_plugins' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'fixlight' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'fixlight_overlay_hide' ); update_user_meta( get_current_user_id(), self::HIDDEN, 1 ); wp_safe_redirect( admin_url( 'admin.php?page=' . Admin::SLUG ) ); exit; } /** * The message after an action, for the Overview. * * @return string */ public static function notice() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only picks which message to show. $state = isset( $_GET['fixlight_overlay'] ) ? sanitize_key( wp_unslash( $_GET['fixlight_overlay'] ) ) : ''; $messages = array( 'deactivated' => __( 'Done: the widget\'s plugin is deactivated. Fixlight\'s fixes keep working. Remember to cancel the subscription with its maker if you no longer need it.', 'fixlight' ), 'not_found' => __( 'That plugin wasn\'t found among the widgets Fixlight knows, so nothing was changed.', 'fixlight' ), 'checked' => __( 'Checked the home page again.', 'fixlight' ), ); return isset( $messages[ $state ] ) ? $messages[ $state ] : ''; } }