['page', 'post'], 'negotiation' => false, 'training' => false]); } public static function boot(): void { add_action('init', [self::class, 'routes']); add_filter('query_vars', static function ($vars) { $vars[] = 'a1local_mdi_format'; return $vars; }); add_filter('request', static function ($vars) { // WordPress discards post_type=any from public requests. Resolve the // real type after that validation so pages and public custom types // use the same document route as posts. Eligibility is checked later. if (($vars['a1local_mdi_format'] ?? '') === 'markdown' && !empty($vars['p'])) { $post = get_post(absint($vars['p'])); if ($post) { $vars['post_type'] = $post->post_type; if ($post->post_type === 'page') { $vars['page_id'] = $post->ID; unset($vars['p']); } } } return $vars; }); add_filter('determine_current_user', static function ($user) { return self::requested() ? 0 : $user; }, PHP_INT_MAX); add_action('init', static function () { if (self::requested()) { wp_set_current_user(0); Http::no_store(); } }, -100); add_action('template_redirect', [self::class, 'prepare'], 0); // WordPress normally exits HEAD requests before template selection. // Run conversion to produce the same headers as GET, then suppress body. add_filter('exit_on_http_head', static function ($exit) { return self::requested() ? false : $exit; }); add_filter('template_include', [self::class, 'template'], PHP_INT_MAX); add_action('wp_head', [self::class, 'discovery'], 1); } public static function routes(): void { add_rewrite_rule('^a1-local-markdown-delivery-inspector/([0-9]+)\.md/?$', 'index.php?p=$matches[1]&a1local_mdi_format=markdown', 'top'); // Updates do not fire the activation hook; replace the old query-variable rule once. if (get_option('a1local_mdi_rewrite_version') !== '1') { flush_rewrite_rules(false); update_option('a1local_mdi_rewrite_version', '1'); } } public static function requested(): bool { $uri = isset($_SERVER['REQUEST_URI']) && is_string($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''; $path = wp_parse_url($uri, PHP_URL_PATH) ?: ''; if (strpos($path, '/wp-admin/') !== false || strpos($path, '/wp-json/') !== false || strpos($path, '/wp-login.php') !== false) { return false; } // This public, read-only format selector intentionally requires no nonce. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $format = isset($_GET['a1local_mdi_format']) && is_string($_GET['a1local_mdi_format']) ? sanitize_text_field(wp_unslash($_GET['a1local_mdi_format'])) : ''; $explicit = $format === 'markdown' || preg_match('#/a1-local-markdown-delivery-inspector/[0-9]+\.md/?$#', $path); $accept = isset($_SERVER['HTTP_ACCEPT']) && is_string($_SERVER['HTTP_ACCEPT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_ACCEPT'])) : ''; return $explicit || (!empty(self::settings()['negotiation']) && Http::wants_markdown($accept)); } public static function url(\WP_Post $post): string { return get_option('permalink_structure') ? home_url('/a1-local-markdown-delivery-inspector/' . $post->ID . '.md') : add_query_arg('a1local_mdi_format', 'markdown', get_permalink($post)); } public static function exclusion($post): string { if (!$post instanceof \WP_Post || !is_post_publicly_viewable($post) || $post->post_status !== 'publish') { return 'Only published, publicly viewable content is eligible.'; } if ($post->post_password !== '') { return 'Password-protected content is excluded.'; } if (!in_array($post->post_type, (array) self::settings()['types'], true) || $post->post_type === 'attachment') { return 'This content type is not enabled.'; } if (!get_option('blog_public')) { return 'The site has search engine visibility disabled.'; } Legacy::post($post->ID); if (get_post_meta($post->ID, '_a1local_mdi_exclude', true)) { return 'This page is excluded in Markdown Delivery Inspector.'; } if (get_post_meta($post->ID, '_yoast_wpseo_meta-robots-noindex', true) === '1' || in_array('noindex', (array) get_post_meta($post->ID, 'rank_math_robots', true), true)) { return 'This page is marked noindex.'; } // Membership/privacy integrations may veto export; they can never override the checks above. if (!apply_filters('a1local_mdi_can_export_post', true, $post)) { return 'A site access rule excludes this page.'; } // Keep pre-release access restrictions effective while sites update their integrations. // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Compatibility with an existing pre-release access filter; new integrations use a1local_mdi_can_export_post. if (!apply_filters('md_for_agents_can_export_post', true, $post)) { return 'A site access rule excludes this page.'; } return ''; } public static function prepare(): void { if (!empty(self::settings()['negotiation'])) { Http::vary_accept(); } if (!self::requested()) { if (is_singular() && !self::exclusion(get_queried_object())) { header('Link: <' . esc_url_raw(self::url(get_queried_object())) . '>; rel="alternate"; type="text/markdown"', false); } return; } Http::no_store(); $method = Http::method(); if (!in_array($method, ['GET', 'HEAD'], true)) { header('Allow: GET, HEAD'); self::fail(405, 'Use GET or HEAD for Markdown.'); } if (is_preview() || is_feed() || is_embed() || !is_singular()) { self::fail(404, 'Markdown is not available for this URL.'); } $post = get_queried_object(); if (self::exclusion($post)) { self::fail(404, 'Markdown is not available for this page.'); } self::$post = $post; remove_action('template_redirect', 'redirect_canonical'); } public static function template(string $template): string { if (!self::requested() || !self::$post) { return $template; } self::$template = $template; return A1LOCAL_MDI_DIR . 'templates/render.php'; } public static function render(): void { $level = ob_get_level(); ob_start(); try { // Preserve the global/query-var scope expected by classic themes. load_template(self::$template, false); $html = (string) ob_get_clean(); $status = http_response_code(); if ($status >= 300) { self::fail($status, 'The public page is not available for conversion.'); } if (Http::has_noindex()) { self::fail(404, 'The public page excludes indexing. Markdown is not available.'); } $result = Document::convert($html, self::$post); } catch (\Throwable $error) { while (ob_get_level() > $level) { ob_end_clean(); } self::fail(503, 'Markdown is temporarily unavailable. Check the page template and PHP extensions.'); } if (is_wp_error($result)) { self::fail($result->get_error_code() === 'a1local_mdi_noindex' ? 404 : 422, $result->get_error_message()); } self::send($result); } private static function send(array $result): void { Http::no_store(); status_header(200); header('Content-Type: text/markdown; charset=UTF-8'); header('X-Content-Type-Options: nosniff'); header('X-Robots-Tag: noindex, follow'); header('Link: <' . esc_url_raw(get_permalink(self::$post)) . '>; rel="canonical"', false); header('X-A1Local-MDI-Version: ' . A1LOCAL_MDI_VERSION); header('X-A1Local-MDI-Source-Bytes: ' . $result['source_bytes']); header('X-A1Local-MDI-Markdown-Bytes: ' . $result['markdown_bytes']); header('X-A1Local-MDI-Container: ' . $result['container']); header('X-A1Local-MDI-Structured-Data: ' . $result['structured_count']); if (!empty(self::settings()['negotiation'])) { Http::vary_accept(); } if (!Http::has_header('Content-Signal')) { header('Content-Signal: ai-train=' . (self::settings()['training'] ? 'yes' : 'no') . ', search=yes, ai-input=yes'); } if (Http::method() !== 'HEAD') { // The response is text/markdown with nosniff. HTML escaping would corrupt its text and URLs. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $result['markdown']; } exit; } public static function fail(int $status, string $message): void { Http::no_store(); status_header($status); header('Content-Type: text/plain; charset=UTF-8'); header('X-Content-Type-Options: nosniff'); header('X-Robots-Tag: noindex'); if (Http::method() !== 'HEAD') { // Plain-text error with nosniff; HTML entities would change the error text. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $message . "\n"; } exit; } public static function discovery(): void { if (is_singular() && !is_preview() && !self::exclusion(get_queried_object())) { echo '' . "\n"; } } }