Plugin: add Google Analytics + Search Console healthcheck steps (hc-u5c)
Slot two new steps between performance (Step 7) and security (Step 8): - 72-analytics.php: sniffs the homepage for GA4 (G-), GTM, and legacy UA measurement IDs plus known loader URLs (gtag.js, gtm.js, analytics.js, ga.js) and detects common analytics/tag plugins. Warns if only UA is still in use. - 74-search-console.php: looks for google-site-verification meta tags on the homepage, probes for a reachable sitemap (wp-sitemap.xml, then sitemap_index.xml, then sitemap.xml), parses robots.txt for a Googlebot/* Disallow: /, flags the WP "Discourage search engines" setting when on, and notes whether Site Kit is active. Titles use the "Step —" (unnumbered) convention already used by the email and handover steps so the existing numbered steps don't shift. steps.md updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
166
includes/steps/74-search-console.php
Normal file
166
includes/steps/74-search-console.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
return new class extends ATT_HC_Step {
|
||||
public function id(): string { return 'search_console'; }
|
||||
public function title(): string { return 'Step — Google Search Console Check'; }
|
||||
public function blurb(): string {
|
||||
return 'Confirm the site is still verified in Google Search Console and that Google is able to crawl and index it. Autocheck looks for verification meta tags, a reachable sitemap, and a robots.txt that isn\'t blocking Googlebot — the human check is in the GSC UI itself.';
|
||||
}
|
||||
public function sub_items(): array {
|
||||
return [
|
||||
'Log into Google Search Console and confirm the property is still verified',
|
||||
'Check the Pages / Coverage report for new indexing errors since the last healthcheck',
|
||||
'Confirm the sitemap is submitted and its "Last read" date is recent',
|
||||
'Skim the Performance report — flag significant drops in impressions or clicks (>25% vs. previous period)',
|
||||
'Check Manual Actions and Security Issues — flag anything that is not "No issues detected"',
|
||||
'Confirm the verified property matches the canonical URL (http vs https, www vs non-www) actually serving',
|
||||
];
|
||||
}
|
||||
public function watch_outs(): array {
|
||||
return [
|
||||
'Verification meta tags can be added by SEO plugins (Yoast, Rank Math, AIOSEO) or by Site Kit — the presence of a tag does not tell you which Google account owns the property',
|
||||
'A missing tag does not mean the site is unverified — DNS TXT and file-based verification are equally valid and not visible from the front end',
|
||||
];
|
||||
}
|
||||
|
||||
public function autocheck(array $session_state): array {
|
||||
$f = [];
|
||||
$home = home_url('/');
|
||||
|
||||
// 1. Homepage HTML — look for verification meta tag(s).
|
||||
$resp = wp_remote_get($home, ['timeout' => 8, 'redirection' => 3]);
|
||||
if (is_wp_error($resp)) {
|
||||
$f[] = $this->finding('fetch', 'warn', 'Homepage fetch', 'failed', $resp->get_error_message());
|
||||
} else {
|
||||
$body = (string) wp_remote_retrieve_body($resp);
|
||||
$tokens = [];
|
||||
if (preg_match_all('/<meta[^>]+name=["\']google-site-verification["\'][^>]*content=["\']([^"\']+)["\']/i', $body, $m)) {
|
||||
$tokens = $m[1];
|
||||
}
|
||||
$f[] = $this->finding(
|
||||
'verification_meta',
|
||||
$tokens ? 'ok' : 'info',
|
||||
'GSC verification meta tag',
|
||||
$tokens ? count($tokens) . ' present' : 'not found on homepage',
|
||||
$tokens
|
||||
? 'Token(s): ' . implode(', ', array_map(fn($t) => substr($t, 0, 12) . '…', $tokens))
|
||||
: 'Absent tag is not a problem if verification is via DNS TXT or an uploaded HTML file — confirm in Search Console.'
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Sitemap reachability. Try common locations, take the first that responds 200.
|
||||
$candidates = [
|
||||
'wp-sitemap.xml', // WP core (5.5+)
|
||||
'sitemap_index.xml', // Yoast/Rank Math default
|
||||
'sitemap.xml',
|
||||
];
|
||||
$found_sitemap = null;
|
||||
foreach ($candidates as $rel) {
|
||||
$url = trailingslashit($home) . $rel;
|
||||
$head = wp_remote_head($url, ['timeout' => 5, 'redirection' => 2]);
|
||||
if (is_wp_error($head)) continue;
|
||||
$code = (int) wp_remote_retrieve_response_code($head);
|
||||
if ($code === 200) {
|
||||
$found_sitemap = $url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($found_sitemap) {
|
||||
$f[] = $this->finding(
|
||||
'sitemap',
|
||||
'ok',
|
||||
'Sitemap',
|
||||
$found_sitemap,
|
||||
'Reachable — confirm this URL is the one submitted in Search Console.'
|
||||
);
|
||||
} else {
|
||||
$f[] = $this->finding(
|
||||
'sitemap',
|
||||
'warn',
|
||||
'Sitemap',
|
||||
'none of the common URLs responded',
|
||||
'Tried: ' . implode(', ', $candidates) . '. A missing sitemap doesn\'t prevent indexing but Search Console will show a fetch error.'
|
||||
);
|
||||
}
|
||||
|
||||
// 3. robots.txt — surface if it blocks Googlebot from the site root.
|
||||
$robots_url = trailingslashit($home) . 'robots.txt';
|
||||
$rob = wp_remote_get($robots_url, ['timeout' => 5, 'redirection' => 2]);
|
||||
if (is_wp_error($rob)) {
|
||||
$f[] = $this->finding('robots', 'info', 'robots.txt', 'unreachable', $rob->get_error_message());
|
||||
} else {
|
||||
$code = (int) wp_remote_retrieve_response_code($rob);
|
||||
if ($code !== 200) {
|
||||
$f[] = $this->finding('robots', 'info', 'robots.txt', 'HTTP ' . $code, 'WordPress serves a virtual robots.txt by default; a non-200 response may indicate a redirect or a plugin intercepting it.');
|
||||
} else {
|
||||
$rb = (string) wp_remote_retrieve_body($rob);
|
||||
$blocks_root = $this->robots_blocks_root($rb);
|
||||
$f[] = $this->finding(
|
||||
'robots',
|
||||
$blocks_root ? 'bad' : 'ok',
|
||||
'robots.txt',
|
||||
$blocks_root ? 'blocks Googlebot from /' : 'does not block /',
|
||||
$blocks_root
|
||||
? 'A "Disallow: /" applying to * or Googlebot will prevent indexing. Check Settings → Reading for "Discourage search engines".'
|
||||
: ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. "Discourage search engines" WP setting — hard block on indexing.
|
||||
if ((int) get_option('blog_public') === 0) {
|
||||
$f[] = $this->finding(
|
||||
'blog_public',
|
||||
'bad',
|
||||
'Search engine indexing',
|
||||
'discouraged (Settings → Reading)',
|
||||
'The "Discourage search engines from indexing this site" checkbox is on. This should be off on production.'
|
||||
);
|
||||
} else {
|
||||
$f[] = $this->finding('blog_public', 'ok', 'Search engine indexing', 'allowed', '');
|
||||
}
|
||||
|
||||
// 5. Site Kit — the WP-side surface for Search Console data. Informational.
|
||||
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$sitekit = is_plugin_active('google-site-kit/google-site-kit.php');
|
||||
$f[] = $this->finding(
|
||||
'site_kit',
|
||||
$sitekit ? 'ok' : 'info',
|
||||
'Site Kit by Google',
|
||||
$sitekit ? 'active' : 'not active',
|
||||
$sitekit ? 'GSC data may be visible on the WP dashboard.' : ''
|
||||
);
|
||||
|
||||
return $f;
|
||||
}
|
||||
|
||||
/** Parse robots.txt and decide whether Googlebot (or *) is disallowed from /. */
|
||||
private function robots_blocks_root(string $robots): bool {
|
||||
$lines = preg_split('/\r?\n/', $robots) ?: [];
|
||||
$current_agents = [];
|
||||
$groups = []; // agent => [disallow rules]
|
||||
foreach ($lines as $line) {
|
||||
$line = trim(preg_replace('/#.*$/', '', $line));
|
||||
if ($line === '') { $current_agents = []; continue; }
|
||||
if (preg_match('/^user-agent:\s*(.+)$/i', $line, $m)) {
|
||||
$current_agents[] = strtolower(trim($m[1]));
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^disallow:\s*(.*)$/i', $line, $m)) {
|
||||
$rule = trim($m[1]);
|
||||
foreach ($current_agents as $ua) {
|
||||
$groups[$ua][] = $rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (['googlebot', '*'] as $ua) {
|
||||
if (!empty($groups[$ua])) {
|
||||
foreach ($groups[$ua] as $rule) {
|
||||
if ($rule === '/') return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user