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 / uninstall / UninstallFeedbackEmail.php

UninstallFeedbackEmail.php in 404 Solution trunk, at includes/uninstall/UninstallFeedbackEmail.php

183 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /**
9 * Composes and dispatches the deactivation feedback email.
10 *
11 * Called by FeedbackEmailFallback::sendNow() when the primary HTTP POST to
12 * the feedback transport fails (cron-context fallback path). Builds a
13 * plain-text email body from a FeedbackTransport payload plus a live
14 * diagnostic snapshot from UninstallDiagnostics, and dispatches via wp_mail().
15 *
16 * @since 4.3.0
17 */
18 class ABJ_404_Solution_UninstallFeedbackEmail {
19
20 /**
21 * Email-fallback for FeedbackTransport when the HTTP POST fails. Builds a
22 * deactivation-feedback email body from a FeedbackTransport payload and
23 * dispatches it via wp_mail(). Public because the cron-context fallback in
24 * FeedbackTransport::sendNow() invokes this for type='uninstall'.
25 *
26 * @param array<string, mixed> $payload FeedbackTransport-built payload.
27 * @return bool True if wp_mail() reported success, false otherwise.
28 */
29 public static function send(array $payload): bool {
30 global $wp_version;
31
32 $site_name = function_exists('get_bloginfo') ? (string)get_bloginfo('name') : '';
33 $rawAdminEmail = function_exists('get_option') ? get_option('admin_email') : '';
34 $admin_email = is_string($rawAdminEmail) ? $rawAdminEmail : '';
35
36 $contactEmail = isset($payload['contact_email']) && is_string($payload['contact_email']) ? $payload['contact_email'] : '';
37 $includeDiag = !empty($payload['include_diagnostics']);
38
39 $subject = sprintf('[404 Solution] Deactivation Feedback from %s', $site_name);
40
41 $body = "Deactivation feedback received:\n\n";
42 $body .= "===============================================\n";
43 $body .= "USER FEEDBACK\n";
44 $body .= "===============================================\n\n";
45 $body = self::appendUserFeedbackSections($body, $payload, $contactEmail);
46
47 if ($includeDiag) {
48 $body = self::appendDiagnosticSections($body, $payload, is_scalar($wp_version) ? (string)$wp_version : '');
49 }
50
51 $body .= "\n===============================================\n";
52 $body .= "This feedback was sent automatically when the user deactivated the plugin.\n";
53
54 $headers = array(
55 'Content-Type: text/plain; charset=UTF-8',
56 'From: ' . $site_name . ' <' . $admin_email . '>'
57 );
58 if ($contactEmail !== '') {
59 $headers[] = 'Reply-To: ' . $contactEmail;
60 }
61
62 $to = defined('ABJ404_AUTHOR_EMAIL') ? ABJ404_AUTHOR_EMAIL : '404solution@ajexperience.com';
63 return (bool) wp_mail($to, $subject, $body, $headers);
64 }
65
66 /**
67 * @param array<string, mixed> $payload
68 * @return string
69 */
70 private static function appendDiagnosticSections(string $body, array $payload, string $wpVersion): string {
71 $body .= self::formatDebugLogSection($payload);
72 $body .= "===============================================\n";
73 $body .= "DATABASE COLLATIONS\n";
74 $body .= "===============================================\n\n";
75 $body .= ABJ_404_Solution_UninstallDiagnostics::getDatabaseCollationSnapshot() . "\n\n";
76 $body .= "===============================================\n";
77 $body .= "SYSTEM INFORMATION\n";
78 $body .= "===============================================\n\n";
79
80 foreach (self::buildSystemInfo($wpVersion) as $label => $value) {
81 $body .= sprintf("%-20s: %s\n", $label, $value);
82 }
83
84 return $body;
85 }
86
87 /**
88 * @param array<string, mixed> $payload
89 * @return string
90 */
91 private static function appendUserFeedbackSections(string $body, array $payload, string $contactEmail): string {
92 $body = self::appendNamedPayloadSection($body, $payload, 'uninstall_reason', 'Reason', true);
93 $body .= self::formatSelectedIssues($payload);
94 $body = self::appendNamedPayloadSection($body, $payload, 'followup_details', 'Additional Details', false);
95 $body = self::appendNamedPayloadSection($body, $payload, 'better_plugin_name', 'Switching to', false);
96 $body = self::appendNamedPayloadSection($body, $payload, 'other_reason_text', 'Other Reason Details', false);
97
98 if ($contactEmail !== '') {
99 $body .= "User Email: " . $contactEmail . "\n\n";
100 }
101
102 return $body;
103 }
104
105 /**
106 * @param array<string, mixed> $payload
107 * @return string
108 */
109 private static function appendNamedPayloadSection(string $body, array $payload, string $key, string $label, bool $titleCase): string {
110 $value = isset($payload[$key]) && is_string($payload[$key]) ? $payload[$key] : '';
111 if ($value === '') {
112 return $body;
113 }
114
115 $displayValue = $titleCase ? ucfirst(str_replace('-', ' ', $value)) : $value;
116 return $body . $label . ": " . $displayValue . "\n\n";
117 }
118
119 /**
120 * @param array<string, mixed> $payload
121 * @return string
122 */
123 private static function formatSelectedIssues(array $payload): string {
124 $selectedIssues = isset($payload['selected_issues']) && is_string($payload['selected_issues']) ? $payload['selected_issues'] : '';
125 if ($selectedIssues === '') {
126 return '';
127 }
128
129 $body = "Specific Issues:\n";
130 foreach (explode(',', $selectedIssues) as $issue) {
131 $body .= " [x] " . ucfirst(str_replace('-', ' ', $issue)) . "\n";
132 }
133 return $body . "\n";
134 }
135
136 /**
137 * @param array<string, mixed> $payload
138 * @return string
139 */
140 private static function formatDebugLogSection(array $payload): string {
141 $debugLog = isset($payload['debug_log']) && is_string($payload['debug_log']) ? $payload['debug_log'] : '';
142 $body = "===============================================\n";
143 $body .= "PLUGIN DEBUG LOG\n";
144 $body .= "===============================================\n\n";
145 return $body . ($debugLog !== '' ? $debugLog : 'Log excerpt unavailable.') . "\n\n";
146 }
147
148 /** @return array<string, string|int|float> */
149 private static function buildSystemInfo(string $wpVersion): array {
150 $pluginStats = ABJ_404_Solution_UninstallDiagnostics::getPluginStatistics();
151 $dbInfo = ABJ_404_Solution_UninstallDiagnostics::getDatabaseInfo();
152 $contentCounts = ABJ_404_Solution_UninstallDiagnostics::getContentCounts();
153
154 return array(
155 'WordPress Version' => $wpVersion,
156 'PHP Version' => phpversion(),
157 'Plugin Version' => defined('ABJ404_VERSION') ? ABJ404_VERSION : 'Unknown',
158 'MySQL Version' => $dbInfo['version'],
159 'DB Charset' => $dbInfo['charset'],
160 'DB Collation' => $dbInfo['collation'],
161 'Multisite' => is_multisite() ? 'Yes' : 'No',
162 'Active Plugins' => ABJ_404_Solution_UninstallDiagnostics::getActivePluginsList(),
163 'Category Count' => $contentCounts['categories'],
164 'Tag Count' => $contentCounts['tags'],
165 'Total Pages' => $contentCounts['pages'],
166 'Total Posts' => $contentCounts['posts'],
167 'Redirects (active)' => $pluginStats['redirects']['all'],
168 ' - Manual' => $pluginStats['redirects']['manual'],
169 ' - Automatic' => $pluginStats['redirects']['auto'],
170 ' - Regex' => $pluginStats['redirects']['regex'],
171 ' - Trashed' => $pluginStats['redirects']['trash'],
172 'Captured 404s (active)' => $pluginStats['captured']['all'],
173 ' - New' => $pluginStats['captured']['captured'],
174 ' - Ignored' => $pluginStats['captured']['ignored'],
175 ' - Later' => $pluginStats['captured']['later'],
176 ' - Trash' => $pluginStats['captured']['trash'],
177 'Log Entries in DB' => $pluginStats['log_count'],
178 'Log Table Size' => $pluginStats['log_table_size_mb'] . ' MB',
179 'Debug File Size' => $pluginStats['debug_file_size_mb'] . ' MB',
180 );
181 }
182 }
183