Files
wp-healthcheck/includes/recovery-bootstrap.php
Steve Hanlon 8ceb756404 Recovery plugin: folder-agnostic detection + one-click install from Gitea
Detection (recovery-bootstrap.php):
- Match on plugin Name + Author instead of hard-coded folder slug, so
  the recovery plugin is found regardless of whether it was unpacked
  as site-recovery/, wp-site-recovery/, or anything else.

One-click install (recovery-installer.php — new):
- Pulls site-recovery from a private Gitea repo via the standard archive
  endpoint with token auth.
- Ref resolution order: latest release → latest tag → main branch HEAD.
  Lets us tag pinned releases in Gitea later without changing code.
- upgrader_source_selection filter forces the unpacked folder name to
  'site-recovery' regardless of the gitea wrapper-folder suffix.
- Config from constants in wp-config.php (WPH_GITEA_HOST/OWNER/REPO/TOKEN)
  beats DB option storage. Constants are visually locked in the settings
  UI so admins can see they're inherited.

Settings page (hidden submenu, reachable at Tools → Site Healthcheck
→ 'Configure gitea source'):
- Host / Owner / Repo / Token fields
- Inline 'Install now' button when configured + plugin not installed

Bootstrap status panel:
- Shows the recovery plugin's actual file path when active
- 'Install from gitea (latest)' button when configured + missing
- 'Configure gitea source' button when not configured + missing

Install handler shows a success message via transient on the main
healthcheck page after a successful install + activate.
2026-06-12 09:43:55 +01:00

93 lines
4.4 KiB
PHP

<?php
if (!defined('ABSPATH')) exit;
/**
* Step-0 bootstrap: ensure wp-site-recovery is installed + active before the
* stepper runs (bead hc-5ix.1, references decision hc-5ix.26).
*
* Detection is by plugin folder slug, not by main-file path, because the slug
* 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".
*/
final class WPH_Recovery_Bootstrap {
/**
* Detect by plugin Name + Author rather than folder slug — the recovery
* plugin can be installed under any folder (site-recovery/, wp-site-recovery/,
* etc.) depending on how it was unpacked. Cache the result per request.
*/
private const NAME = 'Site Recovery';
private const AUTHOR = 'Steve Hanlon';
/** Return the plugin_basename (folder/file.php) if installed, or null. */
public static function plugin_file(): ?string {
static $cached;
if ($cached !== null) return $cached === '' ? null : $cached;
if (!function_exists('get_plugins')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
foreach (get_plugins() as $file => $meta) {
if (($meta['Name'] ?? '') === self::NAME && strpos((string) ($meta['Author'] ?? ''), self::AUTHOR) !== false) {
return $cached = $file;
}
}
$cached = '';
return null;
}
public static function is_installed(): bool {
return self::plugin_file() !== null;
}
public static function is_active(): bool {
$file = self::plugin_file();
if (!$file) return false;
if (!function_exists('is_plugin_active')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return is_plugin_active($file);
}
public static function recovery_admin_url(): string {
return admin_url('tools.php?page=site-recovery');
}
/**
* Render a status panel that the admin page calls from the "Before you start"
* section. Shows install state, links to recovery settings when active, and
* offers a one-click install from gitea when missing+configured.
*/
public static function render_status(): void {
$installed = self::is_installed();
$active = $installed && self::is_active();
$file = self::plugin_file();
$configured = WPH_Recovery_Installer::is_configured();
?>
<div class="wph-bootstrap-panel">
<h3>Recovery plugin status</h3>
<?php if ($active): ?>
<p class="wph-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>
<?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>
<?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>
<?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?');">
<?php wp_nonce_field('wph_recovery_install'); ?>
<input type="hidden" name="action" value="wph_recovery_install">
<button class="button button-primary">Install from gitea (latest)</button>
</form>
<a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck-settings')); ?>" class="button">Configure source</a>
<?php else: ?>
<p>
<a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck-settings')); ?>" class="button button-primary">Configure gitea source</a>
<em>— or install manually via Plugins → Add New → Upload Plugin.</em>
</p>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
}
}