Phase 3 v2: all step automations + cross-cutting UI

Step automations (six more):
- Step 3 (Core): current vs latest WP version, db upgrade flag, auto-
  update policy, safe-update sequence reminder.
- Step 5 (Theme): parent/child detection, customisation warning when
  non-default theme without child, theme update available, inactive
  theme list.
- Step 6 (Visual): mShots screenshot URL, key-page HEAD checks (home,
  login, posts page, WC shop/cart/checkout), mixed-content scan.
- Step 7 (Performance): keyless PageSpeed Insights v5 API (mobile +
  desktop, cached 12h, skipped on .local), caching plugin detection,
  heavy-image scan (>500KB).
- Step 10 (Uptime): monitoring plugin detection (ManageWP, MainWP,
  Jetpack, WP Umbrella, UptimeRobot), www/non-www canonical check.
- Step 12 (Wrap-up): cross-step rollup — bad/warn counts, blocked
  steps, top examples for the technician's final glance.

Cross-cutting:
- Sticky step-index sidebar with status dots per step (the linear-
  stepper alternative that keeps the overview).
- 'Stop & escalate' summary card at top listing blocked steps with
  escalation guidance and notes.
- Previous-session snapshot stored on finish; diff banner on the next
  session shows new/resolved/changed counts.
- HTML report builder (printable, inline-styled). Download HTML,
  Download Markdown, Copy, and Email actions on the finish panel.
  Email uses wp_mail with text/html.

Smoke-tested on testsite: all 12 steps return findings (5/9/4 by level
on a fresh local install), admin page renders with all UI markers,
HTML report is 26KB, Markdown report is 13KB, prev-session diff banner
appears on second session.

Deferred:
- hc-5ix.27 self-hosted update channel — needs hosting infra.
- Full PDF report — would need vendoring Dompdf.
- Step 3 safe-mode update wizard — worth its own bead.
This commit is contained in:
2026-06-11 16:13:15 +01:00
parent 0d51fc3b59
commit cf3007ec37
10 changed files with 706 additions and 20 deletions

View File

@@ -17,4 +17,82 @@ return new class extends WPH_Step {
'Layout changes post-theme update, broken header/footer, missing custom fonts or colours — indicates customisation was done directly in the parent theme.',
];
}
public function autocheck(array $session_state): array {
$f = [];
$active = wp_get_theme();
$is_child = (bool) $active->parent();
$parent = $is_child ? $active->parent() : null;
$f[] = $this->finding(
'active_theme',
'info',
'Active theme',
(string) $active->get('Name') . ' v' . (string) $active->get('Version'),
'Slug: ' . $active->get_stylesheet() . ($is_child ? ' (child of ' . $parent->get_stylesheet() . ')' : '')
);
// Child theme practice: warn if active theme has been customised but isn't a child
$is_default = preg_match('/^twenty/i', $active->get_stylesheet());
if (!$is_child && !$is_default) {
$style_mtime = file_exists($active->get_stylesheet_directory() . '/style.css')
? filemtime($active->get_stylesheet_directory() . '/style.css')
: 0;
$functions_mtime = file_exists($active->get_stylesheet_directory() . '/functions.php')
? filemtime($active->get_stylesheet_directory() . '/functions.php')
: 0;
$modified = max($style_mtime, $functions_mtime);
$f[] = $this->finding(
'no_child_theme',
'warn',
'Child theme practice',
'no child theme',
'Parent theme is being used directly. Last edit to style.css/functions.php: ' . ($modified ? date('Y-m-d', $modified) : 'unknown') . '. An update may overwrite customisations — confirm with client first.'
);
} else {
$f[] = $this->finding(
'child_theme_ok',
'ok',
'Child theme practice',
$is_child ? 'using a child theme' : 'default theme — no customisation expected',
''
);
}
// Updates available
if (!function_exists('wp_get_themes')) require_once ABSPATH . 'wp-includes/theme.php';
if (function_exists('wp_update_themes')) wp_update_themes();
$updates = get_site_transient('update_themes');
$update_map = isset($updates->response) && is_array($updates->response) ? $updates->response : [];
if (isset($update_map[$active->get_stylesheet()])) {
$new = $update_map[$active->get_stylesheet()]['new_version'] ?? '?';
$f[] = $this->finding(
'active_update',
'warn',
'Active theme update',
'v' . $new . ' available',
$is_child ? 'Safe to apply (child in use).' : 'Caution — may overwrite parent-theme customisations.'
);
} else {
$f[] = $this->finding('active_update', 'ok', 'Active theme update', 'up to date', '');
}
// Inactive themes
$all = wp_get_themes();
$inactive = [];
foreach ($all as $slug => $t) {
if ($slug === $active->get_stylesheet() || ($is_child && $slug === $parent->get_stylesheet())) continue;
$inactive[$slug] = (string) $t->get('Name');
}
$f[] = $this->finding(
'inactive_themes',
count($inactive) > 3 ? 'warn' : 'info',
'Inactive themes',
(string) count($inactive),
$inactive ? implode(', ', array_slice($inactive, 0, 6)) . (count($inactive) > 6 ? ' …' : '') : ''
);
return $f;
}
};