'WP Rocket', 'litespeed-cache' => 'LiteSpeed Cache', 'autoptimize' => 'Autoptimize', 'w3-total-cache' => 'W3 Total Cache', 'wp-optimize' => 'WP-Optimize', 'sg-cachepress' => 'SiteGround Speed Optimizer', 'jetpack-boost' => 'Jetpack Boost', 'perfmatters' => 'Perfmatters', ); /** * Optimizers that delay scripts and that the matrix (plan §12) has not run with our marks yet. When one is active, * Site Health says what to exclude, in case it delays the loader. (WP Rocket: tested and handled by its own filters * since 2026-10-13.) */ const UNTESTED = array( 'perfmatters' ); /** * Consent tools, by their folder. */ const CONSENT = array( 'cookiebot' => 'Cookiebot', 'complianz-gdpr' => 'Complianz', 'complianz-gdpr-premium' => 'Complianz', 'cookie-law-info' => 'CookieYes', 'borlabs-cookie' => 'Borlabs Cookie', 'real-cookie-banner' => 'Real Cookie Banner', 'real-cookie-banner-pro' => 'Real Cookie Banner', ); /** * Page builders with an SR Anywhere element, or the shortcode, by their plugin folder. */ const BUILDERS = array( 'elementor' => 'Elementor', 'js_composer' => 'WPBakery Page Builder', 'fusion-builder' => 'Avada Builder', 'bb-plugin' => 'Beaver Builder', ); /** * Themes that bring their own builder, by their folder. */ const BUILDER_THEMES = array( 'Divi' => 'Divi', 'bricks' => 'Bricks', 'betheme' => 'BeTheme (Muffin Builder)', ); /** * Registers the tests, the info section and the async test's route. */ public static function boot() { add_filter( 'site_status_tests', array( self::class, 'tests' ) ); add_filter( 'debug_information', array( self::class, 'info' ) ); add_action( 'rest_api_init', array( self::class, 'route' ) ); } /** * Our tests on WordPress' Site Health screen. * * @param array $tests The tests so far. * @return array */ public static function tests( $tests ) { $tests['direct']['sranywhere_connection'] = array( 'label' => __( 'SR Anywhere connection', 'sr-anywhere' ), 'test' => array( self::class, 'connection_test' ), ); $tests['direct']['sranywhere_optimizers'] = array( 'label' => __( 'SR Anywhere and optimizers', 'sr-anywhere' ), 'test' => array( self::class, 'optimizers_test' ), ); if ( Connection::CONNECTED === Connection::current_state() ) { $tests['async']['sranywhere_service'] = array( 'label' => __( 'SR Anywhere reachable', 'sr-anywhere' ), 'test' => rest_url( 'sr-anywhere/v1/health/service' ), 'has_rest' => true, 'async_direct_test' => array( self::class, 'service_test' ), ); } return $tests; } /** * The async test's REST route, for people who may see Site Health. */ public static function route() { register_rest_route( 'sr-anywhere/v1', '/health/service', array( 'methods' => 'GET', 'callback' => array( self::class, 'service_test' ), 'permission_callback' => static function () { return current_user_can( 'view_site_health_checks' ); }, ) ); } /** * The connection: connected, not yet, or not working any more (and why). * * @return array */ public static function connection_test() { $connection = Connection::get(); $state = $connection ? $connection->state() : Connection::DISCONNECTED; $keep = __( 'Modules you placed keep showing on your pages.', 'sr-anywhere' ); if ( Connection::CONNECTED === $state ) { return self::result( 'sranywhere_connection', 'good', __( 'SR Anywhere is connected', 'sr-anywhere' ), __( 'The editor and the page builders list the published modules of the connected SR Anywhere account.', 'sr-anywhere' ) ); } if ( Connection::DISCONNECTED === $state ) { return self::result( 'sranywhere_connection', 'recommended', __( 'SR Anywhere is not connected', 'sr-anywhere' ), __( 'To place modules, connect this site to your SR Anywhere account.', 'sr-anywhere' ) . ' ' . $keep, __( 'Connect SR Anywhere', 'sr-anywhere' ) ); } $why = Connection::COPIED_SITE === $state && $connection /* translators: %s: the site address the connection was made for. */ ? sprintf( __( 'This site looks like a copy of %s. Connect it on its own, or confirm that it is the same site, moved.', 'sr-anywhere' ), $connection->site() ) : Settings::state_message( $state ); return self::result( 'sranywhere_connection', 'recommended', __( 'The SR Anywhere connection needs you', 'sr-anywhere' ), $why . ' ' . $keep, __( 'Open SR Anywhere\'s settings', 'sr-anywhere' ) ); } /** * SR Anywhere reachable from the server: the API (the editor's list) and the delivery (the modules' starts). * * @return array */ public static function service_test() { $api = Api::get( '/api/connect/account' ); if ( is_wp_error( $api ) && 401 === ( $api->get_error_data()['status'] ?? 0 ) ) { return self::connection_test(); // the account refused the connection: the connection test says why } $api_down = is_wp_error( $api ) && 0 === ( $api->get_error_data()['status'] ?? 0 ); $delivery_down = ! self::delivery_reachable(); if ( ! $api_down && ! $delivery_down ) { return self::result( 'sranywhere_service', 'good', __( 'This site reaches SR Anywhere', 'sr-anywhere' ), __( 'The site reached SR Anywhere\'s API (for the editor\'s list of modules) and its delivery (for the modules\' starts).', 'sr-anywhere' ) ); } $hosts = array(); if ( $api_down ) { $hosts[] = (string) wp_parse_url( Config::api_url(), PHP_URL_HOST ); } if ( $delivery_down ) { $hosts[] = (string) wp_parse_url( Config::start_url(), PHP_URL_HOST ); } $text = $api_down ? __( 'The editor cannot list your modules while SR Anywhere cannot be reached.', 'sr-anywhere' ) : __( 'Modules then show without their start: the page can move a little while they load.', 'sr-anywhere' ); return self::result( 'sranywhere_service', 'recommended', __( 'This site cannot reach SR Anywhere', 'sr-anywhere' ), $text . ' ' . __( 'Placed modules still show: visitors\' browsers load them from SR Anywhere directly.', 'sr-anywhere' ) . ' ' /* translators: %s: host names, such as "anywhere.sliderrevolution.com". */ . sprintf( __( 'Ask your host whether the server may make requests to %s.', 'sr-anywhere' ), implode( ', ', array_unique( $hosts ) ) ) ); } /** * The optimizers and consent tools found, and what, if anything, to set there (decision 0007, point 2). * * @return array */ public static function optimizers_test() { $found = array_merge( self::active( self::OPTIMIZERS ), self::active( self::CONSENT ) ); $check = array_intersect_key( self::active( self::OPTIMIZERS ), array_flip( self::UNTESTED ) ); if ( $check ) { return self::result( 'sranywhere_optimizers', 'recommended', /* translators: %s: plugin names, such as "WP Rocket". */ sprintf( __( 'Check %s for SR Anywhere\'s scripts', 'sr-anywhere' ), implode( ', ', array_unique( $check ) ) ), sprintf( /* translators: 1: plugin names, 2: the delivery's host, 3: "sranywhere". */ __( 'SR Anywhere\'s scripts carry the marks optimizers know, but they were not tested with %1$s yet. If a module appears only after a click or a scroll, add %2$s and %3$s to the scripts it must not delay.', 'sr-anywhere' ), implode( ', ', array_unique( $check ) ), '' . esc_html( (string) wp_parse_url( Config::delivery_url(), PHP_URL_HOST ) ) . '', 'sranywhere' ) ); } $text = $found /* translators: %s: plugin names, such as "LiteSpeed Cache, Complianz". */ ? sprintf( __( 'Found: %s. SR Anywhere\'s scripts mark themselves for these tools, so there is nothing to set.', 'sr-anywhere' ), implode( ', ', array_unique( $found ) ) ) : __( 'No caching, optimizing or consent plugin was found that needs a setting for SR Anywhere.', 'sr-anywhere' ); return self::result( 'sranywhere_optimizers', 'good', __( 'Optimizers leave SR Anywhere\'s scripts alone', 'sr-anywhere' ), $text ); } /** * Our section in Site Health → Info (WordPress' own "Copy site info" takes it along). No key, no token, no e-mail. * * @param array $info The sections so far. * @return array */ public static function info( $info ) { $fields = array(); foreach ( self::facts( false ) as $key => $fact ) { $fields[ $key ] = array( 'label' => $fact[0], 'value' => $fact[1], 'debug' => $fact[2] ?? $fact[1], // what WordPress' "Copy site info" takes: in English ); } $info['sr-anywhere'] = array( 'label' => __( 'SR Anywhere', 'sr-anywhere' ), 'fields' => $fields, ); return $info; } /** * The text "Copy diagnostics" puts on the clipboard, for SR Anywhere's Help: one fact a line. Its labels stay in * English for the support team, whatever the site's language. * * @return string */ public static function diagnostics() { $lines = array(); foreach ( self::facts( true ) as $key => $fact ) { $lines[] = $key . ': ' . ( $fact[2] ?? $fact[1] ); } return implode( "\n", $lines ); } /** * What the site runs: [ label, value, the value in English if it differs ] by key. With the site's basics (WordPress, * PHP, theme) for the diagnostics; Site Health shows those in its own sections. * * @param bool $basics Whether to include WordPress, PHP, the environment, the theme and the builders. * @return array */ private static function facts( $basics ) { $none = array( __( 'none found', 'sr-anywhere' ), 'none found' ); $facts = array( 'sr-anywhere' => array( __( 'Version', 'sr-anywhere' ), SRANYWHERE_VERSION ) ); $connection = Connection::get(); if ( $basics ) { $theme = wp_get_theme(); $parent = $theme->parent(); $facts['wordpress'] = array( __( 'WordPress', 'sr-anywhere' ), get_bloginfo( 'version' ) . ( is_multisite() ? ' (multisite)' : '' ) ); $facts['php'] = array( __( 'PHP', 'sr-anywhere' ), PHP_VERSION ); $facts['env'] = array( __( 'Environment', 'sr-anywhere' ), wp_get_environment_type() ); $facts['theme'] = array( __( 'Theme', 'sr-anywhere' ), $theme->get( 'Name' ) . ' ' . $theme->get( 'Version' ) . ( $parent ? ' (child of ' . $parent->get( 'Name' ) . ' ' . $parent->get( 'Version' ) . ')' : '' ) . ( wp_is_block_theme() ? ', block theme' : '' ), ); $builders = self::active( self::BUILDERS ); if ( isset( self::BUILDER_THEMES[ $theme->get_template() ] ) ) { $builders[ 'theme:' . $theme->get_template() ] = self::BUILDER_THEMES[ $theme->get_template() ]; } $facts['builders'] = self::fact( __( 'Page builders', 'sr-anywhere' ), $builders ? self::with_versions( $builders ) : $none ); } $facts['connection'] = array( __( 'Connection', 'sr-anywhere' ), $connection ? $connection->state() . ( $connection->is_oauth() ? ' (OAuth)' : ' (key)' ) : Connection::DISCONNECTED, ); if ( $connection ) { $facts['account'] = array( __( 'Account', 'sr-anywhere' ), '' !== $connection->account_id() ? $connection->account_id() : '-' ); $facts['site'] = array( __( 'Connected for', 'sr-anywhere' ), '' !== $connection->site() ? $connection->site() : '-' ); } $facts['modules'] = array( __( 'Known modules', 'sr-anywhere' ), (string) count( Placeable::all() ) ); $changed = Config::api_url() . ', ' . Config::delivery_url() . ', ' . Config::start_url(); $facts['service'] = self::fact( __( 'Service', 'sr-anywhere' ), Config::API === Config::api_url() && Config::DELIVERY === Config::delivery_url() && Config::delivery_url() === Config::start_url() ? array( __( 'default', 'sr-anywhere' ), 'default' ) /* translators: %s: the service's addresses. */ : array( sprintf( __( 'changed in wp-config.php: %s', 'sr-anywhere' ), $changed ), 'changed in wp-config.php: ' . $changed ) ); $optimizers = self::active( self::OPTIMIZERS ); $consent = self::active( self::CONSENT ); $facts['optimizers'] = self::fact( __( 'Optimizers', 'sr-anywhere' ), $optimizers ? self::with_versions( $optimizers ) : $none ); $facts['consent'] = self::fact( __( 'Consent tools', 'sr-anywhere' ), $consent ? self::with_versions( $consent ) : $none ); $facts['cloudflare'] = self::fact( __( 'Cloudflare in front', 'sr-anywhere' ), isset( $_SERVER['HTTP_CF_RAY'] ) ? array( __( 'yes', 'sr-anywhere' ), 'yes' ) : array( __( 'no', 'sr-anywhere' ), 'no' ) ); $classic = array( __( 'not active', 'sr-anywhere' ), 'not active' ); if ( Settings::classic_active() ) { $version = defined( 'RS_REVISION' ) ? (string) RS_REVISION : '?'; $classic = Settings::classic_outdated() /* translators: 1: the Slider Revolution version found, 2: the version that runs both side by side. */ ? array( sprintf( __( '%1$s (older than %2$s)', 'sr-anywhere' ), $version, Settings::CLASSIC_FIXED ), $version . ' (older than ' . Settings::CLASSIC_FIXED . ')' ) : $version; } $facts['classic'] = self::fact( __( 'Slider Revolution', 'sr-anywhere' ), $classic ); return $facts; } /** * A fact: its label, its value, and the value in English when the shown one is translated. * * @param string $label The label. * @param string|array $value The value, or [ translated, English ]. * @return array */ private static function fact( $label, $value ) { return is_array( $value ) ? array( $label, $value[0], $value[1] ) : array( $label, $value ); } /** * Whether the delivery answers the server: a module's start, as the cache fetches it (it never counts a view). * True without a known module, as there is nothing to fetch then. * * @return bool */ private static function delivery_reachable() { $keys = array_values( array_filter( Placeable::all(), array( Keys::class, 'is_embed_key' ) ) ); $keys = array_values( array_diff( $keys, array_filter( $keys, array( Keys::class, 'is_group_key' ) ) ) ); if ( ! $keys ) { return true; } $host = (string) wp_parse_url( home_url(), PHP_URL_HOST ); $response = wp_remote_get( Config::start_url() . '/i/' . rawurlencode( $keys[0] ) . '/start.json?host=' . rawurlencode( $host ), array( 'timeout' => 5, 'redirection' => 0, ) ); return ! is_wp_error( $response ) && in_array( (int) wp_remote_retrieve_response_code( $response ), array( 200, 404 ), true ); } /** * The active plugins among a list, by folder: [ folder => name ]. * * @param array $known Names by plugin folder. * @return array */ private static function active( array $known ) { $active = (array) get_option( 'active_plugins', array() ); if ( is_multisite() ) { $active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) ); } $found = array(); foreach ( $active as $file ) { $folder = dirname( (string) $file ); if ( isset( $known[ $folder ] ) ) { $found[ $folder ] = $known[ $folder ]; } } return $found; } /** * "LiteSpeed Cache 7.6, Complianz 7.4": names with the installed versions. * * @param array $found Names by plugin folder (or by "theme:" for a theme). * @return string */ private static function with_versions( array $found ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $versions = array(); foreach ( get_plugins() as $file => $data ) { $versions[ dirname( $file ) ] = $data['Version'] ?? ''; } $parts = array(); foreach ( $found as $folder => $name ) { $version = 0 === strpos( $folder, 'theme:' ) ? wp_get_theme( substr( $folder, 6 ) )->get( 'Version' ) : ( $versions[ $folder ] ?? '' ); $parts[] = trim( $name . ' ' . $version ); } return implode( ', ', array_unique( $parts ) ); } /** * A Site Health result, with the settings page as its action when there is one. * * @param string $test The test's id. * @param string $status `good`, `recommended` or `critical`. * @param string $label The headline. * @param string $description The explanation (HTML allowed: `code`). * @param string $action The settings link's text, or ''. * @return array */ private static function result( $test, $status, $label, $description, $action = '' ) { return array( 'label' => $label, 'status' => $status, 'badge' => array( 'label' => __( 'SR Anywhere', 'sr-anywhere' ), 'color' => 'blue', ), 'description' => '

' . wp_kses( $description, array( 'code' => array() ) ) . '

', 'actions' => '' === $action ? '' : '

' . esc_html( $action ) . '

', 'test' => $test, ); } }