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 / CF7Integration.php

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

773 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Contact Form 7 Integration
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\EmailTemplates\PlaceholderMapper;
13 use forge12\contactform7\CF7DoubleOptIn\Category;
14 use forge12\contactform7\CF7DoubleOptIn\CF7DoubleOptIn;
15 use forge12\contactform7\CF7DoubleOptIn\HTMLSelect;
16 use forge12\contactform7\CF7DoubleOptIn\OptIn;
17 use forge12\contactform7\CF7DoubleOptIn\SanitizeHelper;
18 use Forge12\Shared\LoggerInterface;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23
24 /**
25 * Class CF7Integration
26 *
27 * Integration for Contact Form 7.
28 * Handles opt-in creation, confirmation mail sending, and admin panel.
29 */
30 class CF7Integration extends AbstractFormIntegration implements AdminPanelInterface {
31
32 /**
33 * Current OptIn for mail attachment handling.
34 *
35 * @var OptIn|null
36 */
37 private ?OptIn $currentOptIn = null;
38
39 /**
40 * {@inheritdoc}
41 */
42 public function getIdentifier(): string {
43 return 'cf7';
44 }
45
46 /**
47 * {@inheritdoc}
48 */
49 public function getName(): string {
50 return __( 'Contact Form 7', 'double-opt-in' );
51 }
52
53 /**
54 * {@inheritdoc}
55 */
56 public function isAvailable(): bool {
57 return function_exists( 'wpcf7' ) || class_exists( '\WPCF7_ContactForm' );
58 }
59
60 /**
61 * {@inheritdoc}
62 */
63 protected function getPostType(): string {
64 return 'wpcf7_contact_form';
65 }
66
67 /**
68 * {@inheritdoc}
69 */
70 public function getFormEditUrl( $formId ): string {
71 return admin_url( 'admin.php?page=wpcf7&post=' . (int) $formId . '&action=edit' );
72 }
73
74 /**
75 * {@inheritdoc}
76 */
77 public function registerHooks(): void {
78 // Frontend hooks
79 add_action( 'wpcf7_before_send_mail', array( $this, 'onSubmit' ), $this->getHookPriority(), 3 );
80 add_action( 'init', array( $this, 'handleOptInConfirmation' ) );
81
82 // Register recipient filter
83 add_filter( 'f12_cf7_doubleoptin_get_recipient_cf7', array( $this, 'getRecipientFilter' ), 10, 3 );
84
85 // Confirmation mail hooks
86 add_action( 'f12_cf7_doubleoptin_before_send_default_mail', array( $this, 'beforeSendDefaultMail' ) );
87 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', array( $this, 'afterSendDefaultMail' ) );
88 add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'sendConfirmationMail' ) );
89
90 // File hand-off + pending-cleanup. CF7 attaches files to the
91 // confirmation mail in attachExtraAttachments (hooked on
92 // wpcf7_before_send_mail during sendConfirmationMail). Cleanup
93 // MUST run AFTER the mail is sent — otherwise we'd be deleting
94 // files before they're attached. Hence `after_send_default_mail`,
95 // not `after_confirm` like the other integrations. Priority 20
96 // (default 10) so any third-party listener that introspects the
97 // attachments still sees them.
98 // File-lifecycle plan, Schritt 2c (2026-05-08).
99 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', array( $this, 'cleanupPendingAfterMail' ), 20, 1 );
100
101 // Admin hooks
102 $this->registerAdminHooks();
103
104 $this->getLogger()->debug(
105 'CF7 integration hooks registered',
106 array(
107 'plugin' => 'double-opt-in',
108 )
109 );
110 }
111
112 /**
113 * {@inheritdoc}
114 */
115 public function registerAdminHooks(): void {
116 add_action( 'admin_init', array( $this, 'setupAdminPanel' ) );
117 add_action( 'admin_enqueue_scripts', array( $this, 'enqueueAdminAssets' ) );
118 }
119
120 /**
121 * Setup admin panel hooks.
122 *
123 * @return void
124 */
125 public function setupAdminPanel(): void {
126 add_filter( 'wpcf7_editor_panels', array( $this, 'addEditorPanel' ), 10, 1 );
127 add_action( 'wpcf7_save_contact_form', array( $this, 'saveFormSettings' ), 10, 3 );
128 }
129
130 /**
131 * {@inheritdoc}
132 */
133 public function enqueueAdminAssets( string $hook ): void {
134 wp_enqueue_script(
135 'f12-cf7-doubleoptin-admin',
136 plugins_url( 'compatibility/cf7/assets/f12-cf7-popup.js', F12_DOUBLEOPTIN_PLUGIN_FILE ),
137 array( 'jquery' )
138 );
139
140 wp_localize_script(
141 'f12-cf7-doubleoptin-admin',
142 'doi',
143 array(
144 'ajax_url' => admin_url( 'admin-ajax.php' ),
145 'nonce' => wp_create_nonce( 'f12_doi_details' ),
146 )
147 );
148
149 wp_enqueue_script(
150 'f12-cf7-doubleoptin-templateloader',
151 plugins_url( 'compatibility/cf7/assets/f12-cf7-templateloader.js', F12_DOUBLEOPTIN_PLUGIN_FILE ),
152 array( 'jquery' )
153 );
154
155 wp_localize_script(
156 'f12-cf7-doubleoptin-templateloader',
157 'templateloader',
158 array(
159 'ajax_url' => admin_url( 'admin-ajax.php' ),
160 'nonce' => wp_create_nonce( 'f12_doi_templateloader' ),
161 'label_placeholder' => __( 'Please wait while we load the template...', 'double-opt-in' ),
162 )
163 );
164 }
165
166 /**
167 * {@inheritdoc}
168 */
169 public function getHookPriority(): int {
170 return 5;
171 }
172
173 /**
174 * {@inheritdoc}
175 */
176 public function processSubmission( $context ): ?FormDataInterface {
177 if ( ! is_array( $context ) || ! isset( $context['form'] ) || ! isset( $context['submission'] ) ) {
178 return null;
179 }
180
181 $form = $context['form'];
182 $submission = $context['submission'];
183
184 return FormData::fromCF7( $form, $submission );
185 }
186
187 /**
188 * {@inheritdoc}
189 */
190 public function resolveRecipient( FormDataInterface $formData, array $formParameter ): string {
191 if ( ! isset( $formParameter['recipient'] ) ) {
192 return '';
193 }
194
195 $recipientField = str_replace( array( '[', ']' ), '', $formParameter['recipient'] );
196 $fields = $formData->getFields();
197
198 if ( isset( $fields[ $recipientField ] ) ) {
199 return sanitize_email( $fields[ $recipientField ] );
200 }
201
202 return '';
203 }
204
205 /**
206 * Recipient filter callback for legacy compatibility.
207 *
208 * @param string $recipient Current recipient.
209 * @param array $formParameter Form parameters.
210 * @param array $postParameter Post data.
211 *
212 * @return string The resolved recipient.
213 */
214 public function getRecipientFilter( string $recipient, array $formParameter, array $postParameter ): string {
215 if ( ! isset( $formParameter['recipient'] ) ) {
216 return $recipient;
217 }
218
219 $recipientField = str_replace( array( '[', ']' ), '', $formParameter['recipient'] );
220
221 if ( isset( $postParameter[ $recipientField ] ) ) {
222 return sanitize_email( $postParameter[ $recipientField ] );
223 }
224
225 return $recipient;
226 }
227
228 /**
229 * Handle form submission.
230 *
231 * @param \WPCF7_ContactForm $form The contact form.
232 * @param bool $abort Whether to abort submission.
233 * @param \WPCF7_Submission $submission The submission.
234 *
235 * @return void
236 */
237 public function onSubmit( $form, &$abort, $submission ): void {
238 $formId = $form->id();
239
240 $this->getLogger()->debug(
241 'CF7 form submission received',
242 array(
243 'plugin' => 'double-opt-in',
244 'form_id' => $formId,
245 )
246 );
247
248 if ( ! $this->isOptInEnabled( $formId ) ) {
249 // Handle file attachments for confirmation
250 if ( isset( $_GET['optin'] ) ) {
251 $this->attachStoredFiles( $submission );
252 }
253 return;
254 }
255
256 // Remove CF7 DB integration
257 remove_action( 'wpcf7_before_send_mail', 'cfdb7_before_send_mail' );
258
259 // Create form data
260 $formData = FormData::fromCF7( $form, $submission );
261 $formParameter = $this->getFormParameter( $formId );
262
263 // Check skip filter
264 if ( apply_filters( 'f12_cf7_doubleoptin_skip_option', false, $formId, $formData->getFields(), 'cf7' ) ) {
265 $this->getLogger()->info(
266 'OptIn skipped by filter',
267 array(
268 'plugin' => 'double-opt-in',
269 'form_id' => $formId,
270 )
271 );
272 return;
273 }
274
275 // Set recipient
276 $recipient = $this->resolveRecipient( $formData, $formParameter );
277 $formData = $formData->withRecipientEmail( $recipient );
278
279 // Create OptIn
280 $optIn = $this->createOptIn( $formData, $formParameter );
281
282 if ( ! $optIn ) {
283 // Always prevent the original CF7 mail from being sent when opt-in creation fails
284 add_filter( 'wpcf7_skip_mail', '__return_true' );
285
286 $error = self::getLastError();
287 if ( $error && apply_filters( 'f12_cf7_doubleoptin_show_validation_error', false ) ) {
288 $message = apply_filters( 'f12_cf7_doubleoptin_error_message', $error->getMessage(), $error, $formId );
289 if ( method_exists( $submission, 'set_response' ) ) {
290 $submission->set_response( $message );
291 }
292 $abort = true;
293 }
294 return;
295 }
296
297 // Send opt-in mail
298 $this->sendOptInMail( $optIn, $formData, $formParameter );
299
300 // Skip original mail
301 add_filter( 'wpcf7_skip_mail', '__return_true' );
302 do_action( 'f12_cf7_doubleoptin_sent', $form, $formId );
303 }
304
305 /**
306 * {@inheritdoc}
307 */
308 public function sendOptInMail( OptIn $optIn, FormDataInterface $formData, array $formParameter ): bool {
309 $formParameter['formUrl'] = $formData->getMetaValue( 'source_url', '' );
310
311 // Get template body
312 $body = apply_filters(
313 'f12_cf7_doubleoptin_template_body',
314 $formParameter['body'],
315 $formParameter['template'] ?? 'blank',
316 $formParameter,
317 $optIn
318 );
319
320 // Process placeholders
321 $body = $this->prepareMailBody( $body, $optIn, $formParameter );
322 $body = apply_filters( 'f12_cf7_doubleoptin_body', $body );
323
324 // Store mail content in OptIn
325 $optIn->set_mail_optin( $body );
326 $optIn->save();
327
328 // Prepare mail arguments
329 $args = apply_filters(
330 'f12-cf7-doubleoptin-cf7-args',
331 array(
332 'subject' => $formParameter['subject'] ?? '',
333 'body' => $body,
334 'sender' => $formParameter['sender'] ?? '',
335 'sender_name' => $formParameter['sender_name'] ?? '',
336 'recipient' => $optIn->get_email(),
337 'use_html' => true,
338 'additional_headers' => '',
339 )
340 );
341
342 if ( ! empty( $args['sender_name'] ) ) {
343 $args['additional_headers'] .= 'From: ' . $args['sender_name'] . ' <' . $args['sender'] . '>';
344 }
345
346 // Send via CF7 mail system
347 \WPCF7_Mail::send( $args, 'mail' );
348
349 $this->getLogger()->info(
350 'OptIn mail sent via CF7',
351 array(
352 'plugin' => 'double-opt-in',
353 'form_id' => $formData->getFormId(),
354 'recipient' => $args['recipient'],
355 )
356 );
357
358 return true;
359 }
360
361 /**
362 * {@inheritdoc}
363 */
364 public function sendConfirmationMail( OptIn $optIn ): void {
365 if ( ! $this->isAvailable() ) {
366 $this->getLogger()->warning(
367 'CF7 not available for confirmation mail',
368 array(
369 'plugin' => 'double-opt-in',
370 )
371 );
372 return;
373 }
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
382 $contactForm = \WPCF7_ContactForm::get_instance( $optIn->get_cf_form_id() );
383 if ( ! $contactForm ) {
384 $this->getLogger()->warning(
385 'CF7 form not found for confirmation mail',
386 array(
387 'plugin' => 'double-opt-in',
388 'form_id' => $optIn->get_cf_form_id(),
389 )
390 );
391 return;
392 }
393
394 // Add attachment hook
395 add_action( 'wpcf7_before_send_mail', array( $this, 'attachExtraAttachments' ), 10, 3 );
396
397 // Disable validation and spam checks before creating submission
398 $this->beforeSendConfirmationMail();
399
400 // Create submission and send mail
401 $submission = \WPCF7_Submission::get_instance( $contactForm );
402
403 // Re-enable validation and spam checks
404 $this->afterSendConfirmationMail();
405
406 $this->getLogger()->info(
407 'Confirmation mail triggered via CF7',
408 array(
409 'plugin' => 'double-opt-in',
410 'form_id' => $optIn->get_cf_form_id(),
411 'optin_id' => $optIn->get_id(),
412 )
413 );
414 }
415
416 /**
417 * Handle opt-in confirmation from URL.
418 *
419 * @return void
420 */
421 public function handleOptInConfirmation(): void {
422 if ( ! isset( $_GET['optin'] ) ) {
423 return;
424 }
425
426 $hash = sanitize_text_field( $_GET['optin'] );
427 $this->validateOptIn( $hash );
428 }
429
430 /**
431 * Before sending default mail callback.
432 *
433 * @return void
434 */
435 public function beforeSendDefaultMail(): void {
436 $this->beforeSendConfirmationMail();
437 }
438
439 /**
440 * After sending default mail callback.
441 *
442 * @return void
443 */
444 public function afterSendDefaultMail(): void {
445 $this->afterSendConfirmationMail();
446 }
447
448 /**
449 * Attach extra attachments to the mail.
450 *
451 * @param \WPCF7_ContactForm $form The contact form.
452 * @param bool $abort Whether to abort.
453 * @param \WPCF7_Submission $submission The submission.
454 *
455 * @return void
456 */
457 public function attachExtraAttachments( $form, $abort, $submission ): void {
458 if ( $this->currentOptIn && $this->currentOptIn->get_files() ) {
459 $files = maybe_unserialize( $this->currentOptIn->get_files() );
460 if ( is_array( $files ) ) {
461 foreach ( $files as $file ) {
462 $submission->add_extra_attachments( $file );
463 }
464 }
465 }
466 }
467
468 /**
469 * Attach stored files to submission during confirmation.
470 *
471 * @param \WPCF7_Submission $submission The submission.
472 *
473 * @return void
474 */
475 private function attachStoredFiles( $submission ): void {
476 $hash = sanitize_text_field( $_GET['optin'] );
477 $optIn = OptIn::get_by_hash( $hash );
478
479 if ( ! $optIn ) {
480 return;
481 }
482
483 $files = maybe_unserialize( $optIn->get_files() );
484 if ( empty( $files ) ) {
485 return;
486 }
487
488 foreach ( $files as $file ) {
489 if ( empty( $file ) ) {
490 continue;
491 }
492
493 if ( apply_filters( 'f12_cf7_doubleoptin_files_mail_1', true, $optIn ) ) {
494 $submission->add_extra_attachments( $file );
495 }
496
497 if ( apply_filters( 'f12_cf7_doubleoptin_files_mail_2', true, $optIn ) ) {
498 $submission->add_extra_attachments( $file, 'mail_2' );
499 }
500 }
501 }
502
503 /**
504 * File hand-off — for CF7, the "form system" is the confirmation
505 * mail itself. The hand-off completes when `attachExtraAttachments`
506 * has added the pending files as mail attachments and CF7 has sent
507 * the mail. By the time `cleanupPendingAfterMail` invokes the
508 * template-method (registered on `after_send_default_mail`), this
509 * has already happened and returning true triggers the pending/
510 * cleanup — single source of truth = the recipient's mailbox.
511 *
512 * Differs from WPForms/GF (where the integration's own DB owns the
513 * file URL) in WHEN the hand-off happens, not WHAT it returns. The
514 * hook timing in registerHooks() is the actual difference.
515 *
516 * {@inheritdoc}
517 */
518 public function handOffFilesToFormSystem( OptIn $optIn ): bool {
519 return true;
520 }
521
522 /**
523 * CF7-specific cleanup wrapper. Hooks the file-lifecycle template-
524 * method onto `f12_cf7_doubleoptin_after_send_default_mail` rather
525 * than `f12_cf7_doubleoptin_after_confirm` (the timing the other
526 * integrations use), so pending files survive long enough for
527 * `attachExtraAttachments` to attach them to the confirmation mail.
528 *
529 * Edge case: if `f12_cf7_doubleoptin_send_default_mail` filter is
530 * false (admin opted out of the CF7 confirmation mail), this hook
531 * never fires and pending/ stays populated until the OptIn-deletion
532 * cron sweeps it via cascade-delete. Acceptable per the plan's
533 * fault-tolerance pattern — no silent data loss, just delayed
534 * cleanup.
535 *
536 * @param OptIn $optIn The just-confirmed opt-in (action arg).
537 *
538 * @since 4.3.0
539 */
540 public function cleanupPendingAfterMail( OptIn $optIn ): void {
541 // Template-method's $hash arg is unused inside processFilesOnConfirm;
542 // passing an empty string keeps the contract tight.
543 $this->processFilesOnConfirm( '', $optIn );
544 }
545
546 /**
547 * {@inheritdoc}
548 */
549 public function getFormFields( $formId ): array {
550 $formId = (int) $formId;
551 $post = get_post( $formId );
552 if ( ! $post || $post->post_type !== 'wpcf7_contact_form' ) {
553 return array();
554 }
555
556 $contactForm = \WPCF7_ContactForm::get_instance( $formId );
557 if ( ! $contactForm ) {
558 return array();
559 }
560
561 $fields = array();
562 $tags = $contactForm->scan_form_tags();
563
564 foreach ( $tags as $tag ) {
565 if ( ! empty( $tag->name ) ) {
566 $fields[ $tag->name ] = $tag->name;
567 }
568 }
569
570 return $fields;
571 }
572
573 /**
574 * Add editor panel to CF7.
575 *
576 * @param array $panels The panels array.
577 *
578 * @return array Modified panels.
579 */
580 public function addEditorPanel( array $panels ): array {
581 $panels['optin'] = array(
582 'title' => $this->getPanelTitle(),
583 'callback' => array( $this, 'renderPanel' ),
584 );
585 return $panels;
586 }
587
588 /**
589 * {@inheritdoc}
590 */
591 public function render( $form, array $metadata ): void {
592 $this->renderPanel( $form );
593 }
594
595 /**
596 * Render the CF7 editor panel.
597 *
598 * Displays a notice with link to central form management.
599 * Full settings are now managed centrally in the Forms admin page.
600 *
601 * @param \WPCF7_ContactForm $post The contact form.
602 *
603 * @return void
604 */
605 public function renderPanel( $post ): void {
606 if ( ! $post || ! $post->id() ) {
607 ?>
608 <div class="doi-cf7-notice" style="padding: 20px;">
609 <div style="background: #fff; border: 1px solid #c3c4c7; border-radius: 4px; padding: 20px;">
610 <h2 style="margin-top: 0;"><?php _e( 'Double Opt-In Settings', 'double-opt-in' ); ?></h2>
611 <p style="color: #666;">
612 <?php _e( 'Please save the contact form first before configuring Double Opt-In.', 'double-opt-in' ); ?>
613 </p>
614 </div>
615 </div>
616 <?php
617 return;
618 }
619
620 $metadata = $this->getFormParameter( $post->id() );
621 $centralUrl = admin_url( 'admin.php?page=f12-doi-admin#/forms' );
622 $isEnabled = $this->isOptInEnabled( $post->id() );
623
624 $this->getLogger()->debug(
625 'Rendering CF7 panel notice',
626 array(
627 'plugin' => 'double-opt-in',
628 'form_id' => $post->id(),
629 'enabled' => $isEnabled,
630 )
631 );
632 ?>
633 <div class="doi-cf7-notice" style="padding: 20px;">
634 <div style="background: #fff; border: 1px solid #c3c4c7; border-radius: 4px; padding: 20px;">
635 <h2 style="margin-top: 0;"><?php _e( 'Double Opt-In Settings', 'double-opt-in' ); ?></h2>
636
637 <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 20px;">
638 <span style="font-weight: 600;"><?php _e( 'Status:', 'double-opt-in' ); ?></span>
639 <?php if ( $isEnabled ) : ?>
640 <span style="display: inline-block; padding: 4px 12px; background: #d4edda; color: #155724; border-radius: 3px; font-weight: 500;">
641 <?php _e( 'Enabled', 'double-opt-in' ); ?>
642 </span>
643 <?php else : ?>
644 <span style="display: inline-block; padding: 4px 12px; background: #f8d7da; color: #721c24; border-radius: 3px; font-weight: 500;">
645 <?php _e( 'Disabled', 'double-opt-in' ); ?>
646 </span>
647 <?php endif; ?>
648 </div>
649
650 <p style="color: #666; margin-bottom: 20px;">
651 <?php _e( 'Double Opt-In settings are now managed centrally. Use the button below to configure this form.', 'double-opt-in' ); ?>
652 </p>
653
654 <a href="<?php echo esc_url( $centralUrl ); ?>" class="button button-primary" target="_blank">
655 <?php _e( 'Configure Double Opt-In', 'double-opt-in' ); ?>
656 </a>
657 </div>
658 </div>
659 <?php
660 }
661
662 /**
663 * {@inheritdoc}
664 */
665 public function save( int $formId, array $data ): bool {
666 if ( ! isset( $data['doubleoptin'] ) ) {
667 update_post_meta( $formId, 'f12-cf7-doubleoptin', array() );
668 return true;
669 }
670
671 $parameter = SanitizeHelper::sanitize_array( $data['doubleoptin'] );
672 $metadata = $this->getFormParameter( $formId );
673
674 foreach ( $metadata as $key => $value ) {
675 if ( isset( $parameter[ $key ] ) ) {
676 $metadata[ $key ] = $key === 'enable' ? (int) $parameter[ $key ] : $parameter[ $key ];
677 } elseif ( $key === 'enable' ) {
678 $metadata[ $key ] = 0;
679 }
680 }
681
682 $metadata = apply_filters( 'f12_cf7_doubleoptin_metadata_cf7', $metadata );
683 $metadata = apply_filters( 'f12_cf7_doubleoptin_save_form', $metadata );
684
685 update_post_meta( $formId, 'f12-cf7-doubleoptin', $metadata );
686
687 // Save placeholder mapping
688 if ( isset( $data['doubleoptin']['placeholder_mapping'] ) ) {
689 $mapping = array_map( 'sanitize_text_field', $data['doubleoptin']['placeholder_mapping'] );
690 PlaceholderMapper::saveCustomMapping( $formId, $mapping, 'cf7' );
691 }
692
693 return true;
694 }
695
696 /**
697 * Save form settings callback.
698 *
699 * @param \WPCF7_ContactForm $contactForm The contact form.
700 * @param array $args The arguments.
701 * @param string $context The context.
702 *
703 * @return void
704 */
705 public function saveFormSettings( $contactForm, $args, $context ): void {
706 $formId = $contactForm->id();
707
708 // Verify nonce
709 if ( ! isset( $_POST['f12_cf7_doubleoptin_save_form_nonce'] ) ||
710 ! wp_verify_nonce( wp_unslash( $_POST['f12_cf7_doubleoptin_save_form_nonce'] ), 'f12_cf7_doubleoptin_save_form_action' ) ) {
711 return;
712 }
713
714 $this->save( $formId, $_POST );
715 }
716
717 /**
718 * {@inheritdoc}
719 */
720 public function getPanelTitle(): string {
721 return __( 'Double-Opt-in', 'double-opt-in' );
722 }
723
724 /**
725 * {@inheritdoc}
726 */
727 public function getAvailableTemplates(): array {
728 $templates = array(
729 'blank' => 'blank',
730 'newsletter_en' => 'newsletter_en',
731 'newsletter_en_2' => 'newsletter_en_2',
732 'newsletter_en_3' => 'newsletter_en_3',
733 );
734
735 // Add custom templates
736 try {
737 $container = Container::getInstance();
738 $integration = $container->get( \Forge12\DoubleOptIn\EmailTemplates\EmailTemplateIntegration::class );
739 $custom = $integration->getCustomTemplates();
740
741 foreach ( $custom as $template ) {
742 $templates[ 'custom_' . $template['id'] ] = $template['title'] . ' (' . __( 'Custom', 'double-opt-in' ) . ')';
743 }
744 } catch ( \Exception $e ) {
745 // Ignore if custom templates not available
746 }
747
748 return $templates;
749 }
750
751 /**
752 * {@inheritdoc}
753 */
754 public function getAvailableCategories(): array {
755 $categories = array( 0 => __( 'Please select', 'double-opt-in' ) );
756
757 $list = Category::get_list(
758 array(
759 'perPage' => -1,
760 'orderBy' => 'name',
761 'order' => 'ASC',
762 ),
763 $numberOfPages
764 );
765
766 foreach ( $list as $category ) {
767 $categories[ $category->get_id() ] = $category->get_name();
768 }
769
770 return $categories;
771 }
772 }
773