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.
This commit is contained in:
@@ -11,6 +11,8 @@ add_action('admin_post_wph_refresh_checks', 'wph_handle_refresh_checks');
|
||||
add_action('admin_post_wph_download_html', 'wph_handle_download_html');
|
||||
add_action('admin_post_wph_email_report', 'wph_handle_email_report');
|
||||
add_action('admin_post_wph_step_action', 'wph_handle_step_action');
|
||||
add_action('admin_post_wph_recovery_install', 'wph_handle_recovery_install');
|
||||
add_action('admin_post_wph_save_settings', 'wph_handle_save_settings');
|
||||
add_action('admin_enqueue_scripts', 'wph_enqueue_assets');
|
||||
|
||||
function wph_register_menu(): void {
|
||||
@@ -21,6 +23,14 @@ function wph_register_menu(): void {
|
||||
'site-healthcheck',
|
||||
'wph_render_admin_page'
|
||||
);
|
||||
add_submenu_page(
|
||||
null, // hidden — reachable via direct URL
|
||||
'Site Healthcheck Settings',
|
||||
'Site Healthcheck Settings',
|
||||
'manage_options',
|
||||
'site-healthcheck-settings',
|
||||
'wph_render_settings_page'
|
||||
);
|
||||
}
|
||||
|
||||
function wph_enqueue_assets($hook): void {
|
||||
@@ -100,6 +110,11 @@ function wph_render_admin_page(): void {
|
||||
echo '<div class="wrap">';
|
||||
echo '<h1>Site Healthcheck</h1>';
|
||||
|
||||
if ($msg = get_transient('wph_install_message')) {
|
||||
delete_transient('wph_install_message');
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($msg) . '</p></div>';
|
||||
}
|
||||
|
||||
if (!$session) {
|
||||
wph_render_start_panel();
|
||||
echo '</div>';
|
||||
@@ -429,6 +444,103 @@ function wph_render_autocheck(WPH_Session $session, WPH_Step $step): void {
|
||||
<?php
|
||||
}
|
||||
|
||||
function wph_render_settings_page(): void {
|
||||
if (!current_user_can('manage_options')) wp_die('Forbidden');
|
||||
$c = WPH_Recovery_Installer::config();
|
||||
$o = WPH_Recovery_Installer::config_origin();
|
||||
$saved = isset($_GET['wph_saved']);
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>Site Healthcheck — Settings</h1>
|
||||
<p><a href="<?php echo esc_url(admin_url('tools.php?page=site-healthcheck')); ?>">← Back to healthcheck</a></p>
|
||||
|
||||
<?php if ($saved): ?><div class="notice notice-success is-dismissible"><p>Saved.</p></div><?php endif; ?>
|
||||
|
||||
<div class="wph-card">
|
||||
<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>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')); ?>">
|
||||
<?php wp_nonce_field('wph_save_settings'); ?>
|
||||
<input type="hidden" name="action" value="wph_save_settings">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="wph-host">Gitea host</label></th>
|
||||
<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']); ?>>
|
||||
<?php if ($o['host']): ?><p class="description">Set via <code>WPH_GITEA_HOST</code> constant.</p><?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wph-owner">Owner</label></th>
|
||||
<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']); ?>>
|
||||
<?php if ($o['owner']): ?><p class="description">Set via <code>WPH_GITEA_OWNER</code> constant.</p><?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wph-repo">Repo</label></th>
|
||||
<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']); ?>>
|
||||
<?php if ($o['repo']): ?><p class="description">Set via <code>WPH_GITEA_REPO</code> constant.</p><?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wph-token">Token</label></th>
|
||||
<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']); ?>>
|
||||
<?php if ($o['token']): ?><p class="description">Set via <code>WPH_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; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p><button class="button button-primary">Save settings</button></p>
|
||||
</form>
|
||||
|
||||
<?php if (WPH_Recovery_Installer::is_configured() && !WPH_Recovery_Bootstrap::is_installed()): ?>
|
||||
<hr>
|
||||
<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?');">
|
||||
<?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>
|
||||
<?php elseif (WPH_Recovery_Bootstrap::is_installed()): ?>
|
||||
<hr>
|
||||
<p class="wph-ok">✓ Site Recovery is already installed at <code><?php echo esc_html((string) WPH_Recovery_Bootstrap::plugin_file()); ?></code>.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function wph_handle_save_settings(): void {
|
||||
if (!current_user_can('manage_options')) wp_die('Forbidden');
|
||||
check_admin_referer('wph_save_settings');
|
||||
WPH_Recovery_Installer::save_options([
|
||||
'host' => (string) wp_unslash($_POST['host'] ?? ''),
|
||||
'owner' => (string) wp_unslash($_POST['owner'] ?? ''),
|
||||
'repo' => (string) wp_unslash($_POST['repo'] ?? ''),
|
||||
'token' => (string) wp_unslash($_POST['token'] ?? ''),
|
||||
]);
|
||||
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck-settings&wph_saved=1'));
|
||||
exit;
|
||||
}
|
||||
|
||||
function wph_handle_recovery_install(): void {
|
||||
if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) wp_die('Forbidden');
|
||||
check_admin_referer('wph_recovery_install');
|
||||
@set_time_limit(120);
|
||||
$result = WPH_Recovery_Installer::install_and_activate();
|
||||
if (is_wp_error($result)) {
|
||||
wp_die('Install failed: ' . esc_html($result->get_error_message()) . ' <p><a href="' . esc_url(admin_url('tools.php?page=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);
|
||||
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck'));
|
||||
exit;
|
||||
}
|
||||
|
||||
function wph_handle_step_action(): void {
|
||||
if (!current_user_can('manage_options')) wp_die('Forbidden');
|
||||
$step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : '';
|
||||
|
||||
@@ -11,21 +11,41 @@ if (!defined('ABSPATH')) exit;
|
||||
*/
|
||||
final class WPH_Recovery_Bootstrap {
|
||||
|
||||
public const RECOVERY_SLUG = 'site-recovery';
|
||||
public const RECOVERY_MAIN = 'site-recovery/site-recovery.php';
|
||||
/**
|
||||
* 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';
|
||||
|
||||
public static function is_installed(): bool {
|
||||
/** 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';
|
||||
}
|
||||
return array_key_exists(self::RECOVERY_MAIN, get_plugins());
|
||||
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(self::RECOVERY_MAIN);
|
||||
return is_plugin_active($file);
|
||||
}
|
||||
|
||||
public static function recovery_admin_url(): string {
|
||||
@@ -34,23 +54,37 @@ final class WPH_Recovery_Bootstrap {
|
||||
|
||||
/**
|
||||
* Render a status panel that the admin page calls from the "Before you start"
|
||||
* section. Phase-1: report state + link to manage. Phase-3 may add a
|
||||
* one-click installer from a private URL.
|
||||
* 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.
|
||||
<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. <a href="<?php echo esc_url(admin_url('plugins.php')); ?>">Activate it</a> before starting work.</p>
|
||||
<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>
|
||||
<p class="description">Phase-1: install manually via <em>Plugins → Add New → Upload Plugin</em>. A private update channel for one-click install lands in hc-5ix.27.</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
|
||||
|
||||
182
includes/recovery-installer.php
Normal file
182
includes/recovery-installer.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
/**
|
||||
* One-click install of wp-site-recovery from a private Gitea repo.
|
||||
*
|
||||
* Config sources (in order — constants beat options):
|
||||
* define('WPH_GITEA_HOST', 'https://git.example.com');
|
||||
* define('WPH_GITEA_OWNER', 'steve');
|
||||
* define('WPH_GITEA_REPO', 'site-recovery');
|
||||
* define('WPH_GITEA_TOKEN', 'xxx'); // read-only PAT, scoped to this repo
|
||||
*
|
||||
* …or set the same values via the settings panel (stored in WP options).
|
||||
*
|
||||
* Ref resolution (in order):
|
||||
* 1. Latest release — /api/v1/repos/:o/:r/releases/latest → tag_name
|
||||
* 2. Latest tag — /api/v1/repos/:o/:r/tags?limit=1 → name
|
||||
* 3. main branch HEAD
|
||||
*
|
||||
* Install path:
|
||||
* GET https://<host>/<owner>/<repo>/archive/<ref>.zip with Authorization: token <pat>
|
||||
* → Plugin_Upgrader::install() with upgrader_source_selection filter renaming
|
||||
* the unpacked folder to 'site-recovery' so the destination is consistent.
|
||||
* → activate_plugin() against the freshly-installed plugin file.
|
||||
*/
|
||||
final class WPH_Recovery_Installer {
|
||||
|
||||
private const OPT_HOST = 'wph_gitea_host';
|
||||
private const OPT_OWNER = 'wph_gitea_owner';
|
||||
private const OPT_REPO = 'wph_gitea_repo';
|
||||
private const OPT_TOKEN = 'wph_gitea_token';
|
||||
|
||||
public static function config(): array {
|
||||
return [
|
||||
'host' => defined('WPH_GITEA_HOST') ? (string) WPH_GITEA_HOST : (string) get_option(self::OPT_HOST, ''),
|
||||
'owner' => defined('WPH_GITEA_OWNER') ? (string) WPH_GITEA_OWNER : (string) get_option(self::OPT_OWNER, ''),
|
||||
'repo' => defined('WPH_GITEA_REPO') ? (string) WPH_GITEA_REPO : (string) get_option(self::OPT_REPO, ''),
|
||||
'token' => defined('WPH_GITEA_TOKEN') ? (string) WPH_GITEA_TOKEN : (string) get_option(self::OPT_TOKEN, ''),
|
||||
];
|
||||
}
|
||||
|
||||
/** Returns whether each config field comes from a constant (true) or an option (false). */
|
||||
public static function config_origin(): array {
|
||||
return [
|
||||
'host' => defined('WPH_GITEA_HOST'),
|
||||
'owner' => defined('WPH_GITEA_OWNER'),
|
||||
'repo' => defined('WPH_GITEA_REPO'),
|
||||
'token' => defined('WPH_GITEA_TOKEN'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function is_configured(): bool {
|
||||
$c = self::config();
|
||||
return $c['host'] && $c['owner'] && $c['repo'] && $c['token'];
|
||||
}
|
||||
|
||||
public static function save_options(array $input): void {
|
||||
// Constants win — only persist fields not already constant-overridden.
|
||||
$origin = self::config_origin();
|
||||
if (!$origin['host']) update_option(self::OPT_HOST, esc_url_raw((string) ($input['host'] ?? '')), false);
|
||||
if (!$origin['owner']) update_option(self::OPT_OWNER, sanitize_text_field((string) ($input['owner'] ?? '')), false);
|
||||
if (!$origin['repo']) update_option(self::OPT_REPO, sanitize_text_field((string) ($input['repo'] ?? '')), false);
|
||||
if (!$origin['token']) update_option(self::OPT_TOKEN, sanitize_text_field((string) ($input['token'] ?? '')), false);
|
||||
}
|
||||
|
||||
/** Find the ref to install: release → tag → main. */
|
||||
public static function resolve_latest_ref() {
|
||||
$c = self::config();
|
||||
if (!self::is_configured()) {
|
||||
return new WP_Error('not_configured', 'Gitea host/owner/repo/token not configured.');
|
||||
}
|
||||
$base = rtrim($c['host'], '/');
|
||||
$auth = ['Authorization' => 'token ' . $c['token'], 'Accept' => 'application/json'];
|
||||
|
||||
$r = wp_remote_get("$base/api/v1/repos/{$c['owner']}/{$c['repo']}/releases/latest", ['timeout' => 10, 'headers' => $auth]);
|
||||
if (!is_wp_error($r) && (int) wp_remote_retrieve_response_code($r) === 200) {
|
||||
$body = json_decode((string) wp_remote_retrieve_body($r), true);
|
||||
if (is_array($body) && !empty($body['tag_name'])) {
|
||||
return ['type' => 'release', 'ref' => (string) $body['tag_name']];
|
||||
}
|
||||
}
|
||||
|
||||
$r = wp_remote_get("$base/api/v1/repos/{$c['owner']}/{$c['repo']}/tags?limit=1", ['timeout' => 10, 'headers' => $auth]);
|
||||
if (!is_wp_error($r) && (int) wp_remote_retrieve_response_code($r) === 200) {
|
||||
$body = json_decode((string) wp_remote_retrieve_body($r), true);
|
||||
if (is_array($body) && !empty($body[0]['name'])) {
|
||||
return ['type' => 'tag', 'ref' => (string) $body[0]['name']];
|
||||
}
|
||||
}
|
||||
|
||||
return ['type' => 'branch', 'ref' => 'main'];
|
||||
}
|
||||
|
||||
/** Run install + activate. Returns true on success or a WP_Error. */
|
||||
public static function install_and_activate() {
|
||||
if (!self::is_configured()) {
|
||||
return new WP_Error('not_configured', 'Gitea is not configured — fill in host, owner, repo and a read-only token.');
|
||||
}
|
||||
|
||||
$ref_info = self::resolve_latest_ref();
|
||||
if (is_wp_error($ref_info)) return $ref_info;
|
||||
$c = self::config();
|
||||
$url = rtrim($c['host'], '/') . "/{$c['owner']}/{$c['repo']}/archive/{$ref_info['ref']}.zip";
|
||||
|
||||
// Inject auth header on outbound requests to this host so download_url() succeeds.
|
||||
$auth_filter = function ($args, $req_url) use ($c) {
|
||||
if (strpos((string) $req_url, rtrim($c['host'], '/')) === 0) {
|
||||
if (!isset($args['headers']) || !is_array($args['headers'])) $args['headers'] = [];
|
||||
$args['headers']['Authorization'] = 'token ' . $c['token'];
|
||||
}
|
||||
return $args;
|
||||
};
|
||||
add_filter('http_request_args', $auth_filter, 10, 2);
|
||||
|
||||
// Force the unpacked folder name to 'site-recovery' regardless of the
|
||||
// gitea archive's wrapper folder (which gets a -<ref> suffix).
|
||||
$rename_filter = function ($source, $remote_source, $upgrader, $hook_extra) {
|
||||
if (!is_string($source) || !is_dir($source)) return $source;
|
||||
$name = basename(rtrim($source, '/\\'));
|
||||
if ($name === 'site-recovery') return $source;
|
||||
global $wp_filesystem;
|
||||
$new = trailingslashit(dirname($source)) . 'site-recovery';
|
||||
if ($wp_filesystem->exists($new)) $wp_filesystem->delete($new, true);
|
||||
$wp_filesystem->move($source, $new);
|
||||
return $new;
|
||||
};
|
||||
add_filter('upgrader_source_selection', $rename_filter, 10, 4);
|
||||
|
||||
// Load WP's upgrader machinery.
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/misc.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
if (!class_exists('Automatic_Upgrader_Skin')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php';
|
||||
}
|
||||
|
||||
// Need a writable FS first.
|
||||
$creds = request_filesystem_credentials('', '', false, false, null);
|
||||
if (!$creds || !WP_Filesystem($creds)) {
|
||||
remove_filter('http_request_args', $auth_filter, 10);
|
||||
remove_filter('upgrader_source_selection', $rename_filter, 10);
|
||||
return new WP_Error('fs', 'Could not initialise WP_Filesystem.');
|
||||
}
|
||||
|
||||
$skin = new Automatic_Upgrader_Skin();
|
||||
$upgrader = new Plugin_Upgrader($skin);
|
||||
$result = $upgrader->install($url);
|
||||
|
||||
remove_filter('http_request_args', $auth_filter, 10);
|
||||
remove_filter('upgrader_source_selection', $rename_filter, 10);
|
||||
|
||||
if (is_wp_error($result)) return $result;
|
||||
if ($result === false) {
|
||||
$msgs = method_exists($skin, 'get_upgrade_messages') ? implode(' · ', $skin->get_upgrade_messages()) : 'unknown';
|
||||
return new WP_Error('install_failed', 'Install returned false. Upgrader messages: ' . $msgs);
|
||||
}
|
||||
|
||||
// Find the freshly-installed plugin and activate it.
|
||||
if (method_exists('WPH_Recovery_Bootstrap', 'plugin_file')) {
|
||||
// Bust the static cache so we re-scan get_plugins().
|
||||
$rc = new ReflectionClass('WPH_Recovery_Bootstrap');
|
||||
// No public reset; just call get_plugins() in our context — Bootstrap will re-scan
|
||||
// because get_plugins() builds a fresh array on each call.
|
||||
wp_cache_delete('plugins', 'plugins');
|
||||
}
|
||||
if (function_exists('wp_clean_plugins_cache')) wp_clean_plugins_cache();
|
||||
if (!function_exists('get_plugins')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$plugin_file = null;
|
||||
foreach (get_plugins() as $file => $meta) {
|
||||
if (($meta['Name'] ?? '') === 'Site Recovery') { $plugin_file = $file; break; }
|
||||
}
|
||||
if (!$plugin_file) {
|
||||
return new WP_Error('post_install', 'Install completed but cannot locate the Site Recovery plugin file.');
|
||||
}
|
||||
$activate = activate_plugin($plugin_file);
|
||||
if (is_wp_error($activate)) return $activate;
|
||||
|
||||
return ['plugin_file' => $plugin_file, 'ref' => $ref_info];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user