PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 6.0.1
Contact Form 7 v6.0.1
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 1 year ago css 2 years ago js 1 year ago swv 1 year ago capabilities.php 7 years ago contact-form-functions.php 1 year ago contact-form-template.php 2 years ago contact-form.php 1 year ago controller.php 1 year ago file.php 1 year ago form-tag.php 2 years ago form-tags-manager.php 3 years ago formatting.php 1 year ago functions.php 1 year ago html-formatter.php 1 year ago integration.php 2 years ago l10n.php 3 years ago mail-tag.php 2 years ago mail.php 1 year ago pipe.php 2 years ago pocket-holder.php 3 years ago rest-api.php 2 years ago shortcodes.php 3 years ago special-mail-tags.php 1 year ago submission.php 1 year ago upgrade.php 2 years ago validation-functions.php 1 year ago validation.php 3 years ago
submission.php
925 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' => $_SERVER['REMOTE_PORT'] ?? '',
296 'user_agent' => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 254 ),
297 'url' => $this->get_request_url(),
298 'unit_tag' => wpcf7_sanitize_unit_tag( $_POST['_wpcf7_unit_tag'] ?? '' ),
299 'container_post_id' => absint( $_POST['_wpcf7_container_post'] ?? 0 ),
300 'current_user_id' => get_current_user_id(),
301 'do_not_store' => $this->contact_form->is_true( 'do_not_store' ),
302 );
303
304 return $this->meta;
305 }
306
307
308 /**
309 * Retrieves user input data through this submission.
310 *
311 * @param string $name Optional field name.
312 * @return string|array|null The user input of the field, or array of all
313 * fields values if no field name specified.
314 */
315 public function get_posted_data( $name = '' ) {
316 if ( ! empty( $name ) ) {
317 return $this->posted_data[$name] ?? null;
318 }
319
320 return $this->posted_data;
321 }
322
323
324 /**
325 * Retrieves a user input string value through the specified field.
326 *
327 * @param string $name Field name.
328 * @return string The user input. If the input is an array,
329 * the first item in the array.
330 */
331 public function get_posted_string( $name ) {
332 $data = $this->get_posted_data( $name );
333 $data = wpcf7_array_flatten( $data );
334
335 if ( empty( $data ) ) {
336 return '';
337 }
338
339 // Returns the first array item.
340 return trim( reset( $data ) );
341 }
342
343
344 /**
345 * Constructs posted data property based on user input values.
346 */
347 private function setup_posted_data() {
348 $posted_data = array_filter(
349 (array) $_POST,
350 static function ( $key ) {
351 return ! str_starts_with( $key, '_' );
352 },
353 ARRAY_FILTER_USE_KEY
354 );
355
356 $posted_data = wp_unslash( $posted_data );
357 $posted_data = $this->sanitize_posted_data( $posted_data );
358
359 $tags = $this->contact_form->scan_form_tags( array(
360 'feature' => array(
361 'name-attr',
362 '! not-for-mail',
363 ),
364 ) );
365
366 $tags = array_reduce( $tags, static function ( $carry, $tag ) {
367 if ( $tag->name and ! isset( $carry[$tag->name] ) ) {
368 $carry[$tag->name] = $tag;
369 }
370
371 return $carry;
372 }, array() );
373
374 foreach ( $tags as $tag ) {
375 $value_orig = $value = $posted_data[$tag->name] ?? '';
376
377 if ( wpcf7_form_tag_supports( $tag->type, 'selectable-values' ) ) {
378 $value = ( '' === $value ) ? array() : (array) $value;
379
380 if ( WPCF7_USE_PIPE ) {
381 $pipes = $this->contact_form->get_pipes( $tag->name );
382
383 $value = array_map( static function ( $value ) use ( $pipes ) {
384 return $pipes->do_pipe( $value );
385 }, $value );
386 }
387 }
388
389 $value = apply_filters( "wpcf7_posted_data_{$tag->type}",
390 $value,
391 $value_orig,
392 $tag
393 );
394
395 $posted_data[$tag->name] = $value;
396
397 if ( $tag->has_option( 'consent_for:storage' ) and empty( $value ) ) {
398 $this->meta['do_not_store'] = true;
399 }
400 }
401
402 $this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data );
403
404 $this->posted_data_hash = $this->create_posted_data_hash();
405
406 return $this->posted_data;
407 }
408
409
410 /**
411 * Sanitizes user input data.
412 */
413 private function sanitize_posted_data( $value ) {
414 if ( is_array( $value ) ) {
415 $value = array_map( array( $this, 'sanitize_posted_data' ), $value );
416 } elseif ( is_string( $value ) ) {
417 $value = wp_check_invalid_utf8( $value );
418 $value = wp_kses_no_null( $value );
419 $value = wpcf7_strip_whitespaces( $value );
420 }
421
422 return $value;
423 }
424
425
426 /**
427 * Returns the time-dependent variable for hash creation.
428 *
429 * @return float Float value rounded up to the next highest integer.
430 */
431 private function posted_data_hash_tick() {
432 return ceil( time() / ( HOUR_IN_SECONDS / 2 ) );
433 }
434
435
436 /**
437 * Creates a hash string based on posted data, the remote IP address,
438 * contact form location, and window of time.
439 *
440 * @param string $tick Optional. If not specified, result of
441 * posted_data_hash_tick() will be used.
442 * @return string The hash.
443 */
444 private function create_posted_data_hash( $tick = '' ) {
445 if ( '' === $tick ) {
446 $tick = $this->posted_data_hash_tick();
447 }
448
449 $hash = wp_hash(
450 wpcf7_flat_join( array_merge(
451 array(
452 $tick,
453 $this->get_meta( 'remote_ip' ),
454 $this->get_meta( 'unit_tag' ),
455 ),
456 $this->posted_data
457 ) ),
458 'wpcf7_submission'
459 );
460
461 return $hash;
462 }
463
464
465 /**
466 * Returns the hash string created for this submission.
467 *
468 * @return string The current hash for the submission.
469 */
470 public function get_posted_data_hash() {
471 return $this->posted_data_hash;
472 }
473
474
475 /**
476 * Verifies that the given string is equivalent to the posted data hash.
477 *
478 * @param string $hash Optional. This value will be compared to the
479 * current posted data hash for the submission. If not
480 * specified, the value of $_POST['_wpcf7_posted_data_hash']
481 * will be used.
482 * @return int|bool 1 if $hash is created 0-30 minutes ago,
483 * 2 if $hash is created 30-60 minutes ago,
484 * false if $hash is invalid.
485 */
486 public function verify_posted_data_hash( $hash = '' ) {
487 if ( '' === $hash and ! empty( $_POST['_wpcf7_posted_data_hash'] ) ) {
488 $hash = trim( $_POST['_wpcf7_posted_data_hash'] );
489 }
490
491 if ( '' === $hash ) {
492 return false;
493 }
494
495 $tick = $this->posted_data_hash_tick();
496
497 // Hash created 0-30 minutes ago.
498 $expected_1 = $this->create_posted_data_hash( $tick );
499
500 if ( hash_equals( $expected_1, $hash ) ) {
501 return 1;
502 }
503
504 // Hash created 30-60 minutes ago.
505 $expected_2 = $this->create_posted_data_hash( $tick - 1 );
506
507 if ( hash_equals( $expected_2, $hash ) ) {
508 return 2;
509 }
510
511 return false;
512 }
513
514
515 /**
516 * Retrieves the remote IP address of this submission.
517 */
518 private function get_remote_ip_addr() {
519 $ip_addr = '';
520
521 if ( isset( $_SERVER['REMOTE_ADDR'] )
522 and WP_Http::is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
523 $ip_addr = $_SERVER['REMOTE_ADDR'];
524 }
525
526 return apply_filters( 'wpcf7_remote_ip_addr', $ip_addr );
527 }
528
529
530 /**
531 * Retrieves the request URL of this submission.
532 */
533 private function get_request_url() {
534 $home_url = untrailingslashit( home_url() );
535
536 if ( self::is_restful() ) {
537 $referer = trim( $_SERVER['HTTP_REFERER'] ?? '' );
538
539 if ( $referer and str_starts_with( $referer, $home_url ) ) {
540 return sanitize_url( $referer );
541 }
542 }
543
544 $url = preg_replace( '%(?<!:|/)/.*$%', '', $home_url )
545 . wpcf7_get_request_uri();
546
547 return $url;
548 }
549
550
551 /**
552 * Runs user input validation.
553 *
554 * @return bool True if no invalid field is found.
555 */
556 private function validate() {
557 if ( $this->invalid_fields ) {
558 return false;
559 }
560
561 $result = new WPCF7_Validation();
562
563 $this->contact_form->validate_schema(
564 array(
565 'text' => true,
566 'file' => false,
567 'field' => array(),
568 ),
569 $result
570 );
571
572 $tags = $this->contact_form->scan_form_tags( array(
573 'feature' => '! file-uploading',
574 ) );
575
576 foreach ( $tags as $tag ) {
577 $type = $tag->type;
578 $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag );
579 }
580
581 $result = apply_filters( 'wpcf7_validate', $result, $tags );
582
583 $this->invalid_fields = $result->get_invalid_fields();
584
585 return $result->is_valid();
586 }
587
588
589 /**
590 * Returns true if user consent is obtained.
591 */
592 private function accepted() {
593 return apply_filters( 'wpcf7_acceptance', true, $this );
594 }
595
596
597 /**
598 * Adds user consent data to this submission.
599 *
600 * @param string $name Field name.
601 * @param string $conditions Conditions of consent.
602 */
603 public function add_consent( $name, $conditions ) {
604 $this->consent[$name] = $conditions;
605 return true;
606 }
607
608
609 /**
610 * Collects user consent data.
611 *
612 * @return array User consent data.
613 */
614 public function collect_consent() {
615 return (array) $this->consent;
616 }
617
618
619 /**
620 * Executes spam protections.
621 *
622 * @return bool True if spam captured.
623 */
624 private function spam() {
625 $spam = false;
626
627 $skip_spam_check = apply_filters( 'wpcf7_skip_spam_check',
628 $this->skip_spam_check,
629 $this
630 );
631
632 if ( $skip_spam_check ) {
633 return $spam;
634 }
635
636 if ( $this->contact_form->is_true( 'subscribers_only' )
637 and current_user_can( 'wpcf7_submit', $this->contact_form->id() ) ) {
638 return $spam;
639 }
640
641 $user_agent = (string) $this->get_meta( 'user_agent' );
642
643 if ( strlen( $user_agent ) < 2 ) {
644 $spam = true;
645
646 $this->add_spam_log( array(
647 'agent' => 'wpcf7',
648 'reason' => __( "User-Agent string is unnaturally short.", 'contact-form-7' ),
649 ) );
650 }
651
652 if ( ! $this->verify_nonce() ) {
653 $spam = true;
654
655 $this->add_spam_log( array(
656 'agent' => 'wpcf7',
657 'reason' => __( "Submitted nonce is invalid.", 'contact-form-7' ),
658 ) );
659 }
660
661 return apply_filters( 'wpcf7_spam', $spam, $this );
662 }
663
664
665 /**
666 * Adds a spam log.
667 *
668 * @link https://contactform7.com/2019/05/31/why-is-this-message-marked-spam/
669 */
670 public function add_spam_log( $data = '' ) {
671 $data = wp_parse_args( $data, array(
672 'agent' => '',
673 'reason' => '',
674 ) );
675
676 $this->spam_log[] = $data;
677 }
678
679
680 /**
681 * Retrieves the spam logging data.
682 *
683 * @return array Spam logging data.
684 */
685 public function get_spam_log() {
686 return $this->spam_log;
687 }
688
689
690 /**
691 * Verifies that a correct security nonce was used.
692 */
693 private function verify_nonce() {
694 if ( ! $this->contact_form->nonce_is_active() or ! is_user_logged_in() ) {
695 return true;
696 }
697
698 $nonce = $_POST['_wpnonce'] ?? '';
699
700 return wpcf7_verify_nonce( $nonce );
701 }
702
703
704 /**
705 * Function called just before sending email.
706 */
707 private function before_send_mail() {
708 $abort = false;
709
710 do_action_ref_array( 'wpcf7_before_send_mail', array(
711 $this->contact_form,
712 &$abort,
713 $this,
714 ) );
715
716 return ! $abort;
717 }
718
719
720 /**
721 * Sends emails based on user input values and contact form email templates.
722 */
723 private function mail() {
724 $contact_form = $this->contact_form;
725
726 $skip_mail = apply_filters( 'wpcf7_skip_mail',
727 $this->skip_mail, $contact_form
728 );
729
730 if ( $skip_mail ) {
731 return true;
732 }
733
734 $result = WPCF7_Mail::send( $contact_form->prop( 'mail' ), 'mail' );
735
736 if ( $result ) {
737 $additional_mail = array();
738
739 if ( $mail_2 = $contact_form->prop( 'mail_2' )
740 and $mail_2['active'] ) {
741 $additional_mail['mail_2'] = $mail_2;
742 }
743
744 $additional_mail = apply_filters( 'wpcf7_additional_mail',
745 $additional_mail, $contact_form
746 );
747
748 foreach ( $additional_mail as $name => $template ) {
749 WPCF7_Mail::send( $template, $name );
750 }
751
752 return true;
753 }
754
755 return false;
756 }
757
758
759 /**
760 * Retrieves files uploaded through this submission.
761 */
762 public function uploaded_files() {
763 return $this->uploaded_files;
764 }
765
766
767 /**
768 * Adds a file to the uploaded files array.
769 *
770 * @param string $name Field name.
771 * @param string|array $file_path File path or array of file paths.
772 */
773 private function add_uploaded_file( $name, $file_path ) {
774 if ( ! wpcf7_is_name( $name ) ) {
775 return false;
776 }
777
778 $paths = (array) $file_path;
779 $uploaded_files = array();
780 $hash_strings = array();
781
782 foreach ( $paths as $path ) {
783 if ( @is_file( $path ) and @is_readable( $path ) ) {
784 $uploaded_files[] = $path;
785 $hash_strings[] = md5_file( $path );
786 }
787 }
788
789 $this->uploaded_files[$name] = $uploaded_files;
790
791 if ( empty( $this->posted_data[$name] ) ) {
792 $this->posted_data[$name] = implode( ' ', $hash_strings );
793 }
794 }
795
796
797 /**
798 * Removes uploaded files.
799 */
800 private function remove_uploaded_files() {
801 foreach ( (array) $this->uploaded_files as $file_path ) {
802 $paths = (array) $file_path;
803
804 foreach ( $paths as $path ) {
805 wpcf7_rmdir_p( $path );
806
807 if ( $dir = dirname( $path )
808 and false !== ( $files = scandir( $dir ) )
809 and ! array_diff( $files, array( '.', '..' ) ) ) {
810 // remove parent dir if it's empty.
811 rmdir( $dir );
812 }
813 }
814 }
815 }
816
817
818 /**
819 * Moves uploaded files to the tmp directory and validates them.
820 *
821 * @return bool True if no invalid file is found.
822 */
823 private function unship_uploaded_files() {
824 $result = new WPCF7_Validation();
825
826 $tags = $this->contact_form->scan_form_tags( array(
827 'feature' => 'file-uploading',
828 ) );
829
830 foreach ( $tags as $tag ) {
831 if ( empty( $_FILES[$tag->name] ) ) {
832 continue;
833 }
834
835 $file = $_FILES[$tag->name];
836
837 $options = array(
838 'tag' => $tag,
839 'name' => $tag->name,
840 'required' => $tag->is_required(),
841 'filetypes' => $tag->get_option( 'filetypes' ),
842 'limit' => $tag->get_limit_option(),
843 'schema' => $this->contact_form->get_schema(),
844 );
845
846 $new_files = wpcf7_unship_uploaded_file( $file, $options );
847
848 if ( is_wp_error( $new_files ) ) {
849 $result->invalidate( $tag, $new_files );
850 } else {
851 $this->add_uploaded_file( $tag->name, $new_files );
852 }
853
854 $result = apply_filters(
855 "wpcf7_validate_{$tag->type}",
856 $result, $tag,
857 array(
858 'uploaded_files' => $new_files,
859 )
860 );
861 }
862
863 $this->invalid_fields = $result->get_invalid_fields();
864
865 return $result->is_valid();
866 }
867
868
869 /**
870 * Adds extra email attachment files that are independent from form fields.
871 *
872 * @param string|array $file_path A file path or an array of file paths.
873 * @param string $template Optional. The name of the template to which
874 * the files are attached.
875 * @return bool True if it succeeds to attach a file at least,
876 * or false otherwise.
877 */
878 public function add_extra_attachments( $file_path, $template = 'mail' ) {
879 if ( ! did_action( 'wpcf7_before_send_mail' ) ) {
880 return false;
881 }
882
883 $extra_attachments = array();
884
885 foreach ( (array) $file_path as $path ) {
886 $path = path_join( WP_CONTENT_DIR, $path );
887
888 if ( file_exists( $path ) ) {
889 $extra_attachments[] = $path;
890 }
891 }
892
893 if ( empty( $extra_attachments ) ) {
894 return false;
895 }
896
897 if ( ! isset( $this->extra_attachments[$template] ) ) {
898 $this->extra_attachments[$template] = array();
899 }
900
901 $this->extra_attachments[$template] = array_merge(
902 $this->extra_attachments[$template],
903 $extra_attachments
904 );
905
906 return true;
907 }
908
909
910 /**
911 * Returns extra email attachment files.
912 *
913 * @param string $template An email template name.
914 * @return array Array of file paths.
915 */
916 public function extra_attachments( $template ) {
917 if ( isset( $this->extra_attachments[$template] ) ) {
918 return (array) $this->extra_attachments[$template];
919 }
920
921 return array();
922 }
923
924 }
925