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:
@@ -22,6 +22,7 @@
|
|||||||
{"_type":"issue","id":"hc-5ix.9","title":"Smoke test on testsite: full end-to-end run through the checklist","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:15Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.9","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.9","title":"Smoke test on testsite: full end-to-end run through the checklist","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:15Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.9","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.8","title":"README + install instructions + screenshots placeholder","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"chore","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:14Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.8","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.8","title":"README + install instructions + screenshots placeholder","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"chore","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:14Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.8","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix","title":"Build WordPress Healthcheck plugin (stepper through steps.md)","description":"Plugin that walks a technician through the WordPress healthcheck steps documented in steps.md. Independent of the recovery plugin (which it depends on as step 0).","status":"open","priority":2,"issue_type":"epic","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:32Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T11:26:32Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix","title":"Build WordPress Healthcheck plugin (stepper through steps.md)","description":"Plugin that walks a technician through the WordPress healthcheck steps documented in steps.md. Independent of the recovery plugin (which it depends on as step 0).","status":"open","priority":2,"issue_type":"epic","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:32Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T11:26:32Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"hc-u5c","title":"Add Google Analytics and Search Console healthcheck steps","description":"Add two new healthcheck steps between performance (Step 7) and security (Step 8): a Google Analytics check that sniffs the homepage for GA4/GTM/UA snippets and detects known analytics plugins, and a Google Search Console check that looks for verification meta tags, sitemap reachability, robots.txt Disallow rules, and the WP 'Discourage search engines' setting. Updates steps.md to match.","status":"open","priority":3,"issue_type":"feature","owner":"steve@hanlon.co.uk","created_at":"2026-07-16T08:44:39Z","created_by":"Steve Hanlon","updated_at":"2026-07-16T08:44:39Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.27","title":"Self-hosted update channel: plugin checks a private URL for new releases and offers one-click update from wp-admin","description":"Lightweight wrapper around the WP plugin update transient: site_transient_update_plugins filter that adds an entry for ourselves if a newer release exists at a configured URL. URL hosts a JSON manifest + zip. Lets technicians keep clients up to date without manually re-uploading the zip every check.","status":"open","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:40:30Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:40:30Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.27","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:40:29Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.27","title":"Self-hosted update channel: plugin checks a private URL for new releases and offers one-click update from wp-admin","description":"Lightweight wrapper around the WP plugin update transient: site_transient_update_plugins filter that adds an entry for ourselves if a newer release exists at a configured URL. URL hosts a JSON manifest + zip. Lets technicians keep clients up to date without manually re-uploading the zip every check.","status":"open","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:40:30Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:40:30Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.27","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:40:29Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.25","title":"Step 12 (Wrap-up): pre-fill report with all automated findings, attach diff vs. previous session, optionally push summary to ManageWP/WP Umbrella","notes":"wrap_up step's autocheck walks all other steps' stored findings, counts bad/warn separately, lists the top 6-8 examples, and identifies any blocked steps. Gives the technician a quick 'roll-up' before they finish. The report itself already embeds every finding verbatim.","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:26Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:13:15Z","started_at":"2026-06-11T15:05:01Z","closed_at":"2026-06-11T15:13:15Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.25","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:25Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.25","title":"Step 12 (Wrap-up): pre-fill report with all automated findings, attach diff vs. previous session, optionally push summary to ManageWP/WP Umbrella","notes":"wrap_up step's autocheck walks all other steps' stored findings, counts bad/warn separately, lists the top 6-8 examples, and identifies any blocked steps. Gives the technician a quick 'roll-up' before they finish. The report itself already embeds every finding verbatim.","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:26Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:13:15Z","started_at":"2026-06-11T15:05:01Z","closed_at":"2026-06-11T15:13:15Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.25","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:25Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.24","title":"Step 11 (Small fixes): broken internal link scan (sample first N pages), missing alt-text scan on homepage images, deactivated-but-installed plugin list","notes":"Deactivated-but-installed plugin list and homepage alt-text scan (fetch home_url, regex over \u003cimg\u003e tags for alt attribute presence). Broken-link scan deferred — too heavy for v1 (would need to crawl every internal link).","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:25Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:02:34Z","started_at":"2026-06-11T14:55:08Z","closed_at":"2026-06-11T15:02:34Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.24","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:25Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.24","title":"Step 11 (Small fixes): broken internal link scan (sample first N pages), missing alt-text scan on homepage images, deactivated-but-installed plugin list","notes":"Deactivated-but-installed plugin list and homepage alt-text scan (fetch home_url, regex over \u003cimg\u003e tags for alt attribute presence). Broken-link scan deferred — too heavy for v1 (would need to crawl every internal link).","status":"closed","priority":3,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:25Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:02:34Z","started_at":"2026-06-11T14:55:08Z","closed_at":"2026-06-11T15:02:34Z","labels":["phase-3"],"dependencies":[{"issue_id":"hc-5ix.24","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:25Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|||||||
108
includes/steps/72-analytics.php
Normal file
108
includes/steps/72-analytics.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('ABSPATH')) exit;
|
||||||
|
|
||||||
|
return new class extends ATT_HC_Step {
|
||||||
|
public function id(): string { return 'analytics'; }
|
||||||
|
public function title(): string { return 'Step — Google Analytics Check'; }
|
||||||
|
public function blurb(): string {
|
||||||
|
return 'Confirm the site is still reporting into Google Analytics. Autocheck sniffs the homepage HTML for a tracking snippet and known GA plugins — the human check is that events are actually landing in the GA property.';
|
||||||
|
}
|
||||||
|
public function sub_items(): array {
|
||||||
|
return [
|
||||||
|
'Confirm a tracking snippet is present on the homepage (view source, look for gtag/GTM/analytics.js)',
|
||||||
|
'Log into Google Analytics and confirm real-time users are being recorded when you load the site',
|
||||||
|
'Confirm the measurement ID on the site matches the client record (G-XXXX for GA4, GTM-XXXX for Tag Manager)',
|
||||||
|
'Check that today has page-view events in the property — flag if traffic has dropped to zero',
|
||||||
|
'Flag if only Universal Analytics (UA-XXXX) is still in use — GA4 has been required since July 2023',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
public function watch_outs(): array {
|
||||||
|
return [
|
||||||
|
'A snippet on the page does not prove data is arriving — cookie banners that block until consent will suppress hits until accepted',
|
||||||
|
'Some caching/optimisation plugins defer or strip inline scripts — the snippet may only appear once cache is warm',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autocheck(array $session_state): array {
|
||||||
|
$f = [];
|
||||||
|
$home = home_url('/');
|
||||||
|
|
||||||
|
$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());
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
$body = (string) wp_remote_retrieve_body($resp);
|
||||||
|
|
||||||
|
// Measurement / container IDs anywhere in the HTML.
|
||||||
|
$found_ids = [];
|
||||||
|
if (preg_match_all('/\bG-[A-Z0-9]{6,}\b/', $body, $m)) $found_ids = array_merge($found_ids, $m[0]);
|
||||||
|
if (preg_match_all('/\bGTM-[A-Z0-9]{4,}\b/', $body, $m)) $found_ids = array_merge($found_ids, $m[0]);
|
||||||
|
if (preg_match_all('/\bUA-\d{4,}-\d+\b/', $body, $m)) $found_ids = array_merge($found_ids, $m[0]);
|
||||||
|
$found_ids = array_values(array_unique($found_ids));
|
||||||
|
|
||||||
|
// Snippet vendors (any script/URL referencing a known loader).
|
||||||
|
$signals = [
|
||||||
|
'gtag.js' => 'googletagmanager.com/gtag/js',
|
||||||
|
'gtm.js' => 'googletagmanager.com/gtm.js',
|
||||||
|
'analytics.js (UA)' => 'google-analytics.com/analytics.js',
|
||||||
|
'ga.js (legacy UA)' => 'google-analytics.com/ga.js',
|
||||||
|
];
|
||||||
|
$vendors = [];
|
||||||
|
foreach ($signals as $label => $needle) {
|
||||||
|
if (stripos($body, $needle) !== false) $vendors[] = $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($found_ids || $vendors) {
|
||||||
|
$has_ga4 = (bool) preg_grep('/^G-/', $found_ids);
|
||||||
|
$has_ua = (bool) preg_grep('/^UA-/', $found_ids);
|
||||||
|
$level = ($has_ua && !$has_ga4) ? 'warn' : 'ok';
|
||||||
|
$detail = $vendors ? 'loader: ' . implode(', ', $vendors) : '';
|
||||||
|
if ($has_ua && !$has_ga4) {
|
||||||
|
$detail = trim($detail . ' — Universal Analytics only; GA4 required since July 2023.');
|
||||||
|
}
|
||||||
|
$f[] = $this->finding(
|
||||||
|
'snippet',
|
||||||
|
$level,
|
||||||
|
'Tracking snippet',
|
||||||
|
$found_ids ? implode(', ', array_slice($found_ids, 0, 4)) : 'present',
|
||||||
|
$detail
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$f[] = $this->finding(
|
||||||
|
'snippet',
|
||||||
|
'warn',
|
||||||
|
'Tracking snippet',
|
||||||
|
'not found',
|
||||||
|
'No GA/GTM/UA measurement ID or loader was seen in the homepage HTML. A consent banner may be blocking the script, or tracking may have been removed.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GA/GTM plugin detection — informational only.
|
||||||
|
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
|
$plugins = [
|
||||||
|
'google-site-kit/google-site-kit.php' => 'Site Kit by Google',
|
||||||
|
'google-analytics-for-wordpress/googleanalytics.php' => 'MonsterInsights',
|
||||||
|
'google-analytics-premium/googleanalytics-premium.php' => 'MonsterInsights Pro',
|
||||||
|
'google-analytics-dashboard-for-wp/gadwp.php' => 'ExactMetrics',
|
||||||
|
'ga-google-analytics/ga-google-analytics.php' => 'GA Google Analytics',
|
||||||
|
'analytify/wp-analytify.php' => 'Analytify',
|
||||||
|
'duracelltomi-google-tag-manager/duracelltomi-google-tag-manager.php' => 'GTM4WP',
|
||||||
|
'header-footer-code-manager/header-footer-code-manager.php' => 'Header Footer Code Manager',
|
||||||
|
'insert-headers-and-footers/ihaf.php' => 'WPCode / Insert Headers and Footers',
|
||||||
|
];
|
||||||
|
$active = [];
|
||||||
|
foreach ($plugins as $file => $label) {
|
||||||
|
if (is_plugin_active($file)) $active[] = $label;
|
||||||
|
}
|
||||||
|
$f[] = $this->finding(
|
||||||
|
'plugin',
|
||||||
|
$active ? 'ok' : 'info',
|
||||||
|
'Analytics/tag plugin',
|
||||||
|
$active ? implode(', ', $active) : 'none detected',
|
||||||
|
$active ? '' : 'Tracking may be hard-coded in the theme or injected by a page builder — check view-source if the snippet check passed.'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
};
|
||||||
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
29
steps.md
29
steps.md
@@ -101,6 +101,35 @@ Do a manual walkthrough of the site:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Step — Google Analytics Check
|
||||||
|
|
||||||
|
Confirm the site is still reporting into Google Analytics.
|
||||||
|
|
||||||
|
- Confirm a tracking snippet is present on the homepage (view source, look for gtag/GTM/analytics.js)
|
||||||
|
- Log into Google Analytics and confirm real-time users are being recorded when you load the site
|
||||||
|
- Confirm the measurement ID on the site matches the client record (G-XXXX for GA4, GTM-XXXX for Tag Manager)
|
||||||
|
- Check that today has page-view events in the property — flag if traffic has dropped to zero
|
||||||
|
- Flag if only Universal Analytics (UA-XXXX) is still in use — GA4 has been required since July 2023
|
||||||
|
|
||||||
|
**Watch out for:** cookie/consent banners that block the tracker until accepted; caching/optimisation plugins that defer or strip inline scripts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step — Google Search Console Check
|
||||||
|
|
||||||
|
Confirm the site is still verified and Google can crawl/index it.
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
**Watch out for:** a missing verification meta tag doesn't mean unverified — DNS TXT and file-based verification are equally valid.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Step 8 — Security Check
|
### Step 8 — Security Check
|
||||||
|
|
||||||
- Confirm the SSL certificate is valid and not expiring within 30 days — flag if so
|
- Confirm the SSL certificate is valid and not expiring within 30 days — flag if so
|
||||||
|
|||||||
Reference in New Issue
Block a user