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.
This commit is contained in:
2026-06-12 11:11:42 +01:00
parent 4e030ae5ee
commit 6dd050ea1d
24 changed files with 365 additions and 365 deletions

View File

@@ -5,7 +5,7 @@ Internal WordPress plugin that walks a technician through a structured healthche
## Install ## Install
```sh ```sh
ln -s ~/dev/wp-healthcheck /path/to/wp/wp-content/plugins/site-healthcheck ln -s ~/dev/wp-healthcheck /path/to/wp/wp-content/plugins/att-site-healthcheck
``` ```
Then activate from *Plugins*. Settings appear under *Tools → Site Healthcheck*. Then activate from *Plugins*. Settings appear under *Tools → Site Healthcheck*.
@@ -28,7 +28,7 @@ Each step is a single file under `includes/steps/` named `<order>-<slug>.php`.
```php ```php
<?php <?php
// includes/steps/45-staging.php // includes/steps/45-staging.php
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'staging'; } public function id(): string { return 'staging'; }
public function title(): string { return 'Step 4.5 — Verify Staging Sync'; } public function title(): string { return 'Step 4.5 — Verify Staging Sync'; }
public function sub_items(): array { public function sub_items(): array {
@@ -40,7 +40,7 @@ return new class extends WPH_Step {
- **Add:** drop a new file. - **Add:** drop a new file.
- **Remove:** delete the file. - **Remove:** delete the file.
- **Reorder:** rename the numeric prefix (steps are loaded in `natsort` order). - **Reorder:** rename the numeric prefix (steps are loaded in `natsort` order).
- **Conditionally drop on one install:** use the `wph_steps` filter to `unset` the step by id. - **Conditionally drop on one install:** use the `att_hc_steps` filter to `unset` the step by id.
Stable string IDs (returned by `id()`) are what's stored in session data, so renaming a file does not break in-progress sessions as long as the id stays the same. Stable string IDs (returned by `id()`) are what's stored in session data, so renaming a file does not break in-progress sessions as long as the id stays the same.

33
att-site-healthcheck.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* Plugin Name: ATT Site Healthcheck
* Description: Walks a technician through a structured WordPress site healthcheck. Steps are drop-in PHP files so adding/removing one is a single file change.
* Version: 0.1.0
* Author: Steve Hanlon
* License: GPL-2.0-or-later
* Requires PHP: 7.4
*
* Internal/agency tool — not distributed via WP.org. See beads decision hc-5ix.26.
*/
if (!defined('ABSPATH')) {
exit;
}
define('ATT_HC_VERSION', '0.1.0');
define('ATT_HC_PLUGIN_FILE', __FILE__);
define('ATT_HC_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('ATT_HC_PLUGIN_URL', plugin_dir_url(__FILE__));
define('ATT_HC_OPT_SESSION', 'att_hc_session');
require_once ATT_HC_PLUGIN_DIR . 'includes/class-att-hc-step.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/class-att-hc-steps.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/class-att-hc-session.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/recovery-bootstrap.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/recovery-installer.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/admin-page.php';
require_once ATT_HC_PLUGIN_DIR . 'includes/report.php';
add_action('plugins_loaded', function () {
ATT_HC_Steps::instance()->discover(ATT_HC_PLUGIN_DIR . 'includes/steps/');
});

View File

@@ -1,195 +1,195 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
add_action('admin_menu', 'wph_register_menu'); add_action('admin_menu', 'att_hc_register_menu');
add_action('admin_post_wph_start', 'wph_handle_start'); add_action('admin_post_att_hc_start', 'att_hc_handle_start');
add_action('admin_post_wph_save_step', 'wph_handle_save_step'); add_action('admin_post_att_hc_save_step', 'att_hc_handle_save_step');
add_action('admin_post_wph_finish', 'wph_handle_finish'); add_action('admin_post_att_hc_finish', 'att_hc_handle_finish');
add_action('admin_post_wph_discard', 'wph_handle_discard'); add_action('admin_post_att_hc_discard', 'att_hc_handle_discard');
add_action('admin_post_wph_download_report', 'wph_handle_download_report'); add_action('admin_post_att_hc_download_report', 'att_hc_handle_download_report');
add_action('admin_post_wph_refresh_checks', 'wph_handle_refresh_checks'); add_action('admin_post_att_hc_refresh_checks', 'att_hc_handle_refresh_checks');
add_action('admin_post_wph_download_html', 'wph_handle_download_html'); add_action('admin_post_att_hc_download_html', 'att_hc_handle_download_html');
add_action('admin_post_wph_email_report', 'wph_handle_email_report'); add_action('admin_post_att_hc_email_report', 'att_hc_handle_email_report');
add_action('admin_post_wph_step_action', 'wph_handle_step_action'); add_action('admin_post_att_hc_step_action', 'att_hc_handle_step_action');
add_action('admin_post_wph_recovery_install', 'wph_handle_recovery_install'); add_action('admin_post_att_hc_recovery_install', 'att_hc_handle_recovery_install');
add_action('admin_post_wph_save_settings', 'wph_handle_save_settings'); add_action('admin_post_att_hc_save_settings', 'att_hc_handle_save_settings');
add_action('admin_enqueue_scripts', 'wph_enqueue_assets'); add_action('admin_enqueue_scripts', 'att_hc_enqueue_assets');
function wph_register_menu(): void { function att_hc_register_menu(): void {
add_management_page( add_management_page(
'Site Healthcheck', 'Site Healthcheck',
'Site Healthcheck', 'Site Healthcheck',
'manage_options', 'manage_options',
'site-healthcheck', 'att-site-healthcheck',
'wph_render_admin_page' 'att_hc_render_admin_page'
); );
add_submenu_page( add_submenu_page(
null, // hidden — reachable via direct URL null, // hidden — reachable via direct URL
'Site Healthcheck Settings', 'Site Healthcheck Settings',
'Site Healthcheck Settings', 'Site Healthcheck Settings',
'manage_options', 'manage_options',
'site-healthcheck-settings', 'att-site-healthcheck-settings',
'wph_render_settings_page' 'att_hc_render_settings_page'
); );
} }
function wph_enqueue_assets($hook): void { function att_hc_enqueue_assets($hook): void {
if ($hook !== 'tools_page_site-healthcheck') return; if ($hook !== 'tools_page_att-site-healthcheck') return;
wp_register_style('wph-admin', false); wp_register_style('att-hc-admin', false);
wp_enqueue_style('wph-admin'); wp_enqueue_style('att-hc-admin');
wp_add_inline_style('wph-admin', wph_inline_css()); wp_add_inline_style('att-hc-admin', att_hc_inline_css());
} }
function wph_inline_css(): string { function att_hc_inline_css(): string {
return ' return '
.wph-card { background:#fff; border:1px solid #c3c4c7; border-radius:6px; padding:1rem 1.25rem; margin-bottom:1rem; } .att-hc-card { background:#fff; border:1px solid #c3c4c7; border-radius:6px; padding:1rem 1.25rem; margin-bottom:1rem; }
.wph-card h2 { margin-top:0; } .att-hc-card h2 { margin-top:0; }
.wph-step-status { display:inline-block; padding:.1rem .55rem; border-radius:10px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; } .att-hc-step-status { display:inline-block; padding:.1rem .55rem; border-radius:10px; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; }
.wph-status-not_started { background:#f0f0f1; color:#646970; } .att-hc-status-not_started { background:#f0f0f1; color:#646970; }
.wph-status-done { background:#def7e3; color:#155724; } .att-hc-status-done { background:#def7e3; color:#155724; }
.wph-status-skipped { background:#fff3cd; color:#856404; } .att-hc-status-skipped { background:#fff3cd; color:#856404; }
.wph-status-blocked { background:#fbeae8; color:#721c24; } .att-hc-status-blocked { background:#fbeae8; color:#721c24; }
.wph-status-n_a { background:#e2e3e5; color:#41464b; } .att-hc-status-n_a { background:#e2e3e5; color:#41464b; }
.wph-step { padding:1rem 1.25rem; border:1px solid #dcdcde; border-radius:6px; margin-bottom:.75rem; background:#fff; } .att-hc-step { padding:1rem 1.25rem; border:1px solid #dcdcde; border-radius:6px; margin-bottom:.75rem; background:#fff; }
.wph-step header { display:flex; justify-content:space-between; align-items:center; gap:1rem; margin-bottom:.5rem; } .att-hc-step header { display:flex; justify-content:space-between; align-items:center; gap:1rem; margin-bottom:.5rem; }
.wph-step header h2 { margin:0; font-size:1.1rem; } .att-hc-step header h2 { margin:0; font-size:1.1rem; }
.wph-sub-items { margin:.5rem 0 .75rem 1.25rem; padding:0; } .att-hc-sub-items { margin:.5rem 0 .75rem 1.25rem; padding:0; }
.wph-sub-items li { margin:.15rem 0; } .att-hc-sub-items li { margin:.15rem 0; }
.wph-watch-outs { background:#fff8e1; border-left:3px solid #f5b800; padding:.4rem .8rem; margin:.5rem 0; font-size:.92em; } .att-hc-watch-outs { background:#fff8e1; border-left:3px solid #f5b800; padding:.4rem .8rem; margin:.5rem 0; font-size:.92em; }
.wph-watch-outs strong { display:block; margin-bottom:.2rem; } .att-hc-watch-outs strong { display:block; margin-bottom:.2rem; }
.wph-escalation { background:#fbeae8; border-left:3px solid #c0392b; padding:.4rem .8rem; margin:.5rem 0; font-weight:500; } .att-hc-escalation { background:#fbeae8; border-left:3px solid #c0392b; padding:.4rem .8rem; margin:.5rem 0; font-weight:500; }
.wph-step textarea { width:100%; min-height:60px; } .att-hc-step textarea { width:100%; min-height:60px; }
.wph-progress { font-weight:600; } .att-hc-progress { font-weight:600; }
.wph-ok { color:#155724; } .att-hc-ok { color:#155724; }
.wph-warn { color:#856404; } .att-hc-warn { color:#856404; }
.wph-bad { color:#721c24; } .att-hc-bad { color:#721c24; }
.wph-bootstrap-panel { padding:.6rem 1rem; background:#f6f7f7; border:1px solid #dcdcde; border-radius:6px; margin-bottom:.75rem; } .att-hc-bootstrap-panel { padding:.6rem 1rem; background:#f6f7f7; border:1px solid #dcdcde; border-radius:6px; margin-bottom:.75rem; }
.wph-bootstrap-panel h3 { margin:0 0 .35rem; font-size:1rem; } .att-hc-bootstrap-panel h3 { margin:0 0 .35rem; font-size:1rem; }
.wph-actions { display:flex; gap:.5rem; align-items:center; margin-top:.4rem; } .att-hc-actions { display:flex; gap:.5rem; align-items:center; margin-top:.4rem; }
.wph-autocheck { background:#f6f7f7; border:1px solid #dcdcde; border-radius:6px; padding:.6rem 1rem; margin:.6rem 0; } .att-hc-autocheck { background:#f6f7f7; border:1px solid #dcdcde; border-radius:6px; padding:.6rem 1rem; margin:.6rem 0; }
.wph-autocheck header { display:flex; justify-content:space-between; align-items:center; margin:0 0 .4rem; } .att-hc-autocheck header { display:flex; justify-content:space-between; align-items:center; margin:0 0 .4rem; }
.wph-autocheck header h3 { margin:0; font-size:.95rem; } .att-hc-autocheck header h3 { margin:0; font-size:.95rem; }
.wph-autocheck table { width:100%; border-collapse:collapse; } .att-hc-autocheck table { width:100%; border-collapse:collapse; }
.wph-autocheck td { padding:.25rem .4rem; vertical-align:top; border-bottom:1px solid #f0f0f1; } .att-hc-autocheck td { padding:.25rem .4rem; vertical-align:top; border-bottom:1px solid #f0f0f1; }
.wph-autocheck tr:last-child td { border-bottom:0; } .att-hc-autocheck tr:last-child td { border-bottom:0; }
.wph-autocheck .lvl { width:1.4rem; text-align:center; font-weight:600; } .att-hc-autocheck .lvl { width:1.4rem; text-align:center; font-weight:600; }
.wph-autocheck .lvl-ok { color:#1a8917; } .att-hc-autocheck .lvl-ok { color:#1a8917; }
.wph-autocheck .lvl-warn { color:#b07a00; } .att-hc-autocheck .lvl-warn { color:#b07a00; }
.wph-autocheck .lvl-bad { color:#c0392b; } .att-hc-autocheck .lvl-bad { color:#c0392b; }
.wph-autocheck .lvl-info { color:#646970; } .att-hc-autocheck .lvl-info { color:#646970; }
.wph-autocheck .label { font-weight:600; } .att-hc-autocheck .label { font-weight:600; }
.wph-autocheck .value { color:#1d1d1f; } .att-hc-autocheck .value { color:#1d1d1f; }
.wph-autocheck .detail { color:#646970; font-size:.9em; } .att-hc-autocheck .detail { color:#646970; font-size:.9em; }
.wph-checked-at { color:#646970; font-size:.85em; } .att-hc-checked-at { color:#646970; font-size:.85em; }
.wph-layout { display:grid; grid-template-columns: 220px 1fr; gap:1rem; } .att-hc-layout { display:grid; grid-template-columns: 220px 1fr; gap:1rem; }
.wph-sidebar { position:sticky; top:36px; align-self:start; max-height: calc(100vh - 60px); overflow:auto; } .att-hc-sidebar { position:sticky; top:36px; align-self:start; max-height: calc(100vh - 60px); overflow:auto; }
.wph-sidebar .wph-card { padding:.75rem 1rem; } .att-hc-sidebar .att-hc-card { padding:.75rem 1rem; }
.wph-sidebar h3 { margin:0 0 .4rem; font-size:.9rem; text-transform:uppercase; letter-spacing:.04em; color:#646970; } .att-hc-sidebar h3 { margin:0 0 .4rem; font-size:.9rem; text-transform:uppercase; letter-spacing:.04em; color:#646970; }
.wph-sidebar ol { margin:0; padding:0; list-style:none; } .att-hc-sidebar ol { margin:0; padding:0; list-style:none; }
.wph-sidebar li { padding:.18rem 0; } .att-hc-sidebar li { padding:.18rem 0; }
.wph-sidebar a { text-decoration:none; } .att-hc-sidebar a { text-decoration:none; }
.wph-sidebar .dot { display:inline-block; width:.75rem; height:.75rem; border-radius:50%; margin-right:.45rem; background:#dcdcde; vertical-align:middle; box-shadow:0 0 0 1px rgba(0,0,0,.08) inset; } .att-hc-sidebar .dot { display:inline-block; width:.75rem; height:.75rem; border-radius:50%; margin-right:.45rem; background:#dcdcde; vertical-align:middle; box-shadow:0 0 0 1px rgba(0,0,0,.08) inset; }
.wph-sidebar .dot-done { background:#16a34a; box-shadow:0 0 0 1px rgba(0,0,0,.15) inset; } .att-hc-sidebar .dot-done { background:#16a34a; box-shadow:0 0 0 1px rgba(0,0,0,.15) inset; }
.wph-sidebar .dot-skipped { background:#f59e0b; } .att-hc-sidebar .dot-skipped { background:#f59e0b; }
.wph-sidebar .dot-blocked { background:#dc2626; } .att-hc-sidebar .dot-blocked { background:#dc2626; }
.wph-sidebar .dot-n_a { background:#9ca3af; } .att-hc-sidebar .dot-n_a { background:#9ca3af; }
.wph-sidebar li:has(.dot-done) a { color:#15803d; } .att-hc-sidebar li:has(.dot-done) a { color:#15803d; }
.wph-diff { background:#eef4fb; border:1px solid #cfe0f3; padding:.6rem 1rem; border-radius:6px; margin:.5rem 0; font-size:.9em; } .att-hc-diff { background:#eef4fb; border:1px solid #cfe0f3; padding:.6rem 1rem; border-radius:6px; margin:.5rem 0; font-size:.9em; }
.wph-diff strong { display:inline-block; margin-right:.4rem; } .att-hc-diff strong { display:inline-block; margin-right:.4rem; }
.wph-diff .delta-new { color:#c0392b; } .att-hc-diff .delta-new { color:#c0392b; }
.wph-diff .delta-resolved { color:#1a8917; } .att-hc-diff .delta-resolved { color:#1a8917; }
.wph-diff .delta-changed { color:#b07a00; } .att-hc-diff .delta-changed { color:#b07a00; }
@media (max-width: 980px) { .wph-layout { grid-template-columns: 1fr; } .wph-sidebar { position: static; max-height: none; } } @media (max-width: 980px) { .att-hc-layout { grid-template-columns: 1fr; } .att-hc-sidebar { position: static; max-height: none; } }
'; ';
} }
function wph_render_admin_page(): void { function att_hc_render_admin_page(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
echo '<div class="wrap">'; echo '<div class="wrap">';
echo '<h1>Site Healthcheck</h1>'; echo '<h1>Site Healthcheck</h1>';
if ($msg = get_transient('wph_install_message')) { if ($msg = get_transient('att_hc_install_message')) {
delete_transient('wph_install_message'); delete_transient('att_hc_install_message');
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($msg) . '</p></div>'; echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($msg) . '</p></div>';
} }
if (!$session) { if (!$session) {
wph_render_start_panel(); att_hc_render_start_panel();
echo '</div>'; echo '</div>';
return; return;
} }
if ($session->is_finished()) { if ($session->is_finished()) {
wph_render_finished_panel($session); att_hc_render_finished_panel($session);
echo '</div>'; echo '</div>';
return; return;
} }
wph_render_active_session($session); att_hc_render_active_session($session);
echo '</div>'; echo '</div>';
} }
function wph_render_start_panel(): void { function att_hc_render_start_panel(): void {
?> ?>
<div class="wph-card"> <div class="att-hc-card">
<h2>Start a healthcheck</h2> <h2>Start a healthcheck</h2>
<p>This will create a new in-progress session for <code><?php echo esc_html(get_site_url()); ?></code>. One session per site at a time.</p> <p>This will create a new in-progress session for <code><?php echo esc_html(get_site_url()); ?></code>. One session per site at a time.</p>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<?php wp_nonce_field('wph_start'); ?> <?php wp_nonce_field('att_hc_start'); ?>
<input type="hidden" name="action" value="wph_start"> <input type="hidden" name="action" value="att_hc_start">
<button class="button button-primary">Start new healthcheck</button> <button class="button button-primary">Start new healthcheck</button>
</form> </form>
</div> </div>
<?php <?php
WPH_Recovery_Bootstrap::render_status(); ATT_HC_Recovery_Bootstrap::render_status();
} }
function wph_render_active_session(WPH_Session $session): void { function att_hc_render_active_session(ATT_HC_Session $session): void {
$progress = $session->progress(); $progress = $session->progress();
$tech = get_userdata($session->technician_id()); $tech = get_userdata($session->technician_id());
?> ?>
<div class="wph-card"> <div class="att-hc-card">
<p> <p>
<strong>Session:</strong> <code><?php echo esc_html($session->id()); ?></code> · <strong>Session:</strong> <code><?php echo esc_html($session->id()); ?></code> ·
<strong>Started:</strong> <?php echo esc_html(date('Y-m-d H:i', $session->started_at())); ?> · <strong>Started:</strong> <?php echo esc_html(date('Y-m-d H:i', $session->started_at())); ?> ·
<strong>Technician:</strong> <?php echo esc_html($tech ? $tech->display_name : '#' . $session->technician_id()); ?> · <strong>Technician:</strong> <?php echo esc_html($tech ? $tech->display_name : '#' . $session->technician_id()); ?> ·
<span class="wph-progress"><?php echo (int) $progress['done']; ?> / <?php echo (int) $progress['total']; ?> steps</span> <span class="att-hc-progress"><?php echo (int) $progress['done']; ?> / <?php echo (int) $progress['total']; ?> steps</span>
</p> </p>
<p>WP <code><?php echo esc_html($session->wp_version()); ?></code> · PHP <code><?php echo esc_html($session->php_version()); ?></code> · Site <code><?php echo esc_html($session->site_url()); ?></code></p> <p>WP <code><?php echo esc_html($session->wp_version()); ?></code> · PHP <code><?php echo esc_html($session->php_version()); ?></code> · Site <code><?php echo esc_html($session->site_url()); ?></code></p>
<div class="wph-actions"> <div class="att-hc-actions">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Mark this healthcheck as finished?');" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Mark this healthcheck as finished?');" style="display:inline">
<?php wp_nonce_field('wph_finish'); ?> <?php wp_nonce_field('att_hc_finish'); ?>
<input type="hidden" name="action" value="wph_finish"> <input type="hidden" name="action" value="att_hc_finish">
<button class="button button-primary">Finish &amp; generate report</button> <button class="button button-primary">Finish &amp; generate report</button>
</form> </form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Discard this in-progress session? All notes will be lost.');" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Discard this in-progress session? All notes will be lost.');" style="display:inline">
<?php wp_nonce_field('wph_discard'); ?> <?php wp_nonce_field('att_hc_discard'); ?>
<input type="hidden" name="action" value="wph_discard"> <input type="hidden" name="action" value="att_hc_discard">
<button class="button button-link-delete">Discard</button> <button class="button button-link-delete">Discard</button>
</form> </form>
</div> </div>
</div> </div>
<?php <?php
WPH_Recovery_Bootstrap::render_status(); ATT_HC_Recovery_Bootstrap::render_status();
wph_render_blocked_summary($session); att_hc_render_blocked_summary($session);
wph_render_diff_summary($session); att_hc_render_diff_summary($session);
echo '<div class="wph-layout">'; echo '<div class="att-hc-layout">';
wph_render_sidebar($session); att_hc_render_sidebar($session);
echo '<div>'; echo '<div>';
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
wph_render_step_card($session, $step); att_hc_render_step_card($session, $step);
} }
echo '</div></div>'; echo '</div></div>';
} }
function wph_render_sidebar(WPH_Session $session): void { function att_hc_render_sidebar(ATT_HC_Session $session): void {
echo '<aside class="wph-sidebar"><div class="wph-card"><h3>Steps</h3><ol>'; echo '<aside class="att-hc-sidebar"><div class="att-hc-card"><h3>Steps</h3><ol>';
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
$cls = 'dot-' . $state['status']; $cls = 'dot-' . $state['status'];
printf( printf(
@@ -202,16 +202,16 @@ function wph_render_sidebar(WPH_Session $session): void {
echo '</ol></div></aside>'; echo '</ol></div></aside>';
} }
function wph_render_blocked_summary(WPH_Session $session): void { function att_hc_render_blocked_summary(ATT_HC_Session $session): void {
$blocked = []; $blocked = [];
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
if ($state['status'] === WPH_Session::STATUS_BLOCKED) { if ($state['status'] === ATT_HC_Session::STATUS_BLOCKED) {
$blocked[] = ['step' => $step, 'state' => $state]; $blocked[] = ['step' => $step, 'state' => $state];
} }
} }
if (!$blocked) return; if (!$blocked) return;
echo '<div class="wph-card" style="border-left:4px solid #c0392b">'; echo '<div class="att-hc-card" style="border-left:4px solid #c0392b">';
echo '<h2 style="color:#721c24">Stop &amp; escalate</h2>'; echo '<h2 style="color:#721c24">Stop &amp; escalate</h2>';
echo '<p>The following steps are blocked. Resolve or escalate before continuing:</p>'; echo '<p>The following steps are blocked. Resolve or escalate before continuing:</p>';
echo '<ul style="margin-left:1.25rem">'; echo '<ul style="margin-left:1.25rem">';
@@ -225,13 +225,13 @@ function wph_render_blocked_summary(WPH_Session $session): void {
echo '</ul></div>'; echo '</ul></div>';
} }
function wph_render_diff_summary(WPH_Session $session): void { function att_hc_render_diff_summary(ATT_HC_Session $session): void {
$prev = WPH_Session::previous(); $prev = ATT_HC_Session::previous();
if (!$prev) return; if (!$prev) return;
// Aggregate findings by step+id from each session // Aggregate findings by step+id from each session
$current_idx = []; $current_idx = [];
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$r = $session->get_autocheck($step->id()); $r = $session->get_autocheck($step->id());
if (!$r) continue; if (!$r) continue;
foreach ($r['findings'] as $f) { foreach ($r['findings'] as $f) {
@@ -239,7 +239,7 @@ function wph_render_diff_summary(WPH_Session $session): void {
} }
} }
$prev_idx = []; $prev_idx = [];
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$r = $prev->get_autocheck($step->id()); $r = $prev->get_autocheck($step->id());
if (!$r) continue; if (!$r) continue;
foreach ($r['findings'] as $f) { foreach ($r['findings'] as $f) {
@@ -260,35 +260,35 @@ function wph_render_diff_summary(WPH_Session $session): void {
} }
} }
if (!$new && !$resolved && !$changed) return; if (!$new && !$resolved && !$changed) return;
echo '<div class="wph-diff"><strong>Δ vs. previous session</strong> (finished ' . esc_html(date('Y-m-d', (int) $prev->finished_at())) . ')'; echo '<div class="att-hc-diff"><strong>Δ vs. previous session</strong> (finished ' . esc_html(date('Y-m-d', (int) $prev->finished_at())) . ')';
if ($new) echo ' · <span class="delta-new">' . count($new) . ' new issue(s)</span>'; if ($new) echo ' · <span class="delta-new">' . count($new) . ' new issue(s)</span>';
if ($resolved) echo ' · <span class="delta-resolved">' . count($resolved) . ' resolved</span>'; if ($resolved) echo ' · <span class="delta-resolved">' . count($resolved) . ' resolved</span>';
if ($changed) echo ' · <span class="delta-changed">' . count($changed) . ' changed</span>'; if ($changed) echo ' · <span class="delta-changed">' . count($changed) . ' changed</span>';
echo '</div>'; echo '</div>';
} }
function wph_render_step_card(WPH_Session $session, WPH_Step $step): void { function att_hc_render_step_card(ATT_HC_Session $session, ATT_HC_Step $step): void {
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
$status = $state['status']; $status = $state['status'];
$notes = $state['notes']; $notes = $state['notes'];
?> ?>
<div class="wph-step" id="step-<?php echo esc_attr($step->id()); ?>"> <div class="att-hc-step" id="step-<?php echo esc_attr($step->id()); ?>">
<header> <header>
<h2><?php echo esc_html($step->title()); ?></h2> <h2><?php echo esc_html($step->title()); ?></h2>
<span class="wph-step-status wph-status-<?php echo esc_attr($status); ?>"><?php echo esc_html(str_replace('_', ' ', $status)); ?></span> <span class="att-hc-step-status att-hc-status-<?php echo esc_attr($status); ?>"><?php echo esc_html(str_replace('_', ' ', $status)); ?></span>
</header> </header>
<?php if ($blurb = $step->blurb()): ?> <?php if ($blurb = $step->blurb()): ?>
<p><?php echo esc_html($blurb); ?></p> <p><?php echo esc_html($blurb); ?></p>
<?php endif; ?> <?php endif; ?>
<?php if ($items = $step->sub_items()): ?> <?php if ($items = $step->sub_items()): ?>
<ul class="wph-sub-items"> <ul class="att-hc-sub-items">
<?php foreach ($items as $item): ?> <?php foreach ($items as $item): ?>
<li><?php echo esc_html($item); ?></li> <li><?php echo esc_html($item); ?></li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<?php endif; ?> <?php endif; ?>
<?php if ($watch = $step->watch_outs()): ?> <?php if ($watch = $step->watch_outs()): ?>
<div class="wph-watch-outs"> <div class="att-hc-watch-outs">
<strong>Watch out for:</strong> <strong>Watch out for:</strong>
<ul style="margin:.2rem 0 0 1rem;"> <ul style="margin:.2rem 0 0 1rem;">
<?php foreach ($watch as $w): ?> <?php foreach ($watch as $w): ?>
@@ -297,27 +297,27 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
</ul> </ul>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($status === WPH_Session::STATUS_BLOCKED && ($esc = $step->escalation())): ?> <?php if ($status === ATT_HC_Session::STATUS_BLOCKED && ($esc = $step->escalation())): ?>
<div class="wph-escalation"><?php echo esc_html($esc); ?></div> <div class="att-hc-escalation"><?php echo esc_html($esc); ?></div>
<?php endif; ?> <?php endif; ?>
<?php <?php
// Inner forms (autocheck refresh, step-specific extras like email send) // Inner forms (autocheck refresh, step-specific extras like email send)
// are rendered as siblings of the save form — never nested. Nested forms // are rendered as siblings of the save form — never nested. Nested forms
// are invalid HTML; browsers drop the outer form's submit silently. // are invalid HTML; browsers drop the outer form's submit silently.
wph_render_autocheck($session, $step); att_hc_render_autocheck($session, $step);
$step->render_extra($session->data()); $step->render_extra($session->data());
?> ?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="wph-step-save"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="att-hc-step-save">
<?php wp_nonce_field('wph_save_step_' . $step->id()); ?> <?php wp_nonce_field('att_hc_save_step_' . $step->id()); ?>
<input type="hidden" name="action" value="wph_save_step"> <input type="hidden" name="action" value="att_hc_save_step">
<input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>"> <input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>">
<p> <p>
<label> <label>
<strong>Status:</strong> <strong>Status:</strong>
<select name="status"> <select name="status">
<?php foreach (WPH_Session::VALID_STATUSES as $s): ?> <?php foreach (ATT_HC_Session::VALID_STATUSES as $s): ?>
<option value="<?php echo esc_attr($s); ?>" <?php selected($status, $s); ?>><?php echo esc_html(str_replace('_', ' ', $s)); ?></option> <option value="<?php echo esc_attr($s); ?>" <?php selected($status, $s); ?>><?php echo esc_html(str_replace('_', ' ', $s)); ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
@@ -338,97 +338,97 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
<?php <?php
} }
function wph_render_finished_panel(WPH_Session $session): void { function att_hc_render_finished_panel(ATT_HC_Session $session): void {
$report = wph_build_markdown_report($session); $report = att_hc_build_markdown_report($session);
$admin_email = get_option('admin_email'); $admin_email = get_option('admin_email');
?> ?>
<div class="wph-card"> <div class="att-hc-card">
<h2>Healthcheck finished</h2> <h2>Healthcheck finished</h2>
<p>Started <?php echo esc_html(date('Y-m-d H:i', $session->started_at())); ?> · Finished <?php echo esc_html(date('Y-m-d H:i', (int) $session->finished_at())); ?></p> <p>Started <?php echo esc_html(date('Y-m-d H:i', $session->started_at())); ?> · Finished <?php echo esc_html(date('Y-m-d H:i', (int) $session->finished_at())); ?></p>
<div class="wph-actions"> <div class="att-hc-actions">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline">
<?php wp_nonce_field('wph_download_report'); ?> <?php wp_nonce_field('att_hc_download_report'); ?>
<input type="hidden" name="action" value="wph_download_report"> <input type="hidden" name="action" value="att_hc_download_report">
<button class="button button-primary">Download Markdown</button> <button class="button button-primary">Download Markdown</button>
</form> </form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline">
<?php wp_nonce_field('wph_download_html'); ?> <?php wp_nonce_field('att_hc_download_html'); ?>
<input type="hidden" name="action" value="wph_download_html"> <input type="hidden" name="action" value="att_hc_download_html">
<button class="button">Download HTML</button> <button class="button">Download HTML</button>
</form> </form>
<button class="button" onclick="navigator.clipboard.writeText(document.getElementById('wph-report-md').textContent);this.textContent='Copied!';setTimeout(()=>this.textContent='Copy Markdown',2000)">Copy Markdown</button> <button class="button" onclick="navigator.clipboard.writeText(document.getElementById('att-hc-report-md').textContent);this.textContent='Copied!';setTimeout(()=>this.textContent='Copy Markdown',2000)">Copy Markdown</button>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline">
<?php wp_nonce_field('wph_email_report'); ?> <?php wp_nonce_field('att_hc_email_report'); ?>
<input type="hidden" name="action" value="wph_email_report"> <input type="hidden" name="action" value="att_hc_email_report">
<input type="email" name="to" placeholder="recipient@example.com" value="<?php echo esc_attr($admin_email); ?>" required style="min-width:14em"> <input type="email" name="to" placeholder="recipient@example.com" value="<?php echo esc_attr($admin_email); ?>" required style="min-width:14em">
<button class="button">Email report</button> <button class="button">Email report</button>
</form> </form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Discard this finished session?');" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Discard this finished session?');" style="display:inline">
<?php wp_nonce_field('wph_discard'); ?> <?php wp_nonce_field('att_hc_discard'); ?>
<input type="hidden" name="action" value="wph_discard"> <input type="hidden" name="action" value="att_hc_discard">
<button class="button button-link-delete">Discard</button> <button class="button button-link-delete">Discard</button>
</form> </form>
</div> </div>
</div> </div>
<div class="wph-card"> <div class="att-hc-card">
<h2>Report preview</h2> <h2>Report preview</h2>
<pre id="wph-report-md" style="white-space:pre-wrap;background:#f6f7f7;padding:1rem;border-radius:6px;max-height:30em;overflow:auto"><?php echo esc_html($report); ?></pre> <pre id="att-hc-report-md" style="white-space:pre-wrap;background:#f6f7f7;padding:1rem;border-radius:6px;max-height:30em;overflow:auto"><?php echo esc_html($report); ?></pre>
</div> </div>
<?php <?php
} }
// --- Handlers ---------------------------------------------------------------- // --- Handlers ----------------------------------------------------------------
function wph_handle_start(): void { function att_hc_handle_start(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_start'); check_admin_referer('att_hc_start');
WPH_Session::start(get_current_user_id()); ATT_HC_Session::start(get_current_user_id());
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck'));
exit; exit;
} }
function wph_handle_save_step(): void { function att_hc_handle_save_step(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
$step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : ''; $step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : '';
check_admin_referer('wph_save_step_' . $step_id); check_admin_referer('att_hc_save_step_' . $step_id);
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session || $session->is_finished()) wp_die('No active session.'); if (!$session || $session->is_finished()) wp_die('No active session.');
if (!WPH_Steps::instance()->get($step_id)) wp_die('Unknown step.'); if (!ATT_HC_Steps::instance()->get($step_id)) wp_die('Unknown step.');
$status = isset($_POST['status']) ? sanitize_key((string) $_POST['status']) : WPH_Session::STATUS_NOT_STARTED; $status = isset($_POST['status']) ? sanitize_key((string) $_POST['status']) : ATT_HC_Session::STATUS_NOT_STARTED;
$notes = isset($_POST['notes']) ? wp_unslash((string) $_POST['notes']) : ''; $notes = isset($_POST['notes']) ? wp_unslash((string) $_POST['notes']) : '';
$session->update_step($step_id, $status, $notes); $session->update_step($step_id, $status, $notes);
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck#step-' . rawurlencode($step_id)));
exit; exit;
} }
function wph_handle_finish(): void { function att_hc_handle_finish(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_finish'); check_admin_referer('att_hc_finish');
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session) wp_die('No active session.'); if (!$session) wp_die('No active session.');
$session->finish(); $session->finish();
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck'));
exit; exit;
} }
function wph_handle_discard(): void { function att_hc_handle_discard(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_discard'); check_admin_referer('att_hc_discard');
WPH_Session::discard(); ATT_HC_Session::discard();
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck')); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck'));
exit; exit;
} }
function wph_render_autocheck(WPH_Session $session, WPH_Step $step): void { function att_hc_render_autocheck(ATT_HC_Session $session, ATT_HC_Step $step): void {
if (!$step->has_autocheck()) return; if (!$step->has_autocheck()) return;
$result = $session->get_autocheck($step->id()); $result = $session->get_autocheck($step->id());
?> ?>
<div class="wph-autocheck"> <div class="att-hc-autocheck">
<header> <header>
<h3>Automated checks</h3> <h3>Automated checks</h3>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline">
<?php wp_nonce_field('wph_refresh_checks_' . $step->id()); ?> <?php wp_nonce_field('att_hc_refresh_checks_' . $step->id()); ?>
<input type="hidden" name="action" value="wph_refresh_checks"> <input type="hidden" name="action" value="att_hc_refresh_checks">
<input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>"> <input type="hidden" name="step" value="<?php echo esc_attr($step->id()); ?>">
<button class="button button-small"><?php echo $result ? 'Refresh' : 'Run checks'; ?></button> <button class="button button-small"><?php echo $result ? 'Refresh' : 'Run checks'; ?></button>
</form> </form>
@@ -448,59 +448,59 @@ function wph_render_autocheck(WPH_Session $session, WPH_Step $step): void {
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</table> </table>
<p class="wph-checked-at">Checked <?php echo esc_html(human_time_diff((int) $result['checked_at'], time())); ?> ago (<?php echo esc_html(date('Y-m-d H:i', (int) $result['checked_at'])); ?>)</p> <p class="att-hc-checked-at">Checked <?php echo esc_html(human_time_diff((int) $result['checked_at'], time())); ?> ago (<?php echo esc_html(date('Y-m-d H:i', (int) $result['checked_at'])); ?>)</p>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php <?php
} }
function wph_render_settings_page(): void { function att_hc_render_settings_page(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
$c = WPH_Recovery_Installer::config(); $c = ATT_HC_Recovery_Installer::config();
$o = WPH_Recovery_Installer::config_origin(); $o = ATT_HC_Recovery_Installer::config_origin();
$saved = isset($_GET['wph_saved']); $saved = isset($_GET['att_hc_saved']);
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Site Healthcheck — Settings</h1> <h1>Site Healthcheck — Settings</h1>
<p><a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck')); ?>">&larr; Back to healthcheck</a></p> <p><a href="<?php echo esc_url(admin_url('tools.php?page=att-site-healthcheck')); ?>">&larr; Back to healthcheck</a></p>
<?php if ($saved): ?><div class="notice notice-success is-dismissible"><p>Saved.</p></div><?php endif; ?> <?php if ($saved): ?><div class="notice notice-success is-dismissible"><p>Saved.</p></div><?php endif; ?>
<div class="wph-card"> <div class="att-hc-card">
<h2>Recovery plugin source (Gitea)</h2> <h2>Recovery plugin source (Gitea)</h2>
<p>One-click install pulls <code>site-recovery</code> 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.</p> <p>One-click install pulls <code>site-recovery</code> 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.</p>
<p>Each field can be set via a constant in <code>wp-config.php</code> (then it takes precedence and the field below is locked).</p> <p>Each field can be set via a constant in <code>wp-config.php</code> (then it takes precedence and the field below is locked).</p>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<?php wp_nonce_field('wph_save_settings'); ?> <?php wp_nonce_field('att_hc_save_settings'); ?>
<input type="hidden" name="action" value="wph_save_settings"> <input type="hidden" name="action" value="att_hc_save_settings">
<table class="form-table"> <table class="form-table">
<tr> <tr>
<th><label for="wph-host">Gitea host</label></th> <th><label for="att-hc-host">Gitea host</label></th>
<td> <td>
<input id="wph-host" type="url" name="host" value="<?php echo esc_attr($c['host']); ?>" class="regular-text" placeholder="https://git.example.com" <?php disabled($o['host']); ?>> <input id="att-hc-host" type="url" name="host" value="<?php echo esc_attr($c['host']); ?>" class="regular-text" placeholder="https://git.example.com" <?php disabled($o['host']); ?>>
<?php if ($o['host']): ?><p class="description">Set via <code>WPH_GITEA_HOST</code> constant.</p><?php endif; ?> <?php if ($o['host']): ?><p class="description">Set via <code>ATT_HC_GITEA_HOST</code> constant.</p><?php endif; ?>
</td> </td>
</tr> </tr>
<tr> <tr>
<th><label for="wph-owner">Owner</label></th> <th><label for="att-hc-owner">Owner</label></th>
<td> <td>
<input id="wph-owner" type="text" name="owner" value="<?php echo esc_attr($c['owner']); ?>" class="regular-text" placeholder="steve" <?php disabled($o['owner']); ?>> <input id="att-hc-owner" type="text" name="owner" value="<?php echo esc_attr($c['owner']); ?>" class="regular-text" placeholder="steve" <?php disabled($o['owner']); ?>>
<?php if ($o['owner']): ?><p class="description">Set via <code>WPH_GITEA_OWNER</code> constant.</p><?php endif; ?> <?php if ($o['owner']): ?><p class="description">Set via <code>ATT_HC_GITEA_OWNER</code> constant.</p><?php endif; ?>
</td> </td>
</tr> </tr>
<tr> <tr>
<th><label for="wph-repo">Repo</label></th> <th><label for="att-hc-repo">Repo</label></th>
<td> <td>
<input id="wph-repo" type="text" name="repo" value="<?php echo esc_attr($c['repo']); ?>" class="regular-text" placeholder="site-recovery" <?php disabled($o['repo']); ?>> <input id="att-hc-repo" type="text" name="repo" value="<?php echo esc_attr($c['repo']); ?>" class="regular-text" placeholder="site-recovery" <?php disabled($o['repo']); ?>>
<?php if ($o['repo']): ?><p class="description">Set via <code>WPH_GITEA_REPO</code> constant.</p><?php endif; ?> <?php if ($o['repo']): ?><p class="description">Set via <code>ATT_HC_GITEA_REPO</code> constant.</p><?php endif; ?>
</td> </td>
</tr> </tr>
<tr> <tr>
<th><label for="wph-token">Token</label></th> <th><label for="att-hc-token">Token</label></th>
<td> <td>
<input id="wph-token" type="password" name="token" value="<?php echo esc_attr($c['token']); ?>" class="regular-text" autocomplete="new-password" <?php disabled($o['token']); ?>> <input id="att-hc-token" type="password" name="token" value="<?php echo esc_attr($c['token']); ?>" class="regular-text" autocomplete="new-password" <?php disabled($o['token']); ?>>
<?php if ($o['token']): ?><p class="description">Set via <code>WPH_GITEA_TOKEN</code> constant.</p> <?php if ($o['token']): ?><p class="description">Set via <code>ATT_HC_GITEA_TOKEN</code> constant.</p>
<?php else: ?><p class="description">Stored in WP options. Use a read-only deploy token scoped to this repo if possible.</p><?php endif; ?> <?php else: ?><p class="description">Stored in WP options. Use a read-only deploy token scoped to this repo if possible.</p><?php endif; ?>
</td> </td>
</tr> </tr>
@@ -508,57 +508,57 @@ function wph_render_settings_page(): void {
<p><button class="button button-primary">Save settings</button></p> <p><button class="button button-primary">Save settings</button></p>
</form> </form>
<?php if (WPH_Recovery_Installer::is_configured() && !WPH_Recovery_Bootstrap::is_installed()): ?> <?php if (ATT_HC_Recovery_Installer::is_configured() && !ATT_HC_Recovery_Bootstrap::is_installed()): ?>
<hr> <hr>
<h3>Install now</h3> <h3>Install now</h3>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Download and install Site Recovery now?');"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Download and install Site Recovery now?');">
<?php wp_nonce_field('wph_recovery_install'); ?> <?php wp_nonce_field('att_hc_recovery_install'); ?>
<input type="hidden" name="action" value="wph_recovery_install"> <input type="hidden" name="action" value="att_hc_recovery_install">
<button class="button button-primary">Install from gitea (latest)</button> <button class="button button-primary">Install from gitea (latest)</button>
</form> </form>
<?php elseif (WPH_Recovery_Bootstrap::is_installed()): ?> <?php elseif (ATT_HC_Recovery_Bootstrap::is_installed()): ?>
<hr> <hr>
<p class="wph-ok">✓ Site Recovery is already installed at <code><?php echo esc_html((string) WPH_Recovery_Bootstrap::plugin_file()); ?></code>.</p> <p class="att-hc-ok">✓ Site Recovery is already installed at <code><?php echo esc_html((string) ATT_HC_Recovery_Bootstrap::plugin_file()); ?></code>.</p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<?php <?php
} }
function wph_handle_save_settings(): void { function att_hc_handle_save_settings(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_save_settings'); check_admin_referer('att_hc_save_settings');
WPH_Recovery_Installer::save_options([ ATT_HC_Recovery_Installer::save_options([
'host' => (string) wp_unslash($_POST['host'] ?? ''), 'host' => (string) wp_unslash($_POST['host'] ?? ''),
'owner' => (string) wp_unslash($_POST['owner'] ?? ''), 'owner' => (string) wp_unslash($_POST['owner'] ?? ''),
'repo' => (string) wp_unslash($_POST['repo'] ?? ''), 'repo' => (string) wp_unslash($_POST['repo'] ?? ''),
'token' => (string) wp_unslash($_POST['token'] ?? ''), 'token' => (string) wp_unslash($_POST['token'] ?? ''),
]); ]);
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck-settings&wph_saved=1')); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck-settings&att_hc_saved=1'));
exit; exit;
} }
function wph_handle_recovery_install(): void { function att_hc_handle_recovery_install(): void {
if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) wp_die('Forbidden'); if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) wp_die('Forbidden');
check_admin_referer('wph_recovery_install'); check_admin_referer('att_hc_recovery_install');
@set_time_limit(120); @set_time_limit(120);
$result = WPH_Recovery_Installer::install_and_activate(); $result = ATT_HC_Recovery_Installer::install_and_activate();
if (is_wp_error($result)) { if (is_wp_error($result)) {
wp_die('Install failed: ' . esc_html($result->get_error_message()) . ' <p><a href="' . esc_url(admin_url('tools.php?page=site-healthcheck-settings')) . '">Back to settings</a></p>'); wp_die('Install failed: ' . esc_html($result->get_error_message()) . ' <p><a href="' . esc_url(admin_url('tools.php?page=att-site-healthcheck-settings')) . '">Back to settings</a></p>');
} }
set_transient('wph_install_message', sprintf('Site Recovery installed from %s "%s" and activated.', $result['ref']['type'], $result['ref']['ref']), 60); set_transient('att_hc_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')); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck'));
exit; exit;
} }
function wph_handle_step_action(): void { function att_hc_handle_step_action(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
$step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : ''; $step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : '';
$action_name = isset($_POST['step_action']) ? sanitize_key((string) $_POST['step_action']) : ''; $action_name = isset($_POST['step_action']) ? sanitize_key((string) $_POST['step_action']) : '';
check_admin_referer('wph_step_action_' . $step_id . '_' . $action_name); check_admin_referer('att_hc_step_action_' . $step_id . '_' . $action_name);
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session || $session->is_finished()) wp_die('No active session.'); if (!$session || $session->is_finished()) wp_die('No active session.');
$step = WPH_Steps::instance()->get($step_id); $step = ATT_HC_Steps::instance()->get($step_id);
if (!$step) wp_die('Unknown step.'); if (!$step) wp_die('Unknown step.');
// Pass POST through unslashed so handlers see the raw user input. // Pass POST through unslashed so handlers see the raw user input.
@@ -582,69 +582,69 @@ function wph_handle_step_action(): void {
$session->set_autocheck($step_id, $findings); $session->set_autocheck($step_id, $findings);
} }
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck#step-' . rawurlencode($step_id)));
exit; exit;
} }
function wph_handle_refresh_checks(): void { function att_hc_handle_refresh_checks(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
$step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : ''; $step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : '';
check_admin_referer('wph_refresh_checks_' . $step_id); check_admin_referer('att_hc_refresh_checks_' . $step_id);
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session || $session->is_finished()) wp_die('No active session.'); if (!$session || $session->is_finished()) wp_die('No active session.');
$step = WPH_Steps::instance()->get($step_id); $step = ATT_HC_Steps::instance()->get($step_id);
if (!$step) wp_die('Unknown step.'); if (!$step) wp_die('Unknown step.');
@set_time_limit(60); @set_time_limit(60);
$findings = $step->autocheck($session->data()); $findings = $step->autocheck($session->data());
$session->set_autocheck($step_id, $findings); $session->set_autocheck($step_id, $findings);
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id))); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck#step-' . rawurlencode($step_id)));
exit; exit;
} }
function wph_handle_download_report(): void { function att_hc_handle_download_report(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_download_report'); check_admin_referer('att_hc_download_report');
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session) wp_die('No session.'); if (!$session) wp_die('No session.');
wph_stream_report($session, 'md'); att_hc_stream_report($session, 'md');
} }
function wph_handle_download_html(): void { function att_hc_handle_download_html(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_download_html'); check_admin_referer('att_hc_download_html');
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session) wp_die('No session.'); if (!$session) wp_die('No session.');
wph_stream_report($session, 'html'); att_hc_stream_report($session, 'html');
} }
function wph_stream_report(WPH_Session $session, string $format): void { function att_hc_stream_report(ATT_HC_Session $session, string $format): void {
$host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site'; $host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site';
$host = preg_replace('/[^a-z0-9.-]/i', '', (string) $host); $host = preg_replace('/[^a-z0-9.-]/i', '', (string) $host);
$stamp = date('Ymd', $session->started_at() ?: time()); $stamp = date('Ymd', $session->started_at() ?: time());
nocache_headers(); nocache_headers();
if ($format === 'html') { if ($format === 'html') {
header('Content-Type: text/html; charset=UTF-8'); header('Content-Type: text/html; charset=UTF-8');
header('Content-Disposition: attachment; filename="wph-report-' . $host . '-' . $stamp . '.html"'); header('Content-Disposition: attachment; filename="att-hc-report-' . $host . '-' . $stamp . '.html"');
echo wph_build_html_report($session); echo att_hc_build_html_report($session);
} else { } else {
header('Content-Type: text/markdown; charset=UTF-8'); header('Content-Type: text/markdown; charset=UTF-8');
header('Content-Disposition: attachment; filename="wph-report-' . $host . '-' . $stamp . '.md"'); header('Content-Disposition: attachment; filename="att-hc-report-' . $host . '-' . $stamp . '.md"');
echo wph_build_markdown_report($session); echo att_hc_build_markdown_report($session);
} }
exit; exit;
} }
function wph_handle_email_report(): void { function att_hc_handle_email_report(): void {
if (!current_user_can('manage_options')) wp_die('Forbidden'); if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('wph_email_report'); check_admin_referer('att_hc_email_report');
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if (!$session) wp_die('No session.'); if (!$session) wp_die('No session.');
$to = isset($_POST['to']) ? sanitize_email((string) wp_unslash($_POST['to'])) : ''; $to = isset($_POST['to']) ? sanitize_email((string) wp_unslash($_POST['to'])) : '';
if (!is_email($to)) wp_die('Bad email address.'); if (!is_email($to)) wp_die('Bad email address.');
$host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site'; $host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site';
$subject = 'Site Healthcheck — ' . $host . ' — ' . date('Y-m-d', $session->started_at() ?: time()); $subject = 'Site Healthcheck — ' . $host . ' — ' . date('Y-m-d', $session->started_at() ?: time());
$html = wph_build_html_report($session); $html = att_hc_build_html_report($session);
$ok = wp_mail($to, $subject, $html, ['Content-Type: text/html; charset=UTF-8']); $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'))); wp_safe_redirect(admin_url('tools.php?page=att-site-healthcheck&att_hc_mail=' . ($ok ? '1' : '0')));
exit; exit;
} }

View File

@@ -8,7 +8,7 @@ if (!defined('ABSPATH')) exit;
* engagement, so per-site history living in the DB would die on uninstall. * engagement, so per-site history living in the DB would die on uninstall.
* Reports are exported as Markdown instead see report.php. * Reports are exported as Markdown instead see report.php.
*/ */
final class WPH_Session { final class ATT_HC_Session {
public const STATUS_NOT_STARTED = 'not_started'; public const STATUS_NOT_STARTED = 'not_started';
public const STATUS_DONE = 'done'; public const STATUS_DONE = 'done';
@@ -31,14 +31,14 @@ final class WPH_Session {
} }
public static function current(): ?self { public static function current(): ?self {
$raw = get_option(WPH_OPT_SESSION); $raw = get_option(ATT_HC_OPT_SESSION);
if (!is_array($raw) || empty($raw['id'])) return null; if (!is_array($raw) || empty($raw['id'])) return null;
return new self($raw); return new self($raw);
} }
public static function start(int $technician_id): self { public static function start(int $technician_id): self {
$data = [ $data = [
'id' => uniqid('wph_', true), 'id' => uniqid('att_hc_', true),
'started_at' => time(), 'started_at' => time(),
'finished_at' => null, 'finished_at' => null,
'technician_id'=> $technician_id, 'technician_id'=> $technician_id,
@@ -47,17 +47,17 @@ final class WPH_Session {
'php_version' => PHP_VERSION, 'php_version' => PHP_VERSION,
'steps' => [], // keyed by step id → ['status' => ..., 'notes' => ..., 'updated_at' => ...] 'steps' => [], // keyed by step id → ['status' => ..., 'notes' => ..., 'updated_at' => ...]
]; ];
update_option(WPH_OPT_SESSION, $data, false); update_option(ATT_HC_OPT_SESSION, $data, false);
return new self($data); return new self($data);
} }
public static function discard(): void { public static function discard(): void {
delete_option(WPH_OPT_SESSION); delete_option(ATT_HC_OPT_SESSION);
} }
/** The previous finished session (for diffing). Stored on finish(). */ /** The previous finished session (for diffing). Stored on finish(). */
public static function previous(): ?self { public static function previous(): ?self {
$raw = get_option('wph_previous_session'); $raw = get_option('att_hc_previous_session');
if (!is_array($raw) || empty($raw['id'])) return null; if (!is_array($raw) || empty($raw['id'])) return null;
return new self($raw); return new self($raw);
} }
@@ -89,14 +89,14 @@ final class WPH_Session {
'notes' => $notes, 'notes' => $notes,
'updated_at' => time(), 'updated_at' => time(),
]; ];
update_option(WPH_OPT_SESSION, $this->data, false); update_option(ATT_HC_OPT_SESSION, $this->data, false);
} }
public function finish(): void { public function finish(): void {
$this->data['finished_at'] = time(); $this->data['finished_at'] = time();
update_option(WPH_OPT_SESSION, $this->data, false); update_option(ATT_HC_OPT_SESSION, $this->data, false);
// Snapshot for next-session diff. One slot, overwritten each finish. // Snapshot for next-session diff. One slot, overwritten each finish.
update_option('wph_previous_session', $this->data, false); update_option('att_hc_previous_session', $this->data, false);
} }
/** Store the result of running autocheck() on a step. */ /** Store the result of running autocheck() on a step. */
@@ -105,7 +105,7 @@ final class WPH_Session {
'checked_at' => time(), 'checked_at' => time(),
'findings' => $findings, 'findings' => $findings,
]; ];
update_option(WPH_OPT_SESSION, $this->data, false); update_option(ATT_HC_OPT_SESSION, $this->data, false);
} }
/** Returns ['checked_at'=>int, 'findings'=>array] or null. */ /** Returns ['checked_at'=>int, 'findings'=>array] or null. */
@@ -114,7 +114,7 @@ final class WPH_Session {
} }
public function progress(): array { public function progress(): array {
$steps = WPH_Steps::instance()->all(); $steps = ATT_HC_Steps::instance()->all();
$total = count($steps); $total = count($steps);
$done = 0; $done = 0;
foreach ($steps as $step) { foreach ($steps as $step) {

View File

@@ -5,14 +5,14 @@ if (!defined('ABSPATH')) exit;
* Base class for a healthcheck step. * Base class for a healthcheck step.
* *
* Each step lives in its own file under includes/steps/ with a numeric prefix * Each step lives in its own file under includes/steps/ with a numeric prefix
* (e.g. 10-backup.php). The file returns an instance of a subclass of WPH_Step. * (e.g. 10-backup.php). The file returns an instance of a subclass of ATT_HC_Step.
* Drop a new file in that directory and it shows up; delete a file and it * Drop a new file in that directory and it shows up; delete a file and it
* disappears. The numeric prefix controls order so re-ordering is a rename. * disappears. The numeric prefix controls order so re-ordering is a rename.
* *
* Stable string IDs (returned by id()) are stored in the session, so renaming * Stable string IDs (returned by id()) are stored in the session, so renaming
* the file does NOT lose data as long as id() stays the same. * the file does NOT lose data as long as id() stays the same.
*/ */
abstract class WPH_Step { abstract class ATT_HC_Step {
/** Stable identifier (lowercase slug). NEVER change once shipped. */ /** Stable identifier (lowercase slug). NEVER change once shipped. */
abstract public function id(): string; abstract public function id(): string;
@@ -53,7 +53,7 @@ abstract class WPH_Step {
/** Returns true if any of this step's automation is implemented. Override or rely on autocheck() returning [] by default. */ /** Returns true if any of this step's automation is implemented. Override or rely on autocheck() returning [] by default. */
public function has_autocheck(): bool { public function has_autocheck(): bool {
$r = new ReflectionMethod($this, 'autocheck'); $r = new ReflectionMethod($this, 'autocheck');
return $r->getDeclaringClass()->getName() !== WPH_Step::class; return $r->getDeclaringClass()->getName() !== ATT_HC_Step::class;
} }
/** /**

View File

@@ -5,22 +5,22 @@ if (!defined('ABSPATH')) exit;
* Step registry. Singleton. * Step registry. Singleton.
* *
* discover() globs a directory for *.php files in sort order and requires each. * discover() globs a directory for *.php files in sort order and requires each.
* Each file MUST return a WPH_Step instance. The numeric filename prefix * Each file MUST return a ATT_HC_Step instance. The numeric filename prefix
* (00, 10, 20...) controls the visual order. * (00, 10, 20...) controls the visual order.
* *
* After discovery, the 'wph_steps' filter lets installation-specific code * After discovery, the 'att_hc_steps' filter lets installation-specific code
* remove/replace steps (return the array keyed by step id). * remove/replace steps (return the array keyed by step id).
*/ */
final class WPH_Steps { final class ATT_HC_Steps {
private static ?WPH_Steps $instance = null; private static ?ATT_HC_Steps $instance = null;
/** @var array<string,WPH_Step> */ /** @var array<string,ATT_HC_Step> */
private array $steps = []; private array $steps = [];
private bool $discovered = false; private bool $discovered = false;
public static function instance(): WPH_Steps { public static function instance(): ATT_HC_Steps {
return self::$instance ??= new self(); return self::$instance ??= new self();
} }
@@ -32,7 +32,7 @@ final class WPH_Steps {
$files = array_values($files); $files = array_values($files);
foreach ($files as $file) { foreach ($files as $file) {
$obj = require $file; $obj = require $file;
if ($obj instanceof WPH_Step) { if ($obj instanceof ATT_HC_Step) {
$this->steps[$obj->id()] = $obj; $this->steps[$obj->id()] = $obj;
} }
} }
@@ -40,16 +40,16 @@ final class WPH_Steps {
* Filter the loaded steps. Return an array keyed by step id. * Filter the loaded steps. Return an array keyed by step id.
* To drop a step on a particular install: unset($steps['backup']). * To drop a step on a particular install: unset($steps['backup']).
*/ */
$this->steps = apply_filters('wph_steps', $this->steps); $this->steps = apply_filters('att_hc_steps', $this->steps);
$this->discovered = true; $this->discovered = true;
} }
/** @return array<string,WPH_Step> */ /** @return array<string,ATT_HC_Step> */
public function all(): array { public function all(): array {
return $this->steps; return $this->steps;
} }
public function get(string $id): ?WPH_Step { public function get(string $id): ?ATT_HC_Step {
return $this->steps[$id] ?? null; return $this->steps[$id] ?? null;
} }
} }

View File

@@ -9,7 +9,7 @@ if (!defined('ABSPATH')) exit;
* is stable across releases. If we ever publish a private update channel for * is stable across releases. If we ever publish a private update channel for
* the recovery plugin, this is also where we'd offer "fetch latest". * the recovery plugin, this is also where we'd offer "fetch latest".
*/ */
final class WPH_Recovery_Bootstrap { final class ATT_HC_Recovery_Bootstrap {
/** /**
* Detect by plugin Name + Author rather than folder slug — the recovery * Detect by plugin Name + Author rather than folder slug — the recovery
@@ -61,27 +61,27 @@ final class WPH_Recovery_Bootstrap {
$installed = self::is_installed(); $installed = self::is_installed();
$active = $installed && self::is_active(); $active = $installed && self::is_active();
$file = self::plugin_file(); $file = self::plugin_file();
$configured = WPH_Recovery_Installer::is_configured(); $configured = ATT_HC_Recovery_Installer::is_configured();
?> ?>
<div class="wph-bootstrap-panel"> <div class="att-hc-bootstrap-panel">
<h3>Recovery plugin status</h3> <h3>Recovery plugin status</h3>
<?php if ($active): ?> <?php if ($active): ?>
<p class="wph-ok">✓ <strong>Site Recovery</strong> is installed and active <code><?php echo esc_html((string) $file); ?></code>. <p class="att-hc-ok">✓ <strong>Site Recovery</strong> is installed and active <code><?php echo esc_html((string) $file); ?></code>.
<a href="<?php echo esc_url(self::recovery_admin_url()); ?>">Open recovery settings</a> to copy the URL + password into the client record.</p> <a href="<?php echo esc_url(self::recovery_admin_url()); ?>">Open recovery settings</a> to copy the URL + password into the client record.</p>
<?php elseif ($installed): ?> <?php elseif ($installed): ?>
<p class="wph-warn">⚠ <strong>Site Recovery</strong> is installed but inactive <code><?php echo esc_html((string) $file); ?></code>. <a href="<?php echo esc_url(admin_url('plugins.php')); ?>">Activate it</a> before starting work.</p> <p class="att-hc-warn">⚠ <strong>Site Recovery</strong> is installed but inactive <code><?php echo esc_html((string) $file); ?></code>. <a href="<?php echo esc_url(admin_url('plugins.php')); ?>">Activate it</a> before starting work.</p>
<?php else: ?> <?php else: ?>
<p class="wph-bad">✗ <strong>Site Recovery</strong> is not installed. Install it before starting the healthcheck — it's the safety net while we work on the site.</p> <p class="att-hc-bad">✗ <strong>Site Recovery</strong> is not installed. Install it before starting the healthcheck — it's the safety net while we work on the site.</p>
<?php if ($configured): ?> <?php if ($configured): ?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline" onsubmit="return confirm('Download and install Site Recovery from gitea?');"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline" onsubmit="return confirm('Download and install Site Recovery from gitea?');">
<?php wp_nonce_field('wph_recovery_install'); ?> <?php wp_nonce_field('att_hc_recovery_install'); ?>
<input type="hidden" name="action" value="wph_recovery_install"> <input type="hidden" name="action" value="att_hc_recovery_install">
<button class="button button-primary">Install from gitea (latest)</button> <button class="button button-primary">Install from gitea (latest)</button>
</form> </form>
<a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck-settings')); ?>" class="button">Configure source</a> <a href="<?php echo esc_url(admin_url('tools.php?page=att-site-healthcheck-settings')); ?>" class="button">Configure source</a>
<?php else: ?> <?php else: ?>
<p> <p>
<a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck-settings')); ?>" class="button button-primary">Configure gitea source</a> <a href="<?php echo esc_url(admin_url('tools.php?page=att-site-healthcheck-settings')); ?>" class="button button-primary">Configure gitea source</a>
<em>— or install manually via Plugins → Add New → Upload Plugin.</em> <em>— or install manually via Plugins → Add New → Upload Plugin.</em>
</p> </p>
<?php endif; ?> <?php endif; ?>

View File

@@ -5,10 +5,10 @@ if (!defined('ABSPATH')) exit;
* One-click install of wp-site-recovery from a private Gitea repo. * One-click install of wp-site-recovery from a private Gitea repo.
* *
* Config sources (in order — constants beat options): * Config sources (in order — constants beat options):
* define('WPH_GITEA_HOST', 'https://git.example.com'); * define('ATT_HC_GITEA_HOST', 'https://git.example.com');
* define('WPH_GITEA_OWNER', 'steve'); * define('ATT_HC_GITEA_OWNER', 'steve');
* define('WPH_GITEA_REPO', 'site-recovery'); * define('ATT_HC_GITEA_REPO', 'site-recovery');
* define('WPH_GITEA_TOKEN', 'xxx'); // read-only PAT, scoped to this repo * define('ATT_HC_GITEA_TOKEN', 'xxx'); // read-only PAT, scoped to this repo
* *
* …or set the same values via the settings panel (stored in WP options). * …or set the same values via the settings panel (stored in WP options).
* *
@@ -23,29 +23,29 @@ if (!defined('ABSPATH')) exit;
* the unpacked folder to 'site-recovery' so the destination is consistent. * the unpacked folder to 'site-recovery' so the destination is consistent.
* → activate_plugin() against the freshly-installed plugin file. * → activate_plugin() against the freshly-installed plugin file.
*/ */
final class WPH_Recovery_Installer { final class ATT_HC_Recovery_Installer {
private const OPT_HOST = 'wph_gitea_host'; private const OPT_HOST = 'att_hc_gitea_host';
private const OPT_OWNER = 'wph_gitea_owner'; private const OPT_OWNER = 'att_hc_gitea_owner';
private const OPT_REPO = 'wph_gitea_repo'; private const OPT_REPO = 'att_hc_gitea_repo';
private const OPT_TOKEN = 'wph_gitea_token'; private const OPT_TOKEN = 'att_hc_gitea_token';
public static function config(): array { public static function config(): array {
return [ return [
'host' => defined('WPH_GITEA_HOST') ? (string) WPH_GITEA_HOST : (string) get_option(self::OPT_HOST, ''), 'host' => defined('ATT_HC_GITEA_HOST') ? (string) ATT_HC_GITEA_HOST : (string) get_option(self::OPT_HOST, ''),
'owner' => defined('WPH_GITEA_OWNER') ? (string) WPH_GITEA_OWNER : (string) get_option(self::OPT_OWNER, ''), 'owner' => defined('ATT_HC_GITEA_OWNER') ? (string) ATT_HC_GITEA_OWNER : (string) get_option(self::OPT_OWNER, ''),
'repo' => defined('WPH_GITEA_REPO') ? (string) WPH_GITEA_REPO : (string) get_option(self::OPT_REPO, ''), 'repo' => defined('ATT_HC_GITEA_REPO') ? (string) ATT_HC_GITEA_REPO : (string) get_option(self::OPT_REPO, ''),
'token' => defined('WPH_GITEA_TOKEN') ? (string) WPH_GITEA_TOKEN : (string) get_option(self::OPT_TOKEN, ''), 'token' => defined('ATT_HC_GITEA_TOKEN') ? (string) ATT_HC_GITEA_TOKEN : (string) get_option(self::OPT_TOKEN, ''),
]; ];
} }
/** Returns whether each config field comes from a constant (true) or an option (false). */ /** Returns whether each config field comes from a constant (true) or an option (false). */
public static function config_origin(): array { public static function config_origin(): array {
return [ return [
'host' => defined('WPH_GITEA_HOST'), 'host' => defined('ATT_HC_GITEA_HOST'),
'owner' => defined('WPH_GITEA_OWNER'), 'owner' => defined('ATT_HC_GITEA_OWNER'),
'repo' => defined('WPH_GITEA_REPO'), 'repo' => defined('ATT_HC_GITEA_REPO'),
'token' => defined('WPH_GITEA_TOKEN'), 'token' => defined('ATT_HC_GITEA_TOKEN'),
]; ];
} }
@@ -157,9 +157,9 @@ final class WPH_Recovery_Installer {
} }
// Find the freshly-installed plugin and activate it. // Find the freshly-installed plugin and activate it.
if (method_exists('WPH_Recovery_Bootstrap', 'plugin_file')) { if (method_exists('ATT_HC_Recovery_Bootstrap', 'plugin_file')) {
// Bust the static cache so we re-scan get_plugins(). // Bust the static cache so we re-scan get_plugins().
$rc = new ReflectionClass('WPH_Recovery_Bootstrap'); $rc = new ReflectionClass('ATT_HC_Recovery_Bootstrap');
// No public reset; just call get_plugins() in our context — Bootstrap will re-scan // No public reset; just call get_plugins() in our context — Bootstrap will re-scan
// because get_plugins() builds a fresh array on each call. // because get_plugins() builds a fresh array on each call.
wp_cache_delete('plugins', 'plugins'); wp_cache_delete('plugins', 'plugins');

View File

@@ -6,7 +6,7 @@ if (!defined('ABSPATH')) exit;
* Per bead hc-5ix.7: pure Markdown, downloadable. No DB-side report storage * Per bead hc-5ix.7: pure Markdown, downloadable. No DB-side report storage
* (plugin may be uninstalled at the end of the engagement). * (plugin may be uninstalled at the end of the engagement).
*/ */
function wph_build_markdown_report(WPH_Session $session): string { function att_hc_build_markdown_report(ATT_HC_Session $session): string {
$tech = get_userdata($session->technician_id()); $tech = get_userdata($session->technician_id());
$tech_name = $tech ? $tech->display_name : '#' . $session->technician_id(); $tech_name = $tech ? $tech->display_name : '#' . $session->technician_id();
@@ -31,20 +31,20 @@ function wph_build_markdown_report(WPH_Session $session): string {
$lines[] = ''; $lines[] = '';
$lines[] = '| Step | Status |'; $lines[] = '| Step | Status |';
$lines[] = '|---|---|'; $lines[] = '|---|---|';
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
$lines[] = '| ' . $step->title() . ' | ' . wph_status_label($state['status']) . ' |'; $lines[] = '| ' . $step->title() . ' | ' . att_hc_status_label($state['status']) . ' |';
} }
$lines[] = ''; $lines[] = '';
// Per-step detail // Per-step detail
$lines[] = '## Detail'; $lines[] = '## Detail';
$lines[] = ''; $lines[] = '';
foreach (WPH_Steps::instance()->all() as $step) { foreach (ATT_HC_Steps::instance()->all() as $step) {
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
$lines[] = '### ' . $step->title(); $lines[] = '### ' . $step->title();
$lines[] = ''; $lines[] = '';
$lines[] = '_Status: ' . wph_status_label($state['status']) . '_'; $lines[] = '_Status: ' . att_hc_status_label($state['status']) . '_';
if ($state['updated_at']) { if ($state['updated_at']) {
$lines[] = '_Saved: ' . date('Y-m-d H:i', $state['updated_at']) . '_'; $lines[] = '_Saved: ' . date('Y-m-d H:i', $state['updated_at']) . '_';
} }
@@ -80,7 +80,7 @@ function wph_build_markdown_report(WPH_Session $session): string {
$lines[] = '- ' . implode(' ', $bits); $lines[] = '- ' . implode(' ', $bits);
} }
} }
if ($state['status'] === WPH_Session::STATUS_BLOCKED && ($esc = $step->escalation())) { if ($state['status'] === ATT_HC_Session::STATUS_BLOCKED && ($esc = $step->escalation())) {
$lines[] = ''; $lines[] = '';
$lines[] = '> ⚠ **Escalation:** ' . $esc; $lines[] = '> ⚠ **Escalation:** ' . $esc;
} }
@@ -88,12 +88,12 @@ function wph_build_markdown_report(WPH_Session $session): string {
} }
$lines[] = '---'; $lines[] = '---';
$lines[] = '_Generated by Site Healthcheck plugin v' . WPH_VERSION . '_'; $lines[] = '_Generated by Site Healthcheck plugin v' . ATT_HC_VERSION . '_';
return implode("\n", $lines) . "\n"; return implode("\n", $lines) . "\n";
} }
function wph_build_html_report(WPH_Session $session): string { function att_hc_build_html_report(ATT_HC_Session $session): string {
$tech = get_userdata($session->technician_id()); $tech = get_userdata($session->technician_id());
$tech_name = $tech ? $tech->display_name : '#' . $session->technician_id(); $tech_name = $tech ? $tech->display_name : '#' . $session->technician_id();
@@ -150,7 +150,7 @@ function wph_build_html_report(WPH_Session $session): string {
<table> <table>
<thead><tr><th>Step</th><th>Status</th></tr></thead> <thead><tr><th>Step</th><th>Status</th></tr></thead>
<tbody> <tbody>
<?php foreach (WPH_Steps::instance()->all() as $step): <?php foreach (ATT_HC_Steps::instance()->all() as $step):
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
?> ?>
<tr> <tr>
@@ -162,7 +162,7 @@ function wph_build_html_report(WPH_Session $session): string {
</table> </table>
<h2>Detail</h2> <h2>Detail</h2>
<?php foreach (WPH_Steps::instance()->all() as $step): <?php foreach (ATT_HC_Steps::instance()->all() as $step):
$state = $session->step_state($step->id()); $state = $session->step_state($step->id());
$auto = $session->get_autocheck($step->id()); $auto = $session->get_autocheck($step->id());
?> ?>
@@ -194,7 +194,7 @@ function wph_build_html_report(WPH_Session $session): string {
<?php endif; ?> <?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
<footer>Generated by Site Healthcheck plugin v<?php echo esc_html(WPH_VERSION); ?> · <?php echo esc_html(date('Y-m-d H:i')); ?></footer> <footer>Generated by Site Healthcheck plugin v<?php echo esc_html(ATT_HC_VERSION); ?> · <?php echo esc_html(date('Y-m-d H:i')); ?></footer>
</div> </div>
</body> </body>
</html> </html>
@@ -202,13 +202,13 @@ function wph_build_html_report(WPH_Session $session): string {
return (string) ob_get_clean(); return (string) ob_get_clean();
} }
function wph_status_label(string $status): string { function att_hc_status_label(string $status): string {
switch ($status) { switch ($status) {
case WPH_Session::STATUS_DONE: return '✅ Done'; case ATT_HC_Session::STATUS_DONE: return '✅ Done';
case WPH_Session::STATUS_SKIPPED: return '⏭ Skipped'; case ATT_HC_Session::STATUS_SKIPPED: return '⏭ Skipped';
case WPH_Session::STATUS_BLOCKED: return '🛑 Blocked'; case ATT_HC_Session::STATUS_BLOCKED: return '🛑 Blocked';
case WPH_Session::STATUS_NA: return '— N/A'; case ATT_HC_Session::STATUS_NA: return '— N/A';
case WPH_Session::STATUS_NOT_STARTED: case ATT_HC_Session::STATUS_NOT_STARTED:
default: return '◻ Not started'; default: return '◻ Not started';
} }
} }

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'before'; } public function id(): string { return 'before'; }
public function title(): string { return 'Before You Start'; } public function title(): string { return 'Before You Start'; }
public function blurb(): string { public function blurb(): string {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'backup'; } public function id(): string { return 'backup'; }
public function title(): string { return 'Step 1 — Take a Full Backup'; } public function title(): string { return 'Step 1 — Take a Full Backup'; }
public function blurb(): string { public function blurb(): string {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'uptime'; } public function id(): string { return 'uptime'; }
public function title(): string { return 'Step 10 — Uptime and Availability'; } public function title(): string { return 'Step 10 — Uptime and Availability'; }
public function sub_items(): array { public function sub_items(): array {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'small_fixes'; } public function id(): string { return 'small_fixes'; }
public function title(): string { return 'Step 11 — Small Fixes'; } public function title(): string { return 'Step 11 — Small Fixes'; }
public function blurb(): string { public function blurb(): string {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'email_test'; } public function id(): string { return 'email_test'; }
public function title(): string { return 'Step — Email Delivery Test'; } public function title(): string { return 'Step — Email Delivery Test'; }
@@ -61,10 +61,10 @@ return new class extends WPH_Step {
$default_to = $current && $current->user_email ? $current->user_email : get_option('admin_email'); $default_to = $current && $current->user_email ? $current->user_email : get_option('admin_email');
$debug_data = $session_state['email_debug'] ?? null; $debug_data = $session_state['email_debug'] ?? null;
?> ?>
<div class="wph-card" style="background:#fafafb;border-radius:6px;padding:.75rem 1rem;margin-top:.6rem"> <div class="att-hc-card" style="background:#fafafb;border-radius:6px;padding:.75rem 1rem;margin-top:.6rem">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<?php wp_nonce_field('wph_step_action_email_test_send_test'); ?> <?php wp_nonce_field('att_hc_step_action_email_test_send_test'); ?>
<input type="hidden" name="action" value="wph_step_action"> <input type="hidden" name="action" value="att_hc_step_action">
<input type="hidden" name="step" value="email_test"> <input type="hidden" name="step" value="email_test">
<input type="hidden" name="step_action" value="send_test"> <input type="hidden" name="step_action" value="send_test">
<p style="margin:.2rem 0 .5rem"><strong>Send test email</strong></p> <p style="margin:.2rem 0 .5rem"><strong>Send test email</strong></p>
@@ -148,7 +148,7 @@ return new class extends WPH_Step {
// Stash the debug trace on the session so the UI can render it as a // Stash the debug trace on the session so the UI can render it as a
// collapsible block. Returning it inside the finding's detail would // collapsible block. Returning it inside the finding's detail would
// mangle the formatting. // mangle the formatting.
$session = WPH_Session::current(); $session = ATT_HC_Session::current();
if ($session && $debug_requested) { if ($session && $debug_requested) {
$trace = array_slice($trace, 0, 200); // cap to avoid bloating the option $trace = array_slice($trace, 0, 200); // cap to avoid bloating the option
$data = $session->data(); $data = $session->data();
@@ -157,7 +157,7 @@ return new class extends WPH_Step {
'transport' => $transport, 'transport' => $transport,
'trace' => $trace, 'trace' => $trace,
]; ];
update_option(WPH_OPT_SESSION, $data, false); update_option(ATT_HC_OPT_SESSION, $data, false);
// Append the trace to the step's notes so the technician can edit // Append the trace to the step's notes so the technician can edit
// it and so it lands in the downloadable report (notes are // it and so it lands in the downloadable report (notes are

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'wrap_up'; } public function id(): string { return 'wrap_up'; }
public function title(): string { return 'Step 12 — Wrap Up and Document'; } public function title(): string { return 'Step 12 — Wrap Up and Document'; }
public function sub_items(): array { public function sub_items(): array {
@@ -19,7 +19,7 @@ return new class extends WPH_Step {
$bad = []; $bad = [];
$warn = []; $warn = [];
$blocked = []; $blocked = [];
foreach (WPH_Steps::instance()->all() as $sid => $s) { foreach (ATT_HC_Steps::instance()->all() as $sid => $s) {
if ($sid === 'wrap_up') continue; if ($sid === 'wrap_up') continue;
// Pull stored autocheck results from session data (we get session_state passed in). // Pull stored autocheck results from session data (we get session_state passed in).
$stored = $session_state['autocheck'][$sid] ?? null; $stored = $session_state['autocheck'][$sid] ?? null;

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'environment'; } public function id(): string { return 'environment'; }
public function title(): string { return 'Step 2 — Environment Check'; } public function title(): string { return 'Step 2 — Environment Check'; }
public function blurb(): string { public function blurb(): string {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'core'; } public function id(): string { return 'core'; }
public function title(): string { return 'Step 3 — WordPress Core Update'; } public function title(): string { return 'Step 3 — WordPress Core Update'; }
public function sub_items(): array { public function sub_items(): array {
@@ -126,16 +126,16 @@ return new class extends WPH_Step {
/** Direct call to wp.org's version-check endpoint. Cached 1h via transient. */ /** Direct call to wp.org's version-check endpoint. Cached 1h via transient. */
private function wp_org_core_version(): ?string { private function wp_org_core_version(): ?string {
$cached = get_transient('wph_wp_org_core_latest'); $cached = get_transient('att_hc_wp_org_core_latest');
if ($cached !== false) return $cached === '' ? null : (string) $cached; if ($cached !== false) return $cached === '' ? null : (string) $cached;
$resp = wp_remote_get('https://api.wordpress.org/core/version-check/1.7/', ['timeout' => 5]); $resp = wp_remote_get('https://api.wordpress.org/core/version-check/1.7/', ['timeout' => 5]);
if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) { if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) {
set_transient('wph_wp_org_core_latest', '', 5 * MINUTE_IN_SECONDS); set_transient('att_hc_wp_org_core_latest', '', 5 * MINUTE_IN_SECONDS);
return null; return null;
} }
$data = json_decode((string) wp_remote_retrieve_body($resp), true); $data = json_decode((string) wp_remote_retrieve_body($resp), true);
$latest = isset($data['offers'][0]['version']) ? (string) $data['offers'][0]['version'] : null; $latest = isset($data['offers'][0]['version']) ? (string) $data['offers'][0]['version'] : null;
set_transient('wph_wp_org_core_latest', $latest ?: '', HOUR_IN_SECONDS); set_transient('att_hc_wp_org_core_latest', $latest ?: '', HOUR_IN_SECONDS);
return $latest; return $latest;
} }

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'plugins'; } public function id(): string { return 'plugins'; }
public function title(): string { return 'Step 4 — Plugin Updates'; } public function title(): string { return 'Step 4 — Plugin Updates'; }
public function sub_items(): array { public function sub_items(): array {
@@ -148,7 +148,7 @@ return new class extends WPH_Step {
private function wp_org_info(string $slug) { private function wp_org_info(string $slug) {
// v2: cache now includes 'version' field for cloak detection — fresh prefix // v2: cache now includes 'version' field for cloak detection — fresh prefix
// so existing cached entries get re-fetched on the next refresh. // so existing cached entries get re-fetched on the next refresh.
$cache_key = 'wph_pi2_' . md5($slug); $cache_key = 'att_hc_pi2_' . md5($slug);
$cached = get_transient($cache_key); $cached = get_transient($cache_key);
if ($cached !== false) return $cached; if ($cached !== false) return $cached;

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'theme'; } public function id(): string { return 'theme'; }
public function title(): string { return 'Step 5 — Theme Updates'; } public function title(): string { return 'Step 5 — Theme Updates'; }
public function sub_items(): array { public function sub_items(): array {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'visual'; } public function id(): string { return 'visual'; }
public function title(): string { return 'Step 6 — Visual and Functional Check'; } public function title(): string { return 'Step 6 — Visual and Functional Check'; }
public function blurb(): string { public function blurb(): string {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'performance'; } public function id(): string { return 'performance'; }
public function title(): string { return 'Step 7 — Performance Check'; } public function title(): string { return 'Step 7 — Performance Check'; }
public function sub_items(): array { public function sub_items(): array {
@@ -100,7 +100,7 @@ return new class extends WPH_Step {
} }
private function psi(string $url, string $strategy): ?array { private function psi(string $url, string $strategy): ?array {
$cache_key = 'wph_psi_' . md5($url . '|' . $strategy); $cache_key = 'att_hc_psi_' . md5($url . '|' . $strategy);
$cached = get_transient($cache_key); $cached = get_transient($cache_key);
if ($cached !== false) return $cached; if ($cached !== false) return $cached;
$api = add_query_arg([ $api = add_query_arg([

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'security'; } public function id(): string { return 'security'; }
public function title(): string { return 'Step 8 — Security Check'; } public function title(): string { return 'Step 8 — Security Check'; }
public function sub_items(): array { public function sub_items(): array {

View File

@@ -1,7 +1,7 @@
<?php <?php
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
return new class extends WPH_Step { return new class extends ATT_HC_Step {
public function id(): string { return 'database'; } public function id(): string { return 'database'; }
public function title(): string { return 'Step 9 — Database'; } public function title(): string { return 'Step 9 — Database'; }
public function sub_items(): array { public function sub_items(): array {

View File

@@ -1,33 +0,0 @@
<?php
/**
* Plugin Name: Site Healthcheck
* Description: Walks a technician through a structured WordPress site healthcheck. Steps are drop-in PHP files so adding/removing one is a single file change.
* Version: 0.1.0
* Author: Steve Hanlon
* License: GPL-2.0-or-later
* Requires PHP: 7.4
*
* Internal/agency tool — not distributed via WP.org. See beads decision hc-5ix.26.
*/
if (!defined('ABSPATH')) {
exit;
}
define('WPH_VERSION', '0.1.0');
define('WPH_PLUGIN_FILE', __FILE__);
define('WPH_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPH_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPH_OPT_SESSION', 'wph_session');
require_once WPH_PLUGIN_DIR . 'includes/class-wph-step.php';
require_once WPH_PLUGIN_DIR . 'includes/class-wph-steps.php';
require_once WPH_PLUGIN_DIR . 'includes/class-wph-session.php';
require_once WPH_PLUGIN_DIR . 'includes/recovery-bootstrap.php';
require_once WPH_PLUGIN_DIR . 'includes/recovery-installer.php';
require_once WPH_PLUGIN_DIR . 'includes/admin-page.php';
require_once WPH_PLUGIN_DIR . 'includes/report.php';
add_action('plugins_loaded', function () {
WPH_Steps::instance()->discover(WPH_PLUGIN_DIR . 'includes/steps/');
});