'; echo '

Site Healthcheck

'; if ($msg = get_transient('wph_install_message')) { delete_transient('wph_install_message'); echo '

' . esc_html($msg) . '

'; } if (!$session) { wph_render_start_panel(); echo ''; return; } if ($session->is_finished()) { wph_render_finished_panel($session); echo ''; return; } wph_render_active_session($session); echo ''; } function wph_render_start_panel(): void { ?>

Start a healthcheck

This will create a new in-progress session for . One session per site at a time.

progress(); $tech = get_userdata($session->technician_id()); ?>

Session: id()); ?> · Started: started_at())); ?> · Technician: display_name : '#' . $session->technician_id()); ?> · / steps

WP wp_version()); ?> · PHP php_version()); ?> · Site site_url()); ?>

'; wph_render_sidebar($session); echo '
'; foreach (WPH_Steps::instance()->all() as $step) { wph_render_step_card($session, $step); } echo '
'; } function wph_render_sidebar(WPH_Session $session): void { echo ''; } function wph_render_blocked_summary(WPH_Session $session): void { $blocked = []; foreach (WPH_Steps::instance()->all() as $step) { $state = $session->step_state($step->id()); if ($state['status'] === WPH_Session::STATUS_BLOCKED) { $blocked[] = ['step' => $step, 'state' => $state]; } } if (!$blocked) return; echo '
'; echo '

Stop & escalate

'; echo '

The following steps are blocked. Resolve or escalate before continuing:

'; echo '
'; } function wph_render_diff_summary(WPH_Session $session): void { $prev = WPH_Session::previous(); if (!$prev) return; // Aggregate findings by step+id from each session $current_idx = []; foreach (WPH_Steps::instance()->all() as $step) { $r = $session->get_autocheck($step->id()); if (!$r) continue; foreach ($r['findings'] as $f) { $current_idx[$step->id() . '|' . $f['id']] = $f; } } $prev_idx = []; foreach (WPH_Steps::instance()->all() as $step) { $r = $prev->get_autocheck($step->id()); if (!$r) continue; foreach ($r['findings'] as $f) { $prev_idx[$step->id() . '|' . $f['id']] = $f; } } $new = $resolved = $changed = []; foreach ($current_idx as $k => $f) { if (!isset($prev_idx[$k])) { if (in_array($f['level'], ['warn', 'bad'], true)) $new[] = $f; } elseif ($prev_idx[$k]['level'] !== $f['level'] || $prev_idx[$k]['value'] !== $f['value']) { $changed[] = ['was' => $prev_idx[$k], 'now' => $f]; } } foreach ($prev_idx as $k => $f) { if (!isset($current_idx[$k]) && in_array($f['level'], ['warn', 'bad'], true)) { $resolved[] = $f; } } if (!$new && !$resolved && !$changed) return; echo '
Δ vs. previous session (finished ' . esc_html(date('Y-m-d', (int) $prev->finished_at())) . ')'; if ($new) echo ' · ' . count($new) . ' new issue(s)'; if ($resolved) echo ' · ' . count($resolved) . ' resolved'; if ($changed) echo ' · ' . count($changed) . ' changed'; echo '
'; } function wph_render_step_card(WPH_Session $session, WPH_Step $step): void { $state = $session->step_state($step->id()); $status = $state['status']; $notes = $state['notes']; ?>
id()); ?>

title()); ?>

blurb()): ?>

sub_items()): ?> watch_outs()): ?>
Watch out for:
escalation())): ?>
render_extra($session->data()); ?>

Last saved ago

Healthcheck finished

Started started_at())); ?> · Finished finished_at())); ?>

Report preview

is_finished()) wp_die('No active session.'); if (!WPH_Steps::instance()->get($step_id)) wp_die('Unknown step.'); $status = isset($_POST['status']) ? sanitize_key((string) $_POST['status']) : WPH_Session::STATUS_NOT_STARTED; $notes = isset($_POST['notes']) ? wp_unslash((string) $_POST['notes']) : ''; $session->update_step($step_id, $status, $notes); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); exit; } function wph_handle_finish(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('wph_finish'); $session = WPH_Session::current(); if (!$session) wp_die('No active session.'); $session->finish(); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); exit; } function wph_handle_discard(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('wph_discard'); WPH_Session::discard(); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); exit; } function wph_render_autocheck(WPH_Session $session, WPH_Step $step): void { if (!$step->has_autocheck()) return; $result = $session->get_autocheck($step->id()); ?>

Automated checks

id()); ?>

No automated checks have been run for this step yet.

'✓', 'warn' => '⚠', 'bad' => '✗', 'info' => '·'][$finding['level']] ?? '·'; ?>

Checked ago ()

Site Healthcheck — Settings

← Back to healthcheck

Saved.

Recovery plugin source (Gitea)

One-click install pulls site-recovery from a private Gitea repo. The token needs read access to the repo only — a deploy / read-only PAT is safer than a personal token.

Each field can be set via a constant in wp-config.php (then it takes precedence and the field below is locked).

>

Set via WPH_GITEA_HOST constant.

>

Set via WPH_GITEA_OWNER constant.

>

Set via WPH_GITEA_REPO constant.

>

Set via WPH_GITEA_TOKEN constant.

Stored in WP options. Use a read-only deploy token scoped to this repo if possible.


Install now


✓ Site Recovery is already installed at .

(string) wp_unslash($_POST['host'] ?? ''), 'owner' => (string) wp_unslash($_POST['owner'] ?? ''), 'repo' => (string) wp_unslash($_POST['repo'] ?? ''), 'token' => (string) wp_unslash($_POST['token'] ?? ''), ]); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck-settings&wph_saved=1')); exit; } function wph_handle_recovery_install(): void { if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) wp_die('Forbidden'); check_admin_referer('wph_recovery_install'); @set_time_limit(120); $result = WPH_Recovery_Installer::install_and_activate(); if (is_wp_error($result)) { wp_die('Install failed: ' . esc_html($result->get_error_message()) . '

Back to settings

'); } set_transient('wph_install_message', sprintf('Site Recovery installed from %s "%s" and activated.', $result['ref']['type'], $result['ref']['ref']), 60); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); exit; } function wph_handle_step_action(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); $step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : ''; $action_name = isset($_POST['step_action']) ? sanitize_key((string) $_POST['step_action']) : ''; check_admin_referer('wph_step_action_' . $step_id . '_' . $action_name); $session = WPH_Session::current(); if (!$session || $session->is_finished()) wp_die('No active session.'); $step = WPH_Steps::instance()->get($step_id); if (!$step) wp_die('Unknown step.'); // Pass POST through unslashed so handlers see the raw user input. $input = wp_unslash($_POST); @set_time_limit(60); $finding = $step->handle_action($action_name, is_array($input) ? $input : []); if (is_array($finding)) { // Append (or replace by id) onto this step's stored findings. $existing = $session->get_autocheck($step_id); $findings = $existing['findings'] ?? []; $replaced = false; foreach ($findings as $i => $f) { if (($f['id'] ?? '') === ($finding['id'] ?? '')) { $findings[$i] = $finding; $replaced = true; break; } } if (!$replaced) $findings[] = $finding; $session->set_autocheck($step_id, $findings); } wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); exit; } function wph_handle_refresh_checks(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); $step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : ''; check_admin_referer('wph_refresh_checks_' . $step_id); $session = WPH_Session::current(); if (!$session || $session->is_finished()) wp_die('No active session.'); $step = WPH_Steps::instance()->get($step_id); if (!$step) wp_die('Unknown step.'); @set_time_limit(60); $findings = $step->autocheck($session->data()); $session->set_autocheck($step_id, $findings); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); exit; } function wph_handle_download_report(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('wph_download_report'); $session = WPH_Session::current(); if (!$session) wp_die('No session.'); wph_stream_report($session, 'md'); } function wph_handle_download_html(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('wph_download_html'); $session = WPH_Session::current(); if (!$session) wp_die('No session.'); wph_stream_report($session, 'html'); } function wph_stream_report(WPH_Session $session, string $format): void { $host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site'; $host = preg_replace('/[^a-z0-9.-]/i', '', (string) $host); $stamp = date('Ymd', $session->started_at() ?: time()); nocache_headers(); if ($format === 'html') { header('Content-Type: text/html; charset=UTF-8'); header('Content-Disposition: attachment; filename="wph-report-' . $host . '-' . $stamp . '.html"'); echo wph_build_html_report($session); } else { header('Content-Type: text/markdown; charset=UTF-8'); header('Content-Disposition: attachment; filename="wph-report-' . $host . '-' . $stamp . '.md"'); echo wph_build_markdown_report($session); } exit; } function wph_handle_email_report(): void { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('wph_email_report'); $session = WPH_Session::current(); if (!$session) wp_die('No session.'); $to = isset($_POST['to']) ? sanitize_email((string) wp_unslash($_POST['to'])) : ''; if (!is_email($to)) wp_die('Bad email address.'); $host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site'; $subject = 'Site Healthcheck — ' . $host . ' — ' . date('Y-m-d', $session->started_at() ?: time()); $html = wph_build_html_report($session); $ok = wp_mail($to, $subject, $html, ['Content-Type: text/html; charset=UTF-8']); wp_safe_redirect(admin_url('tools.php?page=site-healthcheck&wph_mail=' . ($ok ? '1' : '0'))); exit; }