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.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 3.0.70 3.0.71 3.0.72 All 35 releases
← All changes | core/OptIn.class.php +779 -819 trunk5.5.0 View file →
@@ -1,819 +1,779 @@
1 -<?php
2 -/**
3 - * OptIn Facade
4 - *
5 - * Backward-compatible facade for the legacy OptIn API.
6 - * Internally uses the new Repository pattern.
7 - *
8 - * @package forge12\contactform7\CF7DoubleOptIn
9 - * @since 4.0.0
10 - */
11 -
12 -namespace forge12\contactform7\CF7DoubleOptIn;
13 -
14 -use Forge12\DoubleOptIn\Container\Container;
15 -use Forge12\DoubleOptIn\Entity\OptIn as OptInEntity;
16 -use Forge12\DoubleOptIn\Repository\OptInRepositoryInterface;
17 -use Forge12\Shared\Logger;
18 -use Forge12\Shared\LoggerInterface;
19 -
20 -if ( ! defined( 'ABSPATH' ) ) {
21 - exit;
22 -}
23 -
24 -/**
25 - * Class OptIn
26 - *
27 - * This class provides backward compatibility with the legacy API
28 - * while using the new Repository pattern internally.
29 - */
30 -class OptIn {
31 -
32 - private LoggerInterface $logger;
33 - private OptInEntity $entity;
34 -
35 - /**
36 - * Constructor.
37 - *
38 - * @param LoggerInterface $logger The logger instance.
39 - * @param array $properties Optional properties to initialize.
40 - */
41 - public function __construct( LoggerInterface $logger, array $properties = [] ) {
42 - $this->logger = $logger;
43 -
44 - if ( ! empty( $properties ) ) {
45 - $this->entity = OptInEntity::fromArray( $this->mapToEntityArray( $properties ) );
46 - } else {
47 - $this->entity = OptInEntity::create();
48 - }
49 -
50 - $this->logger->debug( 'OptIn facade initialized', [
51 - 'plugin' => 'double-opt-in',
52 - 'id' => $this->entity->getId(),
53 - ] );
54 - }
55 -
56 - /**
57 - * Get the logger instance.
58 - *
59 - * @return LoggerInterface
60 - */
61 - public function get_logger(): LoggerInterface {
62 - return $this->logger;
63 - }
64 -
65 - /**
66 - * Get the underlying entity.
67 - *
68 - * @return OptInEntity
69 - */
70 - public function getEntity(): OptInEntity {
71 - return $this->entity;
72 - }
73 -
74 - // =========================================================================
75 - // STATIC FACTORY METHODS
76 - // =========================================================================
77 -
78 - /**
79 - * Get OptIn by hash.
80 - *
81 - * @param string $hash The hash.
82 - *
83 - * @return OptIn|null
84 - */
85 - public static function get_by_hash( string $hash ): ?OptIn {
86 - $logger = Logger::getInstance();
87 - $logger->debug( 'get_by_hash called', [
88 - 'plugin' => 'double-opt-in',
89 - 'hash' => $hash,
90 - ] );
91 -
92 - try {
93 - $repository = self::getRepository();
94 - $entity = $repository->findByHash( $hash );
95 -
96 - if ( ! $entity ) {
97 - return null;
98 - }
99 -
100 - $optIn = new self( $logger );
101 - $optIn->entity = $entity;
102 -
103 - return $optIn;
104 - } catch ( \Exception $e ) {
105 - $logger->error( 'Failed to get OptIn by hash', [
106 - 'plugin' => 'double-opt-in',
107 - 'hash' => $hash,
108 - 'error' => $e->getMessage(),
109 - ] );
110 - return null;
111 - }
112 - }
113 -
114 - /**
115 - * Get OptIn by its database id.
116 - *
117 - * Server-side use only (cron, admin REST). Public links always
118 - * address an opt-in by its random hash, never by id.
119 - *
120 - * @param int $id The opt-in id.
121 - *
122 - * @return OptIn|null
123 - *
124 - * @since 5.6.0
125 - */
126 - public static function get_by_id( int $id ): ?OptIn {
127 - if ( $id <= 0 ) {
128 - return null;
129 - }
130 -
131 - $logger = Logger::getInstance();
132 -
133 - try {
134 - $entity = self::getRepository()->findById( $id );
135 -
136 - if ( ! $entity ) {
137 - return null;
138 - }
139 -
140 - $optIn = new self( $logger );
141 - $optIn->entity = $entity;
142 -
143 - return $optIn;
144 - } catch ( \Exception $e ) {
145 - $logger->error( 'Failed to get OptIn by id', [
146 - 'plugin' => 'double-opt-in',
147 - 'optin_id' => $id,
148 - 'error' => $e->getMessage(),
149 - ] );
150 - return null;
151 - }
152 - }
153 -
154 - /**
155 - * Get count by form ID.
156 - *
157 - * @param int $formId The form ID.
158 - *
159 - * @return int
160 - */
161 - public static function get_count( int $formId ): int {
162 - try {
163 - return self::getRepository()->countByFormId( $formId );
164 - } catch ( \Exception $e ) {
165 - return 0;
166 - }
167 - }
168 -
169 - /**
170 - * Get list of OptIns with pagination.
171 - *
172 - * @param array $atts Query attributes.
173 - * @param int|null $numberOfPages Reference to store page count.
174 - *
175 - * @return array<OptIn>
176 - */
177 - public static function get_list( array $atts = [], ?int &$numberOfPages = null ): array {
178 - $logger = Logger::getInstance();
179 -
180 - try {
181 - $repository = self::getRepository();
182 - $entities = $repository->findAll( $atts, $numberOfPages );
183 -
184 - return array_map( function ( OptInEntity $entity ) use ( $logger ) {
185 - $optIn = new self( $logger );
186 - $optIn->entity = $entity;
187 - return $optIn;
188 - }, $entities );
189 - } catch ( \Exception $e ) {
190 - $logger->error( 'Failed to get OptIn list', [
191 - 'plugin' => 'double-opt-in',
192 - 'error' => $e->getMessage(),
193 - ] );
194 - return [];
195 - }
196 - }
197 -
198 - /**
199 - * Get list by category ID.
200 - *
201 - * @param int $categoryId The category ID.
202 - * @param array $atts Query attributes.
203 - * @param int|null $numberOfPages Reference to store page count.
204 - *
205 - * @return array<OptIn>
206 - */
207 - public static function get_list_by_category_id( int $categoryId, array $atts = [], ?int &$numberOfPages = null ): array {
208 - $logger = Logger::getInstance();
209 -
210 - try {
211 - $repository = self::getRepository();
212 -
213 - // Get total count for pagination
214 - if ( $numberOfPages !== null ) {
215 - $keyword = $atts['keyword'] ?? '';
216 - $perPage = max( 1, (int) ( $atts['perPage'] ?? 10 ) );
217 - $total = $repository->countByCategory( $categoryId, $keyword );
218 - $numberOfPages = $total > 0 ? (int) ceil( $total / $perPage ) : 0;
219 -
220 - if ( $numberOfPages === 0 ) {
221 - return [];
222 - }
223 - }
224 -
225 - $entities = $repository->findByCategory( $categoryId, $atts );
226 -
227 - return array_map( function ( OptInEntity $entity ) use ( $logger ) {
228 - $optIn = new self( $logger );
229 - $optIn->entity = $entity;
230 - return $optIn;
231 - }, $entities );
232 - } catch ( \Exception $e ) {
233 - $logger->error( 'Failed to get OptIn list by category', [
234 - 'plugin' => 'double-opt-in',
235 - 'category' => $categoryId,
236 - 'error' => $e->getMessage(),
237 - ] );
238 - return [];
239 - }
240 - }
241 -
242 - /**
243 - * Get list by email.
244 - *
245 - * @param string $email The email address.
246 - *
247 - * @return array<OptIn>
248 - */
249 - public static function get_list_by_email( string $email ): array {
250 - $logger = Logger::getInstance();
251 -
252 - try {
253 - $repository = self::getRepository();
254 - $entities = $repository->findByEmail( $email );
255 -
256 - return array_map( function ( OptInEntity $entity ) use ( $logger ) {
257 - $optIn = new self( $logger );
258 - $optIn->entity = $entity;
259 - return $optIn;
260 - }, $entities );
261 - } catch ( \Exception $e ) {
262 - return [];
263 - }
264 - }
265 -
266 - /**
267 - * Get confirmed list by email.
268 - *
269 - * @param string $email The email address.
270 - *
271 - * @return array<OptIn>
272 - */
273 - public static function get_list_by_email_confirmed( string $email ): array {
274 - $logger = Logger::getInstance();
275 -
276 - try {
277 - $repository = self::getRepository();
278 - $entities = $repository->findConfirmedByEmail( $email );
279 -
280 - return array_map( function ( OptInEntity $entity ) use ( $logger ) {
281 - $optIn = new self( $logger );
282 - $optIn->entity = $entity;
283 - return $optIn;
284 - }, $entities );
285 - } catch ( \Exception $e ) {
286 - return [];
287 - }
288 - }
289 -
290 - /**
291 - * Get unconfirmed list by email.
292 - *
293 - * @param string $email The email address.
294 - *
295 - * @return array<OptIn>
296 - */
297 - public static function get_list_by_email_unconfirmed( string $email ): array {
298 - $logger = Logger::getInstance();
299 -
300 - try {
301 - $repository = self::getRepository();
302 - $entities = $repository->findUnconfirmedByEmail( $email );
303 -
304 - return array_map( function ( OptInEntity $entity ) use ( $logger ) {
305 - $optIn = new self( $logger );
306 - $optIn->entity = $entity;
307 - return $optIn;
308 - }, $entities );
309 - } catch ( \Exception $e ) {
310 - return [];
311 - }
312 - }
313 -
314 - /**
315 - * Bulk update category.
316 - *
317 - * @param int $fromId The source category ID.
318 - * @param int $toId The target category ID.
319 - *
320 - * @return int
321 - */
322 - public static function bulk_update_category( int $fromId, int $toId ): int {
323 - try {
324 - return self::getRepository()->bulkUpdateCategory( $fromId, $toId );
325 - } catch ( \Exception $e ) {
326 - return 0;
327 - }
328 - }
329 -
330 - /**
331 - * Update category by OptIn ID.
332 - *
333 - * @param int $optInId The OptIn ID.
334 - * @param int $categoryId The new category ID.
335 - *
336 - * @return bool
337 - */
338 - public static function update_category_by_id( int $optInId, int $categoryId ): bool {
339 - try {
340 - return self::getRepository()->updateCategoryById( $optInId, $categoryId );
341 - } catch ( \Exception $e ) {
342 - return false;
343 - }
344 - }
345 -
346 - // =========================================================================
347 - // GETTERS
348 - // =========================================================================
349 -
350 - public function get_id(): int {
351 - return $this->entity->getId();
352 - }
353 -
354 - public function get_hash(): string {
355 - return $this->entity->getHash();
356 - }
357 -
358 - public function get_cf_form_id(): int {
359 - return $this->entity->getFormId();
360 - }
361 -
362 - public function is_confirmed(): bool {
363 - return $this->entity->isConfirmed();
364 - }
365 -
366 - public function get_doubleoptin(): int {
367 - return $this->entity->isConfirmed() ? 1 : 0;
368 - }
369 -
370 - public function is_optout(): bool {
371 - return $this->entity->isOptedOut();
372 - }
373 -
374 - public function get_content(): string {
375 - return $this->entity->getContent();
376 - }
377 -
378 - public function get_createtime( string $view = 'raw' ): string {
379 - if ( $view !== 'raw' ) {
380 - return $this->entity->getCreateTimeFormatted( 'd.m.Y / H:i:s' );
381 - }
382 - return (string) $this->entity->getCreateTime();
383 - }
384 -
385 - public function get_updatetime( string $view = 'raw' ): string {
386 - if ( $view !== 'raw' ) {
387 - return $this->entity->getUpdateTimeFormatted( 'd.m.Y / H:i:s' );
388 - }
389 - return (string) $this->entity->getUpdateTime();
390 - }
391 -
392 - public function get_optouttime( string $view = 'raw' ): string {
393 - $time = $this->entity->getOptOutTime();
394 - if ( $view !== 'raw' && $time > 0 ) {
395 - return wp_date( 'd.m.Y / H:i:s', $time );
396 - }
397 - return (string) $time;
398 - }
399 -
400 - /**
401 - * Get create time as ISO 8601 string.
402 - *
403 - * @return string
404 - */
405 - public function get_createtime_iso(): string {
406 - return $this->entity->getCreateTimeISO();
407 - }
408 -
409 - /**
410 - * Get update time as ISO 8601 string.
411 - *
412 - * @return string
413 - */
414 - public function get_updatetime_iso(): string {
415 - return $this->entity->getUpdateTimeISO();
416 - }
417 -
418 - /**
419 - * Get opt-out time as ISO 8601 string.
420 - *
421 - * @return string
422 - */
423 - public function get_optouttime_iso(): string {
424 - return $this->entity->getOptOutTimeISO();
425 - }
426 -
427 - public function get_ipaddr_register(): string {
428 - return $this->entity->getIpRegister();
429 - }
430 -
431 - public function get_ipaddr_confirmation(): string {
432 - return $this->entity->getIpConfirmation();
433 - }
434 -
435 - public function get_ipaddr_optout(): string {
436 - return $this->entity->getIpOptOut();
437 - }
438 -
439 - public function get_files(): string {
440 - return $this->entity->getFiles();
441 - }
442 -
443 - public function get_category(): int {
444 - return $this->entity->getCategory();
445 - }
446 -
447 - public function get_email(): string {
448 - return $this->entity->getEmail();
449 - }
450 -
451 - public function get_mail_optin(): string {
452 - return $this->entity->getMailOptIn();
453 - }
454 -
455 - public function get_consent_text(): string {
456 - return $this->entity->getConsentText();
457 - }
458 -
459 - public function get_form( bool $encoded = false ): string {
460 - $form = $this->entity->getForm();
461 - if ( $encoded ) {
462 - return base64_encode( $form );
463 - }
464 - return $form;
465 - }
466 -
467 - /**
468 - * Get the validity end date.
469 - *
470 - * @return string
471 - */
472 - public function get_valid_until(): string {
473 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
474 - $dt = new \DateTime();
475 - $dt->setTimestamp( $this->entity->getCreateTime() );
476 -
477 - if ( $this->is_confirmed() ) {
478 - $amount = (int) ( $settings['delete'] ?? 0 );
479 - $period = $settings['delete_period'] ?? 'months';
480 - } else {
481 - $amount = (int) ( $settings['delete_unconfirmed'] ?? 0 );
482 - $period = $settings['delete_unconfirmed_period'] ?? 'months';
483 - }
484 -
485 - $dt->modify( '+' . $amount . ' ' . $period );
486 - $dt->modify( '+1 day' );
487 -
488 - return $dt->format( 'd.m.Y' );
489 - }
490 -
491 - // =========================================================================
492 - // SETTERS
493 - // =========================================================================
494 -
495 - public function set_cf_form_id( int $formId ): void {
496 - $this->entity = $this->entity->withFormId( $formId );
497 - }
498 -
499 - public function set_doubleoptin( int $confirmed ): void {
500 - $this->entity->setConfirmed( (bool) $confirmed );
501 - }
502 -
503 - public function set_createtime( $timestamp ): void {
504 - $this->entity = $this->entity->withCreateTime( (int) $timestamp );
505 - }
506 -
507 - public function set_updatetime( $timestamp ): void {
508 - $this->entity->setUpdateTime( (int) $timestamp );
509 - }
510 -
511 - public function set_optouttime( $timestamp ): void {
512 - $this->entity = $this->entity->withOptOutTime( (int) $timestamp );
513 - }
514 -
515 - public function set_ipaddr_register( string $ip ): void {
516 - $this->entity = $this->entity->withIpRegister( $ip );
517 - }
518 -
519 - public function set_ipaddr_confirmation( string $ip ): void {
520 - $this->entity->setIpConfirmation( $ip );
521 - }
522 -
523 - public function set_ipaddr_optout( string $ip ): void {
524 - $this->entity = $this->entity->withIpOptOut( $ip );
525 - }
526 -
527 - public function set_content( string $content ): void {
528 - $this->entity = $this->entity->withContent( $content );
529 - }
530 -
531 - public function set_files( string $files ): void {
532 - $this->entity = $this->entity->withFiles( $files );
533 - }
534 -
535 - public function set_category( int $categoryId ): void {
536 - $this->entity = $this->entity->withCategory( $categoryId );
537 - }
538 -
539 - public function set_form( string $form ): void {
540 - $this->entity = $this->entity->withForm( $form );
541 - }
542 -
543 - public function set_email( string $email ): void {
544 - $this->entity = $this->entity->withEmail( $email );
545 - }
546 -
547 - public function set_mail_optin( string $mail ): void {
548 - $this->entity = $this->entity->withMailOptIn( $mail );
549 - }
550 -
551 - public function set_consent_text( string $text ): void {
552 - $this->entity = $this->entity->withConsentText( $text );
553 - }
554 -
555 - // =========================================================================
556 - // TYPE CHECKS
557 - // =========================================================================
558 -
559 - public function isTypeCF7(): bool {
560 - return $this->isType( 'cf7' );
561 - }
562 -
563 - public function isTypeAvada(): bool {
564 - return $this->isType( 'avada' );
565 - }
566 -
567 - public function isTypeElementor(): bool {
568 - return $this->isType( 'elementor' );
569 - }
570 -
571 - public function isTypeWPForms(): bool {
572 - return $this->isType( 'wpforms' );
573 - }
574 -
575 - public function isTypeGravityForms(): bool {
576 - return $this->isType( 'gravityforms' );
577 - }
578 -
579 - public function isType( string $type ): bool {
580 - $formId = $this->get_cf_form_id();
581 -
582 - switch ( $type ) {
583 - case 'cf7':
584 - return get_post_type( $formId ) === 'wpcf7_contact_form';
585 - case 'avada':
586 - return get_post_type( $formId ) === 'fusion_form';
587 - case 'elementor':
588 - // Elementor forms are embedded in pages/posts, not stored as separate post types.
589 - // Check if the post has Elementor data AND DOI settings configured.
590 - $hasElementorData = ! empty( get_post_meta( $formId, '_elementor_data', true ) );
591 - $hasDoiSettings = ! empty( get_post_meta( $formId, 'f12-cf7-doubleoptin', true ) );
592 - return $hasElementorData && $hasDoiSettings;
593 - case 'wpforms':
594 - return get_post_type( $formId ) === 'wpforms';
595 - case 'gravityforms':
596 - // Gravity Forms stores forms in a custom table, not as posts.
597 - // Check if the form ID exists in the GF forms table.
598 - if ( class_exists( 'GFAPI' ) ) {
599 - $form = \GFAPI::get_form( $formId );
600 - return $form !== false && ! is_wp_error( $form );
601 - }
602 - return false;
603 - default:
604 - return false;
605 - }
606 - }
607 -
608 - // =========================================================================
609 - // PERSISTENCE
610 - // =========================================================================
611 -
612 - /**
613 - * Save the OptIn to the database.
614 - *
615 - * @return bool|int
616 - */
617 - public function save() {
618 - $this->logger->info( 'Saving OptIn', [
619 - 'plugin' => 'double-opt-in',
620 - 'id' => $this->entity->getId(),
621 - 'hash' => $this->entity->getHash(),
622 - ] );
623 -
624 - try {
625 - $repository = self::getRepository();
626 - $this->entity = $repository->save( $this->entity );
627 -
628 - $this->logger->info( 'OptIn saved successfully', [
629 - 'plugin' => 'double-opt-in',
630 - 'id' => $this->entity->getId(),
631 - 'hash' => $this->entity->getHash(),
632 - ] );
633 -
634 - return true;
635 - } catch ( \Exception $e ) {
636 - $this->logger->error( 'Failed to save OptIn', [
637 - 'plugin' => 'double-opt-in',
638 - 'error' => $e->getMessage(),
639 - ] );
640 - return false;
641 - }
642 - }
643 -
644 - // =========================================================================
645 - // LINK GENERATION
646 - // =========================================================================
647 -
648 - /**
649 - * Get the opt-in confirmation link.
650 - *
651 - * @param array $parameter Additional parameters.
652 - * @param int $formId Optional form ID override.
653 - *
654 - * @return string
655 - */
656 - public function get_link_optin( array $parameter = [], int $formId = 0 ): string {
657 - $formId = $formId > 0 ? $formId : $this->get_cf_form_id();
658 -
659 - $formParameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
660 - $pageId = (int) ( $formParameter['page'] ?? 0 );
661 -
662 - if ( $pageId <= 0 ) {
663 - return home_url( '?optin=' . $this->get_hash() );
664 - }
665 -
666 - $pageUrl = get_permalink( $pageId );
667 - if ( ! $pageUrl ) {
668 - return home_url( '?optin=' . $this->get_hash() );
669 - }
670 -
671 - $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
672 -
673 - return $pageUrl . $separator . 'optin=' . $this->get_hash();
674 - }
675 -
676 - /**
677 - * Get the opt-out link.
678 - *
679 - * @return string
680 - */
681 - public function get_link_optout(): string {
682 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
683 - $pageId = (int) ( $settings['optout_page'] ?? 0 );
684 -
685 - if ( $pageId <= 0 ) {
686 - return home_url( '?optout=' . $this->get_hash() );
687 - }
688 -
689 - $pageUrl = get_permalink( $pageId );
690 - if ( ! $pageUrl ) {
691 - return home_url( '?optout=' . $this->get_hash() );
692 - }
693 -
694 - $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
695 -
696 - return $pageUrl . $separator . 'optout=' . $this->get_hash();
697 - }
698 -
699 - /**
700 - * Get the UI edit link.
701 - *
702 - * @return string
703 - */
704 - public function get_link_ui(): string {
705 - return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&view=single&hash=' . $this->get_hash() );
706 - }
707 -
708 - /**
709 - * Get the delete link.
710 - *
711 - * @return string
712 - */
713 - public function get_link_delete(): string {
714 - $nonce = wp_create_nonce( 'delete_optin_' . $this->get_hash() );
715 - return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&action=delete&hash=' . $this->get_hash() . '&_wpnonce=' . $nonce );
716 - }
717 -
718 - /**
719 - * Get the form name.
720 - *
721 - * @return string
722 - */
723 - public function get_form_name(): string {
724 - $formId = $this->get_cf_form_id();
725 -
726 - if ( $formId <= 0 ) {
727 - return __( 'Unknown', 'double-opt-in' );
728 - }
729 -
730 - $post = get_post( $formId );
731 - if ( ! $post ) {
732 - return __( 'Deleted Form', 'double-opt-in' ) . ' (ID: ' . $formId . ')';
733 - }
734 -
735 - return $post->post_title ?: __( 'Untitled Form', 'double-opt-in' );
736 - }
737 -
738 - /**
739 - * Get the form edit link.
740 - *
741 - * @return string
742 - */
743 - public function get_form_link(): string {
744 - $formId = $this->get_cf_form_id();
745 - $postType = get_post_type( $formId );
746 -
747 - if ( $postType === 'wpcf7_contact_form' ) {
748 - return admin_url( 'admin.php?page=wpcf7&post=' . $formId . '&action=edit' );
749 - }
750 -
751 - if ( $postType === 'fusion_form' ) {
752 - return admin_url( 'post.php?post=' . $formId . '&action=edit' );
753 - }
754 -
755 - return admin_url( 'post.php?post=' . $formId . '&action=edit' );
756 - }
757 -
758 - /**
759 - * Get form settings.
760 - *
761 - * @return array
762 - */
763 - public function get_form_settings(): array {
764 - return CF7DoubleOptIn::getInstance()->getParameter( $this->get_cf_form_id() );
765 - }
766 -
767 - // =========================================================================
768 - // HELPERS
769 - // =========================================================================
770 -
771 - /**
772 - * Get the repository instance.
773 - *
774 - * @return OptInRepositoryInterface
775 - */
776 - private static function getRepository(): OptInRepositoryInterface {
777 - $container = Container::getInstance();
778 - return $container->get( OptInRepositoryInterface::class );
779 - }
780 -
781 - /**
782 - * Map legacy property names to entity array keys.
783 - *
784 - * @param array $properties Legacy properties.
785 - *
786 - * @return array
787 - */
788 - private function mapToEntityArray( array $properties ): array {
789 - $mapping = [
790 - 'cf_form_id' => 'cf_form_id',
791 - 'doubleoptin' => 'doubleoptin',
792 - 'content' => 'content',
793 - 'hash' => 'hash',
794 - 'createtime' => 'createtime',
795 - 'updatetime' => 'updatetime',
796 - 'optouttime' => 'optouttime',
797 - 'ipaddr_register' => 'ipaddr_register',
798 - 'ipaddr_confirmation' => 'ipaddr_confirmation',
799 - 'ipaddr_optout' => 'ipaddr_optout',
800 - 'files' => 'files',
801 - 'category' => 'category',
802 - 'form' => 'form',
803 - 'mail_optin' => 'mail_optin',
804 - 'email' => 'email',
805 - 'id' => 'id',
806 - 'consent_text' => 'consent_text',
807 - 'consent_field' => 'consent_field',
808 - ];
809 -
810 - $result = [];
811 - foreach ( $mapping as $legacy => $entity ) {
812 - if ( isset( $properties[ $legacy ] ) ) {
813 - $result[ $entity ] = $properties[ $legacy ];
814 - }
815 - }
816 -
817 - return $result;
818 - }
819 -}
1 +<?php
2 +/**
3 + * OptIn Facade
4 + *
5 + * Backward-compatible facade for the legacy OptIn API.
6 + * Internally uses the new Repository pattern.
7 + *
8 + * @package forge12\contactform7\CF7DoubleOptIn
9 + * @since 4.0.0
10 + */
11 +
12 +namespace forge12\contactform7\CF7DoubleOptIn;
13 +
14 +use Forge12\DoubleOptIn\Container\Container;
15 +use Forge12\DoubleOptIn\Entity\OptIn as OptInEntity;
16 +use Forge12\DoubleOptIn\Repository\OptInRepositoryInterface;
17 +use Forge12\Shared\Logger;
18 +use Forge12\Shared\LoggerInterface;
19 +
20 +if ( ! defined( 'ABSPATH' ) ) {
21 + exit;
22 +}
23 +
24 +/**
25 + * Class OptIn
26 + *
27 + * This class provides backward compatibility with the legacy API
28 + * while using the new Repository pattern internally.
29 + */
30 +class OptIn {
31 +
32 + private LoggerInterface $logger;
33 + private OptInEntity $entity;
34 +
35 + /**
36 + * Constructor.
37 + *
38 + * @param LoggerInterface $logger The logger instance.
39 + * @param array $properties Optional properties to initialize.
40 + */
41 + public function __construct( LoggerInterface $logger, array $properties = [] ) {
42 + $this->logger = $logger;
43 +
44 + if ( ! empty( $properties ) ) {
45 + $this->entity = OptInEntity::fromArray( $this->mapToEntityArray( $properties ) );
46 + } else {
47 + $this->entity = OptInEntity::create();
48 + }
49 +
50 + $this->logger->debug( 'OptIn facade initialized', [
51 + 'plugin' => 'double-opt-in',
52 + 'id' => $this->entity->getId(),
53 + ] );
54 + }
55 +
56 + /**
57 + * Get the logger instance.
58 + *
59 + * @return LoggerInterface
60 + */
61 + public function get_logger(): LoggerInterface {
62 + return $this->logger;
63 + }
64 +
65 + /**
66 + * Get the underlying entity.
67 + *
68 + * @return OptInEntity
69 + */
70 + public function getEntity(): OptInEntity {
71 + return $this->entity;
72 + }
73 +
74 + // =========================================================================
75 + // STATIC FACTORY METHODS
76 + // =========================================================================
77 +
78 + /**
79 + * Get OptIn by hash.
80 + *
81 + * @param string $hash The hash.
82 + *
83 + * @return OptIn|null
84 + */
85 + public static function get_by_hash( string $hash ): ?OptIn {
86 + $logger = Logger::getInstance();
87 + $logger->debug( 'get_by_hash called', [
88 + 'plugin' => 'double-opt-in',
89 + 'hash' => $hash,
90 + ] );
91 +
92 + try {
93 + $repository = self::getRepository();
94 + $entity = $repository->findByHash( $hash );
95 +
96 + if ( ! $entity ) {
97 + return null;
98 + }
99 +
100 + $optIn = new self( $logger );
101 + $optIn->entity = $entity;
102 +
103 + return $optIn;
104 + } catch ( \Exception $e ) {
105 + $logger->error( 'Failed to get OptIn by hash', [
106 + 'plugin' => 'double-opt-in',
107 + 'hash' => $hash,
108 + 'error' => $e->getMessage(),
109 + ] );
110 + return null;
111 + }
112 + }
113 +
114 + /**
115 + * Get count by form ID.
116 + *
117 + * @param int $formId The form ID.
118 + *
119 + * @return int
120 + */
121 + public static function get_count( int $formId ): int {
122 + try {
123 + return self::getRepository()->countByFormId( $formId );
124 + } catch ( \Exception $e ) {
125 + return 0;
126 + }
127 + }
128 +
129 + /**
130 + * Get list of OptIns with pagination.
131 + *
132 + * @param array $atts Query attributes.
133 + * @param int|null $numberOfPages Reference to store page count.
134 + *
135 + * @return array<OptIn>
136 + */
137 + public static function get_list( array $atts = [], ?int &$numberOfPages = null ): array {
138 + $logger = Logger::getInstance();
139 +
140 + try {
141 + $repository = self::getRepository();
142 + $entities = $repository->findAll( $atts, $numberOfPages );
143 +
144 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
145 + $optIn = new self( $logger );
146 + $optIn->entity = $entity;
147 + return $optIn;
148 + }, $entities );
149 + } catch ( \Exception $e ) {
150 + $logger->error( 'Failed to get OptIn list', [
151 + 'plugin' => 'double-opt-in',
152 + 'error' => $e->getMessage(),
153 + ] );
154 + return [];
155 + }
156 + }
157 +
158 + /**
159 + * Get list by category ID.
160 + *
161 + * @param int $categoryId The category ID.
162 + * @param array $atts Query attributes.
163 + * @param int|null $numberOfPages Reference to store page count.
164 + *
165 + * @return array<OptIn>
166 + */
167 + public static function get_list_by_category_id( int $categoryId, array $atts = [], ?int &$numberOfPages = null ): array {
168 + $logger = Logger::getInstance();
169 +
170 + try {
171 + $repository = self::getRepository();
172 +
173 + // Get total count for pagination
174 + if ( $numberOfPages !== null ) {
175 + $keyword = $atts['keyword'] ?? '';
176 + $perPage = max( 1, (int) ( $atts['perPage'] ?? 10 ) );
177 + $total = $repository->countByCategory( $categoryId, $keyword );
178 + $numberOfPages = $total > 0 ? (int) ceil( $total / $perPage ) : 0;
179 +
180 + if ( $numberOfPages === 0 ) {
181 + return [];
182 + }
183 + }
184 +
185 + $entities = $repository->findByCategory( $categoryId, $atts );
186 +
187 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
188 + $optIn = new self( $logger );
189 + $optIn->entity = $entity;
190 + return $optIn;
191 + }, $entities );
192 + } catch ( \Exception $e ) {
193 + $logger->error( 'Failed to get OptIn list by category', [
194 + 'plugin' => 'double-opt-in',
195 + 'category' => $categoryId,
196 + 'error' => $e->getMessage(),
197 + ] );
198 + return [];
199 + }
200 + }
201 +
202 + /**
203 + * Get list by email.
204 + *
205 + * @param string $email The email address.
206 + *
207 + * @return array<OptIn>
208 + */
209 + public static function get_list_by_email( string $email ): array {
210 + $logger = Logger::getInstance();
211 +
212 + try {
213 + $repository = self::getRepository();
214 + $entities = $repository->findByEmail( $email );
215 +
216 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
217 + $optIn = new self( $logger );
218 + $optIn->entity = $entity;
219 + return $optIn;
220 + }, $entities );
221 + } catch ( \Exception $e ) {
222 + return [];
223 + }
224 + }
225 +
226 + /**
227 + * Get confirmed list by email.
228 + *
229 + * @param string $email The email address.
230 + *
231 + * @return array<OptIn>
232 + */
233 + public static function get_list_by_email_confirmed( string $email ): array {
234 + $logger = Logger::getInstance();
235 +
236 + try {
237 + $repository = self::getRepository();
238 + $entities = $repository->findConfirmedByEmail( $email );
239 +
240 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
241 + $optIn = new self( $logger );
242 + $optIn->entity = $entity;
243 + return $optIn;
244 + }, $entities );
245 + } catch ( \Exception $e ) {
246 + return [];
247 + }
248 + }
249 +
250 + /**
251 + * Get unconfirmed list by email.
252 + *
253 + * @param string $email The email address.
254 + *
255 + * @return array<OptIn>
256 + */
257 + public static function get_list_by_email_unconfirmed( string $email ): array {
258 + $logger = Logger::getInstance();
259 +
260 + try {
261 + $repository = self::getRepository();
262 + $entities = $repository->findUnconfirmedByEmail( $email );
263 +
264 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
265 + $optIn = new self( $logger );
266 + $optIn->entity = $entity;
267 + return $optIn;
268 + }, $entities );
269 + } catch ( \Exception $e ) {
270 + return [];
271 + }
272 + }
273 +
274 + /**
275 + * Bulk update category.
276 + *
277 + * @param int $fromId The source category ID.
278 + * @param int $toId The target category ID.
279 + *
280 + * @return int
281 + */
282 + public static function bulk_update_category( int $fromId, int $toId ): int {
283 + try {
284 + return self::getRepository()->bulkUpdateCategory( $fromId, $toId );
285 + } catch ( \Exception $e ) {
286 + return 0;
287 + }
288 + }
289 +
290 + /**
291 + * Update category by OptIn ID.
292 + *
293 + * @param int $optInId The OptIn ID.
294 + * @param int $categoryId The new category ID.
295 + *
296 + * @return bool
297 + */
298 + public static function update_category_by_id( int $optInId, int $categoryId ): bool {
299 + try {
300 + return self::getRepository()->updateCategoryById( $optInId, $categoryId );
301 + } catch ( \Exception $e ) {
302 + return false;
303 + }
304 + }
305 +
306 + // =========================================================================
307 + // GETTERS
308 + // =========================================================================
309 +
310 + public function get_id(): int {
311 + return $this->entity->getId();
312 + }
313 +
314 + public function get_hash(): string {
315 + return $this->entity->getHash();
316 + }
317 +
318 + public function get_cf_form_id(): int {
319 + return $this->entity->getFormId();
320 + }
321 +
322 + public function is_confirmed(): bool {
323 + return $this->entity->isConfirmed();
324 + }
325 +
326 + public function get_doubleoptin(): int {
327 + return $this->entity->isConfirmed() ? 1 : 0;
328 + }
329 +
330 + public function is_optout(): bool {
331 + return $this->entity->isOptedOut();
332 + }
333 +
334 + public function get_content(): string {
335 + return $this->entity->getContent();
336 + }
337 +
338 + public function get_createtime( string $view = 'raw' ): string {
339 + if ( $view !== 'raw' ) {
340 + return $this->entity->getCreateTimeFormatted( 'd.m.Y / H:i:s' );
341 + }
342 + return (string) $this->entity->getCreateTime();
343 + }
344 +
345 + public function get_updatetime( string $view = 'raw' ): string {
346 + if ( $view !== 'raw' ) {
347 + return $this->entity->getUpdateTimeFormatted( 'd.m.Y / H:i:s' );
348 + }
349 + return (string) $this->entity->getUpdateTime();
350 + }
351 +
352 + public function get_optouttime( string $view = 'raw' ): string {
353 + $time = $this->entity->getOptOutTime();
354 + if ( $view !== 'raw' && $time > 0 ) {
355 + return wp_date( 'd.m.Y / H:i:s', $time );
356 + }
357 + return (string) $time;
358 + }
359 +
360 + /**
361 + * Get create time as ISO 8601 string.
362 + *
363 + * @return string
364 + */
365 + public function get_createtime_iso(): string {
366 + return $this->entity->getCreateTimeISO();
367 + }
368 +
369 + /**
370 + * Get update time as ISO 8601 string.
371 + *
372 + * @return string
373 + */
374 + public function get_updatetime_iso(): string {
375 + return $this->entity->getUpdateTimeISO();
376 + }
377 +
378 + /**
379 + * Get opt-out time as ISO 8601 string.
380 + *
381 + * @return string
382 + */
383 + public function get_optouttime_iso(): string {
384 + return $this->entity->getOptOutTimeISO();
385 + }
386 +
387 + public function get_ipaddr_register(): string {
388 + return $this->entity->getIpRegister();
389 + }
390 +
391 + public function get_ipaddr_confirmation(): string {
392 + return $this->entity->getIpConfirmation();
393 + }
394 +
395 + public function get_ipaddr_optout(): string {
396 + return $this->entity->getIpOptOut();
397 + }
398 +
399 + public function get_files(): string {
400 + return $this->entity->getFiles();
401 + }
402 +
403 + public function get_category(): int {
404 + return $this->entity->getCategory();
405 + }
406 +
407 + public function get_email(): string {
408 + return $this->entity->getEmail();
409 + }
410 +
411 + public function get_mail_optin(): string {
412 + return $this->entity->getMailOptIn();
413 + }
414 +
415 + public function get_consent_text(): string {
416 + return $this->entity->getConsentText();
417 + }
418 +
419 + public function get_form( bool $encoded = false ): string {
420 + $form = $this->entity->getForm();
421 + if ( $encoded ) {
422 + return base64_encode( $form );
423 + }
424 + return $form;
425 + }
426 +
427 + /**
428 + * Get the validity end date.
429 + *
430 + * @return string
431 + */
432 + public function get_valid_until(): string {
433 + $settings = CF7DoubleOptIn::getInstance()->getSettings();
434 + $dt = new \DateTime();
435 + $dt->setTimestamp( $this->entity->getCreateTime() );
436 +
437 + if ( $this->is_confirmed() ) {
438 + $amount = (int) ( $settings['delete'] ?? 0 );
439 + $period = $settings['delete_period'] ?? 'months';
440 + } else {
441 + $amount = (int) ( $settings['delete_unconfirmed'] ?? 0 );
442 + $period = $settings['delete_unconfirmed_period'] ?? 'months';
443 + }
444 +
445 + $dt->modify( '+' . $amount . ' ' . $period );
446 + $dt->modify( '+1 day' );
447 +
448 + return $dt->format( 'd.m.Y' );
449 + }
450 +
451 + // =========================================================================
452 + // SETTERS
453 + // =========================================================================
454 +
455 + public function set_cf_form_id( int $formId ): void {
456 + $this->entity = $this->entity->withFormId( $formId );
457 + }
458 +
459 + public function set_doubleoptin( int $confirmed ): void {
460 + $this->entity->setConfirmed( (bool) $confirmed );
461 + }
462 +
463 + public function set_createtime( $timestamp ): void {
464 + $this->entity = $this->entity->withCreateTime( (int) $timestamp );
465 + }
466 +
467 + public function set_updatetime( $timestamp ): void {
468 + $this->entity->setUpdateTime( (int) $timestamp );
469 + }
470 +
471 + public function set_optouttime( $timestamp ): void {
472 + $this->entity = $this->entity->withOptOutTime( (int) $timestamp );
473 + }
474 +
475 + public function set_ipaddr_register( string $ip ): void {
476 + $this->entity = $this->entity->withIpRegister( $ip );
477 + }
478 +
479 + public function set_ipaddr_confirmation( string $ip ): void {
480 + $this->entity->setIpConfirmation( $ip );
481 + }
482 +
483 + public function set_ipaddr_optout( string $ip ): void {
484 + $this->entity = $this->entity->withIpOptOut( $ip );
485 + }
486 +
487 + public function set_content( string $content ): void {
488 + $this->entity = $this->entity->withContent( $content );
489 + }
490 +
491 + public function set_files( string $files ): void {
492 + $this->entity = $this->entity->withFiles( $files );
493 + }
494 +
495 + public function set_category( int $categoryId ): void {
496 + $this->entity = $this->entity->withCategory( $categoryId );
497 + }
498 +
499 + public function set_form( string $form ): void {
500 + $this->entity = $this->entity->withForm( $form );
501 + }
502 +
503 + public function set_email( string $email ): void {
504 + $this->entity = $this->entity->withEmail( $email );
505 + }
506 +
507 + public function set_mail_optin( string $mail ): void {
508 + $this->entity = $this->entity->withMailOptIn( $mail );
509 + }
510 +
511 + public function set_consent_text( string $text ): void {
512 + $this->entity = $this->entity->withConsentText( $text );
513 + }
514 +
515 + // =========================================================================
516 + // TYPE CHECKS
517 + // =========================================================================
518 +
519 + public function isTypeCF7(): bool {
520 + return $this->isType( 'cf7' );
521 + }
522 +
523 + public function isTypeAvada(): bool {
524 + return $this->isType( 'avada' );
525 + }
526 +
527 + public function isTypeElementor(): bool {
528 + return $this->isType( 'elementor' );
529 + }
530 +
531 + public function isTypeWPForms(): bool {
532 + return $this->isType( 'wpforms' );
533 + }
534 +
535 + public function isTypeGravityForms(): bool {
536 + return $this->isType( 'gravityforms' );
537 + }
538 +
539 + public function isType( string $type ): bool {
540 + $formId = $this->get_cf_form_id();
541 +
542 + switch ( $type ) {
543 + case 'cf7':
544 + return get_post_type( $formId ) === 'wpcf7_contact_form';
545 + case 'avada':
546 + return get_post_type( $formId ) === 'fusion_form';
547 + case 'elementor':
548 + // Elementor forms are embedded in pages/posts, not stored as separate post types.
549 + // Check if the post has Elementor data AND DOI settings configured.
550 + $hasElementorData = ! empty( get_post_meta( $formId, '_elementor_data', true ) );
551 + $hasDoiSettings = ! empty( get_post_meta( $formId, 'f12-cf7-doubleoptin', true ) );
552 + return $hasElementorData && $hasDoiSettings;
553 + case 'wpforms':
554 + return get_post_type( $formId ) === 'wpforms';
555 + case 'gravityforms':
556 + // Gravity Forms stores forms in a custom table, not as posts.
557 + // Check if the form ID exists in the GF forms table.
558 + if ( class_exists( 'GFAPI' ) ) {
559 + $form = \GFAPI::get_form( $formId );
560 + return $form !== false && ! is_wp_error( $form );
561 + }
562 + return false;
563 + default:
564 + return false;
565 + }
566 + }
567 +
568 + // =========================================================================
569 + // PERSISTENCE
570 + // =========================================================================
571 +
572 + /**
573 + * Save the OptIn to the database.
574 + *
575 + * @return bool|int
576 + */
577 + public function save() {
578 + $this->logger->info( 'Saving OptIn', [
579 + 'plugin' => 'double-opt-in',
580 + 'id' => $this->entity->getId(),
581 + 'hash' => $this->entity->getHash(),
582 + ] );
583 +
584 + try {
585 + $repository = self::getRepository();
586 + $this->entity = $repository->save( $this->entity );
587 +
588 + $this->logger->info( 'OptIn saved successfully', [
589 + 'plugin' => 'double-opt-in',
590 + 'id' => $this->entity->getId(),
591 + 'hash' => $this->entity->getHash(),
592 + ] );
593 +
594 + return true;
595 + } catch ( \Exception $e ) {
596 + $this->logger->error( 'Failed to save OptIn', [
597 + 'plugin' => 'double-opt-in',
598 + 'error' => $e->getMessage(),
599 + ] );
600 + return false;
601 + }
602 + }
603 +
604 + // =========================================================================
605 + // LINK GENERATION
606 + // =========================================================================
607 +
608 + /**
609 + * Get the opt-in confirmation link.
610 + *
611 + * @param array $parameter Additional parameters.
612 + * @param int $formId Optional form ID override.
613 + *
614 + * @return string
615 + */
616 + public function get_link_optin( array $parameter = [], int $formId = 0 ): string {
617 + $formId = $formId > 0 ? $formId : $this->get_cf_form_id();
618 +
619 + $formParameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
620 + $pageId = (int) ( $formParameter['page'] ?? 0 );
621 +
622 + if ( $pageId <= 0 ) {
623 + return home_url( '?optin=' . $this->get_hash() );
624 + }
625 +
626 + $pageUrl = get_permalink( $pageId );
627 + if ( ! $pageUrl ) {
628 + return home_url( '?optin=' . $this->get_hash() );
629 + }
630 +
631 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
632 +
633 + return $pageUrl . $separator . 'optin=' . $this->get_hash();
634 + }
635 +
636 + /**
637 + * Get the opt-out link.
638 + *
639 + * @return string
640 + */
641 + public function get_link_optout(): string {
642 + $settings = CF7DoubleOptIn::getInstance()->getSettings();
643 + $pageId = (int) ( $settings['optout_page'] ?? 0 );
644 +
645 + if ( $pageId <= 0 ) {
646 + return home_url( '?optout=' . $this->get_hash() );
647 + }
648 +
649 + $pageUrl = get_permalink( $pageId );
650 + if ( ! $pageUrl ) {
651 + return home_url( '?optout=' . $this->get_hash() );
652 + }
653 +
654 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
655 +
656 + return $pageUrl . $separator . 'optout=' . $this->get_hash();
657 + }
658 +
659 + /**
660 + * Get the UI edit link.
661 + *
662 + * @return string
663 + */
664 + public function get_link_ui(): string {
665 + return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&view=single&hash=' . $this->get_hash() );
666 + }
667 +
668 + /**
669 + * Get the delete link.
670 + *
671 + * @return string
672 + */
673 + public function get_link_delete(): string {
674 + $nonce = wp_create_nonce( 'delete_optin_' . $this->get_hash() );
675 + return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&action=delete&hash=' . $this->get_hash() . '&_wpnonce=' . $nonce );
676 + }
677 +
678 + /**
679 + * Get the form name.
680 + *
681 + * @return string
682 + */
683 + public function get_form_name(): string {
684 + $formId = $this->get_cf_form_id();
685 +
686 + if ( $formId <= 0 ) {
687 + return __( 'Unknown', 'double-opt-in' );
688 + }
689 +
690 + $post = get_post( $formId );
691 + if ( ! $post ) {
692 + return __( 'Deleted Form', 'double-opt-in' ) . ' (ID: ' . $formId . ')';
693 + }
694 +
695 + return $post->post_title ?: __( 'Untitled Form', 'double-opt-in' );
696 + }
697 +
698 + /**
699 + * Get the form edit link.
700 + *
701 + * @return string
702 + */
703 + public function get_form_link(): string {
704 + $formId = $this->get_cf_form_id();
705 + $postType = get_post_type( $formId );
706 +
707 + if ( $postType === 'wpcf7_contact_form' ) {
708 + return admin_url( 'admin.php?page=wpcf7&post=' . $formId . '&action=edit' );
709 + }
710 +
711 + if ( $postType === 'fusion_form' ) {
712 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
713 + }
714 +
715 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
716 + }
717 +
718 + /**
719 + * Get form settings.
720 + *
721 + * @return array
722 + */
723 + public function get_form_settings(): array {
724 + return CF7DoubleOptIn::getInstance()->getParameter( $this->get_cf_form_id() );
725 + }
726 +
727 + // =========================================================================
728 + // HELPERS
729 + // =========================================================================
730 +
731 + /**
732 + * Get the repository instance.
733 + *
734 + * @return OptInRepositoryInterface
735 + */
736 + private static function getRepository(): OptInRepositoryInterface {
737 + $container = Container::getInstance();
738 + return $container->get( OptInRepositoryInterface::class );
739 + }
740 +
741 + /**
742 + * Map legacy property names to entity array keys.
743 + *
744 + * @param array $properties Legacy properties.
745 + *
746 + * @return array
747 + */
748 + private function mapToEntityArray( array $properties ): array {
749 + $mapping = [
750 + 'cf_form_id' => 'cf_form_id',
751 + 'doubleoptin' => 'doubleoptin',
752 + 'content' => 'content',
753 + 'hash' => 'hash',
754 + 'createtime' => 'createtime',
755 + 'updatetime' => 'updatetime',
756 + 'optouttime' => 'optouttime',
757 + 'ipaddr_register' => 'ipaddr_register',
758 + 'ipaddr_confirmation' => 'ipaddr_confirmation',
759 + 'ipaddr_optout' => 'ipaddr_optout',
760 + 'files' => 'files',
761 + 'category' => 'category',
762 + 'form' => 'form',
763 + 'mail_optin' => 'mail_optin',
764 + 'email' => 'email',
765 + 'id' => 'id',
766 + 'consent_text' => 'consent_text',
767 + 'consent_field' => 'consent_field',
768 + ];
769 +
770 + $result = [];
771 + foreach ( $mapping as $legacy => $entity ) {
772 + if ( isset( $properties[ $legacy ] ) ) {
773 + $result[ $entity ] = $properties[ $legacy ];
774 + }
775 + }
776 +
777 + return $result;
778 + }
779 +}