PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 6.1.6
Contact Form 7 v6.1.6
6.1.6 5.0.2 5.0.3 5.0.4 5.0.5 5.1 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.2 5.2.1 5.2.2 5.3 5.3.1 5.3.2 5.4 5.4.1 5.4.2 5.5 5.5.1 5.5.2 5.5.3 5.5.4 5.5.5 5.5.6 5.5.6.1 5.6 5.6.1 5.6.2 5.6.3 5.6.4 5.7 5.7.1 5.7.2 5.7.3 5.7.4 5.7.5 5.7.5.1 5.7.6 5.7.7 5.8 5.8.1 5.8.2 5.8.3 5.8.4 5.8.5 5.8.6 5.8.7 5.9 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 6.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 trunk 1.1 1.10 1.10.0.1 1.10.1 1.2 1.3 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.5 1.6 1.6.1 1.7 1.7.1 1.7.2 1.7.4 1.7.5 1.7.6 1.7.6.1 1.7.7 1.7.7.1 1.7.8 1.8 1.8.0.1 1.8.0.2 1.8.0.3 1.8.0.4 1.8.1 1.8.1.1 1.9 1.9.1 1.9.2 1.9.2.1 1.9.2.2 1.9.3 1.9.4 1.9.5 1.9.5.1 2.0 2.0-beta 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1 2.1.1 2.1.2 2.2 2.2.1 2.3 2.3.1 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 3.0 3.0-beta 3.0.1 3.0.2 3.0.2.1 3.1 3.1.1 3.1.2 3.2 3.2.1 3.3 3.3.1 3.3.2 3.3.3 3.4 3.4.1 3.4.2 3.5 3.5.1 3.5.2 3.5.3 3.5.4 3.6 3.7 3.7.1 3.7.2 3.8 3.8.1 3.9 3.9-beta 3.9.1 3.9.2 3.9.3 4.0 4.0.1 4.0.2 4.0.3 4.1 4.1-beta 4.1.1 4.1.2 4.2 4.2-beta 4.2.1 4.2.2 4.3 4.3.1 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6 4.6.1 4.7 4.8 4.8.1 4.9 4.9.1 4.9.2 5.0 5.0.1
contact-form-7 / includes / submission.php
contact-form-7 / includes Last commit date
block-editor 1 year ago config-validator 11 months ago css 2 weeks ago js 1 year ago swv 9 months ago capabilities.php 7 years ago contact-form-functions.php 11 months ago contact-form-template.php 11 months ago contact-form.php 11 months ago controller.php 11 months ago file.php 9 months ago filesystem.php 11 months ago form-tag.php 7 months ago form-tags-manager.php 11 months ago formatting.php 11 months ago functions.php 9 months ago html-formatter.php 9 months ago integration.php 11 months ago l10n.php 11 months ago mail-tag.php 11 months ago mail.php 3 months ago pipe.php 11 months ago pocket-holder.php 3 years ago rest-api.php 11 months ago shortcodes.php 11 months ago special-mail-tags.php 9 months ago submission.php 9 months ago upgrade.php 11 months ago validation-functions.php 11 months ago validation.php 11 months ago
submission.php
928 lines
1 <?php
2
3 /**
4 * Class representing contact form submission.
5 */
6 class WPCF7_Submission {
7
8 use WPCF7_PocketHolder;
9
10 private static $instance;
11
12 private $contact_form;
13 private $status = 'init';
14 private $posted_data = array();
15 private $posted_data_hash = null;
16 private $skip_spam_check = false;
17 private $uploaded_files = array();
18 private $extra_attachments = array();
19 private $skip_mail = false;
20 private $response = '';
21 private $invalid_fields = array();
22 private $meta = array();
23 private $consent = array();
24 private $spam_log = array();
25 private $result_props = array();
26
27
28 /**
29 * Returns the singleton instance of this class.
30 */
31 public static function get_instance( $contact_form = null, $options = '' ) {
32 if ( $contact_form instanceof WPCF7_ContactForm ) {
33 if ( empty( self::$instance ) ) {
34 self::$instance = new self( $contact_form, $options );
35 self::$instance->proceed();
36 return self::$instance;
37 } else {
38 return null;
39 }
40 } else {
41 if ( empty( self::$instance ) ) {
42 return null;
43 } else {
44 return self::$instance;
45 }
46 }
47 }
48
49
50 /**
51 * Returns true if this submission is created via WP REST API.
52 */
53 public static function is_restful() {
54 return wp_is_serving_rest_request();
55 }
56
57
58 /**
59 * Constructor.
60 */
61 private function __construct( WPCF7_ContactForm $contact_form, $options = '' ) {
62 $options = wp_parse_args( $options, array(
63 'skip_mail' => false,
64 ) );
65
66 $this->contact_form = $contact_form;
67 $this->skip_mail = (bool) $options['skip_mail'];
68 }
69
70
71 /**
72 * Destructor.
73 */
74 public function __destruct() {
75 $this->remove_uploaded_files();
76 }
77
78
79 /**
80 * The main logic of submission.
81 */
82 private function proceed() {
83
84 $callback = function () {
85 $contact_form = $this->contact_form;
86
87 $this->setup_meta_data();
88 $this->setup_posted_data();
89
90 if ( $this->is( 'init' ) and ! $this->validate() ) {
91 $this->set_status( 'validation_failed' );
92 $this->set_response( $contact_form->message( 'validation_error' ) );
93 }
94
95 if ( $this->is( 'init' ) and ! $this->accepted() ) {
96 $this->set_status( 'acceptance_missing' );
97 $this->set_response( $contact_form->message( 'accept_terms' ) );
98 }
99
100 if ( $this->is( 'init' ) and $this->spam() ) {
101 $this->set_status( 'spam' );
102 $this->set_response( $contact_form->message( 'spam' ) );
103 }
104
105 if ( $this->is( 'init' ) and ! $this->unship_uploaded_files() ) {
106 $this->set_status( 'validation_failed' );
107 $this->set_response( $contact_form->message( 'validation_error' ) );
108 }
109
110 if ( $this->is( 'init' ) ) {
111 $abort = ! $this->before_send_mail();
112
113 if ( $abort ) {
114 if ( $this->is( 'init' ) ) {
115 $this->set_status( 'aborted' );
116 }
117
118 if ( '' === $this->get_response() ) {
119 $this->set_response( $contact_form->filter_message(
120 __( 'Sending mail has been aborted.', 'contact-form-7' ) )
121 );
122 }
123 } elseif ( $this->mail() ) {
124 $this->set_status( 'mail_sent' );
125 $this->set_response( $contact_form->message( 'mail_sent_ok' ) );
126
127 do_action( 'wpcf7_mail_sent', $contact_form );
128 } else {
129 $this->set_status( 'mail_failed' );
130 $this->set_response( $contact_form->message( 'mail_sent_ng' ) );
131
132 do_action( 'wpcf7_mail_failed', $contact_form );
133 }
134 }
135 };
136
137 wpcf7_switch_locale( $this->contact_form->locale(), $callback );
138 }
139
140
141 /**
142 * Returns the current status property.
143 */
144 public function get_status() {
145 return $this->status;
146 }
147
148
149 /**
150 * Sets the status property.
151 *
152 * @param string $status The status.
153 */
154 public function set_status( $status ) {
155 if ( preg_match( '/^[a-z][0-9a-z_]+$/', $status ) ) {
156 $this->status = $status;
157 return true;
158 }
159
160 return false;
161 }
162
163
164 /**
165 * Returns true if the specified status is identical to the current
166 * status property.
167 *
168 * @param string $status The status to compare.
169 */
170 public function is( $status ) {
171 return $this->status === $status;
172 }
173
174
175 /**
176 * Returns an associative array of submission result properties.
177 *
178 * @return array Submission result properties.
179 */
180 public function get_result() {
181 $result = array_merge( $this->result_props, array(
182 'status' => $this->get_status(),
183 'message' => $this->get_response(),
184 ) );
185
186 if ( $this->is( 'validation_failed' ) ) {
187 $result['invalid_fields'] = $this->get_invalid_fields();
188 }
189
190 switch ( $this->get_status() ) {
191 case 'init':
192 case 'validation_failed':
193 case 'acceptance_missing':
194 case 'spam':
195 $result['posted_data_hash'] = '';
196 break;
197 default:
198 $result['posted_data_hash'] = $this->get_posted_data_hash();
199 break;
200 }
201
202 $result = apply_filters( 'wpcf7_submission_result', $result, $this );
203
204 return $result;
205 }
206
207
208 /**
209 * Adds items to the array of submission result properties.
210 *
211 * @param string|array|object $data Value to add to result properties.
212 * @return array Added result properties.
213 */
214 public function add_result_props( $data = '' ) {
215 $data = wp_parse_args( $data, array() );
216
217 $this->result_props = array_merge( $this->result_props, $data );
218
219 return $data;
220 }
221
222
223 /**
224 * Retrieves the response property.
225 *
226 * @return string The current response property value.
227 */
228 public function get_response() {
229 return $this->response;
230 }
231
232
233 /**
234 * Sets the response property.
235 *
236 * @param string $response New response property value.
237 */
238 public function set_response( $response ) {
239 $this->response = $response;
240 return true;
241 }
242
243
244 /**
245 * Retrieves the contact form property.
246 *
247 * @return WPCF7_ContactForm A contact form object.
248 */
249 public function get_contact_form() {
250 return $this->contact_form;
251 }
252
253
254 /**
255 * Search an invalid field by field name.
256 *
257 * @param string $name The field name.
258 * @return array|bool An associative array of validation error
259 * or false when no invalid field.
260 */
261 public function get_invalid_field( $name ) {
262 return $this->invalid_fields[$name] ?? false;
263 }
264
265
266 /**
267 * Retrieves all invalid fields.
268 *
269 * @return array Invalid fields.
270 */
271 public function get_invalid_fields() {
272 return $this->invalid_fields;
273 }
274
275
276 /**
277 * Retrieves meta information.
278 *
279 * @param string $name Name of the meta information.
280 * @return string|null The meta information of the given name if it exists,
281 * null otherwise.
282 */
283 public function get_meta( $name ) {
284 return $this->meta[$name] ?? null;
285 }
286
287
288 /**
289 * Collects meta information about this submission.
290 */
291 private function setup_meta_data() {
292 $this->meta = array(
293 'timestamp' => time(),
294 'remote_ip' => $this->get_remote_ip_addr(),
295 'remote_port' => wpcf7_superglobal_server( 'REMOTE_PORT' ),
296 'user_agent' => wpcf7_superglobal_server( 'HTTP_USER_AGENT' ),
297 'url' => $this->get_request_url(),
298 'unit_tag' => wpcf7_sanitize_unit_tag(
299 wpcf7_superglobal_post( '_wpcf7_unit_tag' )
300 ),
301 'container_post_id' => absint(
302 wpcf7_superglobal_post( '_wpcf7_container_post' )
303 ),
304 'current_user_id' => get_current_user_id(),
305 'do_not_store' => $this->contact_form->is_true( 'do_not_store' ),
306 );
307
308 return $this->meta;
309 }
310
311
312 /**
313 * Retrieves user input data through this submission.
314 *
315 * @param string $name Optional field name.
316 * @return string|array|null The user input of the field, or array of all
317 * fields values if no field name specified.
318 */
319 public function get_posted_data( $name = '' ) {
320 if ( ! empty( $name ) ) {
321 return $this->posted_data[$name] ?? null;
322 }
323
324 return $this->posted_data;
325 }
326
327
328 /**
329 * Retrieves a user input string value through the specified field.
330 *
331 * @param string $name Field name.
332 * @return string The user input. If the input is an array,
333 * the first item in the array.
334 */
335 public function get_posted_string( $name ) {
336 $data = $this->get_posted_data( $name );
337 $data = wpcf7_array_flatten( $data );
338
339 if ( empty( $data ) ) {
340 return '';
341 }
342
343 // Returns the first array item.
344 return trim( reset( $data ) );
345 }
346
347
348 /**
349 * Constructs posted data property based on user input values.
350 */
351 private function setup_posted_data() {
352 $posted_data = array_filter(
353 (array) $_POST,
354 static function ( $key ) {
355 return ! str_starts_with( $key, '_' );
356 },
357 ARRAY_FILTER_USE_KEY
358 );
359
360 $posted_data = wp_unslash( $posted_data );
361 $posted_data = $this->sanitize_posted_data( $posted_data );
362
363 $tags = $this->contact_form->scan_form_tags( array(
364 'feature' => array(
365 'name-attr',
366 '! not-for-mail',
367 ),
368 ) );
369
370 $tags = array_reduce( $tags, static function ( $carry, $tag ) {
371 if ( $tag->name and ! isset( $carry[$tag->name] ) ) {
372 $carry[$tag->name] = $tag;
373 }
374
375 return $carry;
376 }, array() );
377
378 foreach ( $tags as $tag ) {
379 $value_orig = $value = $posted_data[$tag->name] ?? '';
380
381 if ( wpcf7_form_tag_supports( $tag->type, 'selectable-values' ) ) {
382 $value = ( '' === $value ) ? array() : (array) $value;
383
384 if ( WPCF7_USE_PIPE ) {
385 $pipes = $this->contact_form->get_pipes( $tag->name );
386
387 $value = array_map( static function ( $value ) use ( $pipes ) {
388 return $pipes->do_pipe( $value );
389 }, $value );
390 }
391 }
392
393 $value = apply_filters( "wpcf7_posted_data_{$tag->type}",
394 $value,
395 $value_orig,
396 $tag
397 );
398
399 $posted_data[$tag->name] = $value;
400
401 if ( $tag->has_option( 'consent_for:storage' ) and empty( $value ) ) {
402 $this->meta['do_not_store'] = true;
403 }
404 }
405
406 $this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data );
407
408 $this->posted_data_hash = $this->create_posted_data_hash();
409
410 return $this->posted_data;
411 }
412
413
414 /**
415 * Sanitizes user input data.
416 */
417 private function sanitize_posted_data( $value ) {
418 return map_deep( $value, static function ( $val ) {
419 $val = (string) $val;
420 $val = wp_check_invalid_utf8( $val );
421 $val = wp_kses_no_null( $val );
422 $val = wpcf7_strip_whitespaces( $val );
423 return $val;
424 } );
425 }
426
427
428 /**
429 * Returns the time-dependent variable for hash creation.
430 *
431 * @return float Float value rounded up to the next highest integer.
432 */
433 private function posted_data_hash_tick() {
434 return ceil( time() / ( HOUR_IN_SECONDS / 2 ) );
435 }
436
437
438 /**
439 * Creates a hash string based on posted data, the remote IP address,
440 * contact form location, and window of time.
441 *
442 * @param string $tick Optional. If not specified, result of
443 * posted_data_hash_tick() will be used.
444 * @return string The hash.
445 */
446 private function create_posted_data_hash( $tick = '' ) {
447 if ( '' === $tick ) {
448 $tick = $this->posted_data_hash_tick();
449 }
450
451 $hash = wp_hash(
452 wpcf7_flat_join( array_merge(
453 array(
454 $tick,
455 $this->get_meta( 'remote_ip' ),
456 $this->get_meta( 'unit_tag' ),
457 ),
458 $this->posted_data
459 ) ),
460 'wpcf7_submission'
461 );
462
463 return $hash;
464 }
465
466
467 /**
468 * Returns the hash string created for this submission.
469 *
470 * @return string The current hash for the submission.
471 */
472 public function get_posted_data_hash() {
473 return $this->posted_data_hash;
474 }
475
476
477 /**
478 * Verifies that the given string is equivalent to the posted data hash.
479 *
480 * @param string $hash Optional. This value will be compared to the
481 * current posted data hash for the submission. If not
482 * specified, the value of $_POST['_wpcf7_posted_data_hash']
483 * will be used.
484 * @return int|bool 1 if $hash is created 0-30 minutes ago,
485 * 2 if $hash is created 30-60 minutes ago,
486 * false if $hash is invalid.
487 */
488 public function verify_posted_data_hash( $hash = '' ) {
489 if ( '' === $hash ) {
490 $hash = wpcf7_superglobal_post( '_wpcf7_posted_data_hash' );
491 }
492
493 if ( '' === $hash ) {
494 return false;
495 }
496
497 $tick = $this->posted_data_hash_tick();
498
499 // Hash created 0-30 minutes ago.
500 $expected_1 = $this->create_posted_data_hash( $tick );
501
502 if ( hash_equals( $expected_1, $hash ) ) {
503 return 1;
504 }
505
506 // Hash created 30-60 minutes ago.
507 $expected_2 = $this->create_posted_data_hash( $tick - 1 );
508
509 if ( hash_equals( $expected_2, $hash ) ) {
510 return 2;
511 }
512
513 return false;
514 }
515
516
517 /**
518 * Retrieves the remote IP address of this submission.
519 */
520 private function get_remote_ip_addr() {
521 $ip_addr = wpcf7_superglobal_server( 'REMOTE_ADDR' );
522
523 if ( ! WP_Http::is_ip_address( $ip_addr ) ) {
524 $ip_addr = '';
525 }
526
527 return apply_filters( 'wpcf7_remote_ip_addr', $ip_addr );
528 }
529
530
531 /**
532 * Retrieves the request URL of this submission.
533 */
534 private function get_request_url() {
535 $home_url = untrailingslashit( home_url() );
536
537 if ( self::is_restful() ) {
538 $referer = wpcf7_superglobal_server( 'HTTP_REFERER' );
539
540 if ( $referer and str_starts_with( $referer, $home_url ) ) {
541 return sanitize_url( $referer );
542 }
543 }
544
545 $url = preg_replace( '%(?<!:|/)/.*$%', '', $home_url )
546 . wpcf7_get_request_uri();
547
548 return $url;
549 }
550
551
552 /**
553 * Runs user input validation.
554 *
555 * @return bool True if no invalid field is found.
556 */
557 private function validate() {
558 if ( $this->invalid_fields ) {
559 return false;
560 }
561
562 $result = new WPCF7_Validation();
563
564 $this->contact_form->validate_schema(
565 array(
566 'text' => true,
567 'file' => false,
568 'field' => array(),
569 ),
570 $result
571 );
572
573 $tags = $this->contact_form->scan_form_tags( array(
574 'feature' => '! file-uploading',
575 ) );
576
577 foreach ( $tags as $tag ) {
578 $type = $tag->type;
579 $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag );
580 }
581
582 $result = apply_filters( 'wpcf7_validate', $result, $tags );
583
584 $this->invalid_fields = $result->get_invalid_fields();
585
586 return $result->is_valid();
587 }
588
589
590 /**
591 * Returns true if user consent is obtained.
592 */
593 private function accepted() {
594 return apply_filters( 'wpcf7_acceptance', true, $this );
595 }
596
597
598 /**
599 * Adds user consent data to this submission.
600 *
601 * @param string $name Field name.
602 * @param string $conditions Conditions of consent.
603 */
604 public function add_consent( $name, $conditions ) {
605 $this->consent[$name] = $conditions;
606 return true;
607 }
608
609
610 /**
611 * Collects user consent data.
612 *
613 * @return array User consent data.
614 */
615 public function collect_consent() {
616 return (array) $this->consent;
617 }
618
619
620 /**
621 * Executes spam protections.
622 *
623 * @return bool True if spam captured.
624 */
625 private function spam() {
626 $spam = false;
627
628 $skip_spam_check = apply_filters( 'wpcf7_skip_spam_check',
629 $this->skip_spam_check,
630 $this
631 );
632
633 if ( $skip_spam_check ) {
634 return $spam;
635 }
636
637 if (
638 $this->contact_form->is_true( 'subscribers_only' ) and
639 current_user_can( 'wpcf7_submit', $this->contact_form->id() )
640 ) {
641 return $spam;
642 }
643
644 $user_agent = (string) $this->get_meta( 'user_agent' );
645
646 if ( strlen( $user_agent ) < 2 ) {
647 $spam = true;
648
649 $this->add_spam_log( array(
650 'agent' => 'wpcf7',
651 'reason' => __( 'User-Agent string is unnaturally short.', 'contact-form-7' ),
652 ) );
653 }
654
655 if ( ! $this->verify_nonce() ) {
656 $spam = true;
657
658 $this->add_spam_log( array(
659 'agent' => 'wpcf7',
660 'reason' => __( 'Submitted nonce is invalid.', 'contact-form-7' ),
661 ) );
662 }
663
664 return apply_filters( 'wpcf7_spam', $spam, $this );
665 }
666
667
668 /**
669 * Adds a spam log.
670 *
671 * @link https://contactform7.com/2019/05/31/why-is-this-message-marked-spam/
672 */
673 public function add_spam_log( $data = '' ) {
674 $data = wp_parse_args( $data, array(
675 'agent' => '',
676 'reason' => '',
677 ) );
678
679 $this->spam_log[] = $data;
680 }
681
682
683 /**
684 * Retrieves the spam logging data.
685 *
686 * @return array Spam logging data.
687 */
688 public function get_spam_log() {
689 return $this->spam_log;
690 }
691
692
693 /**
694 * Verifies that a correct security nonce was used.
695 */
696 private function verify_nonce() {
697 if ( ! $this->contact_form->nonce_is_active() or ! is_user_logged_in() ) {
698 return true;
699 }
700
701 $nonce = wpcf7_superglobal_post( '_wpnonce' );
702
703 return wpcf7_verify_nonce( $nonce );
704 }
705
706
707 /**
708 * Function called just before sending email.
709 */
710 private function before_send_mail() {
711 $abort = false;
712
713 do_action_ref_array( 'wpcf7_before_send_mail', array(
714 $this->contact_form,
715 &$abort,
716 $this,
717 ) );
718
719 return ! $abort;
720 }
721
722
723 /**
724 * Sends emails based on user input values and contact form email templates.
725 */
726 private function mail() {
727 $contact_form = $this->contact_form;
728
729 $skip_mail = apply_filters( 'wpcf7_skip_mail',
730 $this->skip_mail, $contact_form
731 );
732
733 if ( $skip_mail ) {
734 return true;
735 }
736
737 $result = WPCF7_Mail::send( $contact_form->prop( 'mail' ), 'mail' );
738
739 if ( $result ) {
740 $additional_mail = array();
741
742 if (
743 $mail_2 = $contact_form->prop( 'mail_2' ) and
744 $mail_2['active']
745 ) {
746 $additional_mail['mail_2'] = $mail_2;
747 }
748
749 $additional_mail = apply_filters( 'wpcf7_additional_mail',
750 $additional_mail, $contact_form
751 );
752
753 foreach ( $additional_mail as $name => $template ) {
754 WPCF7_Mail::send( $template, $name );
755 }
756
757 return true;
758 }
759
760 return false;
761 }
762
763
764 /**
765 * Retrieves files uploaded through this submission.
766 */
767 public function uploaded_files() {
768 return $this->uploaded_files;
769 }
770
771
772 /**
773 * Adds a file to the uploaded files array.
774 *
775 * @param string $name Field name.
776 * @param string|array $file_path File path or array of file paths.
777 */
778 private function add_uploaded_file( $name, $file_path ) {
779 if ( ! wpcf7_is_name( $name ) ) {
780 return false;
781 }
782
783 $paths = (array) $file_path;
784 $uploaded_files = array();
785 $hash_strings = array();
786
787 foreach ( $paths as $path ) {
788 if ( @is_file( $path ) and @is_readable( $path ) ) {
789 $uploaded_files[] = $path;
790 $hash_strings[] = hash_file( 'sha256', $path );
791 }
792 }
793
794 $this->uploaded_files[$name] = $uploaded_files;
795
796 if ( empty( $this->posted_data[$name] ) ) {
797 $this->posted_data[$name] = implode( ' ', $hash_strings );
798 }
799 }
800
801
802 /**
803 * Removes uploaded files.
804 */
805 private function remove_uploaded_files() {
806 $filesystem = WPCF7_Filesystem::get_instance();
807
808 foreach ( (array) $this->uploaded_files as $file_path ) {
809 foreach ( (array) $file_path as $path ) {
810 if ( wpcf7_is_file_path_in_content_dir( $path ) ) {
811 wpcf7_rmdir_p( $path );
812
813 // Remove parent dir if empty.
814 $filesystem->delete( dirname( $path ), false );
815 }
816 }
817 }
818 }
819
820
821 /**
822 * Moves uploaded files to the tmp directory and validates them.
823 *
824 * @return bool True if no invalid file is found.
825 */
826 private function unship_uploaded_files() {
827 $result = new WPCF7_Validation();
828
829 $tags = $this->contact_form->scan_form_tags( array(
830 'feature' => 'file-uploading',
831 ) );
832
833 foreach ( $tags as $tag ) {
834 if ( empty( $_FILES[$tag->name] ) ) {
835 continue;
836 }
837
838 $file = $_FILES[$tag->name];
839
840 $options = array(
841 'tag' => $tag,
842 'name' => $tag->name,
843 'required' => $tag->is_required(),
844 'filetypes' => $tag->get_option( 'filetypes' ),
845 'limit' => $tag->get_limit_option(),
846 'schema' => $this->contact_form->get_schema(),
847 );
848
849 $new_files = wpcf7_unship_uploaded_file( $file, $options );
850
851 if ( is_wp_error( $new_files ) ) {
852 $result->invalidate( $tag, $new_files );
853 } else {
854 $this->add_uploaded_file( $tag->name, $new_files );
855 }
856
857 $result = apply_filters(
858 "wpcf7_validate_{$tag->type}",
859 $result, $tag,
860 array(
861 'uploaded_files' => $new_files,
862 )
863 );
864 }
865
866 $this->invalid_fields = $result->get_invalid_fields();
867
868 return $result->is_valid();
869 }
870
871
872 /**
873 * Adds extra email attachment files that are independent from form fields.
874 *
875 * @param string|array $file_path A file path or an array of file paths.
876 * @param string $template Optional. The name of the template to which
877 * the files are attached.
878 * @return bool True if it succeeds to attach a file at least,
879 * or false otherwise.
880 */
881 public function add_extra_attachments( $file_path, $template = 'mail' ) {
882 if ( ! did_action( 'wpcf7_before_send_mail' ) ) {
883 return false;
884 }
885
886 $extra_attachments = array();
887
888 foreach ( (array) $file_path as $path ) {
889 $path = path_join( WP_CONTENT_DIR, $path );
890
891 if ( file_exists( $path ) ) {
892 $extra_attachments[] = $path;
893 }
894 }
895
896 if ( empty( $extra_attachments ) ) {
897 return false;
898 }
899
900 if ( ! isset( $this->extra_attachments[$template] ) ) {
901 $this->extra_attachments[$template] = array();
902 }
903
904 $this->extra_attachments[$template] = array_merge(
905 $this->extra_attachments[$template],
906 $extra_attachments
907 );
908
909 return true;
910 }
911
912
913 /**
914 * Returns extra email attachment files.
915 *
916 * @param string $template An email template name.
917 * @return array Array of file paths.
918 */
919 public function extra_attachments( $template ) {
920 if ( isset( $this->extra_attachments[$template] ) ) {
921 return (array) $this->extra_attachments[$template];
922 }
923
924 return array();
925 }
926
927 }
928