get_contents(self::path()); } return (string) get_option('adghost_ads_txt_virtual', ''); } public static function write_ads_txt($content) { $fs = self::filesystem(); if (!$fs) { return false; } if (get_option('adghost_ads_txt_backup', null) === null) { add_option('adghost_ads_txt_backup', [ 'content' => self::read_ads_txt(), 'existed' => self::detect_ads_txt(), 'time' => time(), ], '', false); } if ($fs->put_contents(self::path(), $content, FS_CHMOD_FILE)) { delete_option('adghost_ads_txt_virtual'); return true; } update_option('adghost_ads_txt_virtual', (string) $content, false); return true; } public static function init() { add_action('init', [__CLASS__, 'maybe_serve'], 0); } public static function maybe_serve() { $path = (string) wp_parse_url(isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '', PHP_URL_PATH); if (strtolower(trim($path, '/')) !== 'ads.txt') { return; } $stored = get_option('adghost_ads_txt_virtual', ''); if ($stored === '' || !get_option('adghost_ads_txt_managed', false)) { return; } if (!headers_sent()) { header('Content-Type: text/plain; charset=utf-8'); header('Cache-Control: max-age=86400'); } echo $stored; // phpcs:ignore WordPress.Security.EscapeOutput -- text/plain document, sanitised on save. exit; } public static function is_virtual() { return get_option('adghost_ads_txt_virtual', '') !== '' && !self::detect_ads_txt(); } public static function validate_ads_txt($content) { $lines = explode("\n", $content); $errors = []; $warnings = []; $seen = []; foreach ($lines as $index => $line) { $hash = strpos($line, '#'); $line = trim($hash === false ? $line : substr($line, 0, $hash)); if ($line === '') { continue; } if (preg_match('/^[A-Za-z]+\s*=/', $line)) { continue; } $parts = explode(',', $line); if (count($parts) < 3 || count($parts) > 4) { $errors[] = "Line " . ($index + 1) . ": Invalid field count (expected 3 or 4)"; continue; } $domain = trim($parts[0]); $pub_id = trim($parts[1]); $type = strtoupper(trim($parts[2])); $fingerprint = strtolower($domain . '|' . $pub_id . '|' . $type); if (isset($seen[$fingerprint])) { $warnings[] = 'Line ' . ($index + 1) . ": Duplicate of line {$seen[$fingerprint]}"; } else { $seen[$fingerprint] = $index + 1; } if (!filter_var('http://' . $domain, FILTER_VALIDATE_URL)) { $warnings[] = "Line " . ($index + 1) . ": Possible invalid domain '$domain'"; } if ($type !== 'DIRECT' && $type !== 'RESELLER') { $errors[] = "Line " . ($index + 1) . ": Invalid relationship type '$type' (expected DIRECT or RESELLER)"; } } return [ 'errors' => $errors, 'warnings' => $warnings, 'isValid' => empty($errors) ]; } public static function get_assisted_entries() { return [ 'gam' => "# Google Ad Manager (verify your pub ID before uncommenting)\n# google.com, pub-1234567890, DIRECT, f08c47fec0942fa0", 'adsense' => "# AdSense (verify your pub ID before uncommenting)\n# google.com, pub-1234567890, DIRECT, f08c47fec0942fa0", 'common' => "# Common SSP Template\n# example-ssp.com, 12345, DIRECT, c08c47fec0942fa0" ]; } }