PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.3
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.3
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 3.0.62 All 38 releases
← All changes | CF7DoubleOptIn.class.php +745 -262 3.0.62 → 5.6.3 View file →
@@ -1,262 +1,745 @@
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.62
15 - * Author: Forge12 Interactive GmbH
16 - * Author URI: https://www.forge12.com
17 - */
18 - define( 'FORGE12_OPTIN_VERSION', '3.0.62' );
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/Debug.php');
27 - require_once( 'core/BaseController.class.php' );
28 -
29 - require_once( 'OnActivation.php' );
30 - require_once( 'OnDeactivation.php' );
31 - require_once( 'OnUpdate.php' );
32 - require_once( 'compatibility/OptInFrontend.class.php' );
33 -
34 - require_once( 'core/Messages.class.php' );
35 - require_once( 'core/TemplateHandler.class.php' );
36 - require_once( 'core/IPHelper.class.php' );
37 - require_once( 'core/SanitizeHelper.class.php' );
38 - require_once( 'core/Ajax.class.php' );
39 - require_once( 'core/Compatibility.class.php' );
40 - require_once( 'core/CleanUp.class.php' );
41 - require_once( 'core/HTMLSelect.class.php' );
42 - require_once( 'core/UI.class.php' );
43 - require_once( 'core/UIPage.class.php' );
44 - require_once( 'core/UIPageForm.class.php' );
45 - require_once( 'core/OptIn.class.php' );
46 - require_once( 'core/OptInLimitFilter.class.php' );
47 - require_once( 'core/OptInSearchFilter.class.php' );
48 - require_once( 'core/Category.class.php' );
49 - require_once( 'core/CategoryOptions.class.php' );
50 - require_once( 'core/Pagination.class.php' );
51 - require_once( 'core/Support.class.php' );
52 -
53 - /**
54 - * Class CF7DoubleOptIn
55 - * Controller for the Custom Links.
56 - *
57 - * @package forge12\contactform7
58 - */
59 - class CF7DoubleOptIn {
60 - /**
61 - * @var CF7DoubleOptIn|Null
62 - */
63 - private static $_instance = null;
64 -
65 - /**
66 - * @var TemplateHandler|null
67 - */
68 - private $TemplateHandler = null;
69 -
70 - /**
71 - * Get the singleton instance of CF7DoubleOptIn.
72 - *
73 - * @return CF7DoubleOptIn The singleton instance.
74 - */
75 - public static function getInstance() {
76 - if ( self::$_instance == null ) {
77 - self::$_instance = new self();
78 - }
79 -
80 - return self::$_instance;
81 - }
82 -
83 - /**
84 - * Return a list containing the array with all data stored within the form
85 - *
86 - * @param int $postID
87 - *
88 - * @formatter:off
89 - *
90 - * @return {
91 - * @type int $enable The Status of the OptIn, either 1 for enabled or 0 for disabled. Default: 0
92 - * @type string $sender The E-Mail of the sender of the optIn mail
93 - * @type string $subject The Subject of the OptIn Mail
94 - * @type string $body The Content of the OptIn Mail
95 - * @type string $recipient The Field that contains the E-Mail of the Recipient.
96 - * @type int $page The Post ID of the confirmation page. Default: -1
97 - * @type string $conditions Additional condition to dynamically enable / disable the optin.
98 - * Default: disabled
99 - * @type string $template The Template used for the OptIn Mail
100 - * @type int $category The Category the OptIns will be assigned to.
101 - * }
102 - * @formatter:on
103 - */
104 - public function getParameter( $postID ) {
105 -
106 - $data = [
107 - 'enable' => 0,
108 - 'sender' => get_bloginfo( 'admin_email' ),
109 - 'sender_name' => '',
110 - 'subject' => '',
111 - 'body' => '',
112 - 'recipient' => '',
113 - 'page' => - 1,
114 - 'conditions' => 'disabled',
115 - 'template' => '',
116 - 'category' => 0,
117 - ];
118 -
119 - /**
120 - * DOI Parameter
121 - *
122 - * Filters the parameter for the DOI. This hook allows developers
123 - * to alter DOI fields.
124 - *
125 - * @param array $data Additional Fields
126 - *
127 - * @since 1.0.0
128 - */
129 - $data = apply_filters( 'f12_cf7_doubleoptin_get_parameter', $data );
130 -
131 - if ( ! $postID ) {
132 - return $data;
133 - }
134 -
135 - $options = get_post_meta( $postID, 'f12-cf7-doubleoptin', true );
136 -
137 - if ( ! $options ) {
138 - return $data;
139 - }
140 -
141 - return array_merge( $data, $options );
142 - }
143 -
144 - /**
145 - * Private constructor to prevent direct instantiation.
146 - */
147 - private function __construct() {
148 - load_plugin_textdomain( 'double-opt-in', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
149 - /**
150 - * DOI INIT
151 - *
152 - * Run custom actions after the DOI has been initialized
153 - *
154 - * @param CF7DoubleOptIn $this
155 - *
156 - * @since 3.0.0
157 - */
158 - do_action( 'f12_cf7_doubleoptin_init', $this );
159 -
160 -
161 - $this->TemplateHandler = TemplateHandler::getInstance();
162 -
163 - $UI = new UI( $this->TemplateHandler, FORGE12_OPTIN_SLUG, 'Forge12 Double Opt-In', 'manage_options' );
164 -
165 - add_action( 'after_setup_theme', array( $this, 'init' ) );
166 -
167 - $Compatibility = new Compatibility( $this );
168 - $CleanUp = new CleanUp();
169 -
170 - // Pagination
171 - Pagination::getInstance();
172 -
173 - // initialize filter
174 - CategoryOptions::getInstance();
175 - OptInLimitFilter::getInstance();
176 - OptInSearchFilter::getInstance();
177 -
178 - // Support
179 - Support::getInstance();
180 - }
181 -
182 - /**
183 - * Retrieve the template handler instance.
184 - *
185 - * @return TemplateHandler The template handler instance.
186 - */
187 - public function get_template_handler() {
188 - return $this->TemplateHandler;
189 - }
190 -
191 - /**
192 - * @private WordPress Hook
193 - */
194 - public function init() {
195 - do_action( 'f12_cf7_doubleoptin_register_implementations' );
196 - }
197 -
198 - /**
199 - * Return the settings for the optin.
200 - *
201 - * @param string $single The Key of the setting to return only the required setting
202 - *
203 - * @formatter:off
204 - * @return {
205 - * // Returns the Settings for the DOI
206 - *
207 - * @type string $optout_subject The Subject for the OptOut Mail
208 - * @type string $optout_body The Content for the OptOut Mail
209 - * @type int $optout_page The Post ID for the OptOut Page
210 - * @type int $support Defines if the Support link will be added to the footer
211 - * @type int $delete An integer from 1 to 30
212 - * @type int $delete_unconfirmed An integer from 1 to 30
213 - * @type string $delete_period The time period, either months, days, years
214 - * @type string $delete_unconfirmed_period The time period, either months, days, years
215 - * }
216 - * @formatter:on
217 - */
218 - public function getSettings( $single = '', $container = null ) {
219 - $default = array();
220 -
221 - $default = apply_filters( 'f12_cf7_doubleoptin_settings', $default );
222 -
223 - $settings = get_option( 'f12-doi-settings' );
224 -
225 - if ( ! is_array( $settings ) ) {
226 - $settings = array();
227 - }
228 -
229 - foreach ( $default as $key => $data ) {
230 - if ( isset( $settings[ $key ] ) ) {
231 - if ( is_array( $default[ $key ] ) ) {
232 - $default[ $key ] = array_merge( $default[ $key ], $settings[ $key ] );
233 - } else {
234 - $default[ $key ] = $settings[ $key ];
235 - }
236 - }
237 - }
238 -
239 - $settings = $default;
240 -
241 - if ( ! empty( $single ) ) {
242 - if ( $container != null ) {
243 - if ( isset( $settings[ $container ] ) && isset( $settings[ $container ][ $single ] ) ) {
244 - $settings = $settings[ $container ][ $single ];
245 - }
246 - }
247 - } else {
248 - if ( isset( $settings[ $single ] ) ) {
249 - $settings = $settings[ $single ];
250 - }
251 - }
252 -
253 - return $settings;
254 - }
255 - }
256 -
257 -
258 - add_action('plugins_loaded', function(){
259 - CF7DoubleOptIn::getInstance();
260 - });
261 -
262 -}
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.6.3
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.6.3' );
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 + // Register licensing registry (v4.3.0+ — entitlement state for paid addons)
454 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\LicensingServiceProvider() );
455 +
456 + // Register migration registry (v4.3.0+ — runs pending DB migrations on admin_init)
457 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\MigrationServiceProvider() );
458 +
459 + // Register follow-up coordinator (v5.6.0+ — status + retry of
460 + // post-confirmation actions). Before AddonServiceProvider so
461 + // the adapter registry exists when the form addons boot.
462 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\FollowUpServiceProvider() );
463 +
464 + // Register addon system (v4.3.0+ — public Addon API)
465 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\AddonServiceProvider() );
466 +
467 + // Register health checks (v5.3.0+ — Site Health surfaces for
468 + // broken runtime preconditions such as a missing DB table).
469 + // After AddonServiceProvider so addon-contributed checks are
470 + // picked up by the registry's filter pass.
471 + $container->addProvider( new \Forge12\DoubleOptIn\Providers\HealthServiceProvider() );
472 +
473 + // Register RateLimiter as singleton
474 + $container->singleton(
475 + \Forge12\DoubleOptIn\Service\RateLimiter::class,
476 + function () {
477 + return new \Forge12\DoubleOptIn\Service\RateLimiter();
478 + }
479 + );
480 +
481 + // Boot all providers
482 + $container->boot();
483 +
484 + $this->get_logger()->info(
485 + 'DI Container initialized with Service Providers',
486 + array(
487 + 'plugin' => 'double-opt-in',
488 + 'component' => 'container',
489 + )
490 + );
491 + }
492 +
493 + /**
494 + * Get the DI Container instance.
495 + *
496 + * @since 4.0.0
497 + * @return \Forge12\DoubleOptIn\Container\Container
498 + */
499 + public function getContainer(): \Forge12\DoubleOptIn\Container\Container {
500 + return \Forge12\DoubleOptIn\Container\Container::getInstance();
501 + }
502 +
503 + /**
504 + * Retrieve the template handler instance.
505 + *
506 + * @return TemplateHandler The template handler instance.
507 + */
508 + public function get_template_handler() {
509 + $this->get_logger()->debug(
510 + 'TemplateHandler retrieved',
511 + array(
512 + 'plugin' => 'double-opt-in',
513 + 'class' => __CLASS__,
514 + 'method' => __METHOD__,
515 + )
516 + );
517 +
518 + return $this->TemplateHandler;
519 + }
520 +
521 + /**
522 + * @private WordPress Hook
523 + */
524 + public function init() {
525 + $this->get_logger()->debug(
526 + 'Init started',
527 + array(
528 + 'plugin' => 'double-opt-in',
529 + 'class' => __CLASS__,
530 + 'method' => __METHOD__,
531 + )
532 + );
533 +
534 + do_action( 'f12_cf7_doubleoptin_register_implementations' );
535 +
536 + $this->get_logger()->debug(
537 + 'Action f12_cf7_doubleoptin_register_implementations executed',
538 + array(
539 + 'plugin' => 'double-opt-in',
540 + )
541 + );
542 + }
543 +
544 +
545 + /**
546 + * Return the settings for the optin.
547 + *
548 + * @param string $single The Key of the setting to return only the required setting
549 + *
550 + * @formatter:off
551 + * @return {
552 + * // Returns the Settings for the DOI
553 + *
554 + * @type string $optout_subject The Subject for the OptOut Mail
555 + * @type string $optout_body The Content for the OptOut Mail
556 + * @type int $optout_page The Post ID for the OptOut Page
557 + * @type int $support Defines if the Support link will be added to the footer
558 + * @type int $delete An integer from 1 to 30
559 + * @type int $delete_unconfirmed An integer from 1 to 30
560 + * @type string $delete_period The time period, either months, days, years
561 + * @type string $delete_unconfirmed_period The time period, either months, days, years
562 + * }
563 + * @formatter:on
564 + */
565 +
566 + /**
567 + * Inject the core settings defaults onto the f12_cf7_doubleoptin_settings
568 + * filter. Relocated from the legacy admin UI (UISettings::getSettings) so
569 + * the default key set survives without the legacy admin. Defaults are the
570 + * base; any value already on the filter (saved settings, test overrides,
571 + * addon contributions) wins via array_merge.
572 + *
573 + * @param array $settings Settings collected so far on the filter.
574 + * @return array
575 + */
576 + public function injectDefaultSettings( $settings ) {
577 + $default_settings = array(
578 + 'telemetry' => 1,
579 + 'delete' => 12,
580 + 'delete_unconfirmed' => 7,
581 + 'delete_period' => 'months',
582 + 'delete_unconfirmed_period' => 'months',
583 + 'privacy_policy_page' => 0,
584 + // Must be listed even though the opt-out addon owns the
585 + // feature: getSettings() rebuilds its return value from
586 + // THIS array and silently drops any stored key that is
587 + // missing here. Without the entry, every consumer of
588 + // getSettings()['optout_page'] — OptInLinkGenerator and
589 + // OptIn::get_link_optout(), i.e. the `[doubleoptoutlink]`
590 + // placeholder — fell back to home_url() no matter what
591 + // the admin had configured.
592 + 'optout_page' => 0,
593 + 'token_expiry_hours' => 48,
594 + 'rate_limit_ip' => 5,
595 + 'rate_limit_email' => 3,
596 + 'rate_limit_window' => 60,
597 + 'reminder_enabled' => 0,
598 + 'reminder_delay' => 24,
599 + 'reminder_template' => '',
600 + 'reminder_subject' => '',
601 + 'mx_validation_enabled' => 0,
602 + 'mx_validation_behavior' => 'silent',
603 + 'mx_validation_message' => '',
604 + 'domain_blocklist_enabled' => 0,
605 + 'domain_blocklist' => '',
606 + 'domain_blocklist_behavior' => 'silent',
607 + 'domain_blocklist_message' => '',
608 + );
609 +
610 + return array_merge( $default_settings, is_array( $settings ) ? $settings : array() );
611 + }
612 +
613 + public function getSettings( $single = '', $container = null ) {
614 + $this->get_logger()->debug(
615 + 'Fetching settings',
616 + array(
617 + 'plugin' => 'double-opt-in',
618 + 'class' => __CLASS__,
619 + 'method' => __METHOD__,
620 + 'single' => $single,
621 + 'container' => $container,
622 + )
623 + );
624 +
625 + $default = array();
626 +
627 + $default = apply_filters( 'f12_cf7_doubleoptin_settings', $default );
628 +
629 + $settings = get_option( 'f12-doi-settings' );
630 +
631 + if ( ! is_array( $settings ) ) {
632 + $this->get_logger()->debug(
633 + 'No settings found in options, using empty array',
634 + array(
635 + 'plugin' => 'double-opt-in',
636 + )
637 + );
638 + $settings = array();
639 + }
640 +
641 + foreach ( $default as $key => $data ) {
642 + if ( isset( $settings[ $key ] ) ) {
643 + if ( is_array( $default[ $key ] ) ) {
644 + $default[ $key ] = array_merge( $default[ $key ], $settings[ $key ] );
645 + } else {
646 + $default[ $key ] = $settings[ $key ];
647 + }
648 + $this->get_logger()->debug(
649 + 'Merged settings for key',
650 + array(
651 + 'plugin' => 'double-opt-in',
652 + 'key' => $key,
653 + )
654 + );
655 + }
656 + }
657 +
658 + $settings = $default;
659 +
660 + if ( ! empty( $single ) ) {
661 + if ( $container != null ) {
662 + if ( isset( $settings[ $container ] ) && isset( $settings[ $container ][ $single ] ) ) {
663 + $this->get_logger()->debug(
664 + 'Returning single setting from container',
665 + array(
666 + 'plugin' => 'double-opt-in',
667 + 'container' => $container,
668 + 'single' => $single,
669 + )
670 + );
671 + $settings = $settings[ $container ][ $single ];
672 + }
673 + }
674 + } elseif ( isset( $settings[ $single ] ) ) {
675 + $this->get_logger()->debug(
676 + 'Returning single setting',
677 + array(
678 + 'plugin' => 'double-opt-in',
679 + 'single' => $single,
680 + )
681 + );
682 + $settings = $settings[ $single ];
683 + }
684 +
685 + return $settings;
686 + }
687 + }
688 +
689 +
690 + add_action(
691 + 'plugins_loaded',
692 + function () {
693 + add_cron_jobs();
694 + CF7DoubleOptIn::getInstance();
695 + }
696 + );
697 +
698 + /**
699 + * Display upgrade notice in plugin list when updating to major versions.
700 + *
701 + * @param array $data Plugin update data.
702 + * @param object $response Response object from WordPress.org API.
703 + */
704 + add_action(
705 + 'in_plugin_update_message-' . FORGE12_OPTIN_BASENAME,
706 + function ( $data, $response ) {
707 + $upgrade_notice = '';
708 +
709 + // Check if this is a major update (e.g., 3.1.x -> 3.2.x)
710 + $current_version = FORGE12_OPTIN_VERSION;
711 + $new_version = $response->new_version ?? '';
712 +
713 + if ( empty( $new_version ) ) {
714 + return;
715 + }
716 +
717 + // Extract major.minor from versions
718 + $current_parts = explode( '.', $current_version );
719 + $new_parts = explode( '.', $new_version );
720 +
721 + $current_minor = ( $current_parts[0] ?? '0' ) . '.' . ( $current_parts[1] ?? '0' );
722 + $new_minor = ( $new_parts[0] ?? '0' ) . '.' . ( $new_parts[1] ?? '0' );
723 +
724 + // Show warning for major/minor version changes
725 + if ( version_compare( $new_minor, $current_minor, '>' ) ) {
726 + $upgrade_notice = sprintf(
727 + '</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;">',
728 + esc_html__( 'Important: Major Update – Please backup before updating!', 'double-opt-in' ),
729 + 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' )
730 + );
731 +
732 + echo wp_kses_post( $upgrade_notice );
733 + }
734 +
735 + // Avada deprecation notice is handled by
736 + // Forge12\DoubleOptIn\Migration\AvadaDeprecationNotice (registered in
737 + // __construct). That class renders a proper admin notice on every
738 + // admin page with a grandfather-license claim button, rather than
739 + // a one-shot message at update time.
740 + },
741 + 10,
742 + 2
743 + );
744 +
745 +}