Files
wp-healthcheck/includes/steps/10-backup.php
Steve Hanlon 0d51fc3b59 Phase 3 v1: autocheck framework + six step automations
Infrastructure:
- WPH_Step::autocheck($session_state) returns an array of findings
  shaped {id, level: ok/warn/bad/info, label, value, detail}.
- WPH_Session stores results keyed by step id (persisted in the option-
  backed session).
- WPH_Step::has_autocheck() reflection check so the UI only renders the
  panel for steps that implement automation.
- 'Run checks' / 'Refresh' button per step, admin-post handler runs
  autocheck() and stashes the result on the session.
- Findings rendered as a coloured table on the step card; included
  verbatim in the Markdown report with status icons.

Step automations implemented:
- Step 1 (Backup): detection of 11 known backup plugins by slug;
  active/inactive state; UpdraftPlus last-backup timestamp.
- Step 2 (Environment): PHP version + EOL, WP version vs latest, disk
  usage, wp-config flags, file perms on wp-config/wp-content/uploads,
  error-log sizes.
- Step 4 (Plugins): WP.org API enrichment with 24h transient cache —
  last_updated, active_installs, abandonment flag, removed-from-repo
  flag, update-available count. Summary line at the top.
- Step 8 (Security): SSL cert expiry via stream_socket_client +
  openssl_x509_parse, administrator audit, xmlrpc reachability, login
  URL hardening detection.
- Step 9 (Database): spam comments, post revisions, autoload size (WP
  6.6+ value handling), top 3 largest tables.
- Step 11 (Small fixes): deactivated-but-installed plugin list,
  homepage alt-text scan.

Smoke-tested on testsite — all six steps return findings with
correctly-classified levels. Report regenerated with automated findings
section.
2026-06-11 16:02:34 +01:00

98 lines
4.2 KiB
PHP

<?php
if (!defined('ABSPATH')) exit;
return new class extends WPH_Step {
public function id(): string { return 'backup'; }
public function title(): string { return 'Step 1 — Take a Full Backup'; }
public function blurb(): string {
return 'Before any work takes place, take a complete backup manually. Do not rely on the most recent automated backup.';
}
public function sub_items(): array {
return [
'Back up both the database and all files (wp-content, wp-config.php, .htaccess)',
'Confirm the backup has completed and is accessible/downloadable',
'Note the backup location and timestamp in the client record',
];
}
public function escalation(): ?string {
return 'If the backup fails or cannot be confirmed, stop. Do not proceed until you have a verified backup.';
}
public function autocheck(array $session_state): array {
if (!function_exists('get_plugins')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
$plugins = get_plugins();
// Map known backup plugins: slug → friendly label.
$known = [
'updraftplus/updraftplus.php' => 'UpdraftPlus',
'backwpup/backwpup.php' => 'BackWPup',
'backup-backup/backup-backup.php' => 'Backup Migration',
'duplicator/duplicator.php' => 'Duplicator',
'wpvivid-backuprestore/wpvivid-backuprestore.php' => 'WPvivid',
'all-in-one-wp-migration/all-in-one-wp-migration.php' => 'All-in-One WP Migration',
'blogvault-real-time-backup/blogvault.php' => 'BlogVault',
'jetpack/jetpack.php' => 'Jetpack (VaultPress)',
'solid-backups/backupbuddy.php' => 'Solid Backups',
'backupbuddy/backupbuddy.php' => 'BackupBuddy',
'wp-time-capsule/wp-time-capsule.php' => 'WP Time Capsule',
];
$found = [];
foreach ($known as $file => $label) {
if (isset($plugins[$file])) {
$found[$file] = [
'label' => $label,
'active' => is_plugin_active($file),
];
}
}
$f = [];
if (empty($found)) {
$f[] = $this->finding(
'no_backup_plugin', 'bad',
'No recognised backup plugin installed', '',
'None of the common backup plugins were detected. Confirm host-side backups instead, or install one.'
);
return $f;
}
$any_active = false;
foreach ($found as $file => $meta) {
if ($meta['active']) $any_active = true;
$f[] = $this->finding(
'plugin_' . sanitize_key($file),
$meta['active'] ? 'ok' : 'warn',
$meta['label'],
$meta['active'] ? 'active' : 'installed but inactive',
''
);
}
if (!$any_active) {
$f[] = $this->finding(
'no_active', 'bad',
'No backup plugin is active', '',
'A backup plugin is installed but not active — activate before proceeding.'
);
}
// UpdraftPlus — surface last backup timestamp if available.
if (isset($found['updraftplus/updraftplus.php'])) {
$last = get_option('updraft_last_backup');
if (is_array($last) && !empty($last['backup_time'])) {
$age_days = floor((time() - (int) $last['backup_time']) / DAY_IN_SECONDS);
$level = $age_days > 7 ? 'warn' : 'ok';
$f[] = $this->finding(
'updraft_last',
$level,
'UpdraftPlus — last backup',
date('Y-m-d H:i', (int) $last['backup_time']),
$age_days . ' day(s) ago' . (!empty($last['backup_nonce']) ? '. Nonce ' . substr((string) $last['backup_nonce'], 0, 8) : '')
);
}
}
return $f;
}
};