PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / feedback / FeedbackEmailFallback.php

FeedbackEmailFallback.php in 404 Solution trunk, at includes/feedback/FeedbackEmailFallback.php

127 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 require_once __DIR__ . '/FeedbackTransportLog.php';
8
9 /**
10 * Last-resort wp_mail() transport for feedback reports. Used when the HTTP
11 * POST in FeedbackHttpClient fails. Routes to type-specific email body
12 * builders so the email and HTTP transports share a single source of truth
13 * for what the developer receives.
14 *
15 * - 'uninstall' delegates to UninstallModal::sendFeedbackEmail
16 * - 'error'/'heartbeat' delegates to Logging::emailLogFileToDeveloper
17 * - 'support_request' built inline (user message + reply email + standard
18 * diagnostic block + log excerpt)
19 *
20 * Also owns the development-environment detection that FeedbackPayloadBuilder
21 * uses to tag local reports.
22 */
23 class ABJ_404_Solution_FeedbackEmailFallback {
24
25 /**
26 * Send the payload via wp_mail(), routing to the type-specific body
27 * builder. Returns true on send success.
28 *
29 * @param array<string, mixed> $payload
30 * @param string $type
31 * @return bool
32 */
33 public static function send(array $payload, string $type): bool {
34 if (!function_exists('wp_mail')) {
35 return false;
36 }
37 if ($type === 'uninstall' && class_exists('ABJ_404_Solution_UninstallModal')) {
38 return ABJ_404_Solution_UninstallModal::sendFeedbackEmail($payload);
39 }
40 if ($type === 'support_request') {
41 return self::supportRequest($payload);
42 }
43 if (($type === 'error' || $type === 'heartbeat') && function_exists('abj_service_optional')) {
44 try {
45 $logger = abj_service_optional('logging');
46 if (is_object($logger) && method_exists($logger, 'emailLogFileToDeveloper')) {
47 return (bool) $logger->emailLogFileToDeveloper($payload);
48 }
49 } catch (\Throwable $e) {
50 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackEmailFallback delegate (' . $type . ') failed: ' . $e->getMessage());
51 }
52 }
53 $to = defined('ABJ404_AUTHOR_EMAIL') ? ABJ404_AUTHOR_EMAIL : '404solution@ajexperience.com';
54 $version = defined('ABJ404_VERSION') ? ABJ404_VERSION : '';
55 $subject = sprintf('[404 Solution] %s report (HTTP fallback) v%s', $type, $version);
56 $json = function_exists('wp_json_encode') ? wp_json_encode($payload, JSON_PRETTY_PRINT) : json_encode($payload, JSON_PRETTY_PRINT);
57 $body = is_string($json) ? $json : '';
58 $headers = array('Content-Type: text/plain; charset=UTF-8');
59 $result = wp_mail($to, $subject, $body, $headers);
60 return (bool)$result;
61 }
62
63 /**
64 * Build the wp_mail() body for type='support_request' fallbacks. The
65 * user-facing fields (their message and reply address) lead the body so
66 * a reviewer can act on the request without scrolling past the
67 * diagnostic block. The standard payload dump and any captured log
68 * excerpt follow as appendices.
69 *
70 * @param array<string, mixed> $payload
71 * @return bool
72 */
73 private static function supportRequest(array $payload): bool {
74 $to = defined('ABJ404_AUTHOR_EMAIL') ? ABJ404_AUTHOR_EMAIL : '404solution@ajexperience.com';
75 $version = defined('ABJ404_VERSION') ? ABJ404_VERSION : '';
76 $subject = sprintf('[404 Solution] Support request v%s', $version);
77
78 $userMessage = isset($payload['user_message']) && is_scalar($payload['user_message'])
79 ? (string)$payload['user_message'] : '';
80 $replyEmail = isset($payload['reply_email']) && is_scalar($payload['reply_email'])
81 ? (string)$payload['reply_email'] : '';
82 $triggeredFrom = isset($payload['triggered_from']) && is_scalar($payload['triggered_from'])
83 ? (string)$payload['triggered_from'] : '';
84 $logExcerpt = isset($payload['debug_log_excerpt']) && is_scalar($payload['debug_log_excerpt'])
85 ? (string)$payload['debug_log_excerpt'] : '';
86
87 $bodyLines = array();
88 $bodyLines[] = '=== USER SUPPORT REQUEST ===';
89 $bodyLines[] = '';
90 $bodyLines[] = 'Reply-To: ' . ($replyEmail !== '' ? $replyEmail : '(not provided)');
91 $bodyLines[] = 'Triggered from: ' . ($triggeredFrom !== '' ? $triggeredFrom : '(unknown)');
92 $bodyLines[] = '';
93 $bodyLines[] = '--- User message ---';
94 $bodyLines[] = $userMessage !== '' ? $userMessage : '(no message provided)';
95 $bodyLines[] = '';
96 $bodyLines[] = '=== DIAGNOSTICS ===';
97 $bodyLines[] = '';
98 $scalar = static function ($v, string $fallback = ''): string {
99 return is_scalar($v) ? (string)$v : $fallback;
100 };
101 $bodyLines[] = 'Plugin version: ' . $scalar($payload['plugin_version'] ?? null);
102 $bodyLines[] = 'PHP version: ' . $scalar($payload['php_version'] ?? null, PHP_VERSION);
103 $bodyLines[] = 'WP version: ' . $scalar($payload['wp_version'] ?? null);
104 $bodyLines[] = 'DB: ' . $scalar($payload['db_type'] ?? null) . ' ' . $scalar($payload['db_version'] ?? null);
105 $bodyLines[] = 'Site URL: ' . $scalar($payload['site_url'] ?? null);
106 $bodyLines[] = 'Multisite: ' . (!empty($payload['is_multisite']) ? 'yes' : 'no');
107 $bodyLines[] = '';
108 if ($logExcerpt !== '') {
109 $bodyLines[] = '=== DEBUG LOG EXCERPT ===';
110 $bodyLines[] = '';
111 $bodyLines[] = $logExcerpt;
112 $bodyLines[] = '';
113 }
114 $bodyLines[] = '=== FULL PAYLOAD (JSON) ===';
115 $json = function_exists('wp_json_encode') ? wp_json_encode($payload, JSON_PRETTY_PRINT) : json_encode($payload, JSON_PRETTY_PRINT);
116 $bodyLines[] = is_string($json) ? $json : '';
117
118 $body = implode("\n", $bodyLines);
119 $headers = array('Content-Type: text/plain; charset=UTF-8');
120 if ($replyEmail !== '') {
121 $headers[] = 'Reply-To: ' . $replyEmail;
122 }
123 $result = wp_mail($to, $subject, $body, $headers);
124 return (bool)$result;
125 }
126 }
127