Files
wp-healthcheck/includes/steps/50-theme.php
Steve Hanlon 6dd050ea1d Rename to ATT Site Healthcheck (private prefix)
Wholesale rename to avoid clashes with generic 'site healthcheck'
plugin names on a target site:

  - Plugin Name:        'Site Healthcheck' → 'ATT Site Healthcheck'
  - Main file:          site-healthcheck.php → att-site-healthcheck.php
  - Plugin folder:      site-healthcheck → att-site-healthcheck
  - Admin menu slug:    site-healthcheck → att-site-healthcheck
  - Settings slug:      site-healthcheck-settings → att-site-healthcheck-settings
  - PHP class prefix:   WPH_ → ATT_HC_
  - Function prefix:    wph_ → att_hc_
  - Option / transient: wph_* → att_hc_*
  - Action/filter:      wph_* → att_hc_*
  - CSS class prefix:   wph- → att-hc-
  - Constants:          WPH_GITEA_* → ATT_HC_GITEA_*
  - Class file names:   class-wph-*.php → class-att-hc-*.php
  - Dev folder:         ~/dev/wp-healthcheck → ~/dev/att-site-healthcheck

Existing in-progress sessions on installs that had the old wph_session
option will not migrate — they were intended for dev use only and the
user has confirmed this is OK for the rename window.

Smoke-tested on testsite: classes load, 14 steps discovered, save/load
round-trip works, admin page renders with new att-hc- CSS classes.

Recovery plugin detection unchanged — that lives in wp-site-recovery
and continues to be detected by Name + Author header.
2026-06-12 11:11:42 +01:00

161 lines
7.3 KiB
PHP

<?php
if (!defined('ABSPATH')) exit;
return new class extends ATT_HC_Step {
public function id(): string { return 'theme'; }
public function title(): string { return 'Step 5 — Theme Updates'; }
public function sub_items(): array {
return [
'Update the active theme if an update is available',
'If a child theme is in use (correct practice), the parent theme can be updated safely — confirm child theme is active',
'If no child theme is in use and the parent theme has been customised directly, do not update without flagging to the client first — the update will overwrite customisations',
'Update inactive themes only if they are legitimate fallback themes (e.g. a default Twenty* theme). Unused themes that serve no purpose should be flagged for removal',
];
}
public function watch_outs(): array {
return [
'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', '');
}
// --- Themes to keep vs. removal candidates ---------------------------
// Keep: the active theme, its parent (if a child), and the newest default
// Twenty* theme installed (as a fallback we can swap to if the active
// theme breaks during work). Everything else is a removal candidate.
$all = wp_get_themes();
$keep_slugs = [$active->get_stylesheet()];
$keep_reasons = [$active->get_stylesheet() => 'active theme'];
if ($is_child && $parent && $parent->exists()) {
$parent_slug = $parent->get_stylesheet();
$keep_slugs[] = $parent_slug;
$keep_reasons[$parent_slug] = 'parent of active theme';
}
// Find the newest default theme installed (priority by release year).
$default_priority = [
'twentytwentyfive', 'twentytwentyfour', 'twentytwentythree',
'twentytwentytwo', 'twentytwentyone', 'twentytwenty',
'twentynineteen', 'twentyseventeen', 'twentysixteen',
'twentyfifteen', 'twentyfourteen', 'twentythirteen',
'twentytwelve', 'twentyeleven', 'twentyten',
];
$fallback_slug = null;
foreach ($default_priority as $slug) {
if (isset($all[$slug])) {
$fallback_slug = $slug;
break;
}
}
if ($fallback_slug && !in_array($fallback_slug, $keep_slugs, true)) {
$keep_slugs[] = $fallback_slug;
$keep_reasons[$fallback_slug] = 'fallback default (newest Twenty* installed)';
} elseif ($fallback_slug && in_array($fallback_slug, $keep_slugs, true)) {
// Active theme already is the newest Twenty* — note it but no extra entry.
$keep_reasons[$fallback_slug] .= ' (also serves as the default fallback)';
}
// Emit keeper rows (ok level).
foreach ($keep_slugs as $slug) {
if (!isset($all[$slug])) continue; // theme missing on disk
$f[] = $this->finding(
'keep_' . sanitize_key($slug),
'ok',
'Keep — ' . $keep_reasons[$slug],
(string) $all[$slug]->get('Name') . ' (' . $slug . ')',
''
);
}
// Emit removal candidates (info level).
$candidates = [];
foreach ($all as $slug => $t) {
if (in_array($slug, $keep_slugs, true)) continue;
$candidates[$slug] = (string) $t->get('Name');
}
if (!$candidates) {
$f[] = $this->finding('rm_none', 'ok', 'Removal candidates', 'none', 'No inactive themes left to clear out.');
} else {
// Summary line first, then each candidate.
$f[] = $this->finding(
'rm_count',
count($candidates) > 3 ? 'warn' : 'info',
'Removal candidates',
count($candidates) . ' theme(s)',
'Inactive themes that aren\'t the active theme, its parent, or the fallback default — safe to remove unless flagged by the client.'
);
foreach ($candidates as $slug => $name) {
$f[] = $this->finding(
'rm_' . sanitize_key($slug),
'info',
'Candidate',
$name . ' (' . $slug . ')',
'Delete via Appearance → Themes if not needed.'
);
}
}
return $f;
}
};