Email delivery test step + step-form extension point
Demonstrates the drop-in pattern: adding a step with a form is a single new file under includes/steps/, plus one small extension to the base abstraction. WPH_Step gains: - render_extra($session_state) — emit extra HTML inside the step card - handle_action($name, $input) — handle a step-specific POST and return a finding to record New generic admin-post handler wph_step_action routes form submissions to the matching step's handle_action() with nonce verification. The email step (includes/steps/115-email.php): - Detects 7 known SMTP plugins, surfaces default From address - Renders a To: input prefilled with the current admin's email - Sends via wp_mail() with wp_mail_failed capture so failures show the underlying error - Records the outcome as a 'last_send' finding so it appears on the step card, in the summary, and in the downloadable report
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
{"_type":"issue","id":"hc-5ix.4","title":"Session data model: option-backed in-progress healthcheck record","description":"One in-progress session per site at a time. Stored in a custom option (or a CPT — pick during implementation). Shape: id, started_at, finished_at, technician_id, site_url_snapshot, per_step_state { status: not_started|in_progress|done|skipped|blocked|n_a, notes, completed_at }. Designed so phase-3 automation can attach structured findings later.","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:11Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:27Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:27Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.4","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:11Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.4","title":"Session data model: option-backed in-progress healthcheck record","description":"One in-progress session per site at a time. Stored in a custom option (or a CPT — pick during implementation). Shape: id, started_at, finished_at, technician_id, site_url_snapshot, per_step_state { status: not_started|in_progress|done|skipped|blocked|n_a, notes, completed_at }. Designed so phase-3 automation can attach structured findings later.","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:11Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:27Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:27Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.4","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:11Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.2","title":"Plugin skeleton: header, activation hook, deactivation hook, admin menu (Tools → Site Healthcheck), capability gate","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:10Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:26Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:26Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.2","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:09Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.2","title":"Plugin skeleton: header, activation hook, deactivation hook, admin menu (Tools → Site Healthcheck), capability gate","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:10Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:26Z","started_at":"2026-06-11T14:41:27Z","closed_at":"2026-06-11T14:49:26Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.2","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:09Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.1","title":"Bootstrap: trigger install of wp-site-recovery plugin as step 0","description":"Healthcheck plugin should check on activation if site-recovery is installed; if not, fetch and install from a known URL/ZIP and activate it. Block stepper from starting until recovery is in place.","notes":"Detection half done (status panel + manual link). Auto-install of recovery plugin from a private URL is the wp-site-recovery side of hc-5ix.27 — closing this as 'detection complete'.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:40Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:03:02Z","closed_at":"2026-06-11T15:03:02Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.1","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T12:26:39Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.1","title":"Bootstrap: trigger install of wp-site-recovery plugin as step 0","description":"Healthcheck plugin should check on activation if site-recovery is installed; if not, fetch and install from a known URL/ZIP and activate it. Block stepper from starting until recovery is in place.","notes":"Detection half done (status panel + manual link). Auto-install of recovery plugin from a private URL is the wp-site-recovery side of hc-5ix.27 — closing this as 'detection complete'.","status":"closed","priority":1,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:40Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T15:03:02Z","closed_at":"2026-06-11T15:03:02Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.1","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T12:26:39Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
{"_type":"issue","id":"hc-5ix.28","title":"Step — Email Delivery Test (send wp_mail to chosen address)","description":"Add a new step that sends a test email via wp_mail() to an address the technician types in. Should detect SMTP plugins in use, surface wp_mail_failed errors, and store the result as a finding so it lands in the report.\n\nDrives a small architectural extension: WPH_Step gains optional render_extra() (output HTML inside the card) and handle_action() (handle a step-specific POST), plus a generic wph_step_action admin-post handler. Demonstrates the drop-in extensibility — adding the step is a single file plus a tiny hook addition.","notes":"Added in commit (next). New file includes/steps/115-email.php — drop-in step, no other source changes beyond the small WPH_Step extension (render_extra + handle_action) and one admin-post handler (wph_step_action). Demonstrates the extensibility pattern: a step can declare its own form + handler without touching any other file.\n\nSmoke-tested on testsite: step appears in correct slot (between small_fixes and wrap_up), autocheck surfaces mailer detection (PHP mail vs. 7 known SMTP plugins) and default From address, render_extra outputs a To: input prefilled with current user's email, handle_action rejects invalid addresses and successfully sends via wp_mail (caught by Local's MailHog).","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T17:43:34Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T17:45:47Z","closed_at":"2026-06-11T17:45:47Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.28","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T18:43:33Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.9","title":"Smoke test on testsite: full end-to-end run through the checklist","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:15Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.9","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.9","title":"Smoke test on testsite: full end-to-end run through the checklist","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"task","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:15Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.9","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix.8","title":"README + install instructions + screenshots placeholder","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"chore","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:14Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.8","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix.8","title":"README + install instructions + screenshots placeholder","notes":"Built in phase-1 scaffold; passing lint + WP-eval end-to-end smoke test on testsite.","status":"closed","priority":2,"issue_type":"chore","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T14:36:14Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T14:49:29Z","closed_at":"2026-06-11T14:49:29Z","labels":["phase-1"],"dependencies":[{"issue_id":"hc-5ix.8","depends_on_id":"hc-5ix","type":"parent-child","created_at":"2026-06-11T15:36:14Z","created_by":"Steve Hanlon","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_type":"issue","id":"hc-5ix","title":"Build WordPress Healthcheck plugin (stepper through steps.md)","description":"Plugin that walks a technician through the WordPress healthcheck steps documented in steps.md. Independent of the recovery plugin (which it depends on as step 0).","status":"open","priority":2,"issue_type":"epic","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:32Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T11:26:32Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"_type":"issue","id":"hc-5ix","title":"Build WordPress Healthcheck plugin (stepper through steps.md)","description":"Plugin that walks a technician through the WordPress healthcheck steps documented in steps.md. Independent of the recovery plugin (which it depends on as step 0).","status":"open","priority":2,"issue_type":"epic","owner":"steve@hanlon.co.uk","created_at":"2026-06-11T11:26:32Z","created_by":"Steve Hanlon","updated_at":"2026-06-11T11:26:32Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ add_action('admin_post_wph_download_report', 'wph_handle_download_report');
|
|||||||
add_action('admin_post_wph_refresh_checks', 'wph_handle_refresh_checks');
|
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_download_html', 'wph_handle_download_html');
|
||||||
add_action('admin_post_wph_email_report', 'wph_handle_email_report');
|
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_enqueue_scripts', 'wph_enqueue_assets');
|
add_action('admin_enqueue_scripts', 'wph_enqueue_assets');
|
||||||
|
|
||||||
function wph_register_menu(): void {
|
function wph_register_menu(): void {
|
||||||
@@ -287,6 +288,7 @@ function wph_render_step_card(WPH_Session $session, WPH_Step $step): void {
|
|||||||
<div class="wph-escalation"><?php echo esc_html($esc); ?></div>
|
<div class="wph-escalation"><?php echo esc_html($esc); ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php wph_render_autocheck($session, $step); ?>
|
<?php wph_render_autocheck($session, $step); ?>
|
||||||
|
<?php $step->render_extra($session->data()); ?>
|
||||||
<p>
|
<p>
|
||||||
<label>
|
<label>
|
||||||
<strong>Status:</strong>
|
<strong>Status:</strong>
|
||||||
@@ -427,6 +429,41 @@ function wph_render_autocheck(WPH_Session $session, WPH_Step $step): void {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wph_handle_step_action(): void {
|
||||||
|
if (!current_user_can('manage_options')) wp_die('Forbidden');
|
||||||
|
$step_id = isset($_POST['step']) ? sanitize_key((string) $_POST['step']) : '';
|
||||||
|
$action_name = isset($_POST['step_action']) ? sanitize_key((string) $_POST['step_action']) : '';
|
||||||
|
check_admin_referer('wph_step_action_' . $step_id . '_' . $action_name);
|
||||||
|
$session = WPH_Session::current();
|
||||||
|
if (!$session || $session->is_finished()) wp_die('No active session.');
|
||||||
|
$step = WPH_Steps::instance()->get($step_id);
|
||||||
|
if (!$step) wp_die('Unknown step.');
|
||||||
|
|
||||||
|
// Pass POST through unslashed so handlers see the raw user input.
|
||||||
|
$input = wp_unslash($_POST);
|
||||||
|
@set_time_limit(60);
|
||||||
|
$finding = $step->handle_action($action_name, is_array($input) ? $input : []);
|
||||||
|
|
||||||
|
if (is_array($finding)) {
|
||||||
|
// Append (or replace by id) onto this step's stored findings.
|
||||||
|
$existing = $session->get_autocheck($step_id);
|
||||||
|
$findings = $existing['findings'] ?? [];
|
||||||
|
$replaced = false;
|
||||||
|
foreach ($findings as $i => $f) {
|
||||||
|
if (($f['id'] ?? '') === ($finding['id'] ?? '')) {
|
||||||
|
$findings[$i] = $finding;
|
||||||
|
$replaced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$replaced) $findings[] = $finding;
|
||||||
|
$session->set_autocheck($step_id, $findings);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_safe_redirect(admin_url('tools.php?page=site-healthcheck#step-' . rawurlencode($step_id)));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
function wph_handle_refresh_checks(): void {
|
function wph_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']) : '';
|
||||||
|
|||||||
@@ -55,4 +55,21 @@ abstract class WPH_Step {
|
|||||||
$r = new ReflectionMethod($this, 'autocheck');
|
$r = new ReflectionMethod($this, 'autocheck');
|
||||||
return $r->getDeclaringClass()->getName() !== WPH_Step::class;
|
return $r->getDeclaringClass()->getName() !== WPH_Step::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional: render extra UI inside this step's card (forms, inputs, etc.).
|
||||||
|
* Default: nothing. Override when the step needs interactive controls
|
||||||
|
* beyond the standard status + notes pattern.
|
||||||
|
*/
|
||||||
|
public function render_extra(array $session_state): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional: handle a step-specific POST (submitted from a form rendered by
|
||||||
|
* render_extra). Return a finding to append to the autocheck list, or null
|
||||||
|
* to do nothing.
|
||||||
|
*
|
||||||
|
* @param string $action_name name from the form's step_action field
|
||||||
|
* @param array<string,mixed>$input $_POST contents (sanitised by caller)
|
||||||
|
*/
|
||||||
|
public function handle_action(string $action_name, array $input): ?array { return null; }
|
||||||
}
|
}
|
||||||
|
|||||||
127
includes/steps/115-email.php
Normal file
127
includes/steps/115-email.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('ABSPATH')) exit;
|
||||||
|
|
||||||
|
return new class extends WPH_Step {
|
||||||
|
|
||||||
|
public function id(): string { return 'email_test'; }
|
||||||
|
public function title(): string { return 'Step — Email Delivery Test'; }
|
||||||
|
public function blurb(): string {
|
||||||
|
return 'Send a test email via the site\'s default mailer (wp_mail) to verify outbound delivery is working. Useful for spotting hosts that block PHP mail, or SMTP plugins that have lost auth.';
|
||||||
|
}
|
||||||
|
public function sub_items(): array {
|
||||||
|
return [
|
||||||
|
'Enter a destination address (your own inbox is fine) and click Send',
|
||||||
|
'Confirm the email arrives — check spam folder if not',
|
||||||
|
'If using an SMTP plugin (WP Mail SMTP, FluentSMTP), verify its log shows the message',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inform the user which mailer would be used, before they send. */
|
||||||
|
public function autocheck(array $session_state): array {
|
||||||
|
$f = [];
|
||||||
|
|
||||||
|
// Detect SMTP / mail-routing plugins.
|
||||||
|
$smtp_plugins = [
|
||||||
|
'wp-mail-smtp/wp_mail_smtp.php' => 'WP Mail SMTP',
|
||||||
|
'fluent-smtp/fluent-smtp.php' => 'FluentSMTP',
|
||||||
|
'post-smtp/postman-smtp.php' => 'Post SMTP',
|
||||||
|
'easy-wp-smtp/easy-wp-smtp.php' => 'Easy WP SMTP',
|
||||||
|
'gmail-smtp/main.php' => 'Gmail SMTP',
|
||||||
|
'wp-ses/wp-ses.php' => 'WP Offload SES',
|
||||||
|
'sendgrid-email-delivery-simplified/wpsendgrid.php' => 'SendGrid',
|
||||||
|
];
|
||||||
|
$found = [];
|
||||||
|
if (!function_exists('is_plugin_active')) require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
|
foreach ($smtp_plugins as $file => $label) {
|
||||||
|
if (is_plugin_active($file)) $found[] = $label;
|
||||||
|
}
|
||||||
|
$f[] = $this->finding(
|
||||||
|
'mailer',
|
||||||
|
$found ? 'ok' : 'info',
|
||||||
|
'Mailer',
|
||||||
|
$found ? implode(', ', $found) : 'PHP mail() (default)',
|
||||||
|
$found ? 'Test will route through this plugin.' : 'No SMTP plugin detected — wp_mail will use PHP\'s built-in mail() function, which is often blocked on shared hosts.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default From address (filter into wp_mail_from)
|
||||||
|
$from = apply_filters('wp_mail_from', '');
|
||||||
|
if (!$from) {
|
||||||
|
$sitename = parse_url(get_site_url(), PHP_URL_HOST) ?: 'localhost';
|
||||||
|
$sitename = preg_replace('/^www\./', '', strtolower($sitename));
|
||||||
|
$from = 'wordpress@' . $sitename;
|
||||||
|
}
|
||||||
|
$f[] = $this->finding('from_address', 'info', 'Default From', $from, 'wp_mail_from filter value.');
|
||||||
|
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Render the send-test form inside the step card. */
|
||||||
|
public function render_extra(array $session_state): void {
|
||||||
|
$current = wp_get_current_user();
|
||||||
|
$default_to = $current && $current->user_email ? $current->user_email : get_option('admin_email');
|
||||||
|
?>
|
||||||
|
<div class="wph-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')); ?>">
|
||||||
|
<?php wp_nonce_field('wph_step_action_email_test_send_test'); ?>
|
||||||
|
<input type="hidden" name="action" value="wph_step_action">
|
||||||
|
<input type="hidden" name="step" value="email_test">
|
||||||
|
<input type="hidden" name="step_action" value="send_test">
|
||||||
|
<p style="margin:.2rem 0 .5rem"><strong>Send test email</strong></p>
|
||||||
|
<label>
|
||||||
|
To:
|
||||||
|
<input type="email" name="to" required value="<?php echo esc_attr($default_to); ?>" style="min-width:18em">
|
||||||
|
</label>
|
||||||
|
<button class="button button-primary">Send</button>
|
||||||
|
<p class="description" style="margin:.4rem 0 0">Uses <code>wp_mail()</code> with the default From address. Result is appended to the findings above.</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send the test mail and return a finding to record the result. */
|
||||||
|
public function handle_action(string $action_name, array $input): ?array {
|
||||||
|
if ($action_name !== 'send_test') return null;
|
||||||
|
$to = isset($input['to']) ? sanitize_email((string) $input['to']) : '';
|
||||||
|
if (!is_email($to)) {
|
||||||
|
return $this->finding('last_send', 'bad', 'Last test send', 'invalid address', 'The destination address was not a valid email.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture any wp_mail_failed error so we can surface the underlying reason.
|
||||||
|
$error_msg = '';
|
||||||
|
$capture = function ($wp_error) use (&$error_msg) {
|
||||||
|
if (is_object($wp_error) && method_exists($wp_error, 'get_error_message')) {
|
||||||
|
$error_msg = (string) $wp_error->get_error_message();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add_action('wp_mail_failed', $capture);
|
||||||
|
|
||||||
|
$host = parse_url(get_site_url(), PHP_URL_HOST) ?: 'site';
|
||||||
|
$subject = '[Site Healthcheck] Test from ' . $host . ' — ' . date('Y-m-d H:i');
|
||||||
|
$body = "This is a test email sent from the Site Healthcheck plugin.\n\n"
|
||||||
|
. "Site: " . get_site_url() . "\n"
|
||||||
|
. "Sent: " . date('Y-m-d H:i:s') . "\n"
|
||||||
|
. "Technician: " . (wp_get_current_user()->user_login ?? '?') . "\n\n"
|
||||||
|
. "If you received this, wp_mail() is working from this site.";
|
||||||
|
|
||||||
|
$sent = wp_mail($to, $subject, $body);
|
||||||
|
remove_action('wp_mail_failed', $capture);
|
||||||
|
|
||||||
|
$time = date('Y-m-d H:i');
|
||||||
|
if ($sent) {
|
||||||
|
return $this->finding(
|
||||||
|
'last_send',
|
||||||
|
'ok',
|
||||||
|
'Last test send',
|
||||||
|
'sent to ' . $to . ' at ' . $time,
|
||||||
|
'wp_mail() returned true. Confirm receipt in the inbox (check spam too).'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->finding(
|
||||||
|
'last_send',
|
||||||
|
'bad',
|
||||||
|
'Last test send',
|
||||||
|
'failed at ' . $time,
|
||||||
|
$error_msg ?: 'wp_mail() returned false but no error was captured. Check the host\'s mail logs.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user