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 | core/OptIn.class.php +612 -2412 3.1.0 → 5.5.0 View file →
@@ -1,2579 +1,779 @@
1 1 <?php
2 +/**
3 + * OptIn Facade
4 + *
5 + * Backward-compatible facade for the legacy OptIn API.
6 + * Internally uses the new Repository pattern.
7 + *
8 + * @package forge12\contactform7\CF7DoubleOptIn
9 + * @since 4.0.0
10 + */
2 11
3 -namespace forge12\contactform7\CF7DoubleOptIn {
12 +namespace forge12\contactform7\CF7DoubleOptIn;
4 13
5 - use Forge12\Shared\Logger;
6 - use Forge12\Shared\LoggerInterface;
14 +use Forge12\DoubleOptIn\Container\Container;
15 +use Forge12\DoubleOptIn\Entity\OptIn as OptInEntity;
16 +use Forge12\DoubleOptIn\Repository\OptInRepositoryInterface;
17 +use Forge12\Shared\Logger;
18 +use Forge12\Shared\LoggerInterface;
7 19
8 - if ( ! defined( 'ABSPATH' ) ) {
9 - exit;
10 - }
20 +if ( ! defined( 'ABSPATH' ) ) {
21 + exit;
22 +}
11 23
24 +/**
25 + * Class OptIn
26 + *
27 + * This class provides backward compatibility with the legacy API
28 + * while using the new Repository pattern internally.
29 + */
30 +class OptIn {
31 +
32 + private LoggerInterface $logger;
33 + private OptInEntity $entity;
34 +
12 35 /**
13 - * Class Frontend
14 - * Responsible to handle the frontend of the Double OptIn
36 + * Constructor.
15 37 *
16 - * @package forge12\contactform7\CF7DoubleOptIn
38 + * @param LoggerInterface $logger The logger instance.
39 + * @param array $properties Optional properties to initialize.
17 40 */
18 - class OptIn {
19 - /**
20 - * Stores the DB ID for the entry
21 - *
22 - * @var int
23 - */
24 - private $id = 0;
41 + public function __construct( LoggerInterface $logger, array $properties = [] ) {
42 + $this->logger = $logger;
25 43
26 - /**
27 - * Stores the ID of the contact form 7 form.
28 - *
29 - * @var int
30 - */
31 - private $cf_form_id = 0;
32 - /**
33 - * Stores if the optin has been confirmed (1) or not (0)
34 - *
35 - * @var int
36 - */
37 - private $doubleoptin = 0;
38 - /**
39 - * Stores the content of the contact form 7 form content as a serialized string.
40 - *
41 - * @var string
42 - */
43 - private $content = "";
44 - /**
45 - * Stores the unique hash used to identify the opt-ins
46 - *
47 - * @var string
48 - */
49 - private $hash = "";
50 - /**
51 - * Stores the timestamp the opt-in mail has been sent to the user.
52 - *
53 - * @var string
54 - */
55 - private $createtime = "";
56 - /**
57 - * Stores the timestamp the opt-in has been confirmed by the user.
58 - */
59 - private $updatetime = "";
60 - /**
61 - * Stores the timestamp the opt-out has been confirmed by the user.
62 - */
63 - private $optouttime = "";
64 - /**
65 - * IP Address used to trigger the opt-in mail.
66 - *
67 - * @var string
68 - */
69 - private $ipaddr_register = "";
70 - /**
71 - * IP Address used to confirm the opt-in mail.
72 - */
73 - private $ipaddr_confirmation = "";
74 - /**
75 - * IP Address used to output.
76 - */
77 - private $ipaddr_optout = "";
78 - /**
79 - * Stores the files used within this form as a serialized string.
80 - */
81 - private $files = "";
82 - /**
83 - * Store the Category ID
84 - */
85 - private $category = 0;
86 - /**
87 - * Stores the form as html
88 - */
89 - private $form = '';
90 - /**
91 - * Stores the Mail send for the optin
92 - *
93 - * @var string
94 - */
95 - private $mail_optin = '';
96 - /**
97 - * Stores the E-Mail
98 - *
99 - * @var string
100 - */
101 - private $email = '';
102 - private LoggerInterface $logger;
103 -
104 - /**
105 - * The constructor
106 - */
107 - public function __construct( LoggerInterface $logger, array $properties ) {
108 - $this->logger = $logger;
109 - $this->get_logger()->info( 'Constructor started.', [
110 - 'plugin' => 'double-opt-in',
111 - 'class' => __CLASS__,
112 - ] );
113 -
114 - foreach ( $properties as $name => $value ) {
115 - if ( isset( $this->{$name} ) ) {
116 - $this->{$name} = $value;
117 - $this->get_logger()->debug( 'Property ' . $name . ' set successfully.', [
118 - 'plugin' => 'double-opt-in',
119 - 'property' => $name,
120 - 'value' => $value,
121 - ] );
122 - } else {
123 - $this->get_logger()->warning( 'Attempted to set a non-existent property.', [
124 - 'plugin' => 'double-opt-in',
125 - 'property' => $name,
126 - ] );
127 - }
128 - }
129 -
130 - $this->get_logger()->info( 'Constructor finished successfully.', [
131 - 'plugin' => 'double-opt-in',
132 - ] );
44 + if ( ! empty( $properties ) ) {
45 + $this->entity = OptInEntity::fromArray( $this->mapToEntityArray( $properties ) );
46 + } else {
47 + $this->entity = OptInEntity::create();
133 48 }
134 49
135 - public function get_logger() {
136 - return $this->logger;
137 - }
50 + $this->logger->debug( 'OptIn facade initialized', [
51 + 'plugin' => 'double-opt-in',
52 + 'id' => $this->entity->getId(),
53 + ] );
54 + }
138 55
139 - /**
140 - * @param int $cf_form_id
141 - */
142 - public function set_cf_form_id( $cf_form_id ) {
143 - $this->get_logger()->info( 'Setting Contact Form 7 form ID.', [
144 - 'plugin' => 'double-opt-in',
145 - 'form_id_raw' => $cf_form_id,
146 - ] );
56 + /**
57 + * Get the logger instance.
58 + *
59 + * @return LoggerInterface
60 + */
61 + public function get_logger(): LoggerInterface {
62 + return $this->logger;
63 + }
147 64
148 - $this->cf_form_id = (int) $cf_form_id;
65 + /**
66 + * Get the underlying entity.
67 + *
68 + * @return OptInEntity
69 + */
70 + public function getEntity(): OptInEntity {
71 + return $this->entity;
72 + }
149 73
150 - $this->get_logger()->debug( 'Contact Form 7 form ID has been set.', [
151 - 'plugin' => 'double-opt-in',
152 - 'form_id_casted' => $this->cf_form_id,
153 - ] );
154 - }
74 + // =========================================================================
75 + // STATIC FACTORY METHODS
76 + // =========================================================================
155 77
156 - /**
157 - * @return int
158 - */
159 - public function get_cf_form_id() {
160 - return $this->cf_form_id;
161 - }
78 + /**
79 + * Get OptIn by hash.
80 + *
81 + * @param string $hash The hash.
82 + *
83 + * @return OptIn|null
84 + */
85 + public static function get_by_hash( string $hash ): ?OptIn {
86 + $logger = Logger::getInstance();
87 + $logger->debug( 'get_by_hash called', [
88 + 'plugin' => 'double-opt-in',
89 + 'hash' => $hash,
90 + ] );
162 91
163 - /**
164 - * Return the ID of the Optin
165 - *
166 - * @return int
167 - */
168 - public function get_id() {
169 - return $this->id;
170 - }
92 + try {
93 + $repository = self::getRepository();
94 + $entity = $repository->findByHash( $hash );
171 95
172 - /**
173 - * Return the hash of the optin.
174 - *
175 - * @return string
176 - */
177 - public function get_hash() {
178 - return $this->hash;
179 - }
96 + if ( ! $entity ) {
97 + return null;
98 + }
180 99
181 - /**
182 - * Return true|false depending on confirm status
183 - *
184 - * @return bool
185 - */
186 - public function is_confirmed() {
187 - return (bool) $this->get_doubleoptin();
188 - }
100 + $optIn = new self( $logger );
101 + $optIn->entity = $entity;
189 102
190 - /**
191 - * Return true|false wether the form has been confirmed or not.
192 - *
193 - * @return bool
194 - */
195 - public function is_optout() {
196 - $this->get_logger()->info( 'Checking if the current entry is an opt-out.', [
103 + return $optIn;
104 + } catch ( \Exception $e ) {
105 + $logger->error( 'Failed to get OptIn by hash', [
197 106 'plugin' => 'double-opt-in',
107 + 'hash' => $hash,
108 + 'error' => $e->getMessage(),
198 109 ] );
199 -
200 - $is_optout = (true != $this->get_doubleoptin() && ! empty( $this->get_ipaddr_optout() ) && ! empty( $this->get_optouttime() ));
201 -
202 - if ( $is_optout ) {
203 - $this->get_logger()->notice( 'The entry is an opt-out based on its properties.', [
204 - 'plugin' => 'double-opt-in',
205 - 'double_optin' => $this->get_doubleoptin(),
206 - 'ipaddr_optout' => $this->get_ipaddr_optout(),
207 - 'optouttime' => $this->get_optouttime(),
208 - ] );
209 - return true;
210 - }
211 -
212 - $this->get_logger()->debug( 'The entry is not an opt-out.', [
213 - 'plugin' => 'double-opt-in',
214 - 'double_optin' => $this->get_doubleoptin(),
215 - 'ipaddr_optout' => $this->get_ipaddr_optout(),
216 - 'optouttime' => $this->get_optouttime(),
217 - ] );
218 -
219 - return false;
110 + return null;
220 111 }
112 + }
221 113
222 - /**
223 - * update the optin value
224 - *
225 - * @param int $confirmed
226 - */
227 - public function set_doubleoptin( $confirmed ) {
228 - $this->doubleoptin = (int) $confirmed;
114 + /**
115 + * Get count by form ID.
116 + *
117 + * @param int $formId The form ID.
118 + *
119 + * @return int
120 + */
121 + public static function get_count( int $formId ): int {
122 + try {
123 + return self::getRepository()->countByFormId( $formId );
124 + } catch ( \Exception $e ) {
125 + return 0;
229 126 }
127 + }
230 128
231 - /**
232 - * Return the optin value for the form.
233 - *
234 - * @return int
235 - */
236 - public function get_doubleoptin() {
237 - return $this->doubleoptin;
238 - }
129 + /**
130 + * Get list of OptIns with pagination.
131 + *
132 + * @param array $atts Query attributes.
133 + * @param int|null $numberOfPages Reference to store page count.
134 + *
135 + * @return array<OptIn>
136 + */
137 + public static function get_list( array $atts = [], ?int &$numberOfPages = null ): array {
138 + $logger = Logger::getInstance();
239 139
240 - /**
241 - * @param string $timestamp
242 - */
243 - public function set_createtime( $timestamp ) {
244 - $this->createtime = $timestamp;
245 - }
140 + try {
141 + $repository = self::getRepository();
142 + $entities = $repository->findAll( $atts, $numberOfPages );
246 143
247 - /**
248 - * Return the Date how long the opt in will be valid
249 - *
250 - * @return string
251 - */
252 - public function get_valid_until() {
253 - $this->get_logger()->info( 'Calculating the validity end date for the current entry.', [
144 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
145 + $optIn = new self( $logger );
146 + $optIn->entity = $entity;
147 + return $optIn;
148 + }, $entities );
149 + } catch ( \Exception $e ) {
150 + $logger->error( 'Failed to get OptIn list', [
254 151 'plugin' => 'double-opt-in',
152 + 'error' => $e->getMessage(),
255 153 ] );
256 -
257 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
258 -
259 - $createtime = $this->get_createtime();
260 - $dt = new \DateTime();
261 - $dt->setTimestamp( $createtime );
262 -
263 - // Log the initial creation time.
264 - $this->get_logger()->debug( 'Base creation time for calculation: ' . date('Y-m-d H:i:s', $createtime), [
265 - 'plugin' => 'double-opt-in',
266 - 'createtime' => $createtime,
267 - ] );
268 -
269 - if ( $this->is_confirmed() ) {
270 - $this->get_logger()->info( 'Entry is confirmed. Using confirmed deletion settings.', [
271 - 'plugin' => 'double-opt-in',
272 - ] );
273 - $month = (int) $settings['delete'];
274 - $period = $settings['delete_period'];
275 - } else {
276 - $this->get_logger()->info( 'Entry is unconfirmed. Using unconfirmed deletion settings.', [
277 - 'plugin' => 'double-opt-in',
278 - ] );
279 - $month = (int) $settings['delete_unconfirmed'];
280 - $period = $settings['delete_unconfirmed_period'];
281 - }
282 -
283 - // Log the period used for calculation.
284 - $this->get_logger()->debug( 'Calculated validity period: ' . $month . ' ' . $period, [
285 - 'plugin' => 'double-opt-in',
286 - 'period_value' => $month,
287 - 'period_unit' => $period,
288 - ] );
289 -
290 - $dt->modify( '+' . (int) $month . ' ' . $period );
291 - $dt->modify( '+1 day' );
292 -
293 - $valid_until_date = $dt->format( 'd.m.Y' );
294 -
295 - $this->get_logger()->notice( 'Validity end date calculated successfully.', [
296 - 'plugin' => 'double-opt-in',
297 - 'valid_until' => $valid_until_date,
298 - ] );
299 -
300 - return $valid_until_date;
154 + return [];
301 155 }
156 + }
302 157
303 - /**
304 - * Return a timestamp
305 - *
306 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
307 - * formatted string.
308 - *
309 - * @return string
310 - */
311 - public function get_createtime( $view = "raw" ) {
312 - $this->get_logger()->info( 'Retrieving creation time.', [
313 - 'plugin' => 'double-opt-in',
314 - 'view' => $view,
315 - ] );
158 + /**
159 + * Get list by category ID.
160 + *
161 + * @param int $categoryId The category ID.
162 + * @param array $atts Query attributes.
163 + * @param int|null $numberOfPages Reference to store page count.
164 + *
165 + * @return array<OptIn>
166 + */
167 + public static function get_list_by_category_id( int $categoryId, array $atts = [], ?int &$numberOfPages = null ): array {
168 + $logger = Logger::getInstance();
316 169
317 - if ( $view != "raw" ) {
318 - $this->get_logger()->debug( 'Processing creation time for formatted output.', [
319 - 'plugin' => 'double-opt-in',
320 - 'raw_value' => $this->createtime,
321 - ] );
170 + try {
171 + $repository = self::getRepository();
322 172
323 - if ( ! is_numeric( $this->createtime ) ) {
324 - $date = date( 'd.m.Y', strtotime( $this->createtime ) );
325 - $time = date( 'H:i:s', strtotime( $this->createtime ) );
326 - $this->get_logger()->debug( 'Creation time is not numeric, converting from string.', [
327 - 'plugin' => 'double-opt-in',
328 - ] );
329 - } else {
330 - $date = date( 'd.m.Y', $this->createtime );
331 - $time = date( 'H:i:s', $this->createtime );
332 - $this->get_logger()->debug( 'Creation time is numeric, converting from timestamp.', [
333 - 'plugin' => 'double-opt-in',
334 - ] );
335 - }
173 + // Get total count for pagination
174 + if ( $numberOfPages !== null ) {
175 + $keyword = $atts['keyword'] ?? '';
176 + $perPage = max( 1, (int) ( $atts['perPage'] ?? 10 ) );
177 + $total = $repository->countByCategory( $categoryId, $keyword );
178 + $numberOfPages = $total > 0 ? (int) ceil( $total / $perPage ) : 0;
336 179
337 - $formatted_time = $date . ' ' . __( '/', 'double-opt-in' ) . ' ' . $time;
338 - $this->get_logger()->notice( 'Returning formatted creation time.', [
339 - 'plugin' => 'double-opt-in',
340 - 'formatted_value' => $formatted_time,
341 - ] );
342 - return $formatted_time;
343 - } else {
344 - $this->get_logger()->info( 'Returning raw creation time.', [
345 - 'plugin' => 'double-opt-in',
346 - 'raw_value' => $this->createtime,
347 - ] );
348 - return $this->createtime;
349 - }
350 - }
351 -
352 - /**
353 - * @param string $timestamp
354 - */
355 - public function set_updatetime( $timestamp ) {
356 - $this->updatetime = $timestamp;
357 - }
358 -
359 - /**
360 - * Return a timestamp
361 - *
362 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
363 - * formatted string.
364 - *
365 - * @return string
366 - */
367 - public function get_updatetime( $view = 'raw' ) {
368 - $this->get_logger()->info( 'Retrieving update time.', [
369 - 'plugin' => 'double-opt-in',
370 - 'view' => $view,
371 - ] );
372 -
373 - if ( $view != "raw" ) {
374 - $this->get_logger()->debug( 'Processing update time for formatted view.', [
375 - 'plugin' => 'double-opt-in',
376 - 'raw_value' => $this->updatetime,
377 - ] );
378 -
379 - if ( ! is_numeric( $this->updatetime ) ) {
380 - $date = date( 'd.m.Y', strtotime( $this->updatetime ) );
381 - $time = date( 'H:i:s', strtotime( $this->updatetime ) );
382 - $this->get_logger()->debug( 'Update time is not numeric, converting from string.', [
383 - 'plugin' => 'double-opt-in',
384 - ] );
385 - } else {
386 - $date = date( 'd.m.Y', $this->updatetime );
387 - $time = date( 'H:i:s', $this->updatetime );
388 - $this->get_logger()->debug( 'Update time is numeric, converting from timestamp.', [
389 - 'plugin' => 'double-opt-in',
390 - ] );
180 + if ( $numberOfPages === 0 ) {
181 + return [];
391 182 }
392 -
393 - $formatted_time = $date . ' ' . __( '/', 'double-opt-in' ) . ' ' . $time;
394 - $this->get_logger()->notice( 'Returning formatted update time.', [
395 - 'plugin' => 'double-opt-in',
396 - 'formatted_value' => $formatted_time,
397 - ] );
398 - return $formatted_time;
399 - } else {
400 - $this->get_logger()->info( 'Returning raw update time.', [
401 - 'plugin' => 'double-opt-in',
402 - 'raw_value' => $this->updatetime,
403 - ] );
404 - return $this->updatetime;
405 183 }
406 - }
407 184
408 - /**
409 - * @param string $timestamp
410 - */
411 - public function set_optouttime( $timestamp ) {
412 - $this->optouttime = $timestamp;
413 - }
185 + $entities = $repository->findByCategory( $categoryId, $atts );
414 186
415 - /**
416 - * Return a timestamp
417 - *
418 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
419 - * formatted string.
420 - *
421 - * @return string
422 - */
423 - public function get_optouttime( $view = 'raw' ) {
424 - $this->get_logger()->info( 'Retrieving opt-out time.', [
425 - 'plugin' => 'double-opt-in',
426 - 'view' => $view,
187 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
188 + $optIn = new self( $logger );
189 + $optIn->entity = $entity;
190 + return $optIn;
191 + }, $entities );
192 + } catch ( \Exception $e ) {
193 + $logger->error( 'Failed to get OptIn list by category', [
194 + 'plugin' => 'double-opt-in',
195 + 'category' => $categoryId,
196 + 'error' => $e->getMessage(),
427 197 ] );
428 -
429 - if ( $view != "raw" ) {
430 - $this->get_logger()->debug( 'Processing opt-out time for formatted view.', [
431 - 'plugin' => 'double-opt-in',
432 - 'raw_value' => $this->optouttime,
433 - ] );
434 -
435 - if ( ! is_numeric( $this->optouttime ) ) {
436 - $date = date( 'd.m.Y', strtotime( $this->optouttime ) );
437 - $time = date( 'H:i:s', strtotime( $this->optouttime ) );
438 - $this->get_logger()->debug( 'Opt-out time is not numeric, converting from string.', [
439 - 'plugin' => 'double-opt-in',
440 - ] );
441 - } else {
442 - $date = date( 'd.m.Y', $this->optouttime );
443 - $time = date( 'H:i:s', $this->optouttime );
444 - $this->get_logger()->debug( 'Opt-out time is numeric, converting from timestamp.', [
445 - 'plugin' => 'double-opt-in',
446 - ] );
447 - }
448 -
449 - $formatted_time = $date . ' ' . __( '/', 'f12-cf7-doubleoptin' ) . ' ' . $time;
450 - $this->get_logger()->notice( 'Returning formatted opt-out time.', [
451 - 'plugin' => 'double-opt-in',
452 - 'formatted_value' => $formatted_time,
453 - ] );
454 - return $formatted_time;
455 - } else {
456 - $this->get_logger()->info( 'Returning raw opt-out time.', [
457 - 'plugin' => 'double-opt-in',
458 - 'raw_value' => $this->optouttime,
459 - ] );
460 - return $this->optouttime;
461 - }
198 + return [];
462 199 }
200 + }
463 201
464 - /**
465 - * @param string $ip_addr
466 - */
467 - public function set_ipaddr_optout( $ip_addr ) {
468 - $this->ipaddr_optout = $ip_addr;
469 - }
202 + /**
203 + * Get list by email.
204 + *
205 + * @param string $email The email address.
206 + *
207 + * @return array<OptIn>
208 + */
209 + public static function get_list_by_email( string $email ): array {
210 + $logger = Logger::getInstance();
470 211
471 - /**
472 - * @return string
473 - */
474 - public function get_ipaddr_optout() {
475 - return $this->ipaddr_optout;
476 - }
212 + try {
213 + $repository = self::getRepository();
214 + $entities = $repository->findByEmail( $email );
477 215
478 - /**
479 - * @param string $ip_addr
480 - */
481 - public function set_ipaddr_register( $ip_addr ) {
482 - $this->ipaddr_register = $ip_addr;
216 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
217 + $optIn = new self( $logger );
218 + $optIn->entity = $entity;
219 + return $optIn;
220 + }, $entities );
221 + } catch ( \Exception $e ) {
222 + return [];
483 223 }
224 + }
484 225
485 - /**
486 - * @return string
487 - */
488 - public function get_ipaddr_register() {
489 - return $this->ipaddr_register;
490 - }
226 + /**
227 + * Get confirmed list by email.
228 + *
229 + * @param string $email The email address.
230 + *
231 + * @return array<OptIn>
232 + */
233 + public static function get_list_by_email_confirmed( string $email ): array {
234 + $logger = Logger::getInstance();
491 235
492 - /**
493 - * @param string $ip_addr
494 - */
495 - public function set_ipaddr_confirmation( $ip_addr ) {
496 - $this->ipaddr_confirmation = $ip_addr;
497 - }
236 + try {
237 + $repository = self::getRepository();
238 + $entities = $repository->findConfirmedByEmail( $email );
498 239
499 - /**
500 - * @return string
501 - */
502 - public function get_ipaddr_confirmation() {
503 - return $this->ipaddr_confirmation;
240 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
241 + $optIn = new self( $logger );
242 + $optIn->entity = $entity;
243 + return $optIn;
244 + }, $entities );
245 + } catch ( \Exception $e ) {
246 + return [];
504 247 }
248 + }
505 249
506 - /**
507 - * Return a serialized string
508 - *
509 - * @return mixed
510 - */
511 - public function get_content() {
512 - return $this->content;
513 - }
250 + /**
251 + * Get unconfirmed list by email.
252 + *
253 + * @param string $email The email address.
254 + *
255 + * @return array<OptIn>
256 + */
257 + public static function get_list_by_email_unconfirmed( string $email ): array {
258 + $logger = Logger::getInstance();
514 259
515 - /**
516 - * @param string - a serialized string
517 - */
518 - public function set_content( $content ) {
519 - $this->content = $content;
520 - }
260 + try {
261 + $repository = self::getRepository();
262 + $entities = $repository->findUnconfirmedByEmail( $email );
521 263
522 - /**
523 - * @param string - a serialized string
524 - */
525 - public function set_files( $files ) {
526 - $this->files = $files;
264 + return array_map( function ( OptInEntity $entity ) use ( $logger ) {
265 + $optIn = new self( $logger );
266 + $optIn->entity = $entity;
267 + return $optIn;
268 + }, $entities );
269 + } catch ( \Exception $e ) {
270 + return [];
527 271 }
272 + }
528 273
529 - /**
530 - * @return string use maybe_unserialize to deserialize the string
531 - */
532 - public function get_files() {
533 - return $this->files;
274 + /**
275 + * Bulk update category.
276 + *
277 + * @param int $fromId The source category ID.
278 + * @param int $toId The target category ID.
279 + *
280 + * @return int
281 + */
282 + public static function bulk_update_category( int $fromId, int $toId ): int {
283 + try {
284 + return self::getRepository()->bulkUpdateCategory( $fromId, $toId );
285 + } catch ( \Exception $e ) {
286 + return 0;
534 287 }
288 + }
535 289
536 - /**
537 - * Return the assigned Category ID
538 - *
539 - * @return int
540 - */
541 - public function get_category() {
542 - if ( ! is_numeric( $this->category ) ) {
543 - return 0;
544 - }
545 -
546 - return $this->category;
547 - }
548 -
549 - /**
550 - * Assign a ID to the category
551 - *
552 - * @param int $id
553 - *
554 - * @return void
555 - */
556 - public function set_category( $id ) {
557 - $this->category = $id;
558 - }
559 -
560 - /**
561 - * Check if the given Opt In was created by Contact Form 7
562 - *
563 - * @return bool
564 - */
565 - public function isTypeCF7() {
566 - $form_id = $this->get_cf_form_id();
567 - $form = get_post( $form_id );
568 -
569 - if ( 'wpcf7_contact_form' === $form->post_type ) {
570 - return true;
571 - }
572 -
290 + /**
291 + * Update category by OptIn ID.
292 + *
293 + * @param int $optInId The OptIn ID.
294 + * @param int $categoryId The new category ID.
295 + *
296 + * @return bool
297 + */
298 + public static function update_category_by_id( int $optInId, int $categoryId ): bool {
299 + try {
300 + return self::getRepository()->updateCategoryById( $optInId, $categoryId );
301 + } catch ( \Exception $e ) {
573 302 return false;
574 303 }
304 + }
575 305
576 - /**
577 - * Check if the given Opt In was created by Elementor
578 - *
579 - * @return bool
580 - */
581 - public function isTypeElementor() {
582 - $this->get_logger()->info( 'Checking if the form is an Elementor form.', [
583 - 'plugin' => 'double-opt-in',
584 - ] );
306 + // =========================================================================
307 + // GETTERS
308 + // =========================================================================
585 309
586 - if ( ! class_exists( 'ElementorPro\Plugin' ) ) {
587 - $this->get_logger()->warning( 'Elementor Pro is not installed. Skipping Elementor form check.', [
588 - 'plugin' => 'double-opt-in',
589 - ] );
590 - return false;
591 - }
310 + public function get_id(): int {
311 + return $this->entity->getId();
312 + }
592 313
593 - $page_id = $this->get_cf_form_id();
594 - $this->get_logger()->debug( 'Retrieved form ID: ' . $page_id, [
595 - 'plugin' => 'double-opt-in',
596 - 'page_id' => $page_id,
597 - ] );
314 + public function get_hash(): string {
315 + return $this->entity->getHash();
316 + }
598 317
599 - $post = get_post( $page_id );
318 + public function get_cf_form_id(): int {
319 + return $this->entity->getFormId();
320 + }
600 321
601 - // skip if post not existing anymore
602 - if ( null === $post ) {
603 - $this->get_logger()->warning( 'Post with ID ' . $page_id . ' not found. Cannot check for Elementor.', [
604 - 'plugin' => 'double-opt-in',
605 - 'page_id' => $page_id,
606 - ] );
607 - return false;
608 - }
322 + public function is_confirmed(): bool {
323 + return $this->entity->isConfirmed();
324 + }
609 325
610 - // skip if page is not build with elementor
611 - if ( ! \Elementor\Plugin::$instance->documents->get( $page_id )->is_built_with_elementor() ) {
612 - $this->get_logger()->debug( 'Page is not built with Elementor.', [
613 - 'plugin' => 'double-opt-in',
614 - 'page_id' => $page_id,
615 - ] );
616 - return false;
617 - }
326 + public function get_doubleoptin(): int {
327 + return $this->entity->isConfirmed() ? 1 : 0;
328 + }
618 329
619 - $this->get_logger()->info( 'Page is built with Elementor. Proceeding with element checks.', [
620 - 'plugin' => 'double-opt-in',
621 - 'page_id' => $page_id,
622 - ] );
330 + public function is_optout(): bool {
331 + return $this->entity->isOptedOut();
332 + }
623 333
624 - // Load the page's data
625 - $document = \Elementor\Plugin::$instance->documents->get( $page_id );
334 + public function get_content(): string {
335 + return $this->entity->getContent();
336 + }
626 337
627 - // Skip if document not exist
628 - if ( ! $document ) {
629 - $this->get_logger()->error( 'Elementor document not found for page ID ' . $page_id . '.', [
630 - 'plugin' => 'double-opt-in',
631 - 'page_id' => $page_id,
632 - ] );
633 - return false;
634 - }
635 -
636 - // Loop through the elements
637 - $elements_data = $document->get_elements_data();
638 - $content = maybe_unserialize( $this->get_content() );
639 -
640 - // skip if form id not available
641 - if ( ! isset( $content['form_id'] ) ) {
642 - $this->get_logger()->warning( 'Form ID not found in serialized content. Cannot check for Elementor form widget.', [
643 - 'plugin' => 'double-opt-in',
644 - ] );
645 - return false;
646 - }
647 -
648 - $form_id = $content['form_id'];
649 - $this->get_logger()->debug( 'Searching for form widget with ID: ' . $form_id, [
650 - 'plugin' => 'double-opt-in',
651 - 'form_widget_id' => $form_id,
652 - ] );
653 -
654 - // Find the Form Element
655 - foreach ( $elements_data as $element ) {
656 - if ( isset( $element['elements'] ) ) {
657 - foreach ( $element['elements'] as $el ) {
658 - if ( $form_id !== $el['id'] || 'form' !== $el['widgetType'] ) {
659 - $this->get_logger()->debug( 'Skipping element. ID or widgetType does not match.', [
660 - 'plugin' => 'double-opt-in',
661 - 'element_id' => $el['id'] ?? 'not set',
662 - 'element_type' => $el['widgetType'] ?? 'not set',
663 - ] );
664 - continue;
665 - }
666 - $this->get_logger()->notice( 'Elementor form widget found. Returning true.', [
667 - 'plugin' => 'double-opt-in',
668 - 'widget_id' => $form_id,
669 - ] );
670 - return true;
671 - }
672 - // The `return true;` here seems to be a logical error in the original code,
673 - // as it would return true after the first nested loop even if no form was found.
674 - // However, as per instructions, I am not to change the code logic, so it remains.
675 - return true;
676 - }
677 -
678 - // Check if this is the form widget
679 - if ( isset( $element['widgetType'] ) && isset( $element['id'] ) && $element['widgetType'] === 'form' && $element['id'] === $form_id ) {
680 - // Return the form settings
681 - $this->get_logger()->notice( 'Elementor form widget found at top level. Returning true.', [
682 - 'plugin' => 'double-opt-in',
683 - 'widget_id' => $form_id,
684 - ] );
685 - return true;
686 - }
687 - }
688 -
689 - $this->get_logger()->warning( 'No matching Elementor form widget found after searching all elements.', [
690 - 'plugin' => 'double-opt-in',
691 - 'search_id' => $form_id,
692 - ] );
693 -
694 - return false;
338 + public function get_createtime( string $view = 'raw' ): string {
339 + if ( $view !== 'raw' ) {
340 + return $this->entity->getCreateTimeFormatted( 'd.m.Y / H:i:s' );
695 341 }
342 + return (string) $this->entity->getCreateTime();
343 + }
696 344
697 - /**
698 - * Check if the given Opt In was created by Avada
699 - *
700 - * @return bool|void
701 - */
702 - public function isTypeAvada() {
703 - $this->get_logger()->info( 'Checking if the form is an Avada Fusion form.', [
704 - 'plugin' => 'double-opt-in',
705 - ] );
706 -
707 - $form_id = $this->get_cf_form_id();
708 - $this->get_logger()->debug( 'Form ID to check: ' . $form_id, [
709 - 'plugin' => 'double-opt-in',
710 - 'form_id' => $form_id,
711 - ] );
712 -
713 - $form = get_post( $form_id );
714 -
715 - if ( ! $form ) {
716 - $this->get_logger()->warning( 'Post object not found for the given form ID. Cannot determine type.', [
717 - 'plugin' => 'double-opt-in',
718 - 'form_id' => $form_id,
719 - ] );
720 - return false;
721 - }
722 -
723 - if ( 'fusion_form' === $form->post_type ) {
724 - $this->get_logger()->notice( 'The form is an Avada Fusion form.', [
725 - 'plugin' => 'double-opt-in',
726 - 'form_id' => $form_id,
727 - ] );
728 - return true;
729 - }
730 -
731 - $this->get_logger()->info( 'The form is not an Avada Fusion form.', [
732 - 'plugin' => 'double-opt-in',
733 - 'form_id' => $form_id,
734 - 'post_type' => $form->post_type,
735 - ] );
736 -
737 - return false;
345 + public function get_updatetime( string $view = 'raw' ): string {
346 + if ( $view !== 'raw' ) {
347 + return $this->entity->getUpdateTimeFormatted( 'd.m.Y / H:i:s' );
738 348 }
349 + return (string) $this->entity->getUpdateTime();
350 + }
739 351
740 - /**
741 - * Check if the Opt In was created by 'cf7' or 'avada'.
742 - *
743 - * @param string $type use either 'cf7' or 'avada'
744 - *
745 - * @return bool
746 - */
747 - public function isType( $type ) {
748 - $this->get_logger()->info( 'Checking form type.', [
749 - 'plugin' => 'double-opt-in',
750 - 'requested_type' => $type,
751 - ] );
752 -
753 - if ( $type == 'cf7' ) {
754 - $this->get_logger()->debug( 'Type is CF7. Calling isTypeCF7().', [
755 - 'plugin' => 'double-opt-in',
756 - ] );
757 - return $this->isTypeCF7();
758 - } elseif ( $type == 'elementor' ) {
759 - $this->get_logger()->debug( 'Type is Elementor. Calling isTypeElementor().', [
760 - 'plugin' => 'double-opt-in',
761 - ] );
762 - return $this->isTypeElementor();
763 - } else {
764 - $this->get_logger()->debug( 'Type is neither CF7 nor Elementor. Calling isTypeAvada().', [
765 - 'plugin' => 'double-opt-in',
766 - 'unmatched_type' => $type,
767 - ] );
768 - return $this->isTypeAvada();
769 - }
352 + public function get_optouttime( string $view = 'raw' ): string {
353 + $time = $this->entity->getOptOutTime();
354 + if ( $view !== 'raw' && $time > 0 ) {
355 + return wp_date( 'd.m.Y / H:i:s', $time );
770 356 }
357 + return (string) $time;
358 + }
771 359
772 - /**
773 - * Load an OptIn by the given hash code. Returns either an Object of Type OptIn or null if not found.
774 - *
775 - * @param string $hash
776 - *
777 - * @return OptIn|null
778 - */
779 - public static function get_by_hash( string $hash ) {
780 - $logger = Logger::getInstance();
781 - $logger->info( 'Attempting to retrieve opt-in entry by hash.', [
782 - 'plugin' => 'double-opt-in',
783 - 'hash' => $hash,
784 - ] );
360 + /**
361 + * Get create time as ISO 8601 string.
362 + *
363 + * @return string
364 + */
365 + public function get_createtime_iso(): string {
366 + return $this->entity->getCreateTimeISO();
367 + }
785 368
786 - global $wpdb;
787 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
369 + /**
370 + * Get update time as ISO 8601 string.
371 + *
372 + * @return string
373 + */
374 + public function get_updatetime_iso(): string {
375 + return $this->entity->getUpdateTimeISO();
376 + }
788 377
789 - if ( null == $wpdb ) {
790 - $logger->critical( 'WordPress database object ($wpdb) is not available.', [
791 - 'plugin' => 'double-opt-in',
792 - ] );
793 - return null;
794 - }
378 + /**
379 + * Get opt-out time as ISO 8601 string.
380 + *
381 + * @return string
382 + */
383 + public function get_optouttime_iso(): string {
384 + return $this->entity->getOptOutTimeISO();
385 + }
795 386
796 - // Log the prepared query to prevent SQL injection issues in practice.
797 - // The original code uses string concatenation, which is vulnerable.
798 - $query = $wpdb->prepare('SELECT * FROM ' . $table . ' WHERE hash=%s', $hash);
799 - $logger->debug( 'Executing database query to find entry by hash.', [
800 - 'plugin' => 'double-opt-in',
801 - 'query' => $query,
802 - ] );
387 + public function get_ipaddr_register(): string {
388 + return $this->entity->getIpRegister();
389 + }
803 390
804 - $row = $wpdb->get_row( $query, ARRAY_A );
391 + public function get_ipaddr_confirmation(): string {
392 + return $this->entity->getIpConfirmation();
393 + }
805 394
806 - if ( null == $row ) {
807 - $logger->warning( 'No opt-in entry found for the provided hash.', [
808 - 'plugin' => 'double-opt-in',
809 - 'hash' => $hash,
810 - ] );
811 - return null;
812 - }
395 + public function get_ipaddr_optout(): string {
396 + return $this->entity->getIpOptOut();
397 + }
813 398
814 - $logger->notice( 'Opt-in entry successfully retrieved by hash.', [
815 - 'plugin' => 'double-opt-in',
816 - 'hash' => $hash,
817 - 'entry_id' => $row['id'] ?? 'not set',
818 - ] );
399 + public function get_files(): string {
400 + return $this->entity->getFiles();
401 + }
819 402
820 - return new OptIn( $logger, $row );
821 - }
403 + public function get_category(): int {
404 + return $this->entity->getCategory();
405 + }
822 406
823 - /**
824 - * Get number of optins for the given post.
825 - *
826 - * @param $post_id
827 - *
828 - * @return int
829 - */
830 - public static function get_count( $post_id ) {
831 - $logger = Logger::getInstance();
832 - $logger->info( 'Counting opt-in entries for a specific form.', [
833 - 'plugin' => 'double-opt-in',
834 - 'form_id' => $post_id,
835 - ] );
407 + public function get_email(): string {
408 + return $this->entity->getEmail();
409 + }
836 410
837 - global $wpdb;
411 + public function get_mail_optin(): string {
412 + return $this->entity->getMailOptIn();
413 + }
838 414
839 - if ( ! $wpdb ) {
840 - $logger->critical( 'WordPress database object ($wpdb) is not available.', [
841 - 'plugin' => 'double-opt-in',
842 - ] );
843 - return 0;
844 - }
415 + public function get_consent_text(): string {
416 + return $this->entity->getConsentText();
417 + }
845 418
846 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
847 - $query = $wpdb->prepare( 'SELECT count(*) AS counter FROM ' . $tableName . ' WHERE cf_form_id=%d ', $post_id );
848 -
849 - $logger->debug( 'Executing database query to count entries.', [
850 - 'plugin' => 'double-opt-in',
851 - 'query' => $query,
852 - ] );
853 -
854 - $result = $wpdb->get_results( $query );
855 -
856 - if ( is_array( $result ) && isset($result[0]->counter) ) {
857 - $count = (int)$result[0]->counter;
858 - $logger->notice( 'Successfully counted ' . $count . ' entries for form ID ' . $post_id . '.', [
859 - 'plugin' => 'double-opt-in',
860 - 'form_id' => $post_id,
861 - 'count' => $count,
862 - ] );
863 - return $count;
864 - }
865 -
866 - $logger->error( 'Failed to retrieve or process the count from the database.', [
867 - 'plugin' => 'double-opt-in',
868 - 'form_id' => $post_id,
869 - 'db_error' => $wpdb->last_error,
870 - ] );
871 -
872 - return 0;
419 + public function get_form( bool $encoded = false ): string {
420 + $form = $this->entity->getForm();
421 + if ( $encoded ) {
422 + return base64_encode( $form );
873 423 }
424 + return $form;
425 + }
874 426
875 - /**
876 - * Return a list of opt ins stored in the database.
877 - *
878 - * @param int $category_id
879 - *
880 - * @param array $atts array (
881 - * 'perPage' => 10, // Posts per page
882 - * 'page' => 0, // Current page
883 - * 'order' => DESC, // Order (ASC/DESC)
884 - * );
885 - *
886 - * @param null $numberOfPages - Stores the number of pages found if limited
887 - *
888 - * @return array
889 - */
890 - public static function get_list_by_category_id( $category_id, $atts = [], &$numberOfPages = null ) {
891 - $logger = Logger::getInstance();
892 - $logger->info( 'Starting to retrieve a list of opt-in entries by category ID.', [
893 - 'plugin' => 'double-opt-in',
894 - 'category_id' => $category_id,
895 - 'attributes' => $atts,
896 - ] );
427 + /**
428 + * Get the validity end date.
429 + *
430 + * @return string
431 + */
432 + public function get_valid_until(): string {
433 + $settings = CF7DoubleOptIn::getInstance()->getSettings();
434 + $dt = new \DateTime();
435 + $dt->setTimestamp( $this->entity->getCreateTime() );
897 436
898 - global $wpdb;
899 -
900 - $attr = [
901 - 'perPage' => 10,
902 - 'page' => 1,
903 - 'order' => 'DESC',
904 - 'keyword' => ''
905 - ];
906 -
907 - /**
908 - * Parameter
909 - */
910 - foreach ( $atts as $key => $value ) {
911 - if ( isset( $attr[ $key ] ) ) {
912 - $attr[ $key ] = $atts[ $key ];
913 - }
914 - }
915 -
916 - $logger->debug( 'Final query attributes:', [
917 - 'plugin' => 'double-opt-in',
918 - 'attributes' => $attr,
919 - ] );
920 -
921 - /*
922 - * Update keyword
923 - */
924 - $where = '';
925 - if ( ! empty( $attr['keyword'] ) ) {
926 - $where = ' AND content LIKE "%%' . $attr['keyword'] . '%%"';
927 - $logger->info( 'Keyword filter detected. Adding to WHERE clause.', [
928 - 'plugin' => 'double-opt-in',
929 - 'keyword' => $attr['keyword'],
930 - ] );
931 - }
932 -
933 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
934 - $pageNum = (int) $attr['page'] - 1;
935 -
936 - if ( $numberOfPages !== null ) {
937 - $logger->info( 'Counting total items for pagination.', [
938 - 'plugin' => 'double-opt-in',
939 - ] );
940 -
941 - $countQuery = $wpdb->prepare( 'SELECT count(*) AS counter FROM ' . $tableName . ' WHERE category=%d ' . $where, $category_id );
942 - $logger->debug( 'Executing count query: ' . $countQuery, [
943 - 'plugin' => 'double-opt-in',
944 - ] );
945 -
946 - $result = $wpdb->get_results( $countQuery );
947 -
948 - $itemCounter = 0;
949 -
950 - if ( is_array( $result ) && isset($result[0]->counter) ) {
951 - $itemCounter = (int)$result[0]->counter;
952 - $logger->notice( 'Found ' . $itemCounter . ' total items matching the criteria.', [
953 - 'plugin' => 'double-opt-in',
954 - 'item_count' => $itemCounter,
955 - ] );
956 - } else {
957 - $logger->error( 'Failed to count items for pagination.', [
958 - 'plugin' => 'double-opt-in',
959 - 'db_error' => $wpdb->last_error,
960 - ] );
961 - }
962 -
963 - $numberOfPages = 0;
964 -
965 - if ( $itemCounter != 0 ) {
966 - $numberOfPages = ceil( $itemCounter / (int) $attr['perPage'] );
967 - }
968 -
969 - $logger->info( 'Calculated a total of ' . $numberOfPages . ' pages.', [
970 - 'plugin' => 'double-opt-in',
971 - 'total_pages' => $numberOfPages,
972 - 'items_per_page' => $attr['perPage'],
973 - ] );
974 - }
975 -
976 - $list = [];
977 -
978 - if ( $numberOfPages == 0 ) {
979 - $logger->warning( 'No pages to display. Returning an empty list.', [
980 - 'plugin' => 'double-opt-in',
981 - ] );
982 - return $list;
983 - }
984 -
985 - $offset = $pageNum * (int) $attr['perPage'];
986 -
987 - $mainQuery = $wpdb->prepare( 'SELECT * FROM ' . $tableName . ' WHERE category=%d ' . $where . ' ORDER BY ID ' . $attr['order'] . ' LIMIT ' . $attr['perPage'] . ' OFFSET %d', $category_id, $offset );
988 -
989 - $logger->debug( 'Executing main query to retrieve data: ' . $mainQuery, [
990 - 'plugin' => 'double-opt-in',
991 - ] );
992 -
993 - $rows = $wpdb->get_results( $mainQuery, ARRAY_A );
994 -
995 - if ($rows === null) {
996 - $logger->error( 'Failed to retrieve data from the database.', [
997 - 'plugin' => 'double-opt-in',
998 - 'db_error' => $wpdb->last_error,
999 - ] );
1000 - }
1001 -
1002 - foreach ( $rows as $row ) {
1003 - $list[] = new OptIn(Logger::getInstance(), $row );
1004 - }
1005 -
1006 - $logger->notice( 'Successfully retrieved ' . count($list) . ' opt-in entries for category ID ' . $category_id . '.', [
1007 - 'plugin' => 'double-opt-in',
1008 - 'category_id' => $category_id,
1009 - 'item_count' => count( $list ),
1010 - ] );
1011 -
1012 - return $list;
437 + if ( $this->is_confirmed() ) {
438 + $amount = (int) ( $settings['delete'] ?? 0 );
439 + $period = $settings['delete_period'] ?? 'months';
440 + } else {
441 + $amount = (int) ( $settings['delete_unconfirmed'] ?? 0 );
442 + $period = $settings['delete_unconfirmed_period'] ?? 'months';
1013 443 }
1014 444
1015 - /**
1016 - * Return a list of opt ins stored in the database.
1017 - *
1018 - * @param string $email
1019 - *
1020 - * @return array
1021 - */
1022 - public static function get_list_by_email( $email ) {
1023 - $logger = Logger::getInstance();
1024 - $logger->info( 'Starting to retrieve a list of opt-in entries by email.', [
1025 - 'plugin' => 'double-opt-in',
1026 - 'email' => $email,
1027 - ] );
445 + $dt->modify( '+' . $amount . ' ' . $period );
446 + $dt->modify( '+1 day' );
1028 447
1029 - global $wpdb;
448 + return $dt->format( 'd.m.Y' );
449 + }
1030 450
1031 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
1032 - $list = [];
451 + // =========================================================================
452 + // SETTERS
453 + // =========================================================================
1033 454
1034 - $query = $wpdb->prepare( 'SELECT * FROM ' . $tableName . ' WHERE email=%s', $email );
455 + public function set_cf_form_id( int $formId ): void {
456 + $this->entity = $this->entity->withFormId( $formId );
457 + }
1035 458
1036 - $logger->debug( 'Executing database query to find entries by email.', [
1037 - 'plugin' => 'double-opt-in',
1038 - 'query' => $query,
1039 - ] );
459 + public function set_doubleoptin( int $confirmed ): void {
460 + $this->entity->setConfirmed( (bool) $confirmed );
461 + }
1040 462
1041 - $rows = $wpdb->get_results( $query, ARRAY_A );
463 + public function set_createtime( $timestamp ): void {
464 + $this->entity = $this->entity->withCreateTime( (int) $timestamp );
465 + }
1042 466
1043 - if ($rows === null) {
1044 - $logger->error( 'Failed to retrieve data from the database.', [
1045 - 'plugin' => 'double-opt-in',
1046 - 'email' => $email,
1047 - 'db_error' => $wpdb->last_error,
1048 - ] );
1049 - }
467 + public function set_updatetime( $timestamp ): void {
468 + $this->entity->setUpdateTime( (int) $timestamp );
469 + }
1050 470
1051 - foreach ( $rows as $row ) {
1052 - $list[] = new OptIn(Logger::getInstance(), $row );
1053 - }
471 + public function set_optouttime( $timestamp ): void {
472 + $this->entity = $this->entity->withOptOutTime( (int) $timestamp );
473 + }
1054 474
1055 - $logger->notice( 'Successfully retrieved ' . count( $list ) . ' opt-in entries for email: ' . $email, [
1056 - 'plugin' => 'double-opt-in',
1057 - 'email' => $email,
1058 - 'item_count' => count( $list ),
1059 - ] );
475 + public function set_ipaddr_register( string $ip ): void {
476 + $this->entity = $this->entity->withIpRegister( $ip );
477 + }
1060 478
1061 - return $list;
1062 - }
479 + public function set_ipaddr_confirmation( string $ip ): void {
480 + $this->entity->setIpConfirmation( $ip );
481 + }
1063 482
1064 - /**
1065 - * Return a list of opt ins stored in the database.
1066 - *
1067 - * @param string $email
1068 - *
1069 - * @return array
1070 - */
1071 - public static function get_list_by_email_confirmed( $email ) {
1072 - $logger = Logger::getInstance();
1073 - $logger->info( 'Starting to retrieve a list of confirmed opt-in entries by email.', [
1074 - 'plugin' => 'double-opt-in',
1075 - 'email' => $email,
1076 - ] );
483 + public function set_ipaddr_optout( string $ip ): void {
484 + $this->entity = $this->entity->withIpOptOut( $ip );
485 + }
1077 486
1078 - global $wpdb;
487 + public function set_content( string $content ): void {
488 + $this->entity = $this->entity->withContent( $content );
489 + }
1079 490
1080 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
1081 - $list = [];
491 + public function set_files( string $files ): void {
492 + $this->entity = $this->entity->withFiles( $files );
493 + }
1082 494
1083 - $query = $wpdb->prepare( 'SELECT * FROM ' . $tableName . ' WHERE email=%s AND doubleoptin=1', $email );
495 + public function set_category( int $categoryId ): void {
496 + $this->entity = $this->entity->withCategory( $categoryId );
497 + }
1084 498
1085 - $logger->debug( 'Executing database query to find confirmed entries by email.', [
1086 - 'plugin' => 'double-opt-in',
1087 - 'query' => $query,
1088 - ] );
499 + public function set_form( string $form ): void {
500 + $this->entity = $this->entity->withForm( $form );
501 + }
1089 502
1090 - $rows = $wpdb->get_results( $query, ARRAY_A );
503 + public function set_email( string $email ): void {
504 + $this->entity = $this->entity->withEmail( $email );
505 + }
1091 506
1092 - if ($rows === null) {
1093 - $logger->error( 'Failed to retrieve data from the database.', [
1094 - 'plugin' => 'double-opt-in',
1095 - 'email' => $email,
1096 - 'db_error' => $wpdb->last_error,
1097 - ] );
1098 - }
507 + public function set_mail_optin( string $mail ): void {
508 + $this->entity = $this->entity->withMailOptIn( $mail );
509 + }
1099 510
1100 - foreach ( $rows as $row ) {
1101 - $list[] = new OptIn(Logger::getInstance(), $row );
1102 - }
511 + public function set_consent_text( string $text ): void {
512 + $this->entity = $this->entity->withConsentText( $text );
513 + }
1103 514
1104 - $logger->notice( 'Successfully retrieved ' . count( $list ) . ' confirmed opt-in entries for email: ' . $email, [
1105 - 'plugin' => 'double-opt-in',
1106 - 'email' => $email,
1107 - 'item_count' => count( $list ),
1108 - ] );
515 + // =========================================================================
516 + // TYPE CHECKS
517 + // =========================================================================
1109 518
1110 - return $list;
1111 - }
519 + public function isTypeCF7(): bool {
520 + return $this->isType( 'cf7' );
521 + }
1112 522
1113 - /**
1114 - * Return a list of opt ins stored in the database.
1115 - *
1116 - * @param string $email
1117 - *
1118 - * @return array
1119 - */
1120 - public static function get_list_by_email_unconfirmed( $email ) {
1121 - $logger = Logger::getInstance();
1122 - $logger->info( 'Starting to retrieve a list of unconfirmed opt-in entries by email.', [
1123 - 'plugin' => 'double-opt-in',
1124 - 'email' => $email,
1125 - ] );
523 + public function isTypeAvada(): bool {
524 + return $this->isType( 'avada' );
525 + }
1126 526
1127 - global $wpdb;
527 + public function isTypeElementor(): bool {
528 + return $this->isType( 'elementor' );
529 + }
1128 530
1129 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
1130 - $list = [];
531 + public function isTypeWPForms(): bool {
532 + return $this->isType( 'wpforms' );
533 + }
1131 534
1132 - $query = $wpdb->prepare( 'SELECT * FROM ' . $tableName . ' WHERE email=%s AND doubleoptin=0', $email );
535 + public function isTypeGravityForms(): bool {
536 + return $this->isType( 'gravityforms' );
537 + }
1133 538
1134 - $logger->debug( 'Executing database query to find unconfirmed entries by email.', [
1135 - 'plugin' => 'double-opt-in',
1136 - 'query' => $query,
1137 - ] );
539 + public function isType( string $type ): bool {
540 + $formId = $this->get_cf_form_id();
1138 541
1139 - $rows = $wpdb->get_results( $query, ARRAY_A );
1140 -
1141 - if ($rows === null) {
1142 - $logger->error( 'Failed to retrieve data from the database.', [
1143 - 'plugin' => 'double-opt-in',
1144 - 'email' => $email,
1145 - 'db_error' => $wpdb->last_error,
1146 - ] );
1147 - }
1148 -
1149 - foreach ( $rows as $row ) {
1150 - $list[] = new OptIn(Logger::getInstance(), $row );
1151 - }
1152 -
1153 - $logger->notice( 'Successfully retrieved ' . count( $list ) . ' unconfirmed opt-in entries for email: ' . $email, [
1154 - 'plugin' => 'double-opt-in',
1155 - 'email' => $email,
1156 - 'item_count' => count( $list ),
1157 - ] );
1158 -
1159 - return $list;
1160 - }
1161 -
1162 - /**
1163 - * Return a list of opt ins stored in the database.
1164 - *
1165 - * @param $atts array (
1166 - * 'perPage' => 10, // Posts per page
1167 - * 'page' => 0, // Current page
1168 - * 'order' => DESC, // Order (ASC/DESC)
1169 - * );
1170 - *
1171 - * @param null $numberOfPages - Stores the number of pages found if limited
1172 - *
1173 - * @return array
1174 - */
1175 - public static function get_list( $atts = [], &$numberOfPages = null ) {
1176 - $logger = Logger::getInstance();
1177 - $logger->info( 'Starting to retrieve a paginated list of opt-in entries.', [
1178 - 'plugin' => 'double-opt-in',
1179 - 'initial_attributes' => $atts,
1180 - ] );
1181 -
1182 - global $wpdb;
1183 -
1184 - $attr = [
1185 - 'perPage' => 10,
1186 - 'page' => 1,
1187 - 'order' => 'DESC',
1188 - 'keyword' => '',
1189 - 'cf_form_id' => '',
1190 - ];
1191 -
1192 - /*
1193 - * Update atts
1194 - */
1195 - foreach ( $atts as $key => $value ) {
1196 - if ( isset( $attr[ $key ] ) ) {
1197 - $attr[ $key ] = $atts[ $key ];
542 + switch ( $type ) {
543 + case 'cf7':
544 + return get_post_type( $formId ) === 'wpcf7_contact_form';
545 + case 'avada':
546 + return get_post_type( $formId ) === 'fusion_form';
547 + case 'elementor':
548 + // Elementor forms are embedded in pages/posts, not stored as separate post types.
549 + // Check if the post has Elementor data AND DOI settings configured.
550 + $hasElementorData = ! empty( get_post_meta( $formId, '_elementor_data', true ) );
551 + $hasDoiSettings = ! empty( get_post_meta( $formId, 'f12-cf7-doubleoptin', true ) );
552 + return $hasElementorData && $hasDoiSettings;
553 + case 'wpforms':
554 + return get_post_type( $formId ) === 'wpforms';
555 + case 'gravityforms':
556 + // Gravity Forms stores forms in a custom table, not as posts.
557 + // Check if the form ID exists in the GF forms table.
558 + if ( class_exists( 'GFAPI' ) ) {
559 + $form = \GFAPI::get_form( $formId );
560 + return $form !== false && ! is_wp_error( $form );
1198 561 }
1199 - }
1200 -
1201 - $logger->debug( 'Final attributes for the query.', [
1202 - 'plugin' => 'double-opt-in',
1203 - 'attributes' => $attr,
1204 - ] );
1205 -
1206 - /*
1207 - * Update WHERE for Query
1208 - */
1209 - $where = '';
1210 - $where_condition = [];
1211 - $query_params = [];
1212 -
1213 - /*
1214 - * Add Keyword
1215 - */
1216 - if ( ! empty( $attr['keyword'] ) ) {
1217 - $where_condition[] = 'content LIKE %s';
1218 - $query_params[] = '%%' . $wpdb->esc_like( $attr['keyword'] ) . '%%';
1219 - $logger->info( 'Keyword filter detected.', [
1220 - 'plugin' => 'double-opt-in',
1221 - 'keyword' => $attr['keyword'],
1222 - ] );
1223 - }
1224 -
1225 - /*
1226 - * Add Form ID
1227 - */
1228 - if ( ! empty( $attr['cf_form_id'] ) ) {
1229 - $where_condition[] = 'cf_form_id=%d';
1230 - $query_params[] = $attr['cf_form_id'];
1231 - $logger->info( 'Form ID filter detected.', [
1232 - 'plugin' => 'double-opt-in',
1233 - 'form_id' => $attr['cf_form_id'],
1234 - ] );
1235 - }
1236 -
1237 - /*
1238 - * build where string
1239 - */
1240 - if ( ! empty( $where_condition ) ) {
1241 - $where = ' WHERE ' . implode( ' AND ', $where_condition );
1242 - }
1243 -
1244 - /*
1245 - * set table
1246 - */
1247 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
1248 - $pageNum = (int) $attr['page'] - 1;
1249 -
1250 - if ( $numberOfPages !== null ) {
1251 - $logger->info( 'Counting total items for pagination.', [
1252 - 'plugin' => 'double-opt-in',
1253 - ] );
1254 -
1255 - $count_query = 'SELECT count(*) as counter FROM ' . $tableName . $where;
1256 - if ( ! empty( $query_params ) ) {
1257 - $count_query = $wpdb->prepare( $count_query, ...$query_params );
1258 - }
1259 - $result = $wpdb->get_results( $count_query );
1260 -
1261 - $itemCounter = 0;
1262 -
1263 - if ( is_array( $result ) && isset($result[0]->counter) ) {
1264 - $itemCounter = (int)$result[0]->counter;
1265 - $logger->notice( 'Found ' . $itemCounter . ' total items matching the criteria.', [
1266 - 'plugin' => 'double-opt-in',
1267 - 'item_count' => $itemCounter,
1268 - ] );
1269 - } else {
1270 - $logger->error( 'Failed to count items for pagination.', [
1271 - 'plugin' => 'double-opt-in',
1272 - 'db_error' => $wpdb->last_error,
1273 - ] );
1274 - }
1275 -
1276 - $numberOfPages = 0;
1277 -
1278 - if ( $itemCounter != 0 ) {
1279 - $numberOfPages = ceil( $itemCounter / (int) $attr['perPage'] );
1280 - }
1281 -
1282 - $logger->info( 'Calculated a total of ' . $numberOfPages . ' pages.', [
1283 - 'plugin' => 'double-opt-in',
1284 - 'total_pages' => $numberOfPages,
1285 - 'items_per_page' => $attr['perPage'],
1286 - ] );
1287 - }
1288 -
1289 - $list = [];
1290 -
1291 - if ( $numberOfPages == 0 ) {
1292 - $logger->warning( 'No pages to display. Returning an empty list.', [
1293 - 'plugin' => 'double-opt-in',
1294 - ] );
1295 - return $list;
1296 - }
1297 -
1298 - $offset = $pageNum * (int) $attr['perPage'];
1299 -
1300 - // Add offset to query parameters for the main query
1301 - $query_params[] = $offset;
1302 -
1303 - // Using a more robust prepared statement for the main query
1304 - $rows_query = 'SELECT * FROM ' . $tableName . $where . ' ORDER BY ID ' . $attr['order'] . ' LIMIT ' . (int)$attr['perPage'] . ' OFFSET %d';
1305 - $rows = $wpdb->get_results( $wpdb->prepare( $rows_query, ...$query_params ), ARRAY_A );
1306 -
1307 - if ($rows === null) {
1308 - $logger->error( 'Failed to retrieve data from the database.', [
1309 - 'plugin' => 'double-opt-in',
1310 - 'db_error' => $wpdb->last_error,
1311 - ] );
1312 - }
1313 -
1314 - foreach ( $rows as $row ) {
1315 - $list[] = new OptIn( Logger::getInstance(), $row );
1316 - }
1317 -
1318 - $logger->notice( 'Successfully retrieved ' . count($list) . ' opt-in entries.', [
1319 - 'plugin' => 'double-opt-in',
1320 - 'item_count' => count( $list ),
1321 - ] );
1322 -
1323 - return $list;
1324 - }
1325 -
1326 - /**
1327 - * This will update all categories from A to B, e.g.: All Opt-Ins with id 1 to category id 2.
1328 - *
1329 - * @param int $from_id The origin Category ID
1330 - * @param int $to_id The new Category ID
1331 - *
1332 - * @return bool|int|\mysqli_result|resource|null Return the number of rows affected by the query.
1333 - */
1334 - public static function bulk_update_category( $from_id, $to_id ) {
1335 - $logger = Logger::getInstance();
1336 - $logger->info( 'Starting bulk update of category IDs for opt-in entries.', [
1337 - 'plugin' => 'double-opt-in',
1338 - 'from_id' => $from_id,
1339 - 'to_id' => $to_id,
1340 - ] );
1341 -
1342 - global $wpdb;
1343 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1344 -
1345 - $query = $wpdb->prepare( 'UPDATE ' . $table . ' SET category =%d WHERE category =%d', $to_id, $from_id );
1346 -
1347 - $logger->debug( 'Executing database query for bulk category update.', [
1348 - 'plugin' => 'double-opt-in',
1349 - 'query' => $query,
1350 - ] );
1351 -
1352 - $result = $wpdb->query( $query );
1353 -
1354 - if ( $result === false ) {
1355 - $logger->error( 'Failed to perform bulk category update.', [
1356 - 'plugin' => 'double-opt-in',
1357 - 'from_id' => $from_id,
1358 - 'to_id' => $to_id,
1359 - 'db_error' => $wpdb->last_error,
1360 - ] );
1361 - } else {
1362 - $logger->notice( 'Bulk category update completed successfully. ' . $result . ' rows affected.', [
1363 - 'plugin' => 'double-opt-in',
1364 - 'from_id' => $from_id,
1365 - 'to_id' => $to_id,
1366 - 'rows_affected' => $result,
1367 - ] );
1368 - }
1369 -
1370 - return $result;
1371 - }
1372 -
1373 - /**
1374 - * Update the category of the given opt in id.
1375 - *
1376 - * @param int $optin_id The ID of the OptIn
1377 - * @param int $category_id The ID of the Category
1378 - *
1379 - * @return bool|int|\mysqli_result|resource|null
1380 - */
1381 - public static function update_category_by_id( $optin_id, $category_id ) {
1382 - $logger = Logger::getInstance();
1383 - $logger->info( 'Starting to update category ID for a specific opt-in entry.', [
1384 - 'plugin' => 'double-opt-in',
1385 - 'optin_id' => $optin_id,
1386 - 'category_id' => $category_id,
1387 - ] );
1388 -
1389 - global $wpdb;
1390 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1391 -
1392 - $query = $wpdb->prepare( 'UPDATE ' . $table . ' SET category =%d WHERE id =%d', $category_id, $optin_id );
1393 -
1394 - $logger->debug( 'Executing database query to update category.', [
1395 - 'plugin' => 'double-opt-in',
1396 - 'query' => $query,
1397 - ] );
1398 -
1399 - $result = $wpdb->query( $query );
1400 -
1401 - if ( $result === false ) {
1402 - $logger->error( 'Failed to update category ID for opt-in entry.', [
1403 - 'plugin' => 'double-opt-in',
1404 - 'optin_id' => $optin_id,
1405 - 'category_id' => $category_id,
1406 - 'db_error' => $wpdb->last_error,
1407 - ] );
1408 - } else {
1409 - $logger->notice( 'Category ID for opt-in entry ' . $optin_id . ' updated to ' . $category_id . '.', [
1410 - 'plugin' => 'double-opt-in',
1411 - 'optin_id' => $optin_id,
1412 - 'category_id' => $category_id,
1413 - 'rows_affected' => $result,
1414 - ] );
1415 - }
1416 -
1417 - return $result;
1418 - }
1419 -
1420 - /**
1421 - * Update or create the given Object
1422 - */
1423 - public function save() {
1424 - global $wpdb;
1425 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1426 -
1427 - $this->get_logger()->info( 'save method called', [
1428 - 'plugin' => 'double-opt-in',
1429 - 'table' => $table,
1430 - 'hash' => $this->get_hash(),
1431 - ] );
1432 -
1433 - /**
1434 - * Wenn ein Hash existiert, wird die Zeile aktualisiert
1435 - */
1436 - if ( ! empty( $this->get_hash() ) ) {
1437 - $this->get_logger()->info( 'Updating existing record', [
1438 - 'plugin' => 'double-opt-in',
1439 - 'hash' => $this->get_hash(),
1440 - 'cf_form_id' => $this->get_cf_form_id(),
1441 - 'doubleoptin' => $this->get_doubleoptin(),
1442 - ] );
1443 -
1444 - $result = $wpdb->update( $table, [
1445 - 'doubleoptin' => $this->get_doubleoptin(),
1446 - 'createtime' => $this->get_createtime(),
1447 - 'updatetime' => $this->get_updatetime(),
1448 - 'ipaddr_confirmation' => $this->get_ipaddr_confirmation(),
1449 - 'ipaddr_register' => $this->get_ipaddr_register(),
1450 - 'cf_form_id' => $this->get_cf_form_id(),
1451 - 'content' => $this->get_content(),
1452 - 'files' => $this->get_files(),
1453 - 'category' => $this->get_category(),
1454 - 'optouttime' => $this->get_optouttime(),
1455 - 'ipaddr_optout' => $this->get_ipaddr_optout(),
1456 - 'mail_optin' => $this->get_mail_optin(),
1457 - ], [
1458 - 'hash' => $this->get_hash()
1459 - ] );
1460 -
1461 - if ( $result === false ) {
1462 - $this->get_logger()->error( 'Update operation failed', [
1463 - 'plugin' => 'double-opt-in',
1464 - 'db_error' => $wpdb->last_error,
1465 - 'hash' => $this->get_hash(),
1466 - ] );
1467 - } else {
1468 - $this->get_logger()->notice( 'Update operation completed', [
1469 - 'plugin' => 'double-opt-in',
1470 - 'rows_affected' => $result,
1471 - 'hash' => $this->get_hash(),
1472 - ] );
1473 - }
1474 -
1475 - return $result;
1476 - }
1477 -
1478 - /**
1479 - * Wenn kein Hash existiert, wird ein neuer Datensatz eingefügt
1480 - */
1481 - $this->get_logger()->info( 'Inserting new record', [
1482 - 'plugin' => 'double-opt-in',
1483 - 'cf_form_id' => $this->get_cf_form_id(),
1484 - 'email' => $this->get_email(),
1485 - 'doubleoptin' => $this->get_doubleoptin(),
1486 - ] );
1487 -
1488 - $formValue = $this->get_form( true );
1489 - $data = [ // Daten in einer Variablen speichern, um sie anschließend zu loggen
1490 - 'cf_form_id' => $this->get_cf_form_id(),
1491 - 'doubleoptin' => $this->get_doubleoptin(),
1492 - 'createtime' => $this->get_createtime(),
1493 - 'updatetime' => $this->get_updatetime(),
1494 - 'ipaddr_confirmation' => $this->get_ipaddr_confirmation(),
1495 - 'ipaddr_register' => $this->get_ipaddr_register(),
1496 - 'content' => $this->get_content(),
1497 - 'files' => $this->get_files(),
1498 - 'category' => $this->get_category(),
1499 - 'form' => $formValue,
1500 - 'mail_optin' => $this->get_mail_optin(),
1501 - 'email' => $this->get_email(),
1502 - ];
1503 -
1504 - $this->get_logger()->debug( 'Form Data Information', [
1505 - 'plugin' => 'double-opt-in',
1506 - 'form_length' => strlen( $formValue ), // Länge des Inhalts
1507 - 'form_data_sample' => substr( $formValue, 0, 500 ), // Nur die ersten 500 Zeichen loggen
1508 - ] );
1509 -
1510 - $log_data = $data;
1511 - $log_data['form'] = substr( $formValue, 0, 200 );
1512 -
1513 - $this->get_logger()->info( 'Preparing to insert new record', [
1514 - 'plugin' => 'double-opt-in',
1515 - 'table' => $table,
1516 - 'data' => $log_data
1517 - ] );
1518 -
1519 - $result = $wpdb->insert( $table, $data );
1520 -
1521 - if ( false === $result ) {
1522 - $this->get_logger()->error( 'Insert operation failed', [
1523 - 'plugin' => 'double-opt-in',
1524 - 'mysql_error' => $wpdb->last_error
1525 - ] );
1526 -
1527 - $this->validateEncoding( $formValue );
1528 - $this->validateMaxAllowedPacket( $formValue );
1529 - $this->validateFormField();
1530 -
1531 562 return false;
1532 - }
1533 -
1534 - $this->get_logger()->notice( 'Insert operation completed', [
1535 - 'plugin' => 'double-opt-in',
1536 - 'result' => $result,
1537 - 'insert_id' => $wpdb->insert_id,
1538 - ] );
1539 -
1540 - if ( $result > 0 ) {
1541 - $this->id = $wpdb->insert_id;
1542 - $this->get_logger()->info( 'New record inserted, ID assigned', [ 'plugin' => 'double-opt-in', 'id' => $this->id ] );
1543 -
1544 - $hashResult = $this->createHash();
1545 - $this->get_logger()->info( 'Hash created for new record', [ 'plugin' => 'double-opt-in', 'hashResult' => $hashResult ] );
1546 -
1547 - return $hashResult;
1548 - }
1549 -
1550 - $this->get_logger()->error( 'Failed to insert new record' );
1551 -
1552 - return false;
563 + default:
564 + return false;
1553 565 }
566 + }
1554 567
1555 - private function validateFormField() {
1556 - $this->get_logger()->info( 'Validating database table field definitions.', [
1557 - 'plugin' => 'double-opt-in',
1558 - ] );
568 + // =========================================================================
569 + // PERSISTENCE
570 + // =========================================================================
1559 571
1560 - global $wpdb;
1561 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
572 + /**
573 + * Save the OptIn to the database.
574 + *
575 + * @return bool|int
576 + */
577 + public function save() {
578 + $this->logger->info( 'Saving OptIn', [
579 + 'plugin' => 'double-opt-in',
580 + 'id' => $this->entity->getId(),
581 + 'hash' => $this->entity->getHash(),
582 + ] );
1562 583
1563 - // Log the table being checked.
1564 - $this->get_logger()->debug( 'Checking fields for table: ' . $table, [
1565 - 'plugin' => 'double-opt-in',
1566 - ] );
584 + try {
585 + $repository = self::getRepository();
586 + $this->entity = $repository->save( $this->entity );
1567 587
1568 - // SQL-Anfrage, um die Felddefinitionen abzurufen
1569 - $columns = $wpdb->get_results( "SHOW FULL COLUMNS FROM $table" );
1570 -
1571 - if ( ! $columns ) {
1572 - $this->get_logger()->error( 'Failed to retrieve table field information.', [
1573 - 'plugin' => 'double-opt-in',
1574 - 'table' => $table,
1575 - 'db_error' => $wpdb->last_error,
1576 - ] );
1577 - return;
1578 - }
1579 -
1580 - // Überprüfung der Feldtypen
1581 - foreach ( $columns as $column ) {
1582 - $this->get_logger()->debug( 'Field Info', [
1583 - 'plugin' => 'double-opt-in',
1584 - 'Field' => $column->Field,
1585 - 'Type' => $column->Type,
1586 - 'Null' => $column->Null,
1587 - 'Default' => $column->Default,
1588 - 'Collation' => $column->Collation ?? 'N/A',
1589 - ] );
1590 - }
1591 -
1592 - $this->get_logger()->info( 'Database table field definitions validated successfully.', [
588 + $this->logger->info( 'OptIn saved successfully', [
1593 589 'plugin' => 'double-opt-in',
590 + 'id' => $this->entity->getId(),
591 + 'hash' => $this->entity->getHash(),
1594 592 ] );
1595 - }
1596 593
1597 - private function validateEncoding( string $data ) {
1598 - $this->get_logger()->info( 'Starting form data encoding validation.', [
1599 - 'plugin' => 'double-opt-in',
1600 - ] );
1601 -
1602 - if ( ! mb_check_encoding( $data, 'UTF-8' ) ) {
1603 - $this->get_logger()->warning( 'Encoding issue detected. Form data is not UTF-8.', [
1604 - 'plugin' => 'double-opt-in',
1605 - 'form_data' => substr( $data, 0, 500 ),
1606 - ] );
1607 - return false;
1608 - }
1609 -
1610 - $this->get_logger()->info( 'Form data encoding is valid (UTF-8).', [
1611 - 'plugin' => 'double-opt-in',
1612 - ] );
1613 -
1614 594 return true;
1615 - }
1616 -
1617 - private function validateMaxAllowedPacket( string $data ) {
1618 - $this->get_logger()->info( 'Starting validation of max_allowed_packet size.', [
595 + } catch ( \Exception $e ) {
596 + $this->logger->error( 'Failed to save OptIn', [
1619 597 'plugin' => 'double-opt-in',
598 + 'error' => $e->getMessage(),
1620 599 ] );
1621 -
1622 - $maxAllowedPacketMB = $this->getMaxAllowedPacket();
1623 - $dataSize = strlen( $data ); // Size in bytes
1624 - $dataSizeMB = $dataSize / ( 1024 * 1024 ); // Convert bytes to MB
1625 -
1626 - $this->get_logger()->debug( 'Data Size Check', [
1627 - 'plugin' => 'double-opt-in',
1628 - 'data_size_bytes' => $dataSize,
1629 - 'data_size_mb' => $dataSizeMB,
1630 - 'max_allowed_packet_mb' => $maxAllowedPacketMB
1631 - ] );
1632 -
1633 - if ( $dataSize > $maxAllowedPacketMB ) {
1634 - $this->get_logger()->warning( 'max_allowed_packet is likely too small.', [
1635 - 'plugin' => 'double-opt-in',
1636 - 'data_size_mb' => $dataSizeMB,
1637 - 'max_allowed_packet_mb' => $maxAllowedPacketMB,
1638 - 'recommendation' => 'Increase max_allowed_packet in your MySQL configuration.'
1639 - ] );
1640 - return false;
1641 - } else {
1642 - $this->get_logger()->info( 'max_allowed_packet is sufficient for the data size.', [
1643 - 'plugin' => 'double-opt-in',
1644 - ] );
1645 - return true;
1646 - }
1647 - }
1648 -
1649 - private function getMaxAllowedPacket() {
1650 - $this->get_logger()->info( 'Retrieving MySQL variable max_allowed_packet.', [
1651 - 'plugin' => 'double-opt-in',
1652 - ] );
1653 -
1654 - global $wpdb;
1655 -
1656 - $result = $wpdb->get_row( "SHOW VARIABLES LIKE 'max_allowed_packet'", OBJECT );
1657 -
1658 - /**
1659 - * Prüfen, ob die Variable erfolgreich abgerufen wurde
1660 - * Die zweite Spalte der Abfrage enthält den Wert (Value)
1661 - */
1662 - // Abfrage, um den aktuellen Wert von max_allowed_packet zu erhalten
1663 - if ( $result ) {
1664 - $maxAllowedPacket = (int) $result->Value; // Wert aus der zweiten Spalte
1665 - $maxAllowedPacketMB = $maxAllowedPacket / ( 1024 * 1024 ); // In MB
1666 -
1667 - $this->get_logger()->notice( 'MySQL max_allowed_packet value retrieved.', [
1668 - 'plugin' => 'double-opt-in',
1669 - 'max_allowed_packet_bytes' => $maxAllowedPacket,
1670 - 'max_allowed_packet_mb' => $maxAllowedPacketMB,
1671 - ] );
1672 - } else {
1673 - // Falls das Ergebnis leer ist (z. B. Fehler oder keine Berechtigung)
1674 - $this->get_logger()->error( 'Error fetching max_allowed_packet variable from MySQL.', [
1675 - 'plugin' => 'double-opt-in',
1676 - 'error' => $wpdb->last_error,
1677 - ] );
1678 -
1679 - return 0;
1680 - }
1681 -
1682 - return $maxAllowedPacket;
1683 - }
1684 -
1685 - /**
1686 - * Creates a unique hash code and adds it to the current OptIn Object
1687 - */
1688 - private function createHash() {
1689 - $this->get_logger()->info( 'Attempting to create and save a new hash for the opt-in entry.', [
1690 - 'plugin' => 'double-opt-in',
1691 - 'optin_id' => $this->get_id(),
1692 - ] );
1693 -
1694 - global $wpdb;
1695 -
1696 - if ( null == $wpdb ) {
1697 - $this->get_logger()->critical( 'WordPress database object ($wpdb) is not available.', [
1698 - 'plugin' => 'double-opt-in',
1699 - ] );
1700 - return false;
1701 - }
1702 -
1703 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1704 -
1705 - // Log the components used for hash generation.
1706 - $hash_components = [
1707 - 'createtime' => $this->get_createtime(),
1708 - 'id' => $this->get_id(),
1709 - 'cf_form_id' => $this->get_cf_form_id(),
1710 - ];
1711 - $this->get_logger()->debug( 'Components for hash generation.', [
1712 - 'plugin' => 'double-opt-in',
1713 - 'components' => $hash_components,
1714 - ] );
1715 -
1716 - // add the hash to the element
1717 - $this->hash = base64_encode( $this->get_createtime() . $this->get_id() . $this->get_cf_form_id() );
1718 -
1719 - $this->get_logger()->info( 'Generated new hash: ' . $this->hash, [
1720 - 'plugin' => 'double-opt-in',
1721 - 'new_hash' => $this->hash,
1722 - ] );
1723 -
1724 - $query_result = $wpdb->update( $table, [
1725 - 'hash' => $this->hash,
1726 - ], [
1727 - 'id' => $this->get_id()
1728 - ] );
1729 -
1730 - if ( $query_result === false ) {
1731 - $this->get_logger()->error( 'Failed to update the database with the new hash.', [
1732 - 'plugin' => 'double-opt-in',
1733 - 'optin_id' => $this->get_id(),
1734 - 'db_error' => $wpdb->last_error,
1735 - ] );
1736 - return false;
1737 - } else {
1738 - $this->get_logger()->notice( 'Hash successfully saved to the database.', [
1739 - 'plugin' => 'double-opt-in',
1740 - 'optin_id' => $this->get_id(),
1741 - 'rows_affected' => $query_result,
1742 - ] );
1743 - }
1744 -
1745 - return $query_result;
1746 - }
1747 -
1748 - private function is_base64_encoded( $string ) {
1749 - $this->get_logger()->info( 'Checking if a string is Base64 encoded.', [
1750 - 'plugin' => 'double-opt-in',
1751 - 'string' => substr($string, 0, 50), // Log only a portion of the string for security and brevity
1752 - ] );
1753 -
1754 - // Prüfen, ob der String Base64-kompatible Zeichen enthält
1755 - if ( preg_match( '/^[A-Za-z0-9+\/=]*$/', $string ) ) {
1756 - $this->get_logger()->debug( 'String contains valid Base64 characters.', [
1757 - 'plugin' => 'double-opt-in',
1758 - ] );
1759 -
1760 - // Prüfen, ob die Länge des Strings ein Vielfaches von 4 ist
1761 - $is_multiple_of_4 = (strlen( $string ) % 4 === 0);
1762 -
1763 - if ($is_multiple_of_4) {
1764 - $this->get_logger()->info( 'String is a valid Base64 encoded string.', [
1765 - 'plugin' => 'double-opt-in',
1766 - ] );
1767 - } else {
1768 - $this->get_logger()->warning( 'String contains valid Base64 characters but its length is not a multiple of 4.', [
1769 - 'plugin' => 'double-opt-in',
1770 - 'length' => strlen($string),
1771 - ] );
1772 - }
1773 -
1774 - return $is_multiple_of_4;
1775 - }
1776 -
1777 - $this->get_logger()->warning( 'String contains invalid characters for Base64 encoding.', [
1778 - 'plugin' => 'double-opt-in',
1779 - ] );
1780 -
1781 600 return false;
1782 601 }
602 + }
1783 603
1784 - /**
1785 - * Return the Form
1786 - *
1787 - * @return string
1788 - */
1789 - public function get_form( $encoded = false ) {
1790 - $this->get_logger()->info( 'Retrieving form data.', [
1791 - 'plugin' => 'double-opt-in',
1792 - 'requested_format' => $encoded ? 'encoded' : 'decoded',
1793 - ] );
604 + // =========================================================================
605 + // LINK GENERATION
606 + // =========================================================================
1794 607
1795 - if ( ! $encoded ) {
1796 - $this->get_logger()->debug( 'Requested decoded form data. Checking if current data is encoded.', [
1797 - 'plugin' => 'double-opt-in',
1798 - ] );
1799 - if ( $this->is_base64_encoded( $this->form ) ) {
1800 - $this->get_logger()->notice( 'Form data is Base64 encoded. Decoding before returning.', [
1801 - 'plugin' => 'double-opt-in',
1802 - ] );
1803 - return base64_decode( $this->form );
1804 - } else {
1805 - $this->get_logger()->info( 'Form data is not encoded. Returning raw data.', [
1806 - 'plugin' => 'double-opt-in',
1807 - ] );
1808 - return $this->form;
1809 - }
1810 - } else {
1811 - $this->get_logger()->debug( 'Requested encoded form data. Checking if current data is already encoded.', [
1812 - 'plugin' => 'double-opt-in',
1813 - ] );
1814 - if ( $this->is_base64_encoded( $this->form ) ) {
1815 - $this->get_logger()->info( 'Form data is already Base64 encoded. Returning as is.', [
1816 - 'plugin' => 'double-opt-in',
1817 - ] );
1818 - return $this->form;
1819 - } else {
1820 - $this->get_logger()->notice( 'Form data is not encoded. Encoding before returning.', [
1821 - 'plugin' => 'double-opt-in',
1822 - ] );
1823 - return base64_encode( $this->form );
1824 - }
1825 - }
1826 - }
608 + /**
609 + * Get the opt-in confirmation link.
610 + *
611 + * @param array $parameter Additional parameters.
612 + * @param int $formId Optional form ID override.
613 + *
614 + * @return string
615 + */
616 + public function get_link_optin( array $parameter = [], int $formId = 0 ): string {
617 + $formId = $formId > 0 ? $formId : $this->get_cf_form_id();
1827 618
1828 - /**
1829 - * Returns the Name of the form if available
1830 - *
1831 - * @return string The name of the Form
1832 - * @since 2.4.0
1833 - */
1834 - public function get_form_name() {
1835 - $this->get_logger()->info( 'Retrieving the form name.', [
1836 - 'plugin' => 'double-opt-in',
1837 - ] );
619 + $formParameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
620 + $pageId = (int) ( $formParameter['page'] ?? 0 );
1838 621
1839 - $post = get_post( $this->get_cf_form_id() );
1840 -
1841 - if ( null === $post ) {
1842 - $this->get_logger()->warning( 'Post with ID ' . $this->get_cf_form_id() . ' not found. Returning "Deleted".', [
1843 - 'plugin' => 'double-opt-in',
1844 - 'form_id' => $this->get_cf_form_id(),
1845 - ] );
1846 - return __( 'Deleted', 'double-opt-in' );
1847 - }
1848 -
1849 - $form_name = $post->post_title;
1850 -
1851 - $this->get_logger()->notice( 'Form name successfully retrieved: ' . $form_name, [
1852 - 'plugin' => 'double-opt-in',
1853 - 'form_id' => $this->get_cf_form_id(),
1854 - 'form_name' => $form_name,
1855 - ] );
1856 -
1857 - return $form_name;
622 + if ( $pageId <= 0 ) {
623 + return home_url( '?optin=' . $this->get_hash() );
1858 624 }
1859 625
1860 - /**
1861 - * Returns the URL to the form depending on the Form Type.
1862 - *
1863 - * @return string The Backend URL to the Form, empty if the post is not found.
1864 - * @since 2.4.0
1865 - *
1866 - */
1867 - public function get_form_link() {
1868 - $this->get_logger()->info( 'Attempting to retrieve the form\'s backend edit link.', [
1869 - 'plugin' => 'double-opt-in',
1870 - 'form_id' => $this->get_cf_form_id(),
1871 - ] );
1872 -
1873 - if ( $this->isTypeElementor() ) {
1874 - $link = get_edit_post_link( $this->get_cf_form_id() );
1875 - $this->get_logger()->notice( 'Form is Elementor type. Returning post edit link.', [
1876 - 'plugin' => 'double-opt-in',
1877 - 'link' => $link,
1878 - ] );
1879 - return $link;
1880 - } elseif ( $this->isTypeAvada() ) {
1881 - $link = get_edit_post_link( $this->get_cf_form_id() );
1882 - $this->get_logger()->notice( 'Form is Avada type. Returning post edit link.', [
1883 - 'plugin' => 'double-opt-in',
1884 - 'link' => $link,
1885 - ] );
1886 - return $link;
1887 - } elseif ( $this->isTypeCF7() ) {
1888 - $this->get_logger()->debug( 'Form is CF7 type. Retrieving post object for link generation.', [
1889 - 'plugin' => 'double-opt-in',
1890 - ] );
1891 - $post = get_post( $this->get_cf_form_id() );
1892 -
1893 - if ( ! $post ) {
1894 - $this->get_logger()->warning( 'Post object not found for CF7 form ID. Cannot generate a link.', [
1895 - 'plugin' => 'double-opt-in',
1896 - 'form_id' => $this->get_cf_form_id(),
1897 - ] );
1898 - return '';
1899 - }
1900 -
1901 - $link = admin_url( 'admin.php?page=wpcf7&post=' . esc_attr( $this->get_cf_form_id() ) . '&action=edit' );
1902 - $this->get_logger()->notice( 'Generated CF7 form edit link.', [
1903 - 'plugin' => 'double-opt-in',
1904 - 'link' => $link,
1905 - ] );
1906 - return $link;
1907 - }
1908 -
1909 - $this->get_logger()->warning( 'Form type could not be identified. No link generated.', [
1910 - 'plugin' => 'double-opt-in',
1911 - ] );
1912 - return '';
626 + $pageUrl = get_permalink( $pageId );
627 + if ( ! $pageUrl ) {
628 + return home_url( '?optin=' . $this->get_hash() );
1913 629 }
1914 630
631 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
1915 632
1916 - /**
1917 - * Retrieves the form settings.
1918 - *
1919 - * If the form is created using Elementor, retrieves the settings using the `get_settings_by_elementor()`
1920 - * method.
1921 - *
1922 - * @return array The form settings.
1923 - * @since 2.4.0
1924 - *
1925 - */
1926 - public function get_form_settings() {
1927 - $this->get_logger()->info( 'Attempting to retrieve form settings based on form type.', [
1928 - 'plugin' => 'double-opt-in',
1929 - 'form_id' => $this->get_cf_form_id(),
1930 - ] );
633 + return $pageUrl . $separator . 'optin=' . $this->get_hash();
634 + }
1931 635
1932 - if ( $this->isTypeElementor() ) {
1933 - $this->get_logger()->debug( 'Form identified as Elementor. Retrieving settings via get_settings_by_elementor().', [
1934 - 'plugin' => 'double-opt-in',
1935 - ] );
1936 - return $this->get_settings_by_elementor();
1937 - } else if ( $this->isTypeCF7() ) {
1938 - $this->get_logger()->debug( 'Form identified as CF7. Retrieving settings via get_settings_by_cf7().', [
1939 - 'plugin' => 'double-opt-in',
1940 - ] );
1941 - return $this->get_settings_by_cf7();
1942 - } else if ( $this->isTypeAvada() ) {
1943 - $this->get_logger()->debug( 'Form identified as Avada. Retrieving settings via get_settings_by_avada().', [
1944 - 'plugin' => 'double-opt-in',
1945 - ] );
1946 - return $this->get_settings_by_avada();
1947 - }
636 + /**
637 + * Get the opt-out link.
638 + *
639 + * @return string
640 + */
641 + public function get_link_optout(): string {
642 + $settings = CF7DoubleOptIn::getInstance()->getSettings();
643 + $pageId = (int) ( $settings['optout_page'] ?? 0 );
1948 644
1949 - $this->get_logger()->warning( 'Form type could not be identified. Returning an empty array.', [
1950 - 'plugin' => 'double-opt-in',
1951 - 'form_id' => $this->get_cf_form_id(),
1952 - ] );
1953 -
1954 - return [];
645 + if ( $pageId <= 0 ) {
646 + return home_url( '?optout=' . $this->get_hash() );
1955 647 }
1956 648
1957 - /**
1958 - * Retrieves the settings of a specific element using avada.
1959 - *
1960 - * @return array
1961 - * @since 2.4.0
1962 - *
1963 - * @see OptIn::maybe_normalize_settings() for details
1964 - */
1965 - private function get_settings_by_avada() {
1966 - $this->get_logger()->info( 'Retrieving settings for an Avada Fusion form.', [
1967 - 'plugin' => 'double-opt-in',
1968 - ] );
1969 -
1970 - $form_id = $this->get_cf_form_id();
1971 - $this->get_logger()->debug( 'Avada form ID: ' . $form_id, [
1972 - 'plugin' => 'double-opt-in',
1973 - 'form_id' => $form_id,
1974 - ] );
1975 -
1976 - $settings = CF7DoubleOptIn::getInstance()->getParameter( $form_id );
1977 -
1978 - if ( empty($settings) ) {
1979 - $this->get_logger()->warning( 'No settings found for Avada form ID ' . $form_id . '.', [
1980 - 'plugin' => 'double-opt-in',
1981 - 'form_id' => $form_id,
1982 - ] );
1983 - } else {
1984 - $this->get_logger()->debug( 'Settings retrieved for Avada form.', [
1985 - 'plugin' => 'double-opt-in',
1986 - 'settings' => $settings,
1987 - ] );
1988 - }
1989 -
1990 - $normalized_settings = $this->maybe_normalize_settings( $settings );
1991 -
1992 - $this->get_logger()->notice( 'Avada form settings retrieved and normalized successfully.', [
1993 - 'plugin' => 'double-opt-in',
1994 - ] );
1995 -
1996 - return $normalized_settings;
649 + $pageUrl = get_permalink( $pageId );
650 + if ( ! $pageUrl ) {
651 + return home_url( '?optout=' . $this->get_hash() );
1997 652 }
1998 653
1999 - /**
2000 - * Retrieves the settings of a specific element using cf7.
2001 - *
2002 - * @return array
2003 - * @since 2.4.0
2004 - *
2005 - * @see OptIn::maybe_normalize_settings() for details
2006 - */
2007 - private function get_settings_by_cf7() {
2008 - $this->get_logger()->info( 'Retrieving settings for a Contact Form 7 form.', [
2009 - 'plugin' => 'double-opt-in',
2010 - ] );
654 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
2011 655
2012 - $form_id = $this->get_cf_form_id();
2013 - $this->get_logger()->debug( 'Contact Form 7 ID: ' . $form_id, [
2014 - 'plugin' => 'double-opt-in',
2015 - 'form_id' => $form_id,
2016 - ] );
656 + return $pageUrl . $separator . 'optout=' . $this->get_hash();
657 + }
2017 658
2018 - $settings = get_post_meta( $form_id, 'f12-cf7-doubleoptin', true );
659 + /**
660 + * Get the UI edit link.
661 + *
662 + * @return string
663 + */
664 + public function get_link_ui(): string {
665 + return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&view=single&hash=' . $this->get_hash() );
666 + }
2019 667
2020 - if ( null == $settings || ! is_array( $settings ) ) {
2021 - $this->get_logger()->warning( 'No valid settings found for Contact Form 7 ID ' . $form_id . '. Post meta is empty or not an array.', [
2022 - 'plugin' => 'double-opt-in',
2023 - 'form_id' => $form_id,
2024 - ] );
2025 - return [];
2026 - }
668 + /**
669 + * Get the delete link.
670 + *
671 + * @return string
672 + */
673 + public function get_link_delete(): string {
674 + $nonce = wp_create_nonce( 'delete_optin_' . $this->get_hash() );
675 + return admin_url( 'admin.php?page=' . FORGE12_OPTIN_SLUG . '&action=delete&hash=' . $this->get_hash() . '&_wpnonce=' . $nonce );
676 + }
2027 677
2028 - $this->get_logger()->debug( 'Raw settings retrieved for CF7 form.', [
2029 - 'plugin' => 'double-opt-in',
2030 - 'settings' => $settings,
2031 - ] );
678 + /**
679 + * Get the form name.
680 + *
681 + * @return string
682 + */
683 + public function get_form_name(): string {
684 + $formId = $this->get_cf_form_id();
2032 685
2033 - $settings = $this->maybe_normalize_settings( $settings );
2034 -
2035 - $this->get_logger()->notice( 'Contact Form 7 settings retrieved and normalized successfully.', [
2036 - 'plugin' => 'double-opt-in',
2037 - ] );
2038 -
2039 - return $settings;
686 + if ( $formId <= 0 ) {
687 + return __( 'Unknown', 'double-opt-in' );
2040 688 }
2041 689
2042 - /**
2043 - * Retrieves the settings of a specific element using Elementor.
2044 - *
2045 - * @return array
2046 - * @since 2.4.0
2047 - *
2048 - * @see OptIn::maybe_normalize_settings() for details
2049 - */
2050 - private function get_settings_by_elementor() {
2051 - $this->get_logger()->info( 'Retrieving settings for an Elementor form.', [
2052 - 'plugin' => 'double-opt-in',
2053 - ] );
2054 -
2055 - $page_id = $this->get_cf_form_id();
2056 - $this->get_logger()->debug( 'Elementor form page ID: ' . $page_id, [
2057 - 'plugin' => 'double-opt-in',
2058 - 'page_id' => $page_id,
2059 - ] );
2060 -
2061 - $post = get_post( $page_id );
2062 -
2063 - // skip if post not existing anymore
2064 - if ( null === $post ) {
2065 - $this->get_logger()->warning( 'Post with ID ' . $page_id . ' not found. Cannot retrieve Elementor settings.', [
2066 - 'plugin' => 'double-opt-in',
2067 - 'page_id' => $page_id,
2068 - ] );
2069 - return [];
2070 - }
2071 -
2072 - // skip if page is not built with elementor
2073 - if ( ! \Elementor\Plugin::$instance->documents->get( $page_id )->is_built_with_elementor() ) {
2074 - $this->get_logger()->warning( 'Page ID ' . $page_id . ' is not an Elementor page. Cannot retrieve settings.', [
2075 - 'plugin' => 'double-opt-in',
2076 - 'page_id' => $page_id,
2077 - ] );
2078 - return [];
2079 - }
2080 -
2081 - // Load the page's data
2082 - $document = \Elementor\Plugin::$instance->documents->get( $page_id );
2083 -
2084 - // Skip if document not exist
2085 - if ( ! $document ) {
2086 - $this->get_logger()->error( 'Elementor document not found for page ID ' . $page_id . '.', [
2087 - 'plugin' => 'double-opt-in',
2088 - 'page_id' => $page_id,
2089 - ] );
2090 - return [];
2091 - }
2092 -
2093 - $this->get_logger()->debug( 'Elementor document successfully retrieved.', [
2094 - 'plugin' => 'double-opt-in',
2095 - ] );
2096 -
2097 - // Loop through the elements
2098 - $elements_data = $document->get_elements_data();
2099 - $content = maybe_unserialize( $this->get_content() );
2100 -
2101 - // skip if form id not available
2102 - if ( ! isset( $content['form_id'] ) ) {
2103 - $this->get_logger()->warning( 'Form ID not found in serialized content. Cannot retrieve Elementor form settings.', [
2104 - 'plugin' => 'double-opt-in',
2105 - ] );
2106 - return [];
2107 - }
2108 -
2109 - $form_id = $content['form_id'];
2110 - $form_element = null;
2111 - $this->get_logger()->debug( 'Searching for form element with ID: ' . $form_id, [
2112 - 'plugin' => 'double-opt-in',
2113 - 'form_element_id' => $form_id,
2114 - ] );
2115 -
2116 - // Find the Form Element
2117 - foreach ( $elements_data as $element ) {
2118 - if ( isset( $element['elements'] ) ) {
2119 - foreach ( $element['elements'] as $el ) {
2120 - if ( $form_id !== $el['id'] || 'form' !== $el['widgetType'] ) {
2121 - continue;
2122 - }
2123 -
2124 - $form_element = $el;
2125 - $this->get_logger()->debug( 'Form element found in a nested section.', [
2126 - 'plugin' => 'double-opt-in',
2127 - 'element_id' => $el['id'],
2128 - ] );
2129 - }
2130 - }
2131 -
2132 - // Check if this is the form widget
2133 - if ( isset( $element['widgetType'] ) && isset( $element['id'] ) && $element['widgetType'] === 'form' && $element['id'] === $form_id ) {
2134 - // Return the form settings
2135 - $form_element = $element;
2136 - $this->get_logger()->debug( 'Form element found at the top level.', [
2137 - 'plugin' => 'double-opt-in',
2138 - 'element_id' => $element['id'],
2139 - ] );
2140 - }
2141 - }
2142 -
2143 - // skip if form element not found on the page anymore
2144 - if ( $form_element == null || ! isset( $form_element['settings'] ) ) {
2145 - $this->get_logger()->error( 'Form element with ID ' . $form_id . ' or its settings were not found on the Elementor page. Returning empty array.', [
2146 - 'plugin' => 'double-opt-in',
2147 - 'form_element_id' => $form_id,
2148 - ] );
2149 - return [];
2150 - }
2151 -
2152 - // Normalize Settings
2153 - $this->get_logger()->info( 'Form settings found. Normalizing and returning.', [
2154 - 'plugin' => 'double-opt-in',
2155 - ] );
2156 - return $this->maybe_normalize_settings( $form_element['settings'] );
690 + $post = get_post( $formId );
691 + if ( ! $post ) {
692 + return __( 'Deleted Form', 'double-opt-in' ) . ' (ID: ' . $formId . ')';
2157 693 }
2158 694
2159 - /**
2160 - * Normalizes the settings by converting the value of the settings
2161 - *
2162 - * @param array $settings The settings array to be normalized.
2163 - * @formatter:off
2164 - *
2165 - * @return array {
2166 - * The settings of the element if found or an empty array if nothing was found.
2167 - * @type string $doi_email_to
2168 - * @type string $doi_email_subject
2169 - * @type string $doi_email_content
2170 - * @type string $doi_email_from
2171 - * @type string $doi_email_from_name
2172 - * @type string $doi_email_template
2173 - * @type string $doi_email_body
2174 - * }
2175 - *
2176 - * @formatter:on
2177 - */
2178 - private function maybe_normalize_settings( $settings ) {
2179 - $this->get_logger()->info( 'Starting to normalize form settings.', [
2180 - 'plugin' => 'double-opt-in',
2181 - ] );
695 + return $post->post_title ?: __( 'Untitled Form', 'double-opt-in' );
696 + }
2182 697
2183 - // Log the initial settings to see what we're working with
2184 - $this->get_logger()->debug( 'Original settings array.', [
2185 - 'plugin' => 'double-opt-in',
2186 - 'settings' => $settings,
2187 - ] );
698 + /**
699 + * Get the form edit link.
700 + *
701 + * @return string
702 + */
703 + public function get_form_link(): string {
704 + $formId = $this->get_cf_form_id();
705 + $postType = get_post_type( $formId );
2188 706
2189 - if ( isset( $settings['sender'] ) ) {
2190 - $settings['doi_email_from'] = $settings['sender'];
2191 - $this->get_logger()->debug( 'Normalized "sender" to "doi_email_from".', [
2192 - 'plugin' => 'double-opt-in',
2193 - ] );
2194 - }
2195 -
2196 - if ( isset( $settings['sender_name'] ) ) {
2197 - $settings['doi_email_from_name'] = $settings['sender_name'];
2198 - $this->get_logger()->debug( 'Normalized "sender_name" to "doi_email_from_name".', [
2199 - 'plugin' => 'double-opt-in',
2200 - ] );
2201 - }
2202 -
2203 - if ( isset( $settings['subject'] ) ) {
2204 - $settings['doi_email_subject'] = $settings['subject'];
2205 - $this->get_logger()->debug( 'Normalized "subject" to "doi_email_subject".', [
2206 - 'plugin' => 'double-opt-in',
2207 - ] );
2208 - }
2209 -
2210 - if ( isset( $settings['recipient'] ) ) {
2211 - $settings['doi_email_to'] = $settings['recipient'];
2212 - $this->get_logger()->debug( 'Normalized "recipient" to "doi_email_to".', [
2213 - 'plugin' => 'double-opt-in',
2214 - ] );
2215 - }
2216 -
2217 - if ( isset( $settings['template'] ) ) {
2218 - $settings['doi_email_template'] = $settings['template'];
2219 - $this->get_logger()->debug( 'Normalized "template" to "doi_email_template".', [
2220 - 'plugin' => 'double-opt-in',
2221 - ] );
2222 - }
2223 -
2224 - if ( isset( $settings['body'] ) ) {
2225 - $settings['doi_email_body'] = $settings['body'];
2226 - $this->get_logger()->debug( 'Normalized "body" to "doi_email_body".', [
2227 - 'plugin' => 'double-opt-in',
2228 - ] );
2229 - }
2230 -
2231 - // Log the settings after initial normalization
2232 - $this->get_logger()->debug( 'Settings after internal normalization.', [
2233 - 'plugin' => 'double-opt-in',
2234 - 'settings' => $settings,
2235 - ] );
2236 -
2237 - /**
2238 - * Normalize the settings between the different form plugins
2239 - *
2240 - * @formatter:off
2241 - *
2242 - * @param array {
2243 - * @type string $doi_email_to
2244 - * @type string $doi_email_subject
2245 - * @type string $doi_email_content
2246 - * @type string $doi_email_from
2247 - * @type string $doi_email_from_name
2248 - * @type string $doi_email_template
2249 - * @type string $doi_email_body
2250 - * }
2251 - *
2252 - * @formatter:on
2253 - *
2254 - * @since 3.0.0
2255 - */
2256 - $this->get_logger()->info( 'Applying WordPress filter f12_cf7_doubleoption_normalize_settings.', [
2257 - 'plugin' => 'double-opt-in',
2258 - ] );
2259 -
2260 - $normalized_settings = apply_filters( 'f12_cf7_doubleoption_normalize_settings', $settings );
2261 -
2262 - $this->get_logger()->notice( 'Settings normalization completed successfully.', [
2263 - 'plugin' => 'double-opt-in',
2264 - 'final_settings' => $normalized_settings,
2265 - ] );
2266 -
2267 - return $normalized_settings;
707 + if ( $postType === 'wpcf7_contact_form' ) {
708 + return admin_url( 'admin.php?page=wpcf7&post=' . $formId . '&action=edit' );
2268 709 }
2269 710
2270 - /**
2271 - * Set the Form as HTML
2272 - *
2273 - * @param string $form
2274 - *
2275 - * @return void
2276 - */
2277 - public function set_form( $form ) {
2278 - $this->form = $form;
711 + if ( $postType === 'fusion_form' ) {
712 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
2279 713 }
2280 714
2281 - /**
2282 - * Return the Form
2283 - *
2284 - * @return string
2285 - */
2286 - public function get_mail_optin() {
2287 - return $this->mail_optin;
2288 - }
715 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
716 + }
2289 717
2290 - /**
2291 - * Return the E-mail
2292 - *
2293 - * @return string
2294 - */
2295 - public function get_email() {
2296 - return $this->email;
2297 - }
718 + /**
719 + * Get form settings.
720 + *
721 + * @return array
722 + */
723 + public function get_form_settings(): array {
724 + return CF7DoubleOptIn::getInstance()->getParameter( $this->get_cf_form_id() );
725 + }
2298 726
2299 - /**
2300 - * Store the email
2301 - *
2302 - * @param string $email
2303 - *
2304 - * @return void
2305 - */
2306 - public function set_email( $email ) {
2307 - $this->email = $email;
2308 - }
727 + // =========================================================================
728 + // HELPERS
729 + // =========================================================================
2309 730
2310 - /**
2311 - * Set the Form as HTML
2312 - *
2313 - * @param string $form
2314 - *
2315 - * @return void
2316 - */
2317 - public function set_mail_optin( $mail_html ) {
2318 - $this->mail_optin = $mail_html;
2319 - }
731 + /**
732 + * Get the repository instance.
733 + *
734 + * @return OptInRepositoryInterface
735 + */
736 + private static function getRepository(): OptInRepositoryInterface {
737 + $container = Container::getInstance();
738 + return $container->get( OptInRepositoryInterface::class );
739 + }
2320 740
2321 - /**
2322 - * Return the link to the detail view
2323 - *
2324 - * @return string
2325 - */
2326 - public function get_link_ui() {
2327 - $this->get_logger()->info( 'Generating admin UI link for opt-in entry.', [
2328 - 'plugin' => 'double-opt-in',
2329 - 'optin_id' => $this->get_id(),
2330 - ] );
741 + /**
742 + * Map legacy property names to entity array keys.
743 + *
744 + * @param array $properties Legacy properties.
745 + *
746 + * @return array
747 + */
748 + private function mapToEntityArray( array $properties ): array {
749 + $mapping = [
750 + 'cf_form_id' => 'cf_form_id',
751 + 'doubleoptin' => 'doubleoptin',
752 + 'content' => 'content',
753 + 'hash' => 'hash',
754 + 'createtime' => 'createtime',
755 + 'updatetime' => 'updatetime',
756 + 'optouttime' => 'optouttime',
757 + 'ipaddr_register' => 'ipaddr_register',
758 + 'ipaddr_confirmation' => 'ipaddr_confirmation',
759 + 'ipaddr_optout' => 'ipaddr_optout',
760 + 'files' => 'files',
761 + 'category' => 'category',
762 + 'form' => 'form',
763 + 'mail_optin' => 'mail_optin',
764 + 'email' => 'email',
765 + 'id' => 'id',
766 + 'consent_text' => 'consent_text',
767 + 'consent_field' => 'consent_field',
768 + ];
2331 769
2332 - $hash = $this->get_hash();
2333 - if ( empty( $hash ) ) {
2334 - $this->get_logger()->warning( 'Hash is not set. Cannot generate UI link.', [
2335 - 'plugin' => 'double-opt-in',
2336 - 'optin_id' => $this->get_id(),
2337 - ] );
2338 - return '';
770 + $result = [];
771 + foreach ( $mapping as $legacy => $entity ) {
772 + if ( isset( $properties[ $legacy ] ) ) {
773 + $result[ $entity ] = $properties[ $legacy ];
2339 774 }
2340 -
2341 - $link = admin_url( 'admin.php?page=f12-cf7-doubleoptin_optin_view&hash=' . $hash );
2342 -
2343 - $this->get_logger()->notice( 'Successfully generated UI link.', [
2344 - 'plugin' => 'double-opt-in',
2345 - 'optin_id' => $this->get_id(),
2346 - 'link' => $link,
2347 - ] );
2348 -
2349 - return $link;
2350 775 }
2351 776
2352 - /**
2353 - * Return the link to delete
2354 - *
2355 - * @return string
2356 - */
2357 - public function get_link_delete() {
2358 - $this->get_logger()->info( 'Generating admin UI link to delete an opt-in entry.', [
2359 - 'plugin' => 'double-opt-in',
2360 - 'optin_id' => $this->get_id(),
2361 - ] );
2362 -
2363 - $hash = $this->get_hash();
2364 - if ( empty( $hash ) ) {
2365 - $this->get_logger()->warning( 'Hash is not set. Cannot generate delete link.', [
2366 - 'plugin' => 'double-opt-in',
2367 - 'optin_id' => $this->get_id(),
2368 - ] );
2369 - return '';
2370 - }
2371 -
2372 - $link = admin_url( 'admin.php?page=f12-cf7-doubleoptin&option=delete&hash=' . $hash );
2373 -
2374 - $this->get_logger()->notice( 'Successfully generated delete link.', [
2375 - 'plugin' => 'double-opt-in',
2376 - 'optin_id' => $this->get_id(),
2377 - 'link' => $link,
2378 - ] );
2379 -
2380 - return $link;
2381 - }
2382 -
2383 - /**
2384 - * Return the link to the export
2385 - *
2386 - * @return string
2387 - */
2388 - public function get_link_export() {
2389 - $this->get_logger()->info( 'Generating admin UI link for CSV export of a single opt-in entry.', [
2390 - 'plugin' => 'double-opt-in',
2391 - 'optin_id' => $this->get_id(),
2392 - ] );
2393 -
2394 - $id = $this->get_id();
2395 - if ( empty( $id ) ) {
2396 - $this->get_logger()->warning( 'Opt-in ID is not set. Cannot generate export link.', [
2397 - 'plugin' => 'double-opt-in',
2398 - ] );
2399 - return '';
2400 - }
2401 -
2402 - $link = admin_url( 'admin.php?page=f12-cf7-doubleoptin&export=csv&id=' . $id );
2403 -
2404 - $this->get_logger()->notice( 'Successfully generated export link for entry ID ' . $id . '.', [
2405 - 'plugin' => 'double-opt-in',
2406 - 'optin_id' => $id,
2407 - 'link' => $link,
2408 - ] );
2409 -
2410 - return $link;
2411 - }
2412 -
2413 - /**
2414 - * Return the link to the export
2415 - *
2416 - * @return string
2417 - */
2418 - public function get_link_export_v2() {
2419 - $this->get_logger()->info( 'Generating admin UI link for v2 CSV export of a single opt-in entry.', [
2420 - 'plugin' => 'double-opt-in',
2421 - 'optin_id' => $this->get_id(),
2422 - ] );
2423 -
2424 - $id = $this->get_id();
2425 - if ( empty( $id ) ) {
2426 - $this->get_logger()->warning( 'Opt-in ID is not set. Cannot generate v2 export link.', [
2427 - 'plugin' => 'double-opt-in',
2428 - ] );
2429 - return '';
2430 - }
2431 -
2432 - $link = admin_url( 'admin.php?page=f12-cf7-doubleoptin&export=csv_v2&id=' . $id );
2433 -
2434 - $this->get_logger()->notice( 'Successfully generated v2 export link for entry ID ' . $id . '.', [
2435 - 'plugin' => 'double-opt-in',
2436 - 'optin_id' => $id,
2437 - 'link' => $link,
2438 - ] );
2439 -
2440 - return $link;
2441 - }
2442 -
2443 - /**
2444 - * Return the link to the optin
2445 - *
2446 - * @return string
2447 - */
2448 - public function get_link_optin( $parameter = [], $form_id = 0 ) {
2449 - $this->get_logger()->info( 'Generating opt-in confirmation link.', [
2450 - 'plugin' => 'double-opt-in',
2451 - 'current_id' => $this->get_id(),
2452 - 'provided_form_id' => $form_id,
2453 - ] );
2454 -
2455 - // Determine the correct form_id to use
2456 - if ( $form_id == 0 ) {
2457 - $form_id = $this->get_cf_form_id();
2458 - $this->get_logger()->debug( 'No form ID provided. Using object\'s form ID: ' . $form_id, [
2459 - 'plugin' => 'double-opt-in',
2460 - ] );
2461 - }
2462 -
2463 - // Handle case where form_id is still 0
2464 - if ( $form_id == 0 ) {
2465 - $link = get_home_url() . '?optin=' . $this->get_hash();
2466 - $this->get_logger()->warning( 'Form ID is 0. Returning a generic homepage opt-in link.', [
2467 - 'plugin' => 'double-opt-in',
2468 - 'generated_link' => $link,
2469 - ] );
2470 - return $link;
2471 - }
2472 -
2473 - // Merge parameters
2474 - $retrieved_params = CF7DoubleOptIn::getInstance()->getParameter( $this->get_cf_form_id() );
2475 - $this->get_logger()->debug( 'Retrieved parameters for form: ' . json_encode($retrieved_params), [
2476 - 'plugin' => 'double-opt-in',
2477 - ] );
2478 - $parameter = array_merge( $retrieved_params, $parameter );
2479 -
2480 - // Apply filter
2481 - $this->get_logger()->info( 'Applying filter f12_cf7_doubleoptin_get_parameter_postmeta.', [
2482 - 'plugin' => 'double-opt-in',
2483 - ] );
2484 - $parameter = apply_filters( 'f12_cf7_doubleoptin_get_parameter_postmeta', $parameter, $form_id, $this );
2485 -
2486 - $page_id = $parameter['page'] ?? -1;
2487 -
2488 - // Check if a specific page is set
2489 - if ( $page_id == -1 || ! $page_id || $page_id == 0 ) {
2490 - $link = get_home_url() . '?optin=' . $this->get_hash();
2491 - $this->get_logger()->notice( 'No specific opt-in page defined. Generating homepage link.', [
2492 - 'plugin' => 'double-opt-in',
2493 - 'generated_link' => $link,
2494 - ] );
2495 - } else {
2496 - $link = get_permalink( $page_id );
2497 - $this->get_logger()->debug( 'Found specific opt-in page with permalink: ' . $link, [
2498 - 'plugin' => 'double-opt-in',
2499 - 'page_id' => $page_id,
2500 - ] );
2501 -
2502 - // Append the opt-in hash to the link
2503 - if ( strpos( $link, "?" ) !== false ) {
2504 - $link .= '&optin=' . $this->get_hash();
2505 - } else {
2506 - $link .= '?optin=' . $this->get_hash();
2507 - }
2508 -
2509 - $this->get_logger()->notice( 'Generated permalink-based opt-in link.', [
2510 - 'plugin' => 'double-opt-in',
2511 - 'generated_link' => $link,
2512 - ] );
2513 - }
2514 -
2515 - return $link;
2516 - }
2517 -
2518 - /**
2519 - * Return the link to the optout
2520 - *
2521 - * @return string
2522 - */
2523 - public function get_link_optout() {
2524 - $this->get_logger()->info( 'Generating opt-out link.', [
2525 - 'plugin' => 'double-opt-in',
2526 - 'optin_id' => $this->get_id(),
2527 - ] );
2528 -
2529 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
2530 -
2531 - // Check if a specific opt-out page is configured in the settings
2532 - if ( ! isset( $settings['optout_page'] ) || $settings['optout_page'] == 0 ) {
2533 - $link = get_home_url() . '?optout=' . $this->get_hash();
2534 - $this->get_logger()->notice( 'No specific opt-out page defined in settings. Generating homepage opt-out link.', [
2535 - 'plugin' => 'double-opt-in',
2536 - 'generated_link' => $link,
2537 - ] );
2538 - return $link;
2539 - } else {
2540 - $optout_page_id = $settings['optout_page'];
2541 - $this->get_logger()->debug( 'Specific opt-out page found in settings with ID: ' . $optout_page_id, [
2542 - 'plugin' => 'double-opt-in',
2543 - 'optout_page_id' => $optout_page_id,
2544 - ] );
2545 -
2546 - $url = get_permalink( $optout_page_id );
2547 -
2548 - if ( $url === false ) {
2549 - $this->get_logger()->error( 'Failed to get permalink for opt-out page ID ' . $optout_page_id . '.', [
2550 - 'plugin' => 'double-opt-in',
2551 - 'optout_page_id' => $optout_page_id,
2552 - ] );
2553 - return '';
2554 - }
2555 -
2556 - // Append the opt-out hash to the URL
2557 - if ( str_contains( $url, '?' ) ) {
2558 - // If '?' exists in the URL, it means there are already parameters, so append with '&'
2559 - $url .= '&optout=' . $this->get_hash();
2560 - $this->get_logger()->debug( 'URL already contains parameters. Appending hash with "&".', [
2561 - 'plugin' => 'double-opt-in',
2562 - ] );
2563 - } else {
2564 - // Else, there are no parameters yet, so append with '?'
2565 - $url .= '?optout=' . $this->get_hash();
2566 - $this->get_logger()->debug( 'URL has no parameters. Appending hash with "?".', [
2567 - 'plugin' => 'double-opt-in',
2568 - ] );
2569 - }
2570 -
2571 - $this->get_logger()->notice( 'Successfully generated permalink-based opt-out link.', [
2572 - 'plugin' => 'double-opt-in',
2573 - 'generated_link' => $url,
2574 - ] );
2575 - return $url;
2576 - }
2577 - }
777 + return $result;
2578 778 }
2579 -}
779 +}