loopback = $loopback; $this->registry = $registry; $this->policy = $policy; } /** * Returns the check identifier. * * @return string Identifier. */ public function get_id(): string { return 'host_blocking'; } /** * Whether a loopback request is required. * * @return bool True. */ public function needs_loopback(): bool { return true; } /** * Runs the check. * * @return Result The finding. */ public function run(): Result { $label = __( 'Blocking by your host or another plugin', 'ai-blackout' ); $control = $this->loopback->get( '/', self::CONTROL_UA ); if ( is_wp_error( $control ) ) { return new Result( $this->get_id(), $label, Result::UNKNOWN, __( 'This site could not reach itself, so this test could not run.', 'ai-blackout' ), __( 'We cannot tell whether your host is blocking AI crawlers. Some hosts block a site from requesting its own pages, which is normal and does not mean anything is wrong.', 'ai-blackout' ), __( 'This check can be skipped. Everything else on this screen is unaffected.', 'ai-blackout' ), array( 'error' => $control->get_error_message() ) ); } if ( 200 !== $control['code'] ) { return new Result( $this->get_id(), $label, Result::UNKNOWN, sprintf( /* translators: %d: HTTP status code. */ __( 'An ordinary browser request to your own home page returned %d instead of 200.', 'ai-blackout' ), $control['code'] ), __( 'Because a normal visit does not succeed, we cannot use this test to tell whether AI crawlers are being blocked separately. Something is affecting all requests, not just crawlers.', 'ai-blackout' ), __( 'Check whether your site is reachable normally, or whether a maintenance mode or security plugin is refusing requests.', 'ai-blackout' ), array( 'control_code' => $control['code'] ) ); } $probes = $this->get_probe_agents(); if ( empty( $probes ) ) { return new Result( $this->get_id(), $label, Result::UNKNOWN, __( 'Every AI crawler is set to blocked, so there was no safe way to test this.', 'ai-blackout' ), __( 'When you block a crawler yourself, a refusal could be coming from this plugin or from your host, and the two look identical from outside. We will not guess which.', 'ai-blackout' ), __( 'To test this, temporarily allow one AI crawler and run the test again.', 'ai-blackout' ), array( 'probes' => array() ) ); } $blocked = array(); $attempts = array(); foreach ( $probes as $agent ) { $response = $this->loopback->get( '/', $this->build_user_agent( $agent ) ); if ( is_wp_error( $response ) ) { continue; } $attempts[ $agent['ua'] ] = $response['code']; if ( 200 !== $response['code'] ) { $blocked[ $agent['ua'] ] = $response['code']; } } $detail = array( 'control_code' => $control['code'], 'attempts' => $attempts, 'blocked' => $blocked, ); if ( empty( $attempts ) ) { return new Result( $this->get_id(), $label, Result::UNKNOWN, __( 'The test requests could not be completed.', 'ai-blackout' ), __( 'We cannot tell whether your host is blocking AI crawlers.', 'ai-blackout' ), __( 'Try running the test again in a moment.', 'ai-blackout' ), $detail ); } if ( ! empty( $blocked ) ) { return new Result( $this->get_id(), $label, Result::INFO, sprintf( /* translators: %s: Comma-separated crawler names. */ __( 'Something other than this plugin is refusing these AI crawlers: %s', 'ai-blackout' ), implode( ', ', array_keys( $blocked ) ) ), __( 'You have these crawlers set to allowed, but they are being refused anyway. The block is coming from your host, a security plugin, or a service in front of your site. This is worth knowing either way: you may already be more protected than this screen suggests, or you may be losing traffic you wanted to keep.', 'ai-blackout' ), __( 'If you want these crawlers allowed, ask your host whether they block AI crawlers, and check any security plugins you have installed.', 'ai-blackout' ), $detail ); } return new Result( $this->get_id(), $label, Result::INFO, __( 'Nothing outside this plugin appears to be blocking AI crawlers.', 'ai-blackout' ), __( 'The AI crawlers you allow are being served normally, so your host is not refusing them. Whatever blocking is happening is yours, and under your control.', 'ai-blackout' ), '', $detail ); } /** * Returns crawler agents that are safe to probe with. * * @return array List of agent arrays. */ private function get_probe_agents(): array { $safe = array(); foreach ( $this->registry->get_agents() as $agent ) { if ( Registry::MATCH_UA !== $agent['match'] ) { continue; } if ( Policy::ALLOW !== $this->policy->for_agent( $agent ) ) { continue; } $safe[] = $agent; if ( count( $safe ) >= self::MAX_PROBES ) { break; } } return $safe; } /** * Builds a realistic User-Agent header for a crawler token. * * @param array $agent Agent array. * @return string User-Agent header value. */ private function build_user_agent( array $agent ): string { return sprintf( 'Mozilla/5.0 (compatible; %s/1.0; +ai-blackout-self-test)', $agent['ua'] ); } }