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.12.0 5.13.0 5.13.1 5.11.0 5.10.0 5.9.0 5.8.0 5.8.1 5.7.0 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 All 47 releases
← All changes | src/Integration/CF7Integration.php +34 -410 5.11.0 → 5.5.0 View file →
@@ -9,14 +9,8 @@
9 9 namespace Forge12\DoubleOptIn\Integration;
10 10
11 11 use Forge12\DoubleOptIn\Container\Container;
12 12 use Forge12\DoubleOptIn\EmailTemplates\PlaceholderMapper;
13 -use Forge12\DoubleOptIn\FollowUp\FollowUpAttempt;
14 -use Forge12\DoubleOptIn\FollowUp\FollowUpCoordinator;
15 -use Forge12\DoubleOptIn\FollowUp\FollowUpResult;
16 -use Forge12\DoubleOptIn\Frontend\ErrorNotification;
17 -use Forge12\DoubleOptIn\Frontend\SubmitNotice;
18 -use Forge12\DoubleOptIn\Spam\SubmissionTrap;
19 13 use forge12\contactform7\CF7DoubleOptIn\Category;
20 14 use forge12\contactform7\CF7DoubleOptIn\CF7DoubleOptIn;
21 15 use forge12\contactform7\CF7DoubleOptIn\HTMLSelect;
22 16 use forge12\contactform7\CF7DoubleOptIn\OptIn;
@@ -32,9 +26,9 @@
32 26 *
33 27 * Integration for Contact Form 7.
34 28 * Handles opt-in creation, confirmation mail sending, and admin panel.
35 29 */
36 -class CF7Integration extends AbstractFormIntegration implements AdminPanelInterface, FieldTextProviderInterface {
30 +class CF7Integration extends AbstractFormIntegration implements AdminPanelInterface {
37 31
38 32 /**
39 33 * Current OptIn for mail attachment handling.
40 34 *
@@ -42,23 +36,8 @@
42 36 */
43 37 private ?OptIn $currentOptIn = null;
44 38
45 39 /**
46 - * The confirmation mail sent in this request, for the feedback response
47 - * (5.8.0): recipient, resolved sender and subject, and the outcome.
48 - *
49 - * @var array{email: string, sender: string, subject: string, sent: bool}|null
50 - */
51 - private $lastOptInMail = null;
52 -
53 - /**
54 - * The DOI submission of this request, keyed to its form (5.8.0).
55 - *
56 - * @var array{form_id: int, email: string, sender: string, subject: string, sent: bool}|null
57 - */
58 - private $submitted = null;
59 -
60 - /**
61 40 * {@inheritdoc}
62 41 */
63 42 public function getIdentifier(): string {
64 43 return 'cf7';
@@ -99,16 +78,8 @@
99 78 // Frontend hooks
100 79 add_action( 'wpcf7_before_send_mail', array( $this, 'onSubmit' ), $this->getHookPriority(), 3 );
101 80 add_action( 'init', array( $this, 'handleOptInConfirmation' ) );
102 81
103 - // Honest success message + "open your inbox" (5.8.0).
104 - add_filter( 'wpcf7_feedback_response', array( $this, 'filterFeedbackResponse' ), 10, 2 );
105 - add_action( 'wpcf7_enqueue_scripts', array( $this, 'enqueueSubmitNotice' ) );
106 -
107 - // Honeypot + minimum fill time, through CF7's own spam flow (5.8.0).
108 - add_filter( 'wpcf7_form_elements', array( $this, 'addSubmissionTrap' ) );
109 - add_filter( 'wpcf7_spam', array( $this, 'checkSubmissionTrap' ), 9, 2 );
110 -
111 82 // Register recipient filter
112 83 add_filter( 'f12_cf7_doubleoptin_get_recipient_cf7', array( $this, 'getRecipientFilter' ), 10, 3 );
113 84
114 85 // Confirmation mail hooks
@@ -113,9 +84,9 @@
113 84
114 85 // Confirmation mail hooks
115 86 add_action( 'f12_cf7_doubleoptin_before_send_default_mail', array( $this, 'beforeSendDefaultMail' ) );
116 87 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', array( $this, 'afterSendDefaultMail' ) );
117 - add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'onTriggerDefaultMail' ) );
88 + add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'sendConfirmationMail' ) );
118 89
119 90 // File hand-off + pending-cleanup. CF7 attaches files to the
120 91 // confirmation mail in attachExtraAttachments (hooked on
121 92 // wpcf7_before_send_mail during sendConfirmationMail). Cleanup
@@ -274,10 +245,10 @@
274 245 )
275 246 );
276 247
277 248 if ( ! $this->isOptInEnabled( $formId ) ) {
278 - // Our own post-confirmation replay: attach the stored files.
279 - if ( self::isReplaying() ) {
249 + // Handle file attachments for confirmation
250 + if ( isset( $_GET['optin'] ) ) {
280 251 $this->attachStoredFiles( $submission );
281 252 }
282 253 return;
283 254 }
@@ -312,29 +283,20 @@
312 283 // Always prevent the original CF7 mail from being sent when opt-in creation fails
313 284 add_filter( 'wpcf7_skip_mail', '__return_true' );
314 285
315 286 $error = self::getLastError();
316 - // Abort with the reason instead of CF7's "sent": CF7 then keeps the
317 - // visitor's input and shows the message in the form. A refused
318 - // consent always takes this path (OptInError::isAlwaysShown()).
319 - if ( $error && $error->shouldShowToVisitor( (int) $formId ) ) {
287 + if ( $error && apply_filters( 'f12_cf7_doubleoptin_show_validation_error', false ) ) {
320 288 $message = apply_filters( 'f12_cf7_doubleoptin_error_message', $error->getMessage(), $error, $formId );
321 289 if ( method_exists( $submission, 'set_response' ) ) {
322 290 $submission->set_response( $message );
323 291 }
324 292 $abort = true;
325 - // The form shows it now — no second copy in the toast.
326 - ErrorNotification::forget();
327 293 }
328 294 return;
329 295 }
330 296
331 297 // Send opt-in mail
332 - $this->lastOptInMail = null;
333 298 $this->sendOptInMail( $optIn, $formData, $formParameter );
334 - if ( $this->lastOptInMail !== null ) {
335 - $this->submitted = array( 'form_id' => (int) $formId ) + $this->lastOptInMail;
336 - }
337 299
338 300 // Skip original mail
339 301 add_filter( 'wpcf7_skip_mail', '__return_true' );
340 302 do_action( 'f12_cf7_doubleoptin_sent', $form, $formId );
@@ -380,306 +342,28 @@
380 342 if ( ! empty( $args['sender_name'] ) ) {
381 343 $args['additional_headers'] .= 'From: ' . $args['sender_name'] . ' <' . $args['sender'] . '>';
382 344 }
383 345
384 - // Send via CF7 mail system. Up to 5.7 the result was dropped and a
385 - // failed send looked exactly like a successful one.
386 - $sent = (bool) \WPCF7_Mail::send( $args, 'mail' );
346 + // Send via CF7 mail system
347 + \WPCF7_Mail::send( $args, 'mail' );
387 348
388 - // Record the outcome on the opt-in (OptInMailTracker). No address in
389 - // the log: the id identifies the record.
390 - do_action( 'f12_doi_optin_mail_result', (int) $optIn->get_id(), $sent, '' );
391 -
392 - // What the visitor is told to look for — with CF7's mail tags resolved,
393 - // as WPCF7_Mail::send() did for the mail itself.
394 - $sender = (string) $args['sender'];
395 - $subject = (string) $args['subject'];
396 - if ( function_exists( 'wpcf7_mail_replace_tags' ) ) {
397 - $sender = (string) wpcf7_mail_replace_tags( $sender );
398 - $subject = (string) wpcf7_mail_replace_tags( $subject );
399 - }
400 - $this->lastOptInMail = array(
401 - 'optin_id' => (int) $optIn->get_id(),
402 - 'email' => (string) $optIn->get_email(),
403 - 'sender' => SubmitNotice::senderAddress( $sender ),
404 - 'subject' => trim( wp_strip_all_tags( $subject ) ),
405 - 'sent' => $sent,
406 - );
407 -
408 349 $this->getLogger()->info(
409 - $sent ? 'OptIn mail handed to the mail server via CF7' : 'OptIn mail could not be sent via CF7',
350 + 'OptIn mail sent via CF7',
410 351 array(
411 - 'plugin' => 'double-opt-in',
412 - 'form_id' => $formData->getFormId(),
413 - 'optin_id' => (int) $optIn->get_id(),
352 + 'plugin' => 'double-opt-in',
353 + 'form_id' => $formData->getFormId(),
354 + 'recipient' => $args['recipient'],
414 355 )
415 356 );
416 357
417 - return $sent;
418 - }
419 -
420 - /**
421 - * Make CF7's answer to a double opt-in submission tell the truth (5.8.0).
422 - *
423 - * CF7 answers "Thank you for your message. It has been sent." — but
424 - * nothing is sent until the address is confirmed, and when the
425 - * confirmation mail itself failed CF7 still said so and cleared the form.
426 - * CF7 overwrites any response set during the submission, so this runs on
427 - * the finished feedback response.
428 - *
429 - * @param mixed $response CF7 feedback response.
430 - * @param mixed $result CF7 submission result.
431 - *
432 - * @return mixed
433 - */
434 - public function filterFeedbackResponse( $response, $result = null ) {
435 - if ( ! is_array( $response ) || $this->submitted === null ) {
436 - return $response;
437 - }
438 -
439 - $formId = (int) ( $response['contact_form_id'] ?? 0 );
440 - if ( $formId !== $this->submitted['form_id'] || ( $response['status'] ?? '' ) !== 'mail_sent' ) {
441 - return $response;
442 - }
443 -
444 - $submitted = $this->submitted;
445 - $this->submitted = null;
446 - $form = class_exists( '\WPCF7_ContactForm' ) ? \WPCF7_ContactForm::get_instance( $formId ) : null;
447 -
448 - if ( ! $submitted['sent'] ) {
449 - // CF7 then keeps the visitor's input and fires wpcf7mailfailed.
450 - $response['status'] = 'mail_failed';
451 - $response['message'] = $form ? (string) $form->message( 'mail_sent_ng' ) : __( 'The confirmation mail could not be sent. Please try again later.', 'double-opt-in' );
452 - return $response;
453 - }
454 -
455 - /**
456 - * Whether to show the confirmation hint (masked address, what to look
457 - * for, "open your inbox" link) after a double opt-in submission.
458 - *
459 - * @since 5.8.0
460 - *
461 - * @param bool $show Default true.
462 - * @param int $formId Form ID.
463 - */
464 - if ( ! apply_filters( 'f12_doi_submit_notice', true, $formId ) ) {
465 - return $response;
466 - }
467 -
468 - $notice = SubmitNotice::extend(
469 - SubmitNotice::build( $submitted['email'], $submitted['sender'], $submitted['subject'] ),
470 - array(
471 - 'form_id' => $formId,
472 - 'optin_id' => (int) ( $submitted['optin_id'] ?? 0 ),
473 - 'integration' => 'cf7',
474 - )
475 - );
476 -
477 - // A message the site owner wrote stays; only CF7's own default is wrong.
478 - if ( $form && self::isDefaultSentMessage( (string) $form->message( 'mail_sent_ok', false ) ) ) {
479 - $response['message'] = SubmitNotice::message( $notice['masked'] );
480 - }
481 -
482 - $response['doi'] = $notice;
483 -
484 - return $response;
485 - }
486 -
487 - /**
488 - * Whether a form's success message is still CF7's default, in English or
489 - * in the current language.
490 - */
491 - public static function isDefaultSentMessage( string $message ): bool {
492 - $defaults = array( 'Thank you for your message. It has been sent.' );
493 - if ( function_exists( 'wpcf7_messages' ) ) {
494 - $messages = wpcf7_messages();
495 - $defaults[] = (string) ( $messages['mail_sent_ok']['default'] ?? '' );
496 - }
497 -
498 - return in_array( trim( $message ), array_filter( $defaults ), true );
499 - }
500 -
501 - /**
502 - * Whether a form gets the honeypot and the minimum fill time.
503 - */
504 - private function trapEnabled( int $formId ): bool {
505 - /**
506 - * Whether a double opt-in form gets the honeypot and the minimum
507 - * fill time.
508 - *
509 - * @since 5.8.0
510 - *
511 - * @param bool $enabled Default true.
512 - * @param int $formId Form ID.
513 - */
514 - return $formId > 0 && $this->isOptInEnabled( $formId ) && (bool) apply_filters( 'f12_doi_spam_trap', true, $formId );
515 - }
516 -
517 - /**
518 - * Add the trap fields to a double opt-in form.
519 - *
520 - * @param mixed $elements Form HTML.
521 - *
522 - * @return mixed
523 - */
524 - public function addSubmissionTrap( $elements ) {
525 - if ( ! is_string( $elements ) || ! function_exists( 'wpcf7_get_current_contact_form' ) ) {
526 - return $elements;
527 - }
528 - $form = wpcf7_get_current_contact_form();
529 - if ( ! $form || ! $this->trapEnabled( (int) $form->id() ) ) {
530 - return $elements;
531 - }
532 -
533 - return $elements . SubmissionTrap::markup( time() );
534 - }
535 -
536 - /**
537 - * Mark a bot submission as spam before any opt-in or mail exists.
538 - *
539 - * @param mixed $spam CF7's verdict so far.
540 - * @param mixed $submission The submission.
541 - *
542 - * @return mixed
543 - */
544 - public function checkSubmissionTrap( $spam, $submission = null ) {
545 - if ( $spam || self::isReplaying() || ! is_object( $submission ) || ! method_exists( $submission, 'get_contact_form' ) ) {
546 - return $spam;
547 - }
548 - $form = $submission->get_contact_form();
549 - $formId = $form ? (int) $form->id() : 0;
550 - if ( ! $this->trapEnabled( $formId ) ) {
551 - return $spam;
552 - }
553 -
554 - /**
555 - * Minimum seconds between rendering a double opt-in form and
556 - * submitting it; faster submissions are treated as bots.
557 - *
558 - * @since 5.8.0
559 - *
560 - * @param int $seconds Default 2.
561 - * @param int $formId Form ID.
562 - */
563 - $minSeconds = (int) apply_filters( 'f12_doi_min_fill_seconds', SubmissionTrap::DEFAULT_MIN_SECONDS, $formId );
564 -
565 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- CF7 verified the submission; only our two trap fields are read.
566 - $reason = SubmissionTrap::check( wp_unslash( $_POST ), time(), $minSeconds );
567 - if ( $reason === '' ) {
568 - return $spam;
569 - }
570 -
571 - if ( method_exists( $submission, 'add_spam_log' ) ) {
572 - $submission->add_spam_log(
573 - array(
574 - 'agent' => 'double-opt-in',
575 - 'reason' => $reason,
576 - )
577 - );
578 - }
579 -
580 - /**
581 - * A double opt-in submission was stopped by the honeypot or the
582 - * minimum fill time. No personal data is passed.
583 - *
584 - * @since 5.8.0
585 - *
586 - * @param string $reason 'honeypot', 'stamp_invalid' or 'too_fast'.
587 - * @param int $formId Form ID.
588 - * @param string $integration Integration identifier.
589 - */
590 - do_action( 'f12_doi_spam_trap_hit', $reason, $formId, 'cf7' );
591 -
592 - $this->getLogger()->info(
593 - 'Submission stopped by the spam trap',
594 - array(
595 - 'plugin' => 'double-opt-in',
596 - 'form_id' => $formId,
597 - 'reason' => $reason,
598 - )
599 - );
600 -
601 358 return true;
602 359 }
603 360
604 361 /**
605 - * The script that renders the confirmation hint under the CF7 message.
606 - * Runs only where CF7 enqueues its own scripts.
607 - *
608 - * @return void
609 - */
610 - public function enqueueSubmitNotice(): void {
611 - $version = defined( 'FORGE12_OPTIN_VERSION' ) ? FORGE12_OPTIN_VERSION : false;
612 -
613 - wp_enqueue_script(
614 - 'f12-doi-submit-notice',
615 - plugins_url( 'assets/js/doi-submit-notice.js', F12_DOUBLEOPTIN_PLUGIN_FILE ),
616 - array(),
617 - $version,
618 - true
619 - );
620 -
621 - wp_register_style( 'f12-doi-submit-notice', false, array(), $version );
622 - wp_enqueue_style( 'f12-doi-submit-notice' );
623 - wp_add_inline_style(
624 - 'f12-doi-submit-notice',
625 - '.f12-doi-notice{margin:.5em 0 1em;padding:0 1em}'
626 - . '.f12-doi-notice p{margin:.4em 0}'
627 - . '.f12-doi-notice .f12-doi-inbox{display:inline-block;margin-top:.4em;padding:.5em 1em;border:1px solid currentColor;border-radius:4px;text-decoration:none;font-weight:600}'
628 - // Buttons and links of add-ons (f12_doi_submit_notice_data) look like
629 - // the inbox link instead of a bare browser button.
630 - . '.f12-doi-notice .f12-doi-action{display:inline-block;margin-top:.4em;padding:.5em 1em;border:1px solid currentColor;border-radius:4px;background:transparent;color:inherit;font:inherit;font-weight:600;text-decoration:none;cursor:pointer}'
631 - . '.f12-doi-notice button.f12-doi-action:disabled{opacity:.5;cursor:default}'
632 - );
633 - }
634 -
635 - /**
636 362 * {@inheritdoc}
637 363 */
638 364 public function sendConfirmationMail( OptIn $optIn ): void {
639 - self::runAsReplay(
640 - function () use ( $optIn ) {
641 - $this->replaySubmission( $optIn );
642 - }
643 - );
644 - }
645 -
646 - /**
647 - * Listener on the global `f12_cf7_doubleoptin_trigger_default_mail`.
648 - * That action fires for every integration's opt-in; this used to run
649 - * the CF7 submission for Elementor opt-ins too, overwriting $_POST.
650 - * Managed opt-ins go through the follow-up coordinator, so a second
651 - * trigger never sends twice.
652 - *
653 - * @param OptIn $optIn The confirmed opt-in.
654 - *
655 - * @since 5.6.0
656 - */
657 - public function onTriggerDefaultMail( OptIn $optIn ): void {
658 - if ( ! $optIn->isType( $this->getIdentifier() ) ) {
659 - return;
660 - }
661 -
662 - $coordinator = FollowUpCoordinator::instance();
663 - if ( $coordinator !== null && $coordinator->plan( $optIn, true ) ) {
664 - $coordinator->run( $optIn, FollowUpAttempt::TRIGGER_LEGACY );
665 - return;
666 - }
667 -
668 - $this->sendConfirmationMail( $optIn );
669 - }
670 -
671 - /**
672 - * Re-run the stored submission through CF7 and report what CF7 says.
673 - *
674 - * Must run inside {@see runAsReplay()} so our own
675 - * `wpcf7_before_send_mail` listener processes it as the confirmed
676 - * submission (attachments) instead of creating a new opt-in.
677 - *
678 - * @since 5.6.0
679 - */
680 - public function replaySubmission( OptIn $optIn ): FollowUpResult {
681 - if ( ! $this->isAvailable() || ! class_exists( '\\WPCF7_ContactForm' ) || ! class_exists( '\\WPCF7_Submission' ) ) {
365 + if ( ! $this->isAvailable() ) {
682 366 $this->getLogger()->warning(
683 367 'CF7 not available for confirmation mail',
684 368 array(
685 369 'plugin' => 'double-opt-in',
@@ -684,11 +368,18 @@
684 368 array(
685 369 'plugin' => 'double-opt-in',
686 370 )
687 371 );
688 - return FollowUpResult::failedRetryable( 'integration_unavailable' );
372 + return;
689 373 }
690 374
375 + $this->currentOptIn = $optIn;
376 +
377 + // Restore POST data
378 + $data = maybe_unserialize( $optIn->get_content() );
379 + $_POST = SanitizeHelper::sanitize_array( $data );
380 +
381 + // Get CF7 form
691 382 $contactForm = \WPCF7_ContactForm::get_instance( $optIn->get_cf_form_id() );
692 383 if ( ! $contactForm ) {
693 384 $this->getLogger()->warning(
694 385 'CF7 form not found for confirmation mail',
@@ -696,52 +387,31 @@
696 387 'plugin' => 'double-opt-in',
697 388 'form_id' => $optIn->get_cf_form_id(),
698 389 )
699 390 );
700 - return FollowUpResult::failedPermanent( 'form_missing' );
391 + return;
701 392 }
702 393
703 - $data = maybe_unserialize( $optIn->get_content() );
704 - if ( ! is_array( $data ) ) {
705 - return FollowUpResult::failedPermanent( 'payload_missing' );
706 - }
394 + // Add attachment hook
395 + add_action( 'wpcf7_before_send_mail', array( $this, 'attachExtraAttachments' ), 10, 3 );
707 396
708 - $previousPost = $_POST;
709 - $previousOptIn = $this->currentOptIn;
710 - $this->currentOptIn = $optIn;
711 - $status = '';
397 + // Disable validation and spam checks before creating submission
398 + $this->beforeSendConfirmationMail();
712 399
713 - try {
714 - $_POST = SanitizeHelper::sanitize_array( $data );
400 + // Create submission and send mail
401 + $submission = \WPCF7_Submission::get_instance( $contactForm );
715 402
716 - // Disable validation and spam checks before creating submission
717 - $this->beforeSendConfirmationMail();
403 + // Re-enable validation and spam checks
404 + $this->afterSendConfirmationMail();
718 405
719 - // Create submission and send mail. Attachments are added by
720 - // onSubmit() → attachStoredFiles() while isReplaying().
721 - $submission = \WPCF7_Submission::get_instance( $contactForm );
722 -
723 - if ( is_object( $submission ) && method_exists( $submission, 'get_status' ) ) {
724 - $status = (string) $submission->get_status();
725 - }
726 - } finally {
727 - // Re-enable validation and spam checks, restore request state.
728 - $this->afterSendConfirmationMail();
729 - $_POST = $previousPost;
730 - $this->currentOptIn = $previousOptIn;
731 - }
732 -
733 406 $this->getLogger()->info(
734 407 'Confirmation mail triggered via CF7',
735 408 array(
736 - 'plugin' => 'double-opt-in',
737 - 'form_id' => $optIn->get_cf_form_id(),
738 - 'optin_id' => $optIn->get_id(),
739 - 'cf7_status' => $status,
409 + 'plugin' => 'double-opt-in',
410 + 'form_id' => $optIn->get_cf_form_id(),
411 + 'optin_id' => $optIn->get_id(),
740 412 )
741 413 );
742 -
743 - return CF7FollowUpAdapter::mapStatus( $status );
744 414 }
745 415
746 416 /**
747 417 * Handle opt-in confirmation from URL.
@@ -802,11 +472,10 @@
802 472 *
803 473 * @return void
804 474 */
805 475 private function attachStoredFiles( $submission ): void {
806 - // The opt-in being replayed — not the one named in the URL, which
807 - // a cron or admin retry does not have (and a visitor controls).
808 - $optIn = $this->currentOptIn;
476 + $hash = sanitize_text_field( $_GET['optin'] );
477 + $optIn = OptIn::get_by_hash( $hash );
809 478
810 479 if ( ! $optIn ) {
811 480 return;
812 481 }
@@ -868,16 +537,8 @@
868 537 *
869 538 * @since 4.3.0
870 539 */
871 540 public function cleanupPendingAfterMail( OptIn $optIn ): void {
872 - // Managed opt-ins: CF7FollowUpAdapter::onSettled() cleans up only
873 - // once the mail was actually handed over. This hook fires after
874 - // the attempt regardless of its outcome.
875 - $coordinator = FollowUpCoordinator::instance();
876 - if ( $coordinator !== null && $coordinator->adapterFor( $optIn ) !== null ) {
877 - return;
878 - }
879 -
880 541 // Template-method's $hash arg is unused inside processFilesOnConfirm;
881 542 // passing an empty string keeps the contract tight.
882 543 $this->processFilesOnConfirm( '', $optIn );
883 544 }
@@ -906,45 +567,8 @@
906 567 }
907 568 }
908 569
909 570 return $fields;
910 - }
911 -
912 - /**
913 - * The text of each [acceptance]…[/acceptance] tag, as the visitor reads it.
914 - *
915 - * @param int|string $formId
916 - *
917 - * @return array<string, string>
918 - */
919 - public function getFieldTexts( $formId ): array {
920 - $formId = (int) $formId;
921 - $post = get_post( $formId );
922 - if ( ! $post || $post->post_type !== 'wpcf7_contact_form' || ! class_exists( '\WPCF7_ContactForm' ) ) {
923 - return array();
924 - }
925 -
926 - $contactForm = \WPCF7_ContactForm::get_instance( $formId );
927 - if ( ! $contactForm ) {
928 - return array();
929 - }
930 -
931 - $texts = array();
932 - foreach ( $contactForm->scan_form_tags( array( 'basetype' => 'acceptance' ) ) as $tag ) {
933 - $text = self::plainText( (string) ( $tag->content ?? '' ) );
934 - if ( ! empty( $tag->name ) && $text !== '' ) {
935 - $texts[ (string) $tag->name ] = $text;
936 - }
937 - }
938 - return $texts;
939 - }
940 -
941 - /**
942 - * Markup as the visitor reads it: tags removed, whitespace collapsed.
943 - */
944 - public static function plainText( string $html ): string {
945 - $text = html_entity_decode( wp_strip_all_tags( $html ), ENT_QUOTES, 'UTF-8' );
946 - return trim( (string) preg_replace( '/\s+/u', ' ', $text ) );
947 571 }
948 572
949 573 /**
950 574 * Add editor panel to CF7.