PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 3.0.72 3.1.0 All 34 releases
double-opt-in / src / Integration / FormData.php

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

521 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Form Data Value Object
4 *
5 * @package Forge12\DoubleOptIn\Integration
6 * @since 4.0.0
7 */
8
9 namespace Forge12\DoubleOptIn\Integration;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class FormData
17 *
18 * Immutable value object representing normalized form submission data.
19 * Provides factory methods for creating instances from various form systems.
20 */
21 final class FormData implements FormDataInterface {
22
23 /**
24 * The form ID.
25 *
26 * @var int
27 */
28 private int $formId;
29
30 /**
31 * The form type identifier.
32 *
33 * @var string
34 */
35 private string $formType;
36
37 /**
38 * Submitted form fields.
39 *
40 * @var array<string, mixed>
41 */
42 private array $fields;
43
44 /**
45 * Uploaded files.
46 *
47 * @var array<string, array>
48 */
49 private array $files;
50
51 /**
52 * Submission metadata.
53 *
54 * @var array<string, mixed>
55 */
56 private array $meta;
57
58 /**
59 * Recipient email address.
60 *
61 * @var string
62 */
63 private string $recipientEmail;
64
65 /**
66 * Form HTML content.
67 *
68 * @var string
69 */
70 private string $formHtml;
71
72 /**
73 * Private constructor - use factory methods.
74 *
75 * @param int $formId The form ID.
76 * @param string $formType The form type.
77 * @param array $fields The form fields.
78 * @param array $files The uploaded files.
79 * @param array $meta The metadata.
80 * @param string $recipientEmail The recipient email.
81 * @param string $formHtml The form HTML.
82 */
83 private function __construct(
84 int $formId,
85 string $formType,
86 array $fields,
87 array $files,
88 array $meta,
89 string $recipientEmail,
90 string $formHtml
91 ) {
92 $this->formId = $formId;
93 $this->formType = $formType;
94 $this->fields = $fields;
95 $this->files = $files;
96 $this->meta = $meta;
97 $this->recipientEmail = $recipientEmail;
98 $this->formHtml = $formHtml;
99 }
100
101 /**
102 * Create a new FormData instance.
103 *
104 * @param int $formId The form ID.
105 * @param string $formType The form type (e.g., 'cf7', 'avada').
106 * @param array $fields The submitted form fields.
107 * @param array $files The uploaded files (optional).
108 * @param array $meta The metadata (optional).
109 * @param string $recipientEmail The recipient email (optional).
110 * @param string $formHtml The form HTML (optional).
111 *
112 * @return self
113 */
114 public static function create(
115 int $formId,
116 string $formType,
117 array $fields,
118 array $files = array(),
119 array $meta = array(),
120 string $recipientEmail = '',
121 string $formHtml = ''
122 ): self {
123 // Set default metadata
124 $meta = array_merge(
125 array(
126 'source_url' => '',
127 'timestamp' => time(),
128 'ip_address' => '',
129 'user_agent' => '',
130 ),
131 $meta
132 );
133
134 return new self( $formId, $formType, $fields, $files, $meta, $recipientEmail, $formHtml );
135 }
136
137 /**
138 * Create FormData from a serialized array.
139 *
140 * @param array $data The serialized data array.
141 *
142 * @return self
143 */
144 public static function fromArray( array $data ): self {
145 return new self(
146 (int) ( $data['formId'] ?? $data['form_id'] ?? 0 ),
147 (string) ( $data['formType'] ?? $data['form_type'] ?? '' ),
148 (array) ( $data['fields'] ?? array() ),
149 (array) ( $data['files'] ?? array() ),
150 (array) ( $data['meta'] ?? array() ),
151 (string) ( $data['recipientEmail'] ?? $data['recipient_email'] ?? '' ),
152 (string) ( $data['formHtml'] ?? $data['form_html'] ?? '' )
153 );
154 }
155
156 /**
157 * Create FormData from CF7 submission.
158 *
159 * @param \WPCF7_ContactForm $form The CF7 form.
160 * @param \WPCF7_Submission $submission The CF7 submission.
161 *
162 * @return self
163 */
164 public static function fromCF7( $form, $submission ): self {
165 $postedData = $submission->get_posted_data();
166 $uploadedFiles = $submission->uploaded_files();
167
168 // Normalize files to array format
169 $files = array();
170 foreach ( $uploadedFiles as $key => $fileList ) {
171 $files[ $key ] = is_array( $fileList ) ? $fileList : array( $fileList );
172 }
173
174 $meta = array(
175 'source_url' => $submission->get_meta( 'url' ),
176 'timestamp' => $submission->get_meta( 'timestamp' ),
177 'ip_address' => $submission->get_meta( 'remote_ip' ),
178 'user_agent' => $submission->get_meta( 'user_agent' ),
179 );
180
181 return self::create(
182 $form->id(),
183 'cf7',
184 $postedData,
185 $files,
186 $meta,
187 '',
188 $form->form_html()
189 );
190 }
191
192 /**
193 * Create FormData from Avada form submission.
194 *
195 * @param int $formId The form post ID.
196 * @param array $formData The Avada form data array.
197 *
198 * @return self
199 */
200 public static function fromAvada( int $formId, array $formData ): self {
201 $fields = $formData['data'] ?? array();
202 $files = array();
203
204 // Handle attachments
205 if ( isset( $formData['form_parameter']['attachments'] ) ) {
206 $files['attachments'] = $formData['form_parameter']['attachments'];
207 }
208
209 $meta = array(
210 'source_url' => $formData['submission']['source_url'] ?? '',
211 'timestamp' => time(),
212 'field_labels' => $formData['field_labels'] ?? array(),
213 'field_types' => $formData['field_types'] ?? array(),
214 'hidden_field_names' => $formData['hidden_field_names'] ?? array(),
215 );
216
217 $formPost = get_post( $formId );
218 $formHtml = $formPost ? do_shortcode( $formPost->post_content ) : '';
219
220 return self::create(
221 $formId,
222 'avada',
223 $fields,
224 $files,
225 $meta,
226 '',
227 $formHtml
228 );
229 }
230
231 /**
232 * Create FormData from WPForms submission.
233 *
234 * @param int $formId The form ID.
235 * @param array $fields The submitted fields (sanitized).
236 * @param array $entry The original $_POST data.
237 * @param array $formData The form settings/data.
238 *
239 * @return self
240 */
241 public static function fromWPForms( int $formId, array $fields, array $entry, array $formData ): self {
242 // Normalize fields to simple key => value format.
243 //
244 // WPForms's `wpforms_process_complete` action passes `$fields` in
245 // a shape that varies between WPForms-Lite, WPForms-Pro, and
246 // some Pro versions in particular: the OUTER array key is
247 // sometimes the actual WPForms field id (1, 2, 3, 4 …), but on
248 // at least Pro 1.9.x it's a zero-based array index (0, 1, 2, 3)
249 // while each entry carries the real field id in `$field['id']`.
250 // User-reported 2026-05-13: the bare-key lookup
251 // `$normalizedFields[2]` returned the SUBJECT field (array index
252 // 2) instead of the EMAIL field (id 2), so resolveRecipient
253 // sanitized non-email text to '' and the DOI mail never sent.
254 //
255 // Use `$field['id']` when present so downstream lookups
256 // (resolveRecipient, validateConsentAcceptance, field_mapping)
257 // always see field-ID-keyed entries, regardless of which
258 // WPForms version generated the payload.
259 $normalizedFields = array();
260 foreach ( $fields as $arrayIndex => $field ) {
261 if ( is_array( $field ) ) {
262 $fieldId = isset( $field['id'] ) && $field['id'] !== ''
263 ? $field['id']
264 : $arrayIndex;
265 $normalizedFields[ $fieldId ] = $field['value'] ?? '';
266 $normalizedFields[ 'field_' . $fieldId ] = $field;
267 } else {
268 $normalizedFields[ $arrayIndex ] = $field;
269 }
270 }
271
272 // Extract files from fields
273 $files = array();
274 foreach ( $fields as $fieldId => $field ) {
275 if ( is_array( $field ) && isset( $field['type'] ) && $field['type'] === 'file-upload' ) {
276 if ( ! empty( $field['value_raw'] ) ) {
277 $files[ $fieldId ] = (array) $field['value_raw'];
278 }
279 }
280 }
281
282 $meta = array(
283 'source_url' => wp_get_referer() ?: '',
284 'timestamp' => time(),
285 'entry' => $entry,
286 'form_title' => $formData['settings']['form_title'] ?? '',
287 );
288
289 return self::create(
290 $formId,
291 'wpforms',
292 $normalizedFields,
293 $files,
294 $meta,
295 '',
296 ''
297 );
298 }
299
300 /**
301 * Create FormData from Gravity Forms submission.
302 *
303 * @param int $formId The form ID.
304 * @param array $entry The entry data.
305 * @param array $form The form object.
306 *
307 * @return self
308 */
309 public static function fromGravityForms( int $formId, array $entry, array $form ): self {
310 // Extract field values from entry
311 $fields = array();
312 $files = array();
313
314 if ( ! empty( $form['fields'] ) ) {
315 foreach ( $form['fields'] as $field ) {
316 $fieldId = $field->id;
317
318 // Handle file upload fields
319 if ( $field->type === 'fileupload' ) {
320 $value = $entry[ $fieldId ] ?? '';
321 if ( ! empty( $value ) ) {
322 $files[ $fieldId ] = is_array( $value ) ? $value : array( $value );
323 }
324 continue;
325 }
326
327 // Handle complex fields with inputs
328 if ( ! empty( $field->inputs ) && is_array( $field->inputs ) ) {
329 foreach ( $field->inputs as $input ) {
330 $inputId = $input['id'];
331 if ( isset( $entry[ $inputId ] ) ) {
332 $fields[ $inputId ] = $entry[ $inputId ];
333 }
334 }
335 } elseif ( isset( $entry[ $fieldId ] ) ) {
336 $fields[ $fieldId ] = $entry[ $fieldId ];
337 }
338 }
339 }
340
341 $meta = array(
342 'source_url' => $entry['source_url'] ?? '',
343 'timestamp' => strtotime( $entry['date_created'] ?? 'now' ),
344 'ip_address' => $entry['ip'] ?? '',
345 'user_agent' => $entry['user_agent'] ?? '',
346 'entry_id' => $entry['id'] ?? 0,
347 'form_title' => $form['title'] ?? '',
348 );
349
350 return self::create(
351 $formId,
352 'gravityforms',
353 $fields,
354 $files,
355 $meta,
356 '',
357 ''
358 );
359 }
360
361 /**
362 * {@inheritdoc}
363 */
364 public function getFormId(): int {
365 return $this->formId;
366 }
367
368 /**
369 * {@inheritdoc}
370 */
371 public function getFormType(): string {
372 return $this->formType;
373 }
374
375 /**
376 * {@inheritdoc}
377 */
378 public function getFields(): array {
379 return $this->fields;
380 }
381
382 /**
383 * {@inheritdoc}
384 */
385 public function getField( string $key, $default = null ) {
386 return $this->fields[ $key ] ?? $default;
387 }
388
389 /**
390 * {@inheritdoc}
391 */
392 public function hasField( string $key ): bool {
393 return array_key_exists( $key, $this->fields );
394 }
395
396 /**
397 * {@inheritdoc}
398 */
399 public function getFiles(): array {
400 return $this->files;
401 }
402
403 /**
404 * {@inheritdoc}
405 */
406 public function getMeta(): array {
407 return $this->meta;
408 }
409
410 /**
411 * {@inheritdoc}
412 */
413 public function getMetaValue( string $key, $default = null ) {
414 return $this->meta[ $key ] ?? $default;
415 }
416
417 /**
418 * {@inheritdoc}
419 */
420 public function getRecipientEmail(): string {
421 return $this->recipientEmail;
422 }
423
424 /**
425 * {@inheritdoc}
426 */
427 public function getFormHtml(): string {
428 return $this->formHtml;
429 }
430
431 /**
432 * {@inheritdoc}
433 */
434 public function toArray(): array {
435 return array(
436 'formId' => $this->formId,
437 'formType' => $this->formType,
438 'fields' => $this->fields,
439 'files' => $this->files,
440 'meta' => $this->meta,
441 'recipientEmail' => $this->recipientEmail,
442 'formHtml' => $this->formHtml,
443 );
444 }
445
446 /**
447 * {@inheritdoc}
448 */
449 public function withFields( array $fields ): FormDataInterface {
450 return new self(
451 $this->formId,
452 $this->formType,
453 array_merge( $this->fields, $fields ),
454 $this->files,
455 $this->meta,
456 $this->recipientEmail,
457 $this->formHtml
458 );
459 }
460
461 /**
462 * {@inheritdoc}
463 */
464 public function withMeta( array $meta ): FormDataInterface {
465 return new self(
466 $this->formId,
467 $this->formType,
468 $this->fields,
469 $this->files,
470 array_merge( $this->meta, $meta ),
471 $this->recipientEmail,
472 $this->formHtml
473 );
474 }
475
476 /**
477 * Create a new instance with a specific recipient email.
478 *
479 * @param string $email The recipient email.
480 *
481 * @return self
482 */
483 public function withRecipientEmail( string $email ): self {
484 return new self(
485 $this->formId,
486 $this->formType,
487 $this->fields,
488 $this->files,
489 $this->meta,
490 $email,
491 $this->formHtml
492 );
493 }
494
495 /**
496 * Serialize for storage in database.
497 *
498 * @return string Serialized representation.
499 */
500 public function serialize(): string {
501 return maybe_serialize( $this->toArray() );
502 }
503
504 /**
505 * Unserialize from database storage.
506 *
507 * @param string $data The serialized data.
508 *
509 * @return self|null The FormData instance or null if invalid.
510 */
511 public static function unserialize( string $data ): ?self {
512 $unserialized = maybe_unserialize( $data );
513
514 if ( ! is_array( $unserialized ) ) {
515 return null;
516 }
517
518 return self::fromArray( $unserialized );
519 }
520 }
521