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.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 All 36 releases
← All changes | core/OptIn.class.php +657 -1413 3.0.715.5.0 View file →
@@ -1,1535 +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 {
4 - if (!defined('ABSPATH')) {
5 - exit;
6 - }
12 +namespace forge12\contactform7\CF7DoubleOptIn;
7 13
8 - /**
9 - * Class Frontend
10 - * Responsible to handle the frontend of the Double OptIn
11 - *
12 - * @package forge12\contactform7\CF7DoubleOptIn
13 - */
14 - class OptIn
15 - {
16 - /**
17 - * Stores the DB ID for the entry
18 - *
19 - * @var int
20 - */
21 - private $id = 0;
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;
22 19
23 - /**
24 - * Stores the ID of the contact form 7 form.
25 - *
26 - * @var int
27 - */
28 - private $cf_form_id = 0;
29 - /**
30 - * Stores if the optin has been confirmed (1) or not (0)
31 - *
32 - * @var int
33 - */
34 - private $doubleoptin = 0;
35 - /**
36 - * Stores the content of the contact form 7 form content as a serialized string.
37 - *
38 - * @var string
39 - */
40 - private $content = "";
41 - /**
42 - * Stores the unique hash used to identify the opt-ins
43 - *
44 - * @var string
45 - */
46 - private $hash = "";
47 - /**
48 - * Stores the timestamp the opt-in mail has been sent to the user.
49 - *
50 - * @var string
51 - */
52 - private $createtime = "";
53 - /**
54 - * Stores the timestamp the opt-in has been confirmed by the user.
55 - */
56 - private $updatetime = "";
57 - /**
58 - * Stores the timestamp the opt-out has been confirmed by the user.
59 - */
60 - private $optouttime = "";
61 - /**
62 - * IP Address used to trigger the opt-in mail.
63 - *
64 - * @var string
65 - */
66 - private $ipaddr_register = "";
67 - /**
68 - * IP Address used to confirm the opt-in mail.
69 - */
70 - private $ipaddr_confirmation = "";
71 - /**
72 - * IP Address used to output.
73 - */
74 - private $ipaddr_optout = "";
75 - /**
76 - * Stores the files used within this form as a serialized string.
77 - */
78 - private $files = "";
79 - /**
80 - * Store the Category ID
81 - */
82 - private $category = 0;
83 - /**
84 - * Stores the form as html
85 - */
86 - private $form = '';
87 - /**
88 - * Stores the Mail send for the optin
89 - *
90 - * @var string
91 - */
92 - private $mail_optin = '';
93 - /**
94 - * Stores the E-Mail
95 - *
96 - * @var string
97 - */
98 - private $email = '';
20 +if ( ! defined( 'ABSPATH' ) ) {
21 + exit;
22 +}
99 23
100 - /**
101 - * The constructor
102 - */
103 - public function __construct(array $properties)
104 - {
105 - foreach ($properties as $name => $value) {
106 - if (isset($this->{$name})) {
107 - $this->{$name} = $value;
108 - }
109 - }
110 - }
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 {
111 31
112 - /**
113 - * @param int $cf_form_id
114 - */
115 - public function set_cf_form_id($cf_form_id)
116 - {
117 - $this->cf_form_id = (int)$cf_form_id;
118 - }
32 + private LoggerInterface $logger;
33 + private OptInEntity $entity;
119 34
120 - /**
121 - * @return int
122 - */
123 - public function get_cf_form_id()
124 - {
125 - return $this->cf_form_id;
126 - }
35 + /**
36 + * Constructor.
37 + *
38 + * @param LoggerInterface $logger The logger instance.
39 + * @param array $properties Optional properties to initialize.
40 + */
41 + public function __construct( LoggerInterface $logger, array $properties = [] ) {
42 + $this->logger = $logger;
127 43
128 - /**
129 - * Return the ID of the Optin
130 - *
131 - * @return int
132 - */
133 - public function get_id()
134 - {
135 - return $this->id;
136 - }
44 + if ( ! empty( $properties ) ) {
45 + $this->entity = OptInEntity::fromArray( $this->mapToEntityArray( $properties ) );
46 + } else {
47 + $this->entity = OptInEntity::create();
48 + }
137 49
138 - /**
139 - * Return the hash of the optin.
140 - *
141 - * @return string
142 - */
143 - public function get_hash()
144 - {
145 - return $this->hash;
146 - }
50 + $this->logger->debug( 'OptIn facade initialized', [
51 + 'plugin' => 'double-opt-in',
52 + 'id' => $this->entity->getId(),
53 + ] );
54 + }
147 55
148 - /**
149 - * Return true|false depending on confirm status
150 - *
151 - * @return bool
152 - */
153 - public function is_confirmed()
154 - {
155 - return (bool)$this->get_doubleoptin();
156 - }
56 + /**
57 + * Get the logger instance.
58 + *
59 + * @return LoggerInterface
60 + */
61 + public function get_logger(): LoggerInterface {
62 + return $this->logger;
63 + }
157 64
158 - /**
159 - * Return true|false wether the form has been confirmed or not.
160 - *
161 - * @return bool
162 - */
163 - public function is_optout()
164 - {
165 - if (true != $this->get_doubleoptin() && !empty($this->get_ipaddr_optout()) && !empty($this->get_optouttime())) {
166 - return true;
167 - }
65 + /**
66 + * Get the underlying entity.
67 + *
68 + * @return OptInEntity
69 + */
70 + public function getEntity(): OptInEntity {
71 + return $this->entity;
72 + }
168 73
169 - return false;
170 - }
74 + // =========================================================================
75 + // STATIC FACTORY METHODS
76 + // =========================================================================
171 77
172 - /**
173 - * update the optin value
174 - *
175 - * @param int $confirmed
176 - */
177 - public function set_doubleoptin($confirmed)
178 - {
179 - $this->doubleoptin = (int)$confirmed;
180 - }
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 + ] );
181 91
182 - /**
183 - * Return the optin value for the form.
184 - *
185 - * @return int
186 - */
187 - public function get_doubleoptin()
188 - {
189 - return $this->doubleoptin;
190 - }
92 + try {
93 + $repository = self::getRepository();
94 + $entity = $repository->findByHash( $hash );
191 95
192 - /**
193 - * @param string $timestamp
194 - */
195 - public function set_createtime($timestamp)
196 - {
197 - $this->createtime = $timestamp;
198 - }
96 + if ( ! $entity ) {
97 + return null;
98 + }
199 99
200 - /**
201 - * Return the Date how long the opt in will be valid
202 - *
203 - * @return string
204 - */
205 - public function get_valid_until()
206 - {
207 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
100 + $optIn = new self( $logger );
101 + $optIn->entity = $entity;
208 102
209 - $createtime = $this->get_createtime();
210 - $dt = new \DateTime();
211 - $dt->setTimestamp($createtime);
103 + return $optIn;
104 + } catch ( \Exception $e ) {
105 + $logger->error( 'Failed to get OptIn by hash', [
106 + 'plugin' => 'double-opt-in',
107 + 'hash' => $hash,
108 + 'error' => $e->getMessage(),
109 + ] );
110 + return null;
111 + }
112 + }
212 113
213 - if ($this->is_confirmed()) {
214 - $month = (int)$settings['delete'];
215 - $period = $settings['delete_period'];
216 - } else {
217 - $month = (int)$settings['delete_unconfirmed'];
218 - $period = $settings['delete_unconfirmed_period'];
219 - }
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;
126 + }
127 + }
220 128
221 - $dt->modify('+' . (int)$month . ' ' . $period);
222 - $dt->modify('+1 day');
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();
223 139
224 - return $dt->format('d.m.Y');
225 - }
140 + try {
141 + $repository = self::getRepository();
142 + $entities = $repository->findAll( $atts, $numberOfPages );
226 143
227 - /**
228 - * Return a timestamp
229 - *
230 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
231 - * formatted string.
232 - *
233 - * @return string
234 - */
235 - public function get_createtime($view = "raw")
236 - {
237 - if ($view != "raw") {
238 - if (!is_numeric($this->createtime)) {
239 - $date = date('d.m.Y', strtotime($this->createtime));
240 - $time = date('H:i:s', strtotime($this->createtime));
241 - } else {
242 - $date = date('d.m.Y', $this->createtime);
243 - $time = date('H:i:s', $this->createtime);
244 - }
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', [
151 + 'plugin' => 'double-opt-in',
152 + 'error' => $e->getMessage(),
153 + ] );
154 + return [];
155 + }
156 + }
245 157
246 - return $date . ' ' . __('/', 'double-opt-in') . ' ' . $time;
247 - } else {
248 - return $this->createtime;
249 - }
250 - }
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();
251 169
252 - /**
253 - * @param string $timestamp
254 - */
255 - public function set_updatetime($timestamp)
256 - {
257 - $this->updatetime = $timestamp;
258 - }
170 + try {
171 + $repository = self::getRepository();
259 172
260 - /**
261 - * Return a timestamp
262 - *
263 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
264 - * formatted string.
265 - *
266 - * @return string
267 - */
268 - public function get_updatetime($view = 'raw')
269 - {
270 - if ($view != "raw") {
271 - if (!is_numeric($this->updatetime)) {
272 - $date = date('d.m.Y', strtotime($this->updatetime));
273 - $time = date('H:i:s', strtotime($this->updatetime));
274 - } else {
275 - $date = date('d.m.Y', $this->updatetime);
276 - $time = date('H:i:s', $this->updatetime);
277 - }
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;
278 179
279 - return $date . ' ' . __('/', 'double-opt-in') . ' ' . $time;
280 - } else {
281 - return $this->updatetime;
282 - }
283 - }
180 + if ( $numberOfPages === 0 ) {
181 + return [];
182 + }
183 + }
284 184
285 - /**
286 - * @param string $timestamp
287 - */
288 - public function set_optouttime($timestamp)
289 - {
290 - $this->optouttime = $timestamp;
291 - }
185 + $entities = $repository->findByCategory( $categoryId, $atts );
292 186
293 - /**
294 - * Return a timestamp
295 - *
296 - * @param string $view Select how to return the content. raw is the stored db value. use formatted to return a
297 - * formatted string.
298 - *
299 - * @return string
300 - */
301 - public function get_optouttime($view = 'raw')
302 - {
303 - if ($view != "raw") {
304 - if (!is_numeric($this->optouttime)) {
305 - $date = date('d.m.Y', strtotime($this->optouttime));
306 - $time = date('H:i:s', strtotime($this->optouttime));
307 - } else {
308 - $date = date('d.m.Y', $this->optouttime);
309 - $time = date('H:i:s', $this->optouttime);
310 - }
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(),
197 + ] );
198 + return [];
199 + }
200 + }
311 201
312 - return $date . ' ' . __('/', 'f12-cf7-doubleoptin') . ' ' . $time;
313 - } else {
314 - return $this->optouttime;
315 - }
316 - }
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();
317 211
318 - /**
319 - * @param string $ip_addr
320 - */
321 - public function set_ipaddr_optout($ip_addr)
322 - {
323 - $this->ipaddr_optout = $ip_addr;
324 - }
212 + try {
213 + $repository = self::getRepository();
214 + $entities = $repository->findByEmail( $email );
325 215
326 - /**
327 - * @return string
328 - */
329 - public function get_ipaddr_optout()
330 - {
331 - return $this->ipaddr_optout;
332 - }
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 [];
223 + }
224 + }
333 225
334 - /**
335 - * @param string $ip_addr
336 - */
337 - public function set_ipaddr_register($ip_addr)
338 - {
339 - $this->ipaddr_register = $ip_addr;
340 - }
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();
341 235
342 - /**
343 - * @return string
344 - */
345 - public function get_ipaddr_register()
346 - {
347 - return $this->ipaddr_register;
348 - }
236 + try {
237 + $repository = self::getRepository();
238 + $entities = $repository->findConfirmedByEmail( $email );
349 239
350 - /**
351 - * @param string $ip_addr
352 - */
353 - public function set_ipaddr_confirmation($ip_addr)
354 - {
355 - $this->ipaddr_confirmation = $ip_addr;
356 - }
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 [];
247 + }
248 + }
357 249
358 - /**
359 - * @return string
360 - */
361 - public function get_ipaddr_confirmation()
362 - {
363 - return $this->ipaddr_confirmation;
364 - }
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();
365 259
366 - /**
367 - * Return a serialized string
368 - *
369 - * @return mixed
370 - */
371 - public function get_content()
372 - {
373 - return $this->content;
374 - }
260 + try {
261 + $repository = self::getRepository();
262 + $entities = $repository->findUnconfirmedByEmail( $email );
375 263
376 - /**
377 - * @param string - a serialized string
378 - */
379 - public function set_content($content)
380 - {
381 - $this->content = $content;
382 - }
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 [];
271 + }
272 + }
383 273
384 - /**
385 - * @param string - a serialized string
386 - */
387 - public function set_files($files)
388 - {
389 - $this->files = $files;
390 - }
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;
287 + }
288 + }
391 289
392 - /**
393 - * @return string use maybe_unserialize to deserialize the string
394 - */
395 - public function get_files()
396 - {
397 - return $this->files;
398 - }
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 ) {
302 + return false;
303 + }
304 + }
399 305
400 - /**
401 - * Return the assigned Category ID
402 - *
403 - * @return int
404 - */
405 - public function get_category()
406 - {
407 - if (!is_numeric($this->category)) {
408 - return 0;
409 - }
306 + // =========================================================================
307 + // GETTERS
308 + // =========================================================================
410 309
411 - return $this->category;
412 - }
310 + public function get_id(): int {
311 + return $this->entity->getId();
312 + }
413 313
414 - /**
415 - * Assign a ID to the category
416 - *
417 - * @param int $id
418 - *
419 - * @return void
420 - */
421 - public function set_category($id)
422 - {
423 - $this->category = $id;
424 - }
314 + public function get_hash(): string {
315 + return $this->entity->getHash();
316 + }
425 317
426 - /**
427 - * Check if the given Opt In was created by Contact Form 7
428 - *
429 - * @return bool
430 - */
431 - public function isTypeCF7()
432 - {
433 - $form_id = $this->get_cf_form_id();
434 - $form = get_post($form_id);
318 + public function get_cf_form_id(): int {
319 + return $this->entity->getFormId();
320 + }
435 321
436 - if ('wpcf7_contact_form' === $form->post_type) {
437 - return true;
438 - }
322 + public function is_confirmed(): bool {
323 + return $this->entity->isConfirmed();
324 + }
439 325
440 - return false;
441 - }
326 + public function get_doubleoptin(): int {
327 + return $this->entity->isConfirmed() ? 1 : 0;
328 + }
442 329
443 - /**
444 - * Check if the given Opt In was created by Elementor
445 - *
446 - * @return bool
447 - */
448 - public function isTypeElementor()
449 - {
450 - if (!class_exists('ElementorPro\Plugin')) {
451 - return false;
452 - }
330 + public function is_optout(): bool {
331 + return $this->entity->isOptedOut();
332 + }
453 333
454 - $page_id = $this->get_cf_form_id();
334 + public function get_content(): string {
335 + return $this->entity->getContent();
336 + }
455 337
456 - $post = get_post($page_id);
338 + public function get_createtime( string $view = 'raw' ): string {
339 + if ( $view !== 'raw' ) {
340 + return $this->entity->getCreateTimeFormatted( 'd.m.Y / H:i:s' );
341 + }
342 + return (string) $this->entity->getCreateTime();
343 + }
457 344
458 - // skip if post not existing anymore
459 - if (null === $post) {
460 - return false;
461 - }
345 + public function get_updatetime( string $view = 'raw' ): string {
346 + if ( $view !== 'raw' ) {
347 + return $this->entity->getUpdateTimeFormatted( 'd.m.Y / H:i:s' );
348 + }
349 + return (string) $this->entity->getUpdateTime();
350 + }
462 351
463 - // skip if page is not build with elementor
464 - if (!\Elementor\Plugin::$instance->documents->get($page_id)->is_built_with_elementor()) {
465 - return false;
466 - }
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 );
356 + }
357 + return (string) $time;
358 + }
467 359
468 - // Load the page's data
469 - $document = \Elementor\Plugin::$instance->documents->get($page_id);
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 + }
470 368
471 - // Skip if document not exist
472 - if (!$document) {
473 - return false;
474 - }
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 + }
475 377
476 - // Loop through the elements
477 - $elements_data = $document->get_elements_data();
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 + }
478 386
479 - $content = maybe_unserialize($this->get_content());
387 + public function get_ipaddr_register(): string {
388 + return $this->entity->getIpRegister();
389 + }
480 390
481 - // skip if form id not available
482 - if (!isset($content['form_id'])) {
483 - return false;
484 - }
391 + public function get_ipaddr_confirmation(): string {
392 + return $this->entity->getIpConfirmation();
393 + }
485 394
486 - $form_id = $content['form_id'];
395 + public function get_ipaddr_optout(): string {
396 + return $this->entity->getIpOptOut();
397 + }
487 398
488 - // Find the Form Element
489 - foreach ($elements_data as $element) {
490 - if (isset($element['elements'])) {
491 - foreach ($element['elements'] as $el) {
492 - if ($form_id !== $el['id'] || 'form' !== $el['widgetType']) {
493 - continue;
494 - }
399 + public function get_files(): string {
400 + return $this->entity->getFiles();
401 + }
495 402
496 - return true;
497 - }
498 - return true;
499 - }
403 + public function get_category(): int {
404 + return $this->entity->getCategory();
405 + }
500 406
501 - // Check if this is the form widget
502 - if (isset($element['widgetType']) && isset($element['id']) && $element['widgetType'] === 'form' && $element['id'] === $form_id) {
503 - // Return the form settings
504 - return true;
505 - }
506 - }
507 - return false;
508 - }
407 + public function get_email(): string {
408 + return $this->entity->getEmail();
409 + }
509 410
510 - /**
511 - * Check if the given Opt In was created by Avada
512 - *
513 - * @return bool|void
514 - */
515 - public function isTypeAvada()
516 - {
517 - $form_id = $this->get_cf_form_id();
518 - $form = get_post($form_id);
411 + public function get_mail_optin(): string {
412 + return $this->entity->getMailOptIn();
413 + }
519 414
520 - if ('fusion_form' === $form->post_type) {
521 - return true;
522 - }
415 + public function get_consent_text(): string {
416 + return $this->entity->getConsentText();
417 + }
523 418
524 - return false;
525 - }
419 + public function get_form( bool $encoded = false ): string {
420 + $form = $this->entity->getForm();
421 + if ( $encoded ) {
422 + return base64_encode( $form );
423 + }
424 + return $form;
425 + }
526 426
527 - /**
528 - * Check if the Opt In was created by 'cf7' or 'avada'.
529 - *
530 - * @param string $type use either 'cf7' or 'avada'
531 - *
532 - * @return bool
533 - */
534 - public function isType($type)
535 - {
536 - if ($type == 'cf7') {
537 - return $this->isTypeCF7();
538 - } elseif ($type == 'elementor') {
539 - return $this->isTypeElementor();
540 - } else {
541 - return $this->isTypeAvada();
542 - }
543 - }
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() );
544 436
545 - /**
546 - * Load an OptIn by the given hash code. Returns either an Object of Type OptIn or null if not found.
547 - *
548 - * @param string $hash
549 - *
550 - * @return OptIn|null
551 - */
552 - public static function get_by_hash(string $hash)
553 - {
554 - global $wpdb;
555 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
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';
443 + }
556 444
557 - if (null == $wpdb) {
558 - return null;
559 - }
445 + $dt->modify( '+' . $amount . ' ' . $period );
446 + $dt->modify( '+1 day' );
560 447
561 - $row = $wpdb->get_row('SELECT * FROM ' . $table . ' WHERE hash="' . $hash . '"', ARRAY_A);
448 + return $dt->format( 'd.m.Y' );
449 + }
562 450
563 - if (null == $row) {
564 - return null;
565 - }
451 + // =========================================================================
452 + // SETTERS
453 + // =========================================================================
566 454
567 - return new OptIn($row);
568 - }
455 + public function set_cf_form_id( int $formId ): void {
456 + $this->entity = $this->entity->withFormId( $formId );
457 + }
569 458
570 - /**
571 - * Get number of optins for the given post.
572 - *
573 - * @param $post_id
574 - *
575 - * @return int
576 - */
577 - public static function get_count($post_id)
578 - {
579 - global $wpdb;
459 + public function set_doubleoptin( int $confirmed ): void {
460 + $this->entity->setConfirmed( (bool) $confirmed );
461 + }
580 462
581 - if (!$wpdb) {
582 - return 0;
583 - }
463 + public function set_createtime( $timestamp ): void {
464 + $this->entity = $this->entity->withCreateTime( (int) $timestamp );
465 + }
584 466
585 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
586 - $result = $wpdb->get_results($wpdb->prepare('SELECT count(*) AS counter FROM ' . $tableName . ' WHERE cf_form_id=%d ', $post_id));
467 + public function set_updatetime( $timestamp ): void {
468 + $this->entity->setUpdateTime( (int) $timestamp );
469 + }
587 470
588 - if (is_array($result)) {
589 - return $result[0]->counter;
590 - }
471 + public function set_optouttime( $timestamp ): void {
472 + $this->entity = $this->entity->withOptOutTime( (int) $timestamp );
473 + }
591 474
592 - return 0;
593 - }
475 + public function set_ipaddr_register( string $ip ): void {
476 + $this->entity = $this->entity->withIpRegister( $ip );
477 + }
594 478
595 - /**
596 - * Return a list of opt ins stored in the database.
597 - *
598 - * @param int $category_id
599 - *
600 - * @param array $atts array (
601 - * 'perPage' => 10, // Posts per page
602 - * 'page' => 0, // Current page
603 - * 'order' => DESC, // Order (ASC/DESC)
604 - * );
605 - *
606 - * @param null $numberOfPages - Stores the number of pages found if limited
607 - *
608 - * @return array
609 - */
610 - public static function get_list_by_category_id($category_id, $atts = [], &$numberOfPages = null)
611 - {
612 - global $wpdb;
479 + public function set_ipaddr_confirmation( string $ip ): void {
480 + $this->entity->setIpConfirmation( $ip );
481 + }
613 482
614 - $attr = [
615 - 'perPage' => 10,
616 - 'page' => 1,
617 - 'order' => 'DESC',
618 - 'keyword' => ''
619 - ];
483 + public function set_ipaddr_optout( string $ip ): void {
484 + $this->entity = $this->entity->withIpOptOut( $ip );
485 + }
620 486
621 - /**
622 - * Parameter
623 - */
624 - foreach ($atts as $key => $value) {
625 - if (isset($attr[$key])) {
626 - $attr[$key] = $atts[$key];
627 - }
628 - }
487 + public function set_content( string $content ): void {
488 + $this->entity = $this->entity->withContent( $content );
489 + }
629 490
630 - /*
631 - * Update keyword
632 - */
633 - $where = '';
634 - if (!empty($attr['keyword'])) {
635 - $where = ' AND content LIKE "%%' . $attr['keyword'] . '%%"';
636 - }
491 + public function set_files( string $files ): void {
492 + $this->entity = $this->entity->withFiles( $files );
493 + }
637 494
495 + public function set_category( int $categoryId ): void {
496 + $this->entity = $this->entity->withCategory( $categoryId );
497 + }
638 498
639 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
640 - $pageNum = (int)$attr['page'] - 1;
499 + public function set_form( string $form ): void {
500 + $this->entity = $this->entity->withForm( $form );
501 + }
641 502
642 - if ($numberOfPages !== null) {
643 - $result = $wpdb->get_results($wpdb->prepare('SELECT count(*) AS counter FROM ' . $tableName . ' WHERE category=%d ' . $where, $category_id));
503 + public function set_email( string $email ): void {
504 + $this->entity = $this->entity->withEmail( $email );
505 + }
644 506
645 - $itemCounter = 0;
507 + public function set_mail_optin( string $mail ): void {
508 + $this->entity = $this->entity->withMailOptIn( $mail );
509 + }
646 510
647 - if (is_array($result)) {
648 - $itemCounter = $result[0]->counter;
649 - }
511 + public function set_consent_text( string $text ): void {
512 + $this->entity = $this->entity->withConsentText( $text );
513 + }
650 514
651 - $numberOfPages = 0;
515 + // =========================================================================
516 + // TYPE CHECKS
517 + // =========================================================================
652 518
653 - if ($itemCounter != 0) {
654 - $numberOfPages = ceil($itemCounter / (int)$attr['perPage']);
655 - }
656 - }
519 + public function isTypeCF7(): bool {
520 + return $this->isType( 'cf7' );
521 + }
657 522
658 - $list = [];
523 + public function isTypeAvada(): bool {
524 + return $this->isType( 'avada' );
525 + }
659 526
660 - if ($numberOfPages == 0) {
661 - return $list;
662 - }
527 + public function isTypeElementor(): bool {
528 + return $this->isType( 'elementor' );
529 + }
663 530
664 - $offset = $pageNum * (int)$attr['perPage'];
531 + public function isTypeWPForms(): bool {
532 + return $this->isType( 'wpforms' );
533 + }
665 534
666 - $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . $tableName . ' WHERE category=%d ' . $where . ' ORDER BY ID ' . $attr['order'] . ' LIMIT ' . $attr['perPage'] . ' OFFSET %d', $category_id, $offset), ARRAY_A);
535 + public function isTypeGravityForms(): bool {
536 + return $this->isType( 'gravityforms' );
537 + }
667 538
668 - foreach ($rows as $row) {
669 - $list[] = new OptIn($row);
670 - }
539 + public function isType( string $type ): bool {
540 + $formId = $this->get_cf_form_id();
671 541
672 - return $list;
673 - }
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 );
561 + }
562 + return false;
563 + default:
564 + return false;
565 + }
566 + }
674 567
675 - /**
676 - * Return a list of opt ins stored in the database.
677 - *
678 - * @param string $email
679 - *
680 - * @return array
681 - */
682 - public static function get_list_by_email($email)
683 - {
684 - global $wpdb;
568 + // =========================================================================
569 + // PERSISTENCE
570 + // =========================================================================
685 571
686 - $tableName = $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 + ] );
687 583
688 - $list = [];
584 + try {
585 + $repository = self::getRepository();
586 + $this->entity = $repository->save( $this->entity );
689 587
690 - $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . $tableName . ' WHERE email=%s', $email), ARRAY_A);
588 + $this->logger->info( 'OptIn saved successfully', [
589 + 'plugin' => 'double-opt-in',
590 + 'id' => $this->entity->getId(),
591 + 'hash' => $this->entity->getHash(),
592 + ] );
691 593
692 - foreach ($rows as $row) {
693 - $list[] = new OptIn($row);
694 - }
594 + return true;
595 + } catch ( \Exception $e ) {
596 + $this->logger->error( 'Failed to save OptIn', [
597 + 'plugin' => 'double-opt-in',
598 + 'error' => $e->getMessage(),
599 + ] );
600 + return false;
601 + }
602 + }
695 603
696 - return $list;
697 - }
604 + // =========================================================================
605 + // LINK GENERATION
606 + // =========================================================================
698 607
699 - /**
700 - * Return a list of opt ins stored in the database.
701 - *
702 - * @param string $email
703 - *
704 - * @return array
705 - */
706 - public static function get_list_by_email_confirmed($email)
707 - {
708 - global $wpdb;
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();
709 618
710 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
619 + $formParameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
620 + $pageId = (int) ( $formParameter['page'] ?? 0 );
711 621
712 - $list = [];
622 + if ( $pageId <= 0 ) {
623 + return home_url( '?optin=' . $this->get_hash() );
624 + }
713 625
714 - $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . $tableName . ' WHERE email=%s AND doubleoptin=1', $email), ARRAY_A);
626 + $pageUrl = get_permalink( $pageId );
627 + if ( ! $pageUrl ) {
628 + return home_url( '?optin=' . $this->get_hash() );
629 + }
715 630
716 - foreach ($rows as $row) {
717 - $list[] = new OptIn($row);
718 - }
631 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
719 632
720 - return $list;
721 - }
633 + return $pageUrl . $separator . 'optin=' . $this->get_hash();
634 + }
722 635
723 - /**
724 - * Return a list of opt ins stored in the database.
725 - *
726 - * @param string $email
727 - *
728 - * @return array
729 - */
730 - public static function get_list_by_email_unconfirmed($email)
731 - {
732 - global $wpdb;
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 );
733 644
734 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
645 + if ( $pageId <= 0 ) {
646 + return home_url( '?optout=' . $this->get_hash() );
647 + }
735 648
736 - $list = [];
649 + $pageUrl = get_permalink( $pageId );
650 + if ( ! $pageUrl ) {
651 + return home_url( '?optout=' . $this->get_hash() );
652 + }
737 653
738 - $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . $tableName . ' WHERE email=%s AND doubleoptin=0', $email), ARRAY_A);
654 + $separator = strpos( $pageUrl, '?' ) !== false ? '&' : '?';
739 655
740 - foreach ($rows as $row) {
741 - $list[] = new OptIn($row);
742 - }
656 + return $pageUrl . $separator . 'optout=' . $this->get_hash();
657 + }
743 658
744 - return $list;
745 - }
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 + }
746 667
747 - /**
748 - * Return a list of opt ins stored in the database.
749 - *
750 - * @param $atts array (
751 - * 'perPage' => 10, // Posts per page
752 - * 'page' => 0, // Current page
753 - * 'order' => DESC, // Order (ASC/DESC)
754 - * );
755 - *
756 - * @param null $numberOfPages - Stores the number of pages found if limited
757 - *
758 - * @return array
759 - */
760 - public static function get_list($atts = [], &$numberOfPages = null)
761 - {
762 - global $wpdb;
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 + }
763 677
764 - $attr = [
765 - 'perPage' => 10,
766 - 'page' => 1,
767 - 'order' => 'DESC',
768 - 'keyword' => '',
769 - 'cf_form_id' => '',
770 - ];
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();
771 685
772 - /*
773 - * Update atts
774 - */
775 - foreach ($atts as $key => $value) {
776 - if (isset($attr[$key])) {
777 - $attr[$key] = $atts[$key];
778 - }
779 - }
686 + if ( $formId <= 0 ) {
687 + return __( 'Unknown', 'double-opt-in' );
688 + }
780 689
781 - /*
782 - * Update WHERE for Query
783 - */
784 - $where = '';
785 - $where_condition = [];
690 + $post = get_post( $formId );
691 + if ( ! $post ) {
692 + return __( 'Deleted Form', 'double-opt-in' ) . ' (ID: ' . $formId . ')';
693 + }
786 694
787 - /*
788 - * Add Keyword
789 - */
790 - if (!empty($attr['keyword'])) {
791 - $where_condition[] = 'content LIKE "%%' . $attr['keyword'] . '%%"';
792 - }
695 + return $post->post_title ?: __( 'Untitled Form', 'double-opt-in' );
696 + }
793 697
794 - /*
795 - * Add Form ID
796 - */
797 - if (!empty($attr['cf_form_id'])) {
798 - $where_condition[] = 'cf_form_id="' . $attr['cf_form_id'] . '"';
799 - }
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 );
800 706
801 - /*
802 - * build where string
803 - */
804 - if (!empty($where_condition)) {
805 - $where = ' WHERE ' . implode(' AND ', $where_condition);
806 - }
707 + if ( $postType === 'wpcf7_contact_form' ) {
708 + return admin_url( 'admin.php?page=wpcf7&post=' . $formId . '&action=edit' );
709 + }
807 710
808 - /*
809 - * set table
810 - */
811 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
812 - $pageNum = (int)$attr['page'] - 1;
711 + if ( $postType === 'fusion_form' ) {
712 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
713 + }
813 714
814 - if ($numberOfPages !== null) {
815 - $result = $wpdb->get_results('SELECT count(*) as counter FROM ' . $tableName . $where);
715 + return admin_url( 'post.php?post=' . $formId . '&action=edit' );
716 + }
816 717
817 - $itemCounter = 0;
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 + }
818 726
819 - if (is_array($result)) {
820 - $itemCounter = $result[0]->counter;
821 - }
727 + // =========================================================================
728 + // HELPERS
729 + // =========================================================================
822 730
823 - $numberOfPages = 0;
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 + }
824 740
825 - if ($itemCounter != 0) {
826 - $numberOfPages = ceil($itemCounter / (int)$attr['perPage']);
827 - }
828 - }
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 + ];
829 769
830 - $list = [];
770 + $result = [];
771 + foreach ( $mapping as $legacy => $entity ) {
772 + if ( isset( $properties[ $legacy ] ) ) {
773 + $result[ $entity ] = $properties[ $legacy ];
774 + }
775 + }
831 776
832 - if ($numberOfPages == 0) {
833 - return $list;
834 - }
835 -
836 - $offset = $pageNum * (int)$attr['perPage'];
837 -
838 - $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM ' . $tableName . $where . ' ORDER BY ID ' . $attr['order'] . ' LIMIT ' . $attr['perPage'] . ' OFFSET % d', $offset), ARRAY_A);
839 -
840 - foreach ($rows as $row) {
841 - $list[] = new OptIn($row);
842 - }
843 -
844 - return $list;
845 - }
846 -
847 - /**
848 - * This will update all categories from A to B, e.g.: All Opt-Ins with id 1 to category id 2.
849 - *
850 - * @param int $from_id The origin Category ID
851 - * @param int $to_id The new Category ID
852 - *
853 - * @return bool|int|\mysqli_result|resource|null Return the number of rows affected by the query.
854 - */
855 - public static function bulk_update_category($from_id, $to_id)
856 - {
857 - global $wpdb;
858 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
859 -
860 - return $wpdb->query($wpdb->prepare('UPDATE ' . $table . ' SET category =%d WHERE category =%d', $to_id, $from_id));
861 - }
862 -
863 - /**
864 - * Update the category of the given opt in id.
865 - *
866 - * @param int $optin_id The ID of the OptIn
867 - * @param int $category_id The ID of the Category
868 - *
869 - * @return bool|int|\mysqli_result|resource|null
870 - */
871 - public static function update_category_by_id($optin_id, $category_id)
872 - {
873 - global $wpdb;
874 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
875 -
876 - return $wpdb->query($wpdb->prepare('UPDATE ' . $table . ' SET category =%d WHERE id =%d', $category_id, $optin_id));
877 - }
878 -
879 - /**
880 - * Update or create the given Object
881 - */
882 - public function save()
883 - {
884 - global $wpdb;
885 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
886 -
887 - f12_debug_log('save method called', [
888 - 'table' => $table,
889 - 'hash' => $this->get_hash(),
890 - ]);
891 -
892 - /**
893 - * Wenn ein Hash existiert, wird die Zeile aktualisiert
894 - */
895 - if (!empty($this->get_hash())) {
896 - f12_debug_log('Updating existing record', [
897 - 'hash' => $this->get_hash(),
898 - 'cf_form_id' => $this->get_cf_form_id(),
899 - 'doubleoptin' => $this->get_doubleoptin(),
900 - ]);
901 -
902 - $result = $wpdb->update($table, [
903 - 'doubleoptin' => $this->get_doubleoptin(),
904 - 'createtime' => $this->get_createtime(),
905 - 'updatetime' => $this->get_updatetime(),
906 - 'ipaddr_confirmation' => $this->get_ipaddr_confirmation(),
907 - 'ipaddr_register' => $this->get_ipaddr_register(),
908 - 'cf_form_id' => $this->get_cf_form_id(),
909 - 'content' => $this->get_content(),
910 - 'files' => $this->get_files(),
911 - 'category' => $this->get_category(),
912 - 'optouttime' => $this->get_optouttime(),
913 - 'ipaddr_optout' => $this->get_ipaddr_optout(),
914 - 'mail_optin' => $this->get_mail_optin(),
915 - ], [
916 - 'hash' => $this->get_hash()
917 - ]);
918 -
919 - f12_debug_log('Update operation completed', ['result' => $result]);
920 -
921 - return $result;
922 - }
923 -
924 - /**
925 - * Wenn kein Hash existiert, wird ein neuer Datensatz eingefügt
926 - */
927 - f12_debug_log('Inserting new record', [
928 - 'cf_form_id' => $this->get_cf_form_id(),
929 - 'email' => $this->get_email(),
930 - 'doubleoptin' => $this->get_doubleoptin(),
931 - ]);
932 -
933 - $result = $wpdb->insert(
934 - $table,
935 - $data = [ // Daten in einer Variablen speichern, um sie anschließend zu loggen
936 - 'cf_form_id' => $this->get_cf_form_id(),
937 - 'doubleoptin' => $this->get_doubleoptin(),
938 - 'createtime' => $this->get_createtime(),
939 - 'updatetime' => $this->get_updatetime(),
940 - 'ipaddr_confirmation' => $this->get_ipaddr_confirmation(),
941 - 'ipaddr_register' => $this->get_ipaddr_register(),
942 - 'content' => $this->get_content(),
943 - 'files' => $this->get_files(),
944 - 'category' => $this->get_category(),
945 - 'form' => $this->get_form(true),
946 - 'mail_optin' => $this->get_mail_optin(),
947 - 'email' => $this->get_email(),
948 - ]
949 - );
950 -
951 - $formValue = $this->get_form();
952 - f12_debug_log('Form Data Information', [
953 - 'form_length' => strlen($formValue), // Länge des Inhalts
954 - 'form_data_sample' => substr($formValue, 0, 500), // Nur die ersten 500 Zeichen loggen
955 - ]);
956 -
957 - $log_data = $data;
958 - $log_data['form'] = substr($formValue, 0, 200);
959 -
960 - f12_debug_log('Preparing to insert new record', [
961 - 'table' => $table,
962 - 'data' => $log_data
963 - ]);
964 -
965 - if (false === $result) {
966 - f12_debug_log('Insert operation failed', [
967 - 'mysql_error' => $wpdb->last_error
968 - ]);
969 -
970 - $this->validateEncoding($formValue);
971 - $this->validateMaxAllowedPacket($formValue);
972 - $this->validateFormField();
973 -
974 - return false;
975 - }
976 -
977 - f12_debug_log('Insert operation completed', [
978 - 'result' => $result,
979 - 'insert_id' => $wpdb->insert_id,
980 - ]);
981 -
982 - if ($result > 0) {
983 - $this->id = $wpdb->insert_id;
984 - f12_debug_log('New record inserted, ID assigned', ['id' => $this->id]);
985 -
986 - $hashResult = $this->createHash();
987 - f12_debug_log('Hash created for new record', ['hashResult' => $hashResult]);
988 -
989 - return $hashResult;
990 - }
991 -
992 - f12_debug_log('Failed to insert new record');
993 - return false;
994 - }
995 -
996 - private function validateFormField()
997 - {
998 - global $wpdb;
999 - // SQL-Anfrage, um die Felddefinitionen abzurufen
1000 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1001 -
1002 - $columns = $wpdb->get_results("SHOW FIELDS FROM $table");
1003 -
1004 - // Überprüfung der Feldtypen
1005 - foreach ($columns as $column) {
1006 - f12_debug_log('Field Info', [
1007 - 'Field' => $column->Field,
1008 - 'Type' => $column->Type,
1009 - 'Null' => $column->Null,
1010 - 'Default' => $column->Default
1011 - ]);
1012 - }
1013 - }
1014 -
1015 - private function validateEncoding(string $data)
1016 - {
1017 - if (!mb_check_encoding($data, 'UTF-8')) {
1018 - f12_debug_log('Encoding Issue', ['form_data' => substr($data, 0, 500)]);
1019 - return false;
1020 - }
1021 - f12_debug_log('Encoding OK', []);
1022 - return true;
1023 - }
1024 -
1025 - private function validateMaxAllowedPacket(string $data)
1026 - {
1027 - $maxAllowedPacketMB = $this->getMaxAllowedPacket();
1028 -
1029 - $dataSize = strlen($data); // Size in bytes
1030 - $dataSizeMB = $dataSize / (1024 * 1024); // Convert bytes to MB
1031 -
1032 - f12_debug_log('Data Size Check', [
1033 - 'data_size_bytes' => $dataSize,
1034 - 'data_size_mb' => $dataSizeMB,
1035 - ]);
1036 -
1037 - if ($dataSize > $maxAllowedPacketMB) {
1038 - f12_debug_log('max_allowed_packet Too Small', [
1039 - 'data_size_mb' => $dataSizeMB,
1040 - 'max_allowed_packet_mb' => $maxAllowedPacketMB
1041 - ]);
1042 - return false;
1043 - } else {
1044 - f12_debug_log('max_allowed_packet OK', []);
1045 - return true;
1046 - }
1047 - }
1048 -
1049 - private function getMaxAllowedPacket()
1050 - {
1051 - global $wpdb;
1052 -
1053 - $result = $wpdb->get_row("SHOW VARIABLES LIKE 'max_allowed_packet'", OBJECT);
1054 -
1055 - /**
1056 - * Prüfen, ob die Variable erfolgreich abgerufen wurde
1057 - * Die zweite Spalte der Abfrage enthält den Wert (Value)
1058 - */
1059 - // Abfrage, um den aktuellen Wert von max_allowed_packet zu erhalten
1060 - if ($result) {
1061 - $maxAllowedPacket = $result->Value; // Wert aus der zweiten Spalte
1062 - f12_debug_log('MySQL max_allowed_packet Value', [
1063 - 'max_allowed_packet_bytes' => $maxAllowedPacket,
1064 - 'max_allowed_packet_mb' => $maxAllowedPacket / (1024 * 1024) // In MB
1065 - ]);
1066 - } else {
1067 - // Falls das Ergebnis leer ist (z. B. Fehler oder keine Berechtigung)
1068 - f12_debug_log('Error fetching max_allowed_packet', [
1069 - 'error' => $wpdb->last_error
1070 - ]);
1071 - return 0;
1072 - }
1073 - return $maxAllowedPacket;
1074 - }
1075 -
1076 - /**
1077 - * Creates a unique hash code and adds it to the current OptIn Object
1078 - */
1079 - private function createHash()
1080 - {
1081 - global $wpdb;
1082 -
1083 - if (null == $wpdb) {
1084 - return false;
1085 - }
1086 -
1087 - $table = $wpdb->prefix . 'f12_cf7_doubleoptin';
1088 -
1089 - // add the hash to the element
1090 -
1091 - $this->hash = base64_encode($this->get_createtime() . $this->get_id() . $this->get_cf_form_id());
1092 -
1093 - return $wpdb->update($table, [
1094 - 'hash' => $this->hash,
1095 - ], [
1096 - 'id' => $this->get_id()
1097 - ]);
1098 - }
1099 -
1100 - private function is_base64_encoded($string)
1101 - {
1102 - // Prüfen, ob der String Base64-kompatible Zeichen enthält
1103 - if (preg_match('/^[A-Za-z0-9+\/=]*$/', $string)) {
1104 - // Prüfen, ob die Länge des Strings ein Vielfaches von 4 ist
1105 - return strlen($string) % 4 === 0;
1106 - }
1107 - return false;
1108 - }
1109 -
1110 - /**
1111 - * Return the Form
1112 - *
1113 - * @return string
1114 - */
1115 - public function get_form($encoded = false)
1116 - {
1117 - if (!$encoded) {
1118 - if ($this->is_base64_encoded($this->form)) {
1119 - return base64_decode($this->form);
1120 - } else {
1121 - return $this->form;
1122 - }
1123 - } else {
1124 - if ($this->is_base64_encoded($this->form)) {
1125 - return $this->form;
1126 - } else {
1127 - return base64_encode($this->form);
1128 - }
1129 - }
1130 - }
1131 -
1132 - /**
1133 - * Returns the Name of the form if available
1134 - *
1135 - * @return string The name of the Form
1136 - * @since 2.4.0
1137 - */
1138 - public function get_form_name()
1139 - {
1140 - $post = get_post($this->get_cf_form_id());
1141 -
1142 - if (null === $post) {
1143 - return __('Deleted', 'double-opt-in');
1144 - }
1145 -
1146 - return $post->post_title;
1147 - }
1148 -
1149 - /**
1150 - * Returns the URL to the form depending on the Form Type.
1151 - *
1152 - * @return string The Backend URL to the Form, empty if the post is not found.
1153 - * @since 2.4.0
1154 - *
1155 - */
1156 - public function get_form_link()
1157 - {
1158 - if ($this->isTypeElementor()) {
1159 - return get_edit_post_link($this->get_cf_form_id());
1160 - } elseif ($this->isTypeAvada()) {
1161 - return get_edit_post_link($this->get_cf_form_id());
1162 - } elseif ($this->isTypeCF7()) {
1163 - $post = get_post($this->get_cf_form_id());
1164 -
1165 - if (!$post) {
1166 - return '';
1167 - }
1168 -
1169 - return admin_url('admin.php?page=wpcf7&post=' . esc_attr($this->get_cf_form_id()) . '&action=edit');
1170 - }
1171 -
1172 - return '';
1173 - }
1174 -
1175 -
1176 - /**
1177 - * Retrieves the form settings.
1178 - *
1179 - * If the form is created using Elementor, retrieves the settings using the `get_settings_by_elementor()`
1180 - * method.
1181 - *
1182 - * @return array The form settings.
1183 - * @since 2.4.0
1184 - *
1185 - */
1186 - public function get_form_settings()
1187 - {
1188 - if ($this->isTypeElementor()) {
1189 - return $this->get_settings_by_elementor();
1190 - } else if ($this->isTypeCF7()) {
1191 - return $this->get_settings_by_cf7();
1192 - } else if ($this->isTypeAvada()) {
1193 - return $this->get_settings_by_avada();
1194 - }
1195 -
1196 - return [];
1197 - }
1198 -
1199 - /**
1200 - * Retrieves the settings of a specific element using avada.
1201 - *
1202 - * @return array
1203 - * @since 2.4.0
1204 - *
1205 - * @see OptIn::maybe_normalize_settings() for details
1206 - */
1207 - private function get_settings_by_avada()
1208 - {
1209 -
1210 - $form_id = $this->get_cf_form_id();
1211 -
1212 - $settings = CF7DoubleOptIn::getInstance()->getParameter($form_id);
1213 -
1214 - return $this->maybe_normalize_settings($settings);
1215 - }
1216 -
1217 - /**
1218 - * Retrieves the settings of a specific element using cf7.
1219 - *
1220 - * @return array
1221 - * @since 2.4.0
1222 - *
1223 - * @see OptIn::maybe_normalize_settings() for details
1224 - */
1225 - private function get_settings_by_cf7()
1226 - {
1227 - $form_id = $this->get_cf_form_id();
1228 -
1229 - $settings = get_post_meta($form_id, 'f12-cf7-doubleoptin', true);
1230 -
1231 - if (null == $settings || !is_array($settings)) {
1232 - return [];
1233 - }
1234 -
1235 - $settings = $this->maybe_normalize_settings($settings);
1236 -
1237 - return $settings;
1238 - }
1239 -
1240 - /**
1241 - * Retrieves the settings of a specific element using Elementor.
1242 - *
1243 - * @return array
1244 - * @since 2.4.0
1245 - *
1246 - * @see OptIn::maybe_normalize_settings() for details
1247 - */
1248 - private function get_settings_by_elementor()
1249 - {
1250 - $page_id = $this->get_cf_form_id();
1251 -
1252 - $post = get_post($page_id);
1253 -
1254 - // skip if post not existing anymore
1255 - if (null === $post) {
1256 - return [];
1257 - }
1258 -
1259 - // skip if page is not build with elementor
1260 - if (!\Elementor\Plugin::$instance->documents->get($page_id)->is_built_with_elementor()) {
1261 - return [];
1262 - }
1263 -
1264 - // Load the page's data
1265 - $document = \Elementor\Plugin::$instance->documents->get($page_id);
1266 -
1267 - // Skip if document not exist
1268 - if (!$document) {
1269 - return [];
1270 - }
1271 -
1272 - // Loop through the elements
1273 - $elements_data = $document->get_elements_data();
1274 -
1275 - $content = maybe_unserialize($this->get_content());
1276 -
1277 - // skip if form id not available
1278 - if (!isset($content['form_id'])) {
1279 - return [];
1280 - }
1281 -
1282 - $form_id = $content['form_id'];
1283 -
1284 - $form_element = null;
1285 -
1286 - // Find the Form Element
1287 - foreach ($elements_data as $element) {
1288 - if (isset($element['elements'])) {
1289 - foreach ($element['elements'] as $el) {
1290 - if ($form_id !== $el['id'] || 'form' !== $el['widgetType']) {
1291 - continue;
1292 - }
1293 -
1294 - $form_element = $el;
1295 - }
1296 - }
1297 -
1298 - // Check if this is the form widget
1299 - if (isset($element['widgetType']) && isset($element['id']) && $element['widgetType'] === 'form' && $element['id'] === $form_id) {
1300 - // Return the form settings
1301 - $form_element = $element;
1302 - }
1303 - }
1304 -
1305 - // skip if form element not found on the page anymore
1306 - if ($form_element == null || !isset($form_element['settings'])) {
1307 - return [];
1308 - }
1309 -
1310 - // Normalize Settings
1311 - return $this->maybe_normalize_settings($form_element['settings']);
1312 - }
1313 -
1314 - /**
1315 - * Normalizes the settings by converting the value of the settings
1316 - *
1317 - * @param array $settings The settings array to be normalized.
1318 - * @formatter:off
1319 - *
1320 - * @return array {
1321 - * The settings of the element if found or an empty array if nothing was found.
1322 - * @type string $doi_email_to
1323 - * @type string $doi_email_subject
1324 - * @type string $doi_email_content
1325 - * @type string $doi_email_from
1326 - * @type string $doi_email_from_name
1327 - * @type string $doi_email_template
1328 - * @type string $doi_email_body
1329 - * }
1330 - *
1331 - * @formatter:on
1332 - */
1333 - private function maybe_normalize_settings($settings)
1334 - {
1335 - if (isset($settings['sender'])) {
1336 - $settings['doi_email_from'] = $settings['sender'];
1337 - }
1338 -
1339 - if (isset($settings['sender_name'])) {
1340 - $settings['doi_email_from_name'] = $settings['sender_name'];
1341 - }
1342 -
1343 - if (isset($settings['subject'])) {
1344 - $settings['doi_email_subject'] = $settings['subject'];
1345 - }
1346 -
1347 - if (isset($settings['recipient'])) {
1348 - $settings['doi_email_to'] = $settings['recipient'];
1349 - }
1350 -
1351 - if (isset($settings['template'])) {
1352 - $settings['doi_email_template'] = $settings['template'];
1353 - }
1354 -
1355 - if (isset($settings['body'])) {
1356 - $settings['doi_email_body'] = $settings['body'];
1357 - }
1358 -
1359 - /**
1360 - * Normalize the settings between the different form plugins
1361 - *
1362 - * @formatter:off
1363 - *
1364 - * @param array {
1365 - * @type string $doi_email_to
1366 - * @type string $doi_email_subject
1367 - * @type string $doi_email_content
1368 - * @type string $doi_email_from
1369 - * @type string $doi_email_from_name
1370 - * @type string $doi_email_template
1371 - * @type string $doi_email_body
1372 - * }
1373 - *
1374 - * @formatter:on
1375 - *
1376 - * @since 3.0.0
1377 - */
1378 - return apply_filters('f12_cf7_doubleoption_normalize_settings', $settings);
1379 - }
1380 -
1381 - /**
1382 - * Set the Form as HTML
1383 - *
1384 - * @param string $form
1385 - *
1386 - * @return void
1387 - */
1388 - public function set_form($form)
1389 - {
1390 - $this->form = $form;
1391 - }
1392 -
1393 - /**
1394 - * Return the Form
1395 - *
1396 - * @return string
1397 - */
1398 - public function get_mail_optin()
1399 - {
1400 - return $this->mail_optin;
1401 - }
1402 -
1403 - /**
1404 - * Return the E-mail
1405 - *
1406 - * @return string
1407 - */
1408 - public function get_email()
1409 - {
1410 - return $this->email;
1411 - }
1412 -
1413 - /**
1414 - * Store the email
1415 - *
1416 - * @param string $email
1417 - *
1418 - * @return void
1419 - */
1420 - public function set_email($email)
1421 - {
1422 - $this->email = $email;
1423 - }
1424 -
1425 - /**
1426 - * Set the Form as HTML
1427 - *
1428 - * @param string $form
1429 - *
1430 - * @return void
1431 - */
1432 - public function set_mail_optin($mail_html)
1433 - {
1434 - $this->mail_optin = $mail_html;
1435 - }
1436 -
1437 - /**
1438 - * Return the link to the detail view
1439 - *
1440 - * @return string
1441 - */
1442 - public function get_link_ui()
1443 - {
1444 - return admin_url('admin.php?page=f12-cf7-doubleoptin_optin_view&hash=' . $this->get_hash());
1445 - }
1446 -
1447 - /**
1448 - * Return the link to delete
1449 - *
1450 - * @return string
1451 - */
1452 - public function get_link_delete()
1453 - {
1454 - return admin_url('admin.php?page=f12-cf7-doubleoptin&option=delete&hash=' . $this->get_hash());
1455 - }
1456 -
1457 - /**
1458 - * Return the link to the export
1459 - *
1460 - * @return string
1461 - */
1462 - public function get_link_export()
1463 - {
1464 - return admin_url('admin.php?page=f12-cf7-doubleoptin&export=csv&id=' . $this->get_id());
1465 - }
1466 -
1467 - /**
1468 - * Return the link to the export
1469 - *
1470 - * @return string
1471 - */
1472 - public function get_link_export_v2()
1473 - {
1474 - return admin_url('admin.php?page=f12-cf7-doubleoptin&export=csv_v2&id=' . $this->get_id());
1475 - }
1476 -
1477 - /**
1478 - * Return the link to the optin
1479 - *
1480 - * @return string
1481 - */
1482 - public function get_link_optin($parameter = [])
1483 - {
1484 - if ($this->get_cf_form_id() == 0) {
1485 - return get_home_url() . '?optin=' . $this->get_hash();
1486 - }
1487 -
1488 - $parameter = array_merge(CF7DoubleOptIn::getInstance()->getParameter($this->get_cf_form_id()), $parameter);
1489 -
1490 - $page_id = $parameter['page'] ?? -1;
1491 -
1492 - if ($page_id == -1 || !$page_id || $page_id == 0) {
1493 - $link = get_home_url() . '?optin=' . $this->get_hash();
1494 - } else {
1495 -
1496 - $link = get_permalink($page_id);
1497 -
1498 - if (strpos($link, "?") !== false) {
1499 - $link .= '&optin=' . $this->get_hash();
1500 - } else {
1501 - $link .= '?optin=' . $this->get_hash();
1502 - }
1503 - }
1504 -
1505 - return $link;
1506 - }
1507 -
1508 - /**
1509 - * Return the link to the optout
1510 - *
1511 - * @return string
1512 - */
1513 - public function get_link_optout()
1514 - {
1515 - $settings = CF7DoubleOptIn::getInstance()->getSettings();
1516 -
1517 - if (!isset($settings['optout_page']) || $settings['optout_page'] == 0) {
1518 - return get_home_url() . '?optout=' . $this->get_hash();
1519 - } else {
1520 - $url = get_permalink($settings['optout_page']);
1521 -
1522 - if (str_contains($url, '?')) {
1523 - // If '?' exists in the URL, it means there are already parameters, so append with '&'
1524 - $url .= '&optout=' . $this->get_hash();
1525 - } else {
1526 - // Else, there are no parameters yet, so append with '?'
1527 - $url .= '?optout=' . $this->get_hash();
1528 - }
1529 -
1530 - return $url;
1531 -
1532 - }
1533 - }
1534 - }
1535 -}
777 + return $result;
778 + }
779 +}