PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.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 3.0.70 3.0.71 3.0.72 3.1.0 All 34 releases
double-opt-in / src / Integration / FormIntegrationRegistry.php

FormIntegrationRegistry.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.5.0, at src/Integration/FormIntegrationRegistry.php

482 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Form Integration Registry
4 *
5 * @package Forge12\DoubleOptIn\Integration
6 * @since 4.0.0
7 */
8
9 namespace Forge12\DoubleOptIn\Integration;
10
11 use Forge12\DoubleOptIn\Container\Container;
12 use Forge12\DoubleOptIn\EventSystem\EventDispatcherInterface;
13 use Forge12\DoubleOptIn\Events\Integration\IntegrationRegisteredEvent;
14 use Forge12\Shared\LoggerInterface;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Class FormIntegrationRegistry
22 *
23 * @api
24 *
25 * Central registry for all form integrations.
26 * Manages registration, discovery, and lifecycle of form system integrations.
27 *
28 * Covered by the Addon API semver policy as of Core API 4.3.0. Addons
29 * that provide a form integration register their FormIntegrationInterface
30 * implementation with this registry inside their boot() method.
31 */
32 final class FormIntegrationRegistry {
33
34 /**
35 * Singleton instance.
36 *
37 * @var FormIntegrationRegistry|null
38 */
39 private static ?FormIntegrationRegistry $instance = null;
40
41 /**
42 * Registered integrations.
43 *
44 * @var array<string, FormIntegrationInterface>
45 */
46 private array $integrations = array();
47
48 /**
49 * Whether integrations have been initialized.
50 *
51 * @var bool
52 */
53 private bool $initialized = false;
54
55 /**
56 * Logger instance.
57 *
58 * @var LoggerInterface|null
59 */
60 private ?LoggerInterface $logger = null;
61
62 /**
63 * Private constructor - use getInstance().
64 */
65 private function __construct() {}
66
67 /**
68 * Prevent cloning.
69 */
70 private function __clone() {}
71
72 /**
73 * Prevent unserialization.
74 *
75 * @throws \Exception
76 */
77 public function __wakeup() {
78 throw new \Exception( 'Cannot unserialize singleton' );
79 }
80
81 /**
82 * Get the singleton instance.
83 *
84 * @return FormIntegrationRegistry
85 */
86 public static function getInstance(): FormIntegrationRegistry {
87 if ( self::$instance === null ) {
88 self::$instance = new self();
89 }
90 return self::$instance;
91 }
92
93 /**
94 * Reset the singleton instance.
95 *
96 * Useful for testing.
97 *
98 * @return void
99 */
100 public static function resetInstance(): void {
101 self::$instance = null;
102 }
103
104 /**
105 * Set the logger instance.
106 *
107 * @param LoggerInterface $logger The logger.
108 *
109 * @return void
110 */
111 public function setLogger( LoggerInterface $logger ): void {
112 $this->logger = $logger;
113 }
114
115 /**
116 * Register an integration.
117 *
118 * @param FormIntegrationInterface $integration The integration to register.
119 *
120 * @return bool True if registered successfully, false if already exists.
121 */
122 public function register( FormIntegrationInterface $integration ): bool {
123 $identifier = $integration->getIdentifier();
124
125 if ( isset( $this->integrations[ $identifier ] ) ) {
126 $this->log(
127 'warning',
128 'Integration already registered',
129 array(
130 'identifier' => $identifier,
131 )
132 );
133 return false;
134 }
135
136 $this->integrations[ $identifier ] = $integration;
137
138 $this->log(
139 'info',
140 'Integration registered',
141 array(
142 'identifier' => $identifier,
143 'available' => $integration->isAvailable(),
144 )
145 );
146
147 // If registry is already initialized and the integration is available,
148 // register its hooks immediately (late registration support)
149 if ( $this->initialized && $integration->isAvailable() ) {
150 try {
151 $integration->registerHooks();
152 $this->log(
153 'info',
154 'Late registration: Integration hooks registered',
155 array(
156 'identifier' => $identifier,
157 )
158 );
159 } catch ( \Exception $e ) {
160 $this->log(
161 'error',
162 'Late registration: Failed to register integration hooks',
163 array(
164 'identifier' => $identifier,
165 'error' => $e->getMessage(),
166 )
167 );
168 }
169 }
170
171 // Dispatch event
172 $this->dispatchIntegrationRegisteredEvent( $integration );
173
174 // Allow external code to react to registration
175 do_action( 'f12_cf7_doubleoptin_integration_registered', $integration, $identifier );
176
177 return true;
178 }
179
180 /**
181 * Unregister an integration.
182 *
183 * @param string $identifier The integration identifier.
184 *
185 * @return bool True if unregistered successfully.
186 */
187 public function unregister( string $identifier ): bool {
188 if ( ! isset( $this->integrations[ $identifier ] ) ) {
189 return false;
190 }
191
192 unset( $this->integrations[ $identifier ] );
193
194 $this->log(
195 'info',
196 'Integration unregistered',
197 array(
198 'identifier' => $identifier,
199 )
200 );
201
202 return true;
203 }
204
205 /**
206 * Get an integration by identifier.
207 *
208 * @param string $identifier The integration identifier.
209 *
210 * @return FormIntegrationInterface|null The integration or null if not found.
211 */
212 public function get( string $identifier ): ?FormIntegrationInterface {
213 return $this->integrations[ $identifier ] ?? null;
214 }
215
216 /**
217 * Check if an integration exists.
218 *
219 * @param string $identifier The integration identifier.
220 *
221 * @return bool True if the integration is registered.
222 */
223 public function has( string $identifier ): bool {
224 return isset( $this->integrations[ $identifier ] );
225 }
226
227 /**
228 * Get all registered integrations.
229 *
230 * @return array<string, FormIntegrationInterface>
231 */
232 public function getAll(): array {
233 return $this->integrations;
234 }
235
236 /**
237 * Get all available integrations (where isAvailable() returns true).
238 *
239 * @return array<string, FormIntegrationInterface>
240 */
241 public function getAvailable(): array {
242 return array_filter(
243 $this->integrations,
244 function ( FormIntegrationInterface $integration ) {
245 return $integration->isAvailable();
246 }
247 );
248 }
249
250 /**
251 * Get integration identifiers as a list.
252 *
253 * @return array<string>
254 */
255 public function getIdentifiers(): array {
256 return array_keys( $this->integrations );
257 }
258
259 /**
260 * Initialize all available integrations.
261 *
262 * Registers hooks for all integrations that are available.
263 *
264 * @return void
265 */
266 public function initialize(): void {
267 if ( $this->initialized ) {
268 return;
269 }
270
271 $this->log( 'debug', 'Initializing integrations' );
272
273 foreach ( $this->getAvailable() as $identifier => $integration ) {
274 try {
275 $integration->registerHooks();
276
277 $this->log(
278 'info',
279 'Integration hooks registered',
280 array(
281 'identifier' => $identifier,
282 )
283 );
284 } catch ( \Exception $e ) {
285 $this->log(
286 'error',
287 'Failed to register integration hooks',
288 array(
289 'identifier' => $identifier,
290 'error' => $e->getMessage(),
291 )
292 );
293 }
294 }
295
296 $this->initialized = true;
297
298 do_action( 'f12_cf7_doubleoptin_integrations_initialized', $this );
299 }
300
301 /**
302 * Check if integrations have been initialized.
303 *
304 * @return bool
305 */
306 public function isInitialized(): bool {
307 return $this->initialized;
308 }
309
310 /**
311 * Get integrations as options for admin dropdown.
312 *
313 * @param bool $onlyAvailable Only include available integrations.
314 *
315 * @return array<string, string> Identifier => Name
316 */
317 public function getAsOptions( bool $onlyAvailable = true ): array {
318 $integrations = $onlyAvailable ? $this->getAvailable() : $this->getAll();
319 $options = array();
320
321 foreach ( $integrations as $identifier => $integration ) {
322 $options[ $identifier ] = $integration->getName();
323 }
324
325 return $options;
326 }
327
328 /**
329 * Find integration by form ID and type.
330 *
331 * @param int $formId The form ID.
332 * @param string $formType The expected form type (optional, for validation).
333 *
334 * @return FormIntegrationInterface|null The matching integration.
335 */
336 public function findForForm( int $formId, string $formType = '' ): ?FormIntegrationInterface {
337 if ( ! empty( $formType ) && isset( $this->integrations[ $formType ] ) ) {
338 return $this->integrations[ $formType ];
339 }
340
341 // Try to detect the form type from post type or other indicators
342 $post = get_post( $formId );
343 if ( ! $post ) {
344 return null;
345 }
346
347 switch ( $post->post_type ) {
348 case 'wpcf7_contact_form':
349 return $this->get( 'cf7' );
350 case 'fusion_form':
351 return $this->get( 'avada' );
352 case 'wpforms':
353 return $this->get( 'wpforms' );
354 default:
355 return null;
356 }
357 }
358
359 /**
360 * Get all forms from all available integrations.
361 *
362 * Returns forms grouped by integration.
363 *
364 * @since 4.1.0
365 *
366 * @return array<string, array{name: string, forms: array}>
367 */
368 public function getAllForms(): array {
369 $result = array();
370
371 foreach ( $this->getAvailable() as $identifier => $integration ) {
372 $forms = $integration->getForms();
373
374 if ( ! empty( $forms ) ) {
375 $result[ $identifier ] = array(
376 'name' => $integration->getName(),
377 'forms' => $forms,
378 );
379 }
380 }
381
382 $this->log(
383 'debug',
384 'Retrieved all forms from integrations',
385 array(
386 'integration_count' => count( $result ),
387 )
388 );
389
390 return $result;
391 }
392
393 /**
394 * Get a flat list of all forms from all integrations.
395 *
396 * @since 4.1.0
397 *
398 * @return array<array{id: int, title: string, integration: string, integration_name: string, enabled: bool, edit_url: string}>
399 */
400 public function getAllFormsFlat(): array {
401 $forms = array();
402
403 foreach ( $this->getAvailable() as $identifier => $integration ) {
404 $integrationForms = $integration->getForms();
405
406 foreach ( $integrationForms as $form ) {
407 $form['integration_name'] = $integration->getName();
408 $forms[] = $form;
409 }
410 }
411
412 return $forms;
413 }
414
415 /**
416 * Dispatch IntegrationRegisteredEvent.
417 *
418 * @param FormIntegrationInterface $integration The registered integration.
419 *
420 * @return void
421 */
422 private function dispatchIntegrationRegisteredEvent( FormIntegrationInterface $integration ): void {
423 try {
424 $container = Container::getInstance();
425 if ( $container->has( EventDispatcherInterface::class ) ) {
426 $dispatcher = $container->get( EventDispatcherInterface::class );
427 $event = new IntegrationRegisteredEvent(
428 $integration->getIdentifier(),
429 $integration->getIdentifier(),
430 $integration->isAvailable()
431 );
432 $dispatcher->dispatch( $event );
433 }
434 } catch ( \Exception $e ) {
435 $this->log(
436 'warning',
437 'Failed to dispatch IntegrationRegisteredEvent',
438 array(
439 'error' => $e->getMessage(),
440 )
441 );
442 }
443 }
444
445 /**
446 * Log a message.
447 *
448 * @param string $level The log level.
449 * @param string $message The message.
450 * @param array $context The context.
451 *
452 * @return void
453 */
454 private function log( string $level, string $message, array $context = array() ): void {
455 if ( ! $this->logger ) {
456 return;
457 }
458
459 $context = array_merge(
460 array(
461 'plugin' => 'double-opt-in',
462 'component' => 'integration-registry',
463 ),
464 $context
465 );
466
467 switch ( $level ) {
468 case 'error':
469 $this->logger->error( $message, $context );
470 break;
471 case 'warning':
472 $this->logger->warning( $message, $context );
473 break;
474 case 'info':
475 $this->logger->info( $message, $context );
476 break;
477 default:
478 $this->logger->debug( $message, $context );
479 }
480 }
481 }
482