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; } };