PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.3.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.3.1
5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 All 38 releases
double-opt-in / src / Health / SiteHealthIntegration.php

SiteHealthIntegration.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.3.1, at src/Health/SiteHealthIntegration.php

256 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Surfaces health check results where operators actually look.
4 *
5 * @package Forge12\DoubleOptIn\Health
6 * @since 5.3.0
7 */
8
9 declare( strict_types=1 );
10
11 namespace Forge12\DoubleOptIn\Health;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Three surfaces, one source of truth:
19 *
20 * - **Site Health → Status** (`site_status_tests`): the canonical place
21 * a WordPress operator — or a support agent talking one through a
22 * problem — goes to ask "is anything wrong?".
23 * - **Site Health → Info** (`debug_information`): the exportable
24 * report. WordPress' own support handbook points users at it when
25 * asked to send diagnostics, which makes it the cheapest possible
26 * channel for us to receive them.
27 * - **Admin notice**: because a broken precondition should not wait
28 * for someone to think of opening Site Health.
29 *
30 * All three read the same {@see HealthCheckRegistry}, so a check that
31 * is registered once shows up everywhere without further work.
32 */
33 final class SiteHealthIntegration {
34
35 /**
36 * Site Health test ids must be globally unique across all plugins.
37 */
38 private const TEST_PREFIX = 'f12_doi_';
39
40 /**
41 * @var HealthCheckRegistry
42 */
43 private $registry;
44
45 public function __construct( HealthCheckRegistry $registry ) {
46 $this->registry = $registry;
47 }
48
49 public function register(): void {
50 add_filter( 'site_status_tests', array( $this, 'addStatusTests' ) );
51 add_filter( 'debug_information', array( $this, 'addDebugInformation' ) );
52 add_action( 'admin_notices', array( $this, 'renderCriticalNotice' ) );
53 }
54
55 /**
56 * Contribute one direct test per registered check.
57 *
58 * Direct rather than async: every check we ship is a single indexed
59 * lookup or an option read. Async would add a REST round-trip for
60 * work measured in microseconds.
61 *
62 * @param array<string,array<string,mixed>> $tests
63 *
64 * @return array<string,array<string,mixed>>
65 */
66 public function addStatusTests( $tests ) {
67 if ( ! is_array( $tests ) ) {
68 return $tests;
69 }
70 if ( ! isset( $tests['direct'] ) || ! is_array( $tests['direct'] ) ) {
71 $tests['direct'] = array();
72 }
73
74 foreach ( $this->registry->all() as $id => $check ) {
75 $testId = $this->testId( $id );
76
77 $tests['direct'][ $testId ] = array(
78 'label' => $check->getLabel(),
79 'test' => function () use ( $id ) {
80 return $this->renderTest( $id );
81 },
82 );
83 }
84
85 return $tests;
86 }
87
88 /**
89 * Map one HealthCheckResult onto the array shape WP_Site_Health
90 * expects. Status values are shared vocabulary, so this is a
91 * pass-through rather than a translation.
92 *
93 * @return array<string,mixed>
94 */
95 private function renderTest( string $id ): array {
96 $results = $this->registry->runAll();
97 $result = $results[ $id ] ?? null;
98
99 if ( ! $result instanceof HealthCheckResult ) {
100 return array(
101 'label' => __( 'Double Opt-In', 'double-opt-in' ),
102 'status' => HealthCheckResult::STATUS_RECOMMENDED,
103 'badge' => $this->badge(),
104 'test' => $this->testId( $id ),
105 );
106 }
107
108 $test = array(
109 'label' => $result->getLabel(),
110 'status' => $result->getStatus(),
111 'badge' => $this->badge(),
112 'description' => sprintf( '<p>%s</p>', esc_html( $result->getDescription() ) ),
113 'test' => $this->testId( $id ),
114 );
115
116 if ( $result->getActionUrl() !== '' && $result->getActionLabel() !== '' ) {
117 $test['actions'] = sprintf(
118 '<p><a href="%s">%s</a></p>',
119 esc_url( $result->getActionUrl() ),
120 esc_html( $result->getActionLabel() )
121 );
122 }
123
124 return $test;
125 }
126
127 /**
128 * Shared badge so every DOI test groups under one heading in the
129 * Site Health UI. Colour is WordPress' own "not performance, not
130 * security" neutral.
131 *
132 * @return array<string,string>
133 */
134 private function badge(): array {
135 return array(
136 'label' => __( 'Double Opt-In', 'double-opt-in' ),
137 'color' => 'purple',
138 );
139 }
140
141 private function testId( string $id ): string {
142 return strpos( $id, self::TEST_PREFIX ) === 0 ? $id : self::TEST_PREFIX . $id;
143 }
144
145 /**
146 * Add a "Double Opt-In" section to Site Health → Info.
147 *
148 * This is what a customer exports and pastes into a support ticket,
149 * so it carries versions alongside the check outcomes. No personal
150 * data — table names and version strings only (see rules/gdpr.md).
151 *
152 * @param array<string,mixed> $info
153 *
154 * @return array<string,mixed>
155 */
156 public function addDebugInformation( $info ) {
157 if ( ! is_array( $info ) ) {
158 return $info;
159 }
160
161 $fields = array(
162 'core_version' => array(
163 'label' => __( 'Core version', 'double-opt-in' ),
164 'value' => defined( 'FORGE12_OPTIN_VERSION' ) ? FORGE12_OPTIN_VERSION : __( 'unknown', 'double-opt-in' ),
165 ),
166 'core_api_version' => array(
167 'label' => __( 'Core API version', 'double-opt-in' ),
168 'value' => defined( 'F12_DOI_CORE_API_VERSION' ) ? F12_DOI_CORE_API_VERSION : __( 'unknown', 'double-opt-in' ),
169 ),
170 );
171
172 foreach ( $this->registry->runAll() as $id => $result ) {
173 $check = $this->registry->all()[ $id ] ?? null;
174 $name = $check instanceof HealthCheckInterface
175 ? sprintf( '%s (%s)', $check->getLabel(), $check->getPackage() )
176 : $id;
177
178 $fields[ $id ] = array(
179 'label' => $name,
180 'value' => $result->getDebugValue(),
181 'debug' => $result->getStatus(),
182 );
183 }
184
185 $info['f12-double-opt-in'] = array(
186 'label' => __( 'Double Opt-In', 'double-opt-in' ),
187 'description' => __( 'Runtime preconditions checked by the Double Opt-In plugin family. Send this section along when you contact support.', 'double-opt-in' ),
188 'fields' => $fields,
189 );
190
191 return $info;
192 }
193
194 /**
195 * Nag on the screens an operator passes through anyway.
196 *
197 * Deliberately not dismissible: the condition is a hard failure,
198 * and a dismissed notice would hide it until the next customer
199 * complains. It disappears by itself the moment the check passes.
200 */
201 public function renderCriticalNotice(): void {
202 if ( ! current_user_can( 'manage_options' ) ) {
203 return;
204 }
205 if ( ! $this->isRelevantScreen() ) {
206 return;
207 }
208
209 $criticals = $this->registry->criticals();
210 if ( empty( $criticals ) ) {
211 return;
212 }
213
214 echo '<div class="notice notice-error"><p><strong>';
215 echo esc_html__( 'Double Opt-In has detected a problem that will affect your visitors.', 'double-opt-in' );
216 echo '</strong></p><ul style="list-style:disc;margin-left:1.5em">';
217
218 foreach ( $criticals as $result ) {
219 printf(
220 '<li><strong>%s</strong><br>%s</li>',
221 esc_html( $result->getLabel() ),
222 esc_html( $result->getDescription() )
223 );
224 }
225
226 printf(
227 '</ul><p><a class="button button-primary" href="%s">%s</a></p></div>',
228 esc_url( admin_url( 'site-health.php' ) ),
229 esc_html__( 'Open Site Health', 'double-opt-in' )
230 );
231 }
232
233 /**
234 * Dashboard, plugins list and the plugin's own screens. Not every
235 * admin page — a site-wide red banner for a scoped problem trains
236 * people to ignore banners.
237 */
238 private function isRelevantScreen(): bool {
239 if ( ! function_exists( 'get_current_screen' ) ) {
240 return false;
241 }
242
243 $screen = get_current_screen();
244 if ( ! $screen ) {
245 return false;
246 }
247
248 if ( in_array( $screen->id, array( 'dashboard', 'plugins', 'plugins-network' ), true ) ) {
249 return true;
250 }
251
252 return strpos( $screen->id, 'f12-doi' ) !== false
253 || strpos( $screen->id, 'f12-cf7-doubleoptin' ) !== false;
254 }
255 }
256