PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.7.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.7.0
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 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 All 39 releases
← All changes | CF7DoubleOptIn.class.php +748 -259 3.0.1 → 5.7.0 View file →
@@ -1,259 +1,748 @@
1 -<?php
2 -
3 -namespace forge12\contactform7\CF7DoubleOptIn {
4 - if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 - }
7 -
8 - /**
9 - * Plugin Name: Double Opt-In (Contact Form 7, Avada) - GDPR Ready
10 - * Plugin URI: https://www.forge12.com/blog/so-verwendest-du-das-double-opt-in-fuer-contact-form-7/
11 - * Description: This plugin allows you to add a double OptIn System to your Contact Form 7 & Avada Forms.
12 - * Text Domain: double-opt-in
13 - * Domain Path: /languages
14 - * Version: 3.0.0
15 - * Author: Forge12 Interactive GmbH
16 - * Author URI: https://www.forge12.com
17 - */
18 - define( 'FORGE12_OPTIN_VERSION', '3.0.1' );
19 - define( 'FORGE12_OPTIN_SLUG', 'f12-cf7-doubleoptin' );
20 - define( 'FORGE12_OPTIN_BASENAME', plugin_basename( __FILE__ ) );
21 -
22 -
23 - /**
24 - * Dependencies
25 - */
26 - require_once( 'core/BaseController.class.php' );
27 -
28 - require_once( 'OnActivation.php' );
29 - require_once( 'OnDeactivation.php' );
30 - require_once( 'OnUpdate.php' );
31 - require_once( 'compatibility/OptInFrontend.class.php' );
32 -
33 - require_once( 'core/Messages.class.php' );
34 - require_once( 'core/TemplateHandler.class.php' );
35 - require_once( 'core/IPHelper.class.php' );
36 - require_once( 'core/SanitizeHelper.class.php' );
37 - require_once( 'core/Ajax.class.php' );
38 - require_once( 'core/Compatibility.class.php' );
39 - require_once( 'core/CleanUp.class.php' );
40 - require_once( 'core/HTMLSelect.class.php' );
41 - require_once( 'core/UI.class.php' );
42 - require_once( 'core/UIPage.class.php' );
43 - require_once( 'core/UIPageForm.class.php' );
44 - require_once( 'core/OptIn.class.php' );
45 - require_once( 'core/OptInLimitFilter.class.php' );
46 - require_once( 'core/OptInSearchFilter.class.php' );
47 - require_once( 'core/Category.class.php' );
48 - require_once( 'core/CategoryOptions.class.php' );
49 - require_once( 'core/Pagination.class.php' );
50 - require_once( 'core/Support.class.php' );
51 -
52 - /**
53 - * Class CF7DoubleOptIn
54 - * Controller for the Custom Links.
55 - *
56 - * @package forge12\contactform7
57 - */
58 - class CF7DoubleOptIn {
59 - /**
60 - * @var CF7DoubleOptIn|Null
61 - */
62 - private static $_instance = null;
63 -
64 - /**
65 - * @var TemplateHandler|null
66 - */
67 - private $TemplateHandler = null;
68 -
69 - /**
70 - * Get the singleton instance of CF7DoubleOptIn.
71 - *
72 - * @return CF7DoubleOptIn The singleton instance.
73 - */
74 - public static function getInstance() {
75 - if ( self::$_instance == null ) {
76 - self::$_instance = new self();
77 - }
78 -
79 - return self::$_instance;
80 - }
81 -
82 - /**
83 - * Return a list containing the array with all data stored within the form
84 - *
85 - * @param int $postID
86 - *
87 - * @formatter:off
88 - *
89 - * @return {
90 - * @type int $enable The Status of the OptIn, either 1 for enabled or 0 for disabled. Default: 0
91 - * @type string $sender The E-Mail of the sender of the optIn mail
92 - * @type string $subject The Subject of the OptIn Mail
93 - * @type string $body The Content of the OptIn Mail
94 - * @type string $recipient The Field that contains the E-Mail of the Recipient.
95 - * @type int $page The Post ID of the confirmation page. Default: -1
96 - * @type string $conditions Additional condition to dynamically enable / disable the optin.
97 - * Default: disabled
98 - * @type string $template The Template used for the OptIn Mail
99 - * @type int $category The Category the OptIns will be assigned to.
100 - * }
101 - * @formatter:on
102 - */
103 - public function getParameter( $postID ) {
104 -
105 - $data = [
106 - 'enable' => 0,
107 - 'sender' => get_bloginfo( 'admin_email' ),
108 - 'sender_name' => '',
109 - 'subject' => '',
110 - 'body' => '',
111 - 'recipient' => '',
112 - 'page' => - 1,
113 - 'conditions' => 'disabled',
114 - 'template' => '',
115 - 'category' => 0,
116 - ];
117 -
118 - /**
119 - * DOI Parameter
120 - *
121 - * Filters the parameter for the DOI. This hook allows developers
122 - * to alter DOI fields.
123 - *
124 - * @param array $data Additional Fields
125 - *
126 - * @since 1.0.0
127 - */
128 - $data = apply_filters( 'f12_cf7_doubleoptin_get_parameter', $data );
129 -
130 - if ( ! $postID ) {
131 - return $data;
132 - }
133 -
134 - $options = get_post_meta( $postID, 'f12-cf7-doubleoptin', true );
135 -
136 - if ( ! $options ) {
137 - return $data;
138 - }
139 -
140 - return array_merge( $data, $options );
141 - }
142 -
143 - /**
144 - * Private constructor to prevent direct instantiation.
145 - */
146 - private function __construct() {
147 - load_plugin_textdomain( 'double-opt-in', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
148 - /**
149 - * DOI INIT
150 - *
151 - * Run custom actions after the DOI has been initialized
152 - *
153 - * @param CF7DoubleOptIn $this
154 - *
155 - * @since 3.0.0
156 - */
157 - do_action( 'f12_cf7_doubleoptin_init', $this );
158 -
159 -
160 - $this->TemplateHandler = TemplateHandler::getInstance();
161 -
162 - $UI = new UI( $this->TemplateHandler, FORGE12_OPTIN_SLUG, 'Forge12 Double Opt-In', 'manage_options' );
163 -
164 - add_action( 'after_setup_theme', array( $this, 'init' ) );
165 -
166 - $Compatibility = new Compatibility( $this );
167 - $CleanUp = new CleanUp();
168 -
169 - // Pagination
170 - Pagination::getInstance();
171 -
172 - // initialize filter
173 - CategoryOptions::getInstance();
174 - OptInLimitFilter::getInstance();
175 - OptInSearchFilter::getInstance();
176 -
177 - // Support
178 - Support::getInstance();
179 - }
180 -
181 - /**
182 - * Retrieve the template handler instance.
183 - *
184 - * @return TemplateHandler The template handler instance.
185 - */
186 - public function get_template_handler() {
187 - return $this->TemplateHandler;
188 - }
189 -
190 - /**
191 - * @private WordPress Hook
192 - */
193 - public function init() {
194 - do_action( 'f12_cf7_doubleoptin_register_implementations' );
195 - }
196 -
197 - /**
198 - * Return the settings for the optin.
199 - *
200 - * @param string $single The Key of the setting to return only the required setting
201 - *
202 - * @formatter:off
203 - * @return {
204 - * // Returns the Settings for the DOI
205 - *
206 - * @type string $optout_subject The Subject for the OptOut Mail
207 - * @type string $optout_body The Content for the OptOut Mail
208 - * @type int $optout_page The Post ID for the OptOut Page
209 - * @type int $support Defines if the Support link will be added to the footer
210 - * @type int $delete An integer from 1 to 30
211 - * @type int $delete_unconfirmed An integer from 1 to 30
212 - * @type string $delete_period The time period, either months, days, years
213 - * @type string $delete_unconfirmed_period The time period, either months, days, years
214 - * }
215 - * @formatter:on
216 - */
217 - public function getSettings( $single = '', $container = null ) {
218 - $default = array();
219 -
220 - $default = apply_filters( 'f12_cf7_doubleoptin_settings', $default );
221 -
222 - $settings = get_option( 'f12-doi-settings' );
223 -
224 - if ( ! is_array( $settings ) ) {
225 - $settings = array();
226 - }
227 -
228 - foreach ( $default as $key => $data ) {
229 - if ( isset( $settings[ $key ] ) ) {
230 - if ( is_array( $default[ $key ] ) ) {
231 - $default[ $key ] = array_merge( $default[ $key ], $settings[ $key ] );
232 - } else {
233 - $default[ $key ] = $settings[ $key ];
234 - }
235 - }
236 - }
237 -
238 - $settings = $default;
239 -
240 - if ( ! empty( $single ) ) {
241 - if ( $container != null ) {
242 - if ( isset( $settings[ $container ] ) && isset( $settings[ $container ][ $single ] ) ) {
243 - $settings = $settings[ $container ][ $single ];
244 - }
245 - }
246 - } else {
247 - if ( isset( $settings[ $single ] ) ) {
248 - $settings = $settings[ $single ];
249 - }
250 - }
251 -
252 - return $settings;
253 - }
254 - }
255 -
256 -
257 - CF7DoubleOptIn::getInstance();
258 -
259 -}
1 +<?php
2 +
3 +namespace forge12\contactform7\CF7DoubleOptIn {
4 +
5 + use Forge12\Shared\Logger;
6 + use Forge12\Shared\LoggerInterface;
7 +
8 + if ( ! defined( 'ABSPATH' ) ) {
9 + exit;
10 + }
11 +
12 + /**
13 + * Plugin Name: Double Opt-In (Contact Form 7, Avada) - GDPR Ready
14 + * Plugin URI: https://www.forge12.com/blog/so-verwendest-du-das-double-opt-in-fuer-contact-form-7/
15 + * Description: This plugin allows you to add a double OptIn System to your Contact Form 7 & Avada Forms.
16 + * Text Domain: double-opt-in
17 + * Domain Path: /languages
18 + * Version: 5.7.0
19 + * Requires at least: 6.0
20 + * Requires PHP: 7.4
21 + * Author: Forge12 Interactive GmbH
22 + * Author URI: https://www.forge12.com
23 + */
24 +
25 + /**
26 + * Minimum-PHP fail-safe.
27 + *
28 + * The "Requires PHP" header above makes WordPress refuse *activation* and
29 + * *updates* on an unsupported version, but it is not re-checked when a host
30 + * later moves an already-active site to an older PHP. Without this guard the
31 + * next request would fatal on 7.4-only syntax inside the files required
32 + * below, leaving the site with a white screen and no explanation.
33 + *
34 + * Everything above this point must stay parseable by old PHP — a parse error
35 + * happens before any code runs, so a guard in an unparseable file is dead
36 + * weight. That is also why CF7DoubleOptIn::$logger carries its type in a
37 + * DocBlock instead of a native (PHP 7.4) property type.
38 + */
39 + if ( PHP_VERSION_ID < 70400 ) {
40 + add_action(
41 + 'admin_notices',
42 + function () {
43 + echo '<div class="notice notice-error"><p>';
44 + echo esc_html(
45 + sprintf(
46 + /* translators: 1: minimum required PHP version, 2: PHP version currently running */
47 + __( 'Double Opt-In requires PHP %1$s or newer. This server is running PHP %2$s, so the plugin was stopped to prevent a fatal error. Please ask your host to update PHP.', 'double-opt-in' ),
48 + '7.4',
49 + PHP_VERSION
50 + )
51 + );
52 + echo '</p></div>';
53 + }
54 + );
55 +
56 + return;
57 + }
58 +
59 + if ( ! defined( 'FORGE12_OPTIN_VERSION' ) ) {
60 + define( 'FORGE12_OPTIN_VERSION', '5.7.0' );
61 + }
62 +
63 + // Addon API version — semver-independent from the plugin's marketing
64 + // version. Bumped only on breaking changes to the Addon API surface
65 + // (AddonInterface, AddonRegistry, AddonLicenseRegistry, FormIntegrationInterface,
66 + // event payloads). Addons declare their requirement against this constant,
67 + // not FORGE12_OPTIN_VERSION.
68 + //
69 + // 4.4.0 (additive): FollowUp\FollowUpAdapterInterface, FollowUpCoordinator,
70 + // FollowUpAdapterRegistry. Addons that implement follow-up adapters
71 + // require ^4.4; everything else keeps working against 4.3.
72 + //
73 + // 4.5.0 (additive): AbstractFormIntegration::refusedConsentBeforeSubmit(),
74 + // OptInError::isAlwaysShown()/shouldShowToVisitor(),
75 + // OptInFrontend::getLastCreationError(), ErrorNotification::forget().
76 + // The Elementor, WPForms and Gravity Forms add-ons call them only when they
77 + // exist (method_exists), so their requirement stays ^4.4.
78 + if ( ! defined( 'F12_DOI_CORE_API_VERSION' ) ) {
79 + define( 'F12_DOI_CORE_API_VERSION', '4.5.0' );
80 + }
81 + if ( ! defined( 'FORGE12_OPTIN_SLUG' ) ) {
82 + define( 'FORGE12_OPTIN_SLUG', 'f12-cf7-doubleoptin' );
83 + }
84 + if ( ! defined( 'FORGE12_OPTIN_BASENAME' ) ) {
85 + define( 'FORGE12_OPTIN_BASENAME', plugin_basename( __FILE__ ) );
86 + }
87 + if ( ! defined( 'F12_DOUBLEOPTIN_PLUGIN_FILE' ) ) {
88 + define( 'F12_DOUBLEOPTIN_PLUGIN_FILE', __FILE__ );
89 + }
90 +
91 +
92 + /**
93 + * Dependencies
94 + */
95 + require_once 'logger/logger.php';
96 + require_once 'core/helpers/uuid.php';
97 + require_once 'core/telemetry.php';
98 + // feedback.php first: review.php, credit_nudge.php and the deactivation
99 + // survey all build their links with it.
100 + require_once 'core/feedback.php';
101 + require_once 'core/review.php';
102 + require_once 'core/confirmation_output.php';
103 + require_once 'core/credit_link.php';
104 + require_once 'core/credit_nudge.php';
105 + require_once 'core/deactivation_survey.php';
106 + require_once 'core/admin_links.php';
107 + require_once 'core/cron.php';
108 + require_once 'core/BaseController.class.php';
109 +
110 + require_once 'OnActivation.php';
111 + require_once 'OnDeactivation.php';
112 + // OnUpdate runs at include time, before autoload.php is registered
113 + // further down — load the one PSR-4 class it needs explicitly.
114 + require_once 'src/Repository/FollowUpSchema.php';
115 + require_once 'OnUpdate.php';
116 + require_once 'compatibility/OptInFrontend.class.php';
117 + require_once 'core/SpamMechanics.class.php';
118 +
119 + require_once 'core/Messages.class.php';
120 + require_once 'core/TemplateHandler.class.php';
121 + require_once 'core/IPHelper.class.php';
122 + require_once 'core/SanitizeHelper.class.php';
123 + require_once 'core/Ajax.class.php';
124 + require_once 'core/Compatibility.class.php';
125 + require_once 'core/CleanUp.class.php';
126 + require_once 'core/HTMLSelect.class.php';
127 + require_once 'core/OptIn.class.php';
128 + require_once 'core/OptInLimitFilter.class.php';
129 + require_once 'core/OptInSearchFilter.class.php';
130 + require_once 'core/Category.class.php';
131 + require_once 'core/CategoryOptions.class.php';
132 + require_once 'core/Pagination.class.php';
133 + if ( file_exists( __DIR__ . '/core/TestEmailBlocker.class.php' ) ) {
134 + require_once 'core/TestEmailBlocker.class.php';
135 + }
136 +
137 + /**
138 + * PSR-4 Autoloader for new Enterprise Architecture (v4.0+)
139 + */
140 + require_once 'autoload.php';
141 +
142 + /**
143 + * Class CF7DoubleOptIn
144 + * Controller for the Custom Links.
145 + *
146 + * @package forge12\contactform7
147 + */
148 + class CF7DoubleOptIn {
149 + /**
150 + * Deliberately untyped: a native property type is PHP 7.4 syntax and
151 + * would make this file unparseable on older PHP, which would defeat the
152 + * minimum-PHP guard at the top of this file.
153 + *
154 + * @var LoggerInterface
155 + */
156 + private $logger;
157 + /**
158 + * @var CF7DoubleOptIn|Null
159 + */
160 + private static $_instance = null;
161 +
162 + /**
163 + * @var TemplateHandler|null
164 + */
165 + private $TemplateHandler = null;
166 +
167 + /**
168 + * Get the singleton instance of CF7DoubleOptIn.
169 + *
170 + * @return CF7DoubleOptIn The singleton instance.
171 + */
172 + public static function getInstance() {
173 + if ( self::$_instance == null ) {
174 + self::$_instance = new self();
175 + }
176 +
177 + return self::$_instance;
178 + }
179 +
180 + /**
181 + * Return a list containing the array with all data stored within the form
182 + *
183 + * @param int $postID
184 + *
185 + * @formatter:off
186 + *
187 + * @return {
188 + * @type int $enable The Status of the OptIn, either 1 for enabled or 0 for disabled. Default: 0
189 + * @type string $sender The E-Mail of the sender of the optIn mail
190 + * @type string $subject The Subject of the OptIn Mail
191 + * @type string $body The Content of the OptIn Mail
192 + * @type string $recipient The Field that contains the E-Mail of the Recipient.
193 + * @type int $page The Post ID of the confirmation page. Default: -1
194 + * @type string $conditions Additional condition to dynamically enable / disable the optin.
195 + * Default: disabled
196 + * @type string $template The Template used for the OptIn Mail
197 + * @type int $category The Category the OptIns will be assigned to.
198 + * }
199 + * @formatter:on
200 + */
201 + public function getParameter( $postID ) {
202 + $this->get_logger()->debug(
203 + 'Fetching parameters',
204 + array(
205 + 'plugin' => 'double-opt-in',
206 + 'class' => __CLASS__,
207 + 'method' => __METHOD__,
208 + 'post_id' => $postID,
209 + )
210 + );
211 +
212 + $data = array(
213 + 'enable' => 0,
214 + 'sender' => get_bloginfo( 'admin_email' ),
215 + 'sender_name' => '',
216 + 'subject' => '',
217 + 'body' => '',
218 + 'recipient' => '',
219 + 'page' => - 1,
220 + 'conditions' => 'disabled',
221 + 'template' => '',
222 + 'category' => 0,
223 + );
224 +
225 + $data = apply_filters( 'f12_cf7_doubleoptin_get_parameter', $data );
226 +
227 + if ( ! $postID ) {
228 + $this->get_logger()->debug(
229 + 'No postID provided, returning defaults',
230 + array(
231 + 'plugin' => 'double-opt-in',
232 + )
233 + );
234 +
235 + return $data;
236 + }
237 +
238 + $options = get_post_meta( $postID, 'f12-cf7-doubleoptin', true );
239 +
240 + if ( ! $options ) {
241 + $this->get_logger()->debug(
242 + 'No options found for postID, returning defaults',
243 + array(
244 + 'plugin' => 'double-opt-in',
245 + 'post_id' => $postID,
246 + )
247 + );
248 +
249 + return $data;
250 + }
251 +
252 + $this->get_logger()->debug(
253 + 'Options merged with defaults',
254 + array(
255 + 'plugin' => 'double-opt-in',
256 + 'post_id' => $postID,
257 + )
258 + );
259 +
260 + return array_merge( $data, $options );
261 + }
262 +
263 + /**
264 + * Private constructor to prevent direct instantiation.
265 + */
266 + private function __construct() {
267 + $this->logger = Logger::getInstance();
268 +
269 + // Initialize test email blocker (blocks @example.com during E2E tests)
270 + if ( class_exists( __NAMESPACE__ . '\\TestEmailBlocker' ) ) {
271 + TestEmailBlocker::init();
272 + }
273 +
274 + // Initialize the DI Container and Service Providers (v4.0+ Enterprise Architecture)
275 + $this->initializeContainer();
276 +
277 + // Register the Avada deprecation notice + grandfather-license claim flow.
278 + // Covers the migration of Avada support out of Core into the paid
279 + // addon-avada plugin planned for 5.0. The notice only renders on
280 + // sites that actually use DOI with an Avada form.
281 + \Forge12\DoubleOptIn\Migration\AvadaDeprecationNotice::register();
282 +
283 + if ( ! get_option( 'f12_cf7_doubleoptin_installed_at' ) ) {
284 + update_option( 'f12_cf7_doubleoptin_installed_at', time() );
285 + }
286 +
287 + // Handle Spam Mechanics
288 + $SpamMechanics = new SpamMechanics( $this->logger );
289 +
290 + // Resend Confirmation Mail (Admin AJAX)
291 + new \Forge12\DoubleOptIn\Admin\ResendController( $this->logger );
292 +
293 + $this->get_logger()->info(
294 + 'Initialization of Forge12 Double Opt-In started',
295 + array(
296 + 'plugin' => 'double-opt-in',
297 + 'class' => __CLASS__,
298 + 'method' => __METHOD__,
299 + )
300 + );
301 +
302 + add_action(
303 + 'init',
304 + function () {
305 + load_plugin_textdomain(
306 + 'double-opt-in',
307 + false,
308 + dirname( plugin_basename( __FILE__ ) ) . '/languages'
309 + );
310 + $this->get_logger()->debug(
311 + 'Textdomain loaded',
312 + array(
313 + 'plugin' => 'double-opt-in',
314 + 'domain' => 'double-opt-in',
315 + )
316 + );
317 + }
318 + );
319 +
320 + do_action( 'f12_cf7_doubleoptin_init', $this );
321 + $this->get_logger()->debug(
322 + 'Action f12_cf7_doubleoptin_init executed',
323 + array(
324 + 'plugin' => 'double-opt-in',
325 + )
326 + );
327 +
328 + $this->TemplateHandler = TemplateHandler::getInstance();
329 + $this->get_logger()->debug(
330 + 'TemplateHandler initialized',
331 + array(
332 + 'plugin' => 'double-opt-in',
333 + )
334 + );
335 +
336 + // Settings-defaults filter — historically registered by the legacy
337 + // admin UI (UISettings::getSettings). Registered here at runtime so
338 + // getSettings() keeps its default key set (and the whitelist it builds
339 + // from it) even without the legacy admin. The test-override mu-plugin
340 + // and any addon still layer on top of the filter chain.
341 + add_filter( 'f12_cf7_doubleoptin_settings', array( $this, 'injectDefaultSettings' ) );
342 +
343 + // Legacy admin UI (the `f12-cf7-doubleoptin` menu + its list-table
344 + // screens) removed 2026-07-02 — the React SPA (`f12-doi-admin`,
345 + // AdminPageController) is the sole admin UI. Runtime opt-in processing
346 + // (OptIn, CleanUp, OptInFrontend, the CF7 flow) is unaffected.
347 +
348 + add_action( 'after_setup_theme', array( $this, 'init' ) );
349 + $this->get_logger()->debug(
350 + 'Hook after_setup_theme registered',
351 + array(
352 + 'plugin' => 'double-opt-in',
353 + )
354 + );
355 +
356 + $Compatibility = new Compatibility( $this );
357 + $this->get_logger()->debug(
358 + 'Compatibility initialized',
359 + array(
360 + 'plugin' => 'double-opt-in',
361 + )
362 + );
363 +
364 + $CleanUp = new CleanUp( $this->get_logger() );
365 + $this->get_logger()->debug(
366 + 'CleanUp initialized',
367 + array(
368 + 'plugin' => 'double-opt-in',
369 + )
370 + );
371 +
372 + // Pagination
373 + Pagination::getInstance();
374 + $this->get_logger()->debug(
375 + 'Pagination initialized',
376 + array(
377 + 'plugin' => 'double-opt-in',
378 + )
379 + );
380 +
381 + // initialize filter
382 + CategoryOptions::getInstance();
383 + $this->get_logger()->debug(
384 + 'CategoryOptions initialized',
385 + array(
386 + 'plugin' => 'double-opt-in',
387 + )
388 + );
389 +
390 + OptInLimitFilter::getInstance();
391 + $this->get_logger()->debug(
392 + 'OptInLimitFilter initialized',
393 + array(
394 + 'plugin' => 'double-opt-in',
395 + )
396 + );
397 +
398 + OptInSearchFilter::getInstance();
399 + $this->get_logger()->debug(
400 + 'OptInSearchFilter initialized',
401 + array(
402 + 'plugin' => 'double-opt-in',
403 + )
404 + );
405 +
406 + $this->get_logger()->info(
407 + 'Initialization of Forge12 Double Opt-In completed',
408 + array(
409 + 'plugin' => 'double-opt-in',
410 + 'class' => __CLASS__,
411 + 'method' => __METHOD__,
412 + )
413 + );
414 + }
415 +
416 + public function get_logger() {
417 + return $this->logger;
418 + }
419 +
420 + /**
421 + * Initialize the DI Container and register Service Providers.
422 + *
423 + * @since 4.0.0
424 + * @return void
425 + */
426 + private function initializeContainer(): void {
427 + $container = \Forge12\DoubleOptIn\Container\Container::getInstance();
428 +
429 + // Register core services
430 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\CoreServiceProvider() );
431 +
432 + // Register event system
433 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\EventServiceProvider() );
434 +
435 + // Register repositories and services
436 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\RepositoryServiceProvider() );
437 +
438 + // Register email template services
439 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\EmailTemplateServiceProvider() );
440 +
441 + // Register form integration system (v4.0+ Event-based Architecture)
442 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\IntegrationServiceProvider() );
443 +
444 + // Register form settings services (v4.1+ Central Form Management)
445 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\FormSettingsServiceProvider() );
446 +
447 + // Register GDPR compliance services (v3.2.0+)
448 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\GdprServiceProvider() );
449 +
450 + // Register admin REST API and audit services (v4.2.0+)
451 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\AdminServiceProvider() );
452 +
453 + // Setup wizard (v5.7.0+)
454 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\SetupServiceProvider() );
455 +
456 + // Register licensing registry (v4.3.0+ — entitlement state for paid addons)
457 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\LicensingServiceProvider() );
458 +
459 + // Register migration registry (v4.3.0+ — runs pending DB migrations on admin_init)
460 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\MigrationServiceProvider() );
461 +
462 + // Register follow-up coordinator (v5.6.0+ — status + retry of
463 + // post-confirmation actions). Before AddonServiceProvider so
464 + // the adapter registry exists when the form addons boot.
465 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\FollowUpServiceProvider() );
466 +
467 + // Register addon system (v4.3.0+ — public Addon API)
468 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\AddonServiceProvider() );
469 +
470 + // Register health checks (v5.3.0+ — Site Health surfaces for
471 + // broken runtime preconditions such as a missing DB table).
472 + // After AddonServiceProvider so addon-contributed checks are
473 + // picked up by the registry's filter pass.
474 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\HealthServiceProvider() );
475 +
476 + // Register RateLimiter as singleton
477 + $container->singleton(
478 + \Forge12\DoubleOptIn\Service\RateLimiter::class,
479 + function () {
480 + return new \Forge12\DoubleOptIn\Service\RateLimiter();
481 + }
482 + );
483 +
484 + // Boot all providers
485 + $container->boot();
486 +
487 + $this->get_logger()->info(
488 + 'DI Container initialized with Service Providers',
489 + array(
490 + 'plugin' => 'double-opt-in',
491 + 'component' => 'container',
492 + )
493 + );
494 + }
495 +
496 + /**
497 + * Get the DI Container instance.
498 + *
499 + * @since 4.0.0
500 + * @return \Forge12\DoubleOptIn\Container\Container
501 + */
502 + public function getContainer(): \Forge12\DoubleOptIn\Container\Container {
503 + return \Forge12\DoubleOptIn\Container\Container::getInstance();
504 + }
505 +
506 + /**
507 + * Retrieve the template handler instance.
508 + *
509 + * @return TemplateHandler The template handler instance.
510 + */
511 + public function get_template_handler() {
512 + $this->get_logger()->debug(
513 + 'TemplateHandler retrieved',
514 + array(
515 + 'plugin' => 'double-opt-in',
516 + 'class' => __CLASS__,
517 + 'method' => __METHOD__,
518 + )
519 + );
520 +
521 + return $this->TemplateHandler;
522 + }
523 +
524 + /**
525 + * @private WordPress Hook
526 + */
527 + public function init() {
528 + $this->get_logger()->debug(
529 + 'Init started',
530 + array(
531 + 'plugin' => 'double-opt-in',
532 + 'class' => __CLASS__,
533 + 'method' => __METHOD__,
534 + )
535 + );
536 +
537 + do_action( 'f12_cf7_doubleoptin_register_implementations' );
538 +
539 + $this->get_logger()->debug(
540 + 'Action f12_cf7_doubleoptin_register_implementations executed',
541 + array(
542 + 'plugin' => 'double-opt-in',
543 + )
544 + );
545 + }
546 +
547 +
548 + /**
549 + * Return the settings for the optin.
550 + *
551 + * @param string $single The Key of the setting to return only the required setting
552 + *
553 + * @formatter:off
554 + * @return {
555 + * // Returns the Settings for the DOI
556 + *
557 + * @type string $optout_subject The Subject for the OptOut Mail
558 + * @type string $optout_body The Content for the OptOut Mail
559 + * @type int $optout_page The Post ID for the OptOut Page
560 + * @type int $support Defines if the Support link will be added to the footer
561 + * @type int $delete An integer from 1 to 30
562 + * @type int $delete_unconfirmed An integer from 1 to 30
563 + * @type string $delete_period The time period, either months, days, years
564 + * @type string $delete_unconfirmed_period The time period, either months, days, years
565 + * }
566 + * @formatter:on
567 + */
568 +
569 + /**
570 + * Inject the core settings defaults onto the f12_cf7_doubleoptin_settings
571 + * filter. Relocated from the legacy admin UI (UISettings::getSettings) so
572 + * the default key set survives without the legacy admin. Defaults are the
573 + * base; any value already on the filter (saved settings, test overrides,
574 + * addon contributions) wins via array_merge.
575 + *
576 + * @param array $settings Settings collected so far on the filter.
577 + * @return array
578 + */
579 + public function injectDefaultSettings( $settings ) {
580 + $default_settings = array(
581 + 'telemetry' => 1,
582 + 'delete' => 12,
583 + 'delete_unconfirmed' => 7,
584 + 'delete_period' => 'months',
585 + 'delete_unconfirmed_period' => 'months',
586 + 'privacy_policy_page' => 0,
587 + // Must be listed even though the opt-out addon owns the
588 + // feature: getSettings() rebuilds its return value from
589 + // THIS array and silently drops any stored key that is
590 + // missing here. Without the entry, every consumer of
591 + // getSettings()['optout_page'] — OptInLinkGenerator and
592 + // OptIn::get_link_optout(), i.e. the `[doubleoptoutlink]`
593 + // placeholder — fell back to home_url() no matter what
594 + // the admin had configured.
595 + 'optout_page' => 0,
596 + 'token_expiry_hours' => 48,
597 + 'rate_limit_ip' => 5,
598 + 'rate_limit_email' => 3,
599 + 'rate_limit_window' => 60,
600 + 'reminder_enabled' => 0,
601 + 'reminder_delay' => 24,
602 + 'reminder_template' => '',
603 + 'reminder_subject' => '',
604 + 'mx_validation_enabled' => 0,
605 + 'mx_validation_behavior' => 'silent',
606 + 'mx_validation_message' => '',
607 + 'domain_blocklist_enabled' => 0,
608 + 'domain_blocklist' => '',
609 + 'domain_blocklist_behavior' => 'silent',
610 + 'domain_blocklist_message' => '',
611 + );
612 +
613 + return array_merge( $default_settings, is_array( $settings ) ? $settings : array() );
614 + }
615 +
616 + public function getSettings( $single = '', $container = null ) {
617 + $this->get_logger()->debug(
618 + 'Fetching settings',
619 + array(
620 + 'plugin' => 'double-opt-in',
621 + 'class' => __CLASS__,
622 + 'method' => __METHOD__,
623 + 'single' => $single,
624 + 'container' => $container,
625 + )
626 + );
627 +
628 + $default = array();
629 +
630 + $default = apply_filters( 'f12_cf7_doubleoptin_settings', $default );
631 +
632 + $settings = get_option( 'f12-doi-settings' );
633 +
634 + if ( ! is_array( $settings ) ) {
635 + $this->get_logger()->debug(
636 + 'No settings found in options, using empty array',
637 + array(
638 + 'plugin' => 'double-opt-in',
639 + )
640 + );
641 + $settings = array();
642 + }
643 +
644 + foreach ( $default as $key => $data ) {
645 + if ( isset( $settings[ $key ] ) ) {
646 + if ( is_array( $default[ $key ] ) ) {
647 + $default[ $key ] = array_merge( $default[ $key ], $settings[ $key ] );
648 + } else {
649 + $default[ $key ] = $settings[ $key ];
650 + }
651 + $this->get_logger()->debug(
652 + 'Merged settings for key',
653 + array(
654 + 'plugin' => 'double-opt-in',
655 + 'key' => $key,
656 + )
657 + );
658 + }
659 + }
660 +
661 + $settings = $default;
662 +
663 + if ( ! empty( $single ) ) {
664 + if ( $container != null ) {
665 + if ( isset( $settings[ $container ] ) && isset( $settings[ $container ][ $single ] ) ) {
666 + $this->get_logger()->debug(
667 + 'Returning single setting from container',
668 + array(
669 + 'plugin' => 'double-opt-in',
670 + 'container' => $container,
671 + 'single' => $single,
672 + )
673 + );
674 + $settings = $settings[ $container ][ $single ];
675 + }
676 + }
677 + } elseif ( isset( $settings[ $single ] ) ) {
678 + $this->get_logger()->debug(
679 + 'Returning single setting',
680 + array(
681 + 'plugin' => 'double-opt-in',
682 + 'single' => $single,
683 + )
684 + );
685 + $settings = $settings[ $single ];
686 + }
687 +
688 + return $settings;
689 + }
690 + }
691 +
692 +
693 + add_action(
694 + 'plugins_loaded',
695 + function () {
696 + add_cron_jobs();
697 + CF7DoubleOptIn::getInstance();
698 + }
699 + );
700 +
701 + /**
702 + * Display upgrade notice in plugin list when updating to major versions.
703 + *
704 + * @param array $data Plugin update data.
705 + * @param object $response Response object from WordPress.org API.
706 + */
707 + add_action(
708 + 'in_plugin_update_message-' . FORGE12_OPTIN_BASENAME,
709 + function ( $data, $response ) {
710 + $upgrade_notice = '';
711 +
712 + // Check if this is a major update (e.g., 3.1.x -> 3.2.x)
713 + $current_version = FORGE12_OPTIN_VERSION;
714 + $new_version = $response->new_version ?? '';
715 +
716 + if ( empty( $new_version ) ) {
717 + return;
718 + }
719 +
720 + // Extract major.minor from versions
721 + $current_parts = explode( '.', $current_version );
722 + $new_parts = explode( '.', $new_version );
723 +
724 + $current_minor = ( $current_parts[0] ?? '0' ) . '.' . ( $current_parts[1] ?? '0' );
725 + $new_minor = ( $new_parts[0] ?? '0' ) . '.' . ( $new_parts[1] ?? '0' );
726 +
727 + // Show warning for major/minor version changes
728 + if ( version_compare( $new_minor, $current_minor, '>' ) ) {
729 + $upgrade_notice = sprintf(
730 + '</p><div class="notice inline notice-warning notice-alt" style="margin: 10px 0; padding: 10px; border-left-color: #ffb900;"><p><strong>%s</strong></p><p>%s</p></div><p style="display:none;">',
731 + esc_html__( 'Important: Major Update – Please backup before updating!', 'double-opt-in' ),
732 + esc_html__( 'This version includes significant changes to the form management system, email templates, and database structure. We strongly recommend creating a full site backup before updating.', 'double-opt-in' )
733 + );
734 +
735 + echo wp_kses_post( $upgrade_notice );
736 + }
737 +
738 + // Avada deprecation notice is handled by
739 + // Forge12\DoubleOptIn\Migration\AvadaDeprecationNotice (registered in
740 + // __construct). That class renders a proper admin notice on every
741 + // admin page with a grandfather-license claim button, rather than
742 + // a one-shot message at update time.
743 + },
744 + 10,
745 + 2
746 + );
747 +
748 +}