<?php
/**
 * Durcissement headless : aucun rendu public WordPress, XML-RPC off,
 * endpoint REST users masqué pour les non-connectés, pings désactivés.
 */

defined('ABSPATH') || exit;

// Les types natifs restent utilisables en BO/REST/GraphQL, mais jamais comme pages WP publiques.
add_filter('register_post_type_args', 'cvb_make_native_content_headless', 20, 2);

function cvb_make_native_content_headless(array $args, string $post_type): array {
    if (in_array($post_type, ['post', 'page'], true)) {
        $args['publicly_queryable'] = false;
        $args['has_archive'] = false;
    }

    return $args;
}

// WordPress est headless : tout rendu de template public doit répondre comme inexistant.
add_action('template_redirect', 'cvb_block_headless_frontend_rendering', 0);

function cvb_block_headless_frontend_rendering(): void {
    if (cvb_is_headless_request_allowed()) {
        return;
    }

    global $wp_query;

    if ($wp_query instanceof WP_Query) {
        $wp_query->set_404();
    }

    status_header(404);
    nocache_headers();
    header('X-Robots-Tag: noindex, nofollow', true);
    header('Content-Type: text/plain; charset=' . get_option('blog_charset'), true);
    echo 'Not Found';
    exit;
}

function cvb_is_headless_request_allowed(): bool {
    if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
        return true;
    }

    if (defined('REST_REQUEST') && REST_REQUEST) {
        return true;
    }

    $path = wp_parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
    $path = is_string($path) ? '/' . ltrim($path, '/') : '/';

    $allowed_exact_paths = [
        '/graphql',
        '/robots.txt',
        '/wp-admin',
        '/wp-cron.php',
        '/wp-json',
        '/wp-login.php',
    ];

    if (in_array(untrailingslashit($path), $allowed_exact_paths, true)) {
        return true;
    }

    $allowed_prefixes = [
        '/graphql/',
        '/wp-admin/',
        '/wp-json/',
        '/wp-content/uploads/',
    ];

    foreach ($allowed_prefixes as $prefix) {
        if (str_starts_with($path, $prefix)) {
            return true;
        }
    }

    return isset($_GET['rest_route']) || array_key_exists('graphql', $_GET);
}

// Le sitemap et les flux exposeraient du contenu WP public qui appartient au front Next.
add_filter('wp_sitemaps_enabled', '__return_false');
add_action('init', 'cvb_disable_headless_public_metadata');

function cvb_disable_headless_public_metadata(): void {
    remove_action('wp_head', 'feed_links', 2);
    remove_action('wp_head', 'feed_links_extra', 3);
    remove_action('wp_head', 'rsd_link');
    remove_action('wp_head', 'wlwmanifest_link');
    remove_action('wp_head', 'wp_generator');
    remove_action('wp_head', 'wp_shortlink_wp_head');
    remove_action('template_redirect', 'redirect_canonical');
    remove_action('template_redirect', 'wp_shortlink_header', 11);
}

add_filter('robots_txt', 'cvb_headless_robots_txt', 20, 2);

function cvb_headless_robots_txt(string $output, bool $public): string {
    return "User-agent: *\nDisallow: /\n";
}

// XML-RPC inutile en headless.
add_filter('xmlrpc_enabled', '__return_false');

// Pas d'énumération d'utilisateurs via /wp/v2/users pour les non-connectés.
add_filter('rest_endpoints', 'cvb_restrict_users_rest_endpoint');

function cvb_restrict_users_rest_endpoint(array $endpoints): array {
    if (is_user_logged_in()) {
        return $endpoints;
    }
    unset($endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)']);

    return $endpoints;
}

// Désactiver les pings (pingbacks/trackbacks).
add_filter('pings_open', '__return_false', 20);
add_filter('xmlrpc_methods', 'cvb_remove_pingback_methods');

function cvb_remove_pingback_methods(array $methods): array {
    unset($methods['pingback.ping'], $methods['pingback.extensions.getPingbacks']);

    return $methods;
}
