PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 5.7.3
Contact Form 7 v5.7.3
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 3 years ago css 3 years ago js 3 years ago swv 3 years ago capabilities.php 7 years ago config-validator.php 3 years ago contact-form-functions.php 3 years ago contact-form-template.php 3 years ago contact-form.php 3 years ago controller.php 3 years ago file.php 3 years ago form-tag.php 3 years ago form-tags-manager.php 3 years ago formatting.php 3 years ago functions.php 3 years ago html-formatter.php 3 years ago integration.php 3 years ago l10n.php 3 years ago mail.php 3 years ago pipe.php 4 years ago pocket-holder.php 3 years ago rest-api.php 4 years ago shortcodes.php 3 years ago special-mail-tags.php 3 years ago submission.php 3 years ago upgrade.php 7 years ago validation-functions.php 3 years ago validation.php 3 years ago
submission.php
987 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, $args = '' ) {
32 if ( $contact_form instanceof WPCF7_ContactForm ) {
33 if ( empty( self::$instance ) ) {
34 self::$instance = new self( $contact_form, $args );
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 defined( 'REST_REQUEST' ) && REST_REQUEST;
55 }
56
57
58 /**
59 * Constructor.
60 */
61 private function __construct( WPCF7_ContactForm $contact_form, $args = '' ) {
62 $args = wp_parse_args( $args, array(
63 'skip_mail' => false,
64 ) );
65
66 $this->contact_form = $contact_form;
67 $this->skip_mail = (bool) $args['skip_mail'];
68 }
69
70
71 /**
72 * The main logic of submission.
73 */
74 private function proceed() {
75
76 $callback = function () {
77 $contact_form = $this->contact_form;
78
79 $this->setup_meta_data();
80 $this->setup_posted_data();
81
82 if ( $this->is( 'init' ) and ! $this->validate() ) {
83 $this->set_status( 'validation_failed' );
84 $this->set_response( $contact_form->message( 'validation_error' ) );
85 }
86
87 if ( $this->is( 'init' ) and ! $this->accepted() ) {
88 $this->set_status( 'acceptance_missing' );
89 $this->set_response( $contact_form->message( 'accept_terms' ) );
90 }
91
92 if ( $this->is( 'init' ) and $this->spam() ) {
93 $this->set_status( 'spam' );
94 $this->set_response( $contact_form->message( 'spam' ) );
95 }
96
97 if ( $this->is( 'init' ) and ! $this->unship_uploaded_files() ) {
98 $this->set_status( 'validation_failed' );
99 $this->set_response( $contact_form->message( 'validation_error' ) );
100 }
101
102 if ( $this->is( 'init' ) ) {
103 $abort = ! $this->before_send_mail();
104
105 if ( $abort ) {
106 if ( $this->is( 'init' ) ) {
107 $this->set_status( 'aborted' );
108 }
109
110 if ( '' === $this->get_response() ) {
111 $this->set_response( $contact_form->filter_message(
112 __( "Sending mail has been aborted.", 'contact-form-7' ) )
113 );
114 }
115 } elseif ( $this->mail() ) {
116 $this->set_status( 'mail_sent' );
117 $this->set_response( $contact_form->message( 'mail_sent_ok' ) );
118
119 do_action( 'wpcf7_mail_sent', $contact_form );
120 } else {
121 $this->set_status( 'mail_failed' );
122 $this->set_response( $contact_form->message( 'mail_sent_ng' ) );
123
124 do_action( 'wpcf7_mail_failed', $contact_form );
125 }
126 }
127
128 $this->remove_uploaded_files();
129 };
130
131 wpcf7_switch_locale( $this->contact_form->locale(), $callback );
132 }
133
134
135 /**
136 * Returns the current status property.
137 */
138 public function get_status() {
139 return $this->status;
140 }
141
142
143 /**
144 * Sets the status property.
145 *
146 * @param string $status The status.
147 */
148 public function set_status( $status ) {
149 if ( preg_match( '/^[a-z][0-9a-z_]+$/', $status ) ) {
150 $this->status = $status;
151 return true;
152 }
153
154 return false;
155 }
156
157
158 /**
159 * Returns true if the specified status is identical to the current
160 * status property.
161 *
162 * @param string $status The status to compare.
163 */
164 public function is( $status ) {
165 return $this->status === $status;
166 }
167
168
169 /**
170 * Returns an associative array of submission result properties.
171 *
172 * @return array Submission result properties.
173 */
174 public function get_result() {
175 $result = array_merge( $this->result_props, array(
176 'status' => $this->get_status(),
177 'message' => $this->get_response(),
178 ) );
179
180 if ( $this->is( 'validation_failed' ) ) {
181 $result['invalid_fields'] = $this->get_invalid_fields();
182 }
183
184 switch ( $this->get_status() ) {
185 case 'init':
186 case 'validation_failed':
187 case 'acceptance_missing':
188 case 'spam':
189 $result['posted_data_hash'] = '';
190 break;
191 default:
192 $result['posted_data_hash'] = $this->get_posted_data_hash();
193 break;
194 }
195
196 $result = apply_filters( 'wpcf7_submission_result', $result, $this );
197
198 return $result;
199 }
200
201
202 /**
203 * Adds items to the array of submission result properties.
204 *
205 * @param string|array|object $args Value to add to result properties.
206 * @return array Added result properties.
207 */
208 public function add_result_props( $args = '' ) {
209 $args = wp_parse_args( $args, array() );
210
211 $this->result_props = array_merge( $this->result_props, $args );
212
213 return $args;
214 }
215
216
217 /**
218 * Retrieves the response property.
219 *
220 * @return string The current response property value.
221 */
222 public function get_response() {
223 return $this->response;
224 }
225
226
227 /**
228 * Sets the response property.
229 *
230 * @param string $response New response property value.
231 */
232 public function set_response( $response ) {
233 $this->response = $response;
234 return true;
235 }
236
237
238 /**
239 * Retrieves the contact form property.
240 *
241 * @return WPCF7_ContactForm A contact form object.
242 */
243 public function get_contact_form() {
244 return $this->contact_form;
245 }
246
247
248 /**
249 * Search an invalid field by field name.
250 *
251 * @param string $name The field name.
252 * @return array|bool An associative array of validation error
253 * or false when no invalid field.
254 */
255 public function get_invalid_field( $name ) {
256 if ( isset( $this->invalid_fields[$name] ) ) {
257 return $this->invalid_fields[$name];
258 } else {
259 return false;
260 }
261 }
262
263
264 /**
265 * Retrieves all invalid fields.
266 *
267 * @return array Invalid fields.
268 */
269 public function get_invalid_fields() {
270 return $this->invalid_fields;
271 }
272
273
274 /**
275 * Retrieves meta information.
276 *
277 * @param string $name Name of the meta information.
278 * @return string|null The meta information of the given name if it exists,
279 * null otherwise.
280 */
281 public function get_meta( $name ) {
282 if ( isset( $this->meta[$name] ) ) {
283 return $this->meta[$name];
284 }
285 }
286
287
288 /**
289 * Collects meta information about this submission.
290 */
291 private function setup_meta_data() {
292 $timestamp = time();
293
294 $remote_ip = $this->get_remote_ip_addr();
295
296 $remote_port = isset( $_SERVER['REMOTE_PORT'] )
297 ? (int) $_SERVER['REMOTE_PORT'] : '';
298
299 $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
300 ? substr( $_SERVER['HTTP_USER_AGENT'], 0, 254 ) : '';
301
302 $url = $this->get_request_url();
303
304 $unit_tag = isset( $_POST['_wpcf7_unit_tag'] )
305 ? wpcf7_sanitize_unit_tag( $_POST['_wpcf7_unit_tag'] ) : '';
306
307 $container_post_id = isset( $_POST['_wpcf7_container_post'] )
308 ? (int) $_POST['_wpcf7_container_post'] : 0;
309
310 $current_user_id = get_current_user_id();
311
312 $do_not_store = $this->contact_form->is_true( 'do_not_store' );
313
314 $this->meta = array(
315 'timestamp' => $timestamp,
316 'remote_ip' => $remote_ip,
317 'remote_port' => $remote_port,
318 'user_agent' => $user_agent,
319 'url' => $url,
320 'unit_tag' => $unit_tag,
321 'container_post_id' => $container_post_id,
322 'current_user_id' => $current_user_id,
323 'do_not_store' => $do_not_store,
324 );
325
326 return $this->meta;
327 }
328
329
330 /**
331 * Retrieves user input data through this submission.
332 *
333 * @param string $name Optional field name.
334 * @return string|array|null The user input of the field, or array of all
335 * fields values if no field name specified.
336 */
337 public function get_posted_data( $name = '' ) {
338 if ( ! empty( $name ) ) {
339 if ( isset( $this->posted_data[$name] ) ) {
340 return $this->posted_data[$name];
341 } else {
342 return null;
343 }
344 }
345
346 return $this->posted_data;
347 }
348
349
350 /**
351 * Retrieves a user input string value through the specified field.
352 *
353 * @param string $name Field name.
354 * @return string The user input. If the input is an array,
355 * the first item in the array.
356 */
357 public function get_posted_string( $name ) {
358 $data = $this->get_posted_data( $name );
359 $data = wpcf7_array_flatten( $data );
360
361 if ( empty( $data ) ) {
362 return '';
363 }
364
365 // Returns the first array item.
366 return trim( reset( $data ) );
367 }
368
369
370 /**
371 * Constructs posted data property based on user input values.
372 */
373 private function setup_posted_data() {
374 $posted_data = array_filter( (array) $_POST, function( $key ) {
375 return '_' !== substr( $key, 0, 1 );
376 }, ARRAY_FILTER_USE_KEY );
377
378 $posted_data = wp_unslash( $posted_data );
379 $posted_data = $this->sanitize_posted_data( $posted_data );
380
381 $tags = $this->contact_form->scan_form_tags();
382
383 foreach ( (array) $tags as $tag ) {
384 if ( empty( $tag->name ) ) {
385 continue;
386 }
387
388 $type = $tag->type;
389 $name = $tag->name;
390 $pipes = $tag->pipes;
391
392 if ( wpcf7_form_tag_supports( $type, 'do-not-store' ) ) {
393 unset( $posted_data[$name] );
394 continue;
395 }
396
397 $value_orig = $value = '';
398
399 if ( isset( $posted_data[$name] ) ) {
400 $value_orig = $value = $posted_data[$name];
401 }
402
403 if ( WPCF7_USE_PIPE
404 and $pipes instanceof WPCF7_Pipes
405 and ! $pipes->zero() ) {
406 if ( is_array( $value_orig ) ) {
407 $value = array();
408
409 foreach ( $value_orig as $v ) {
410 $value[] = $pipes->do_pipe( $v );
411 }
412 } else {
413 $value = $pipes->do_pipe( $value_orig );
414 }
415 }
416
417 if ( wpcf7_form_tag_supports( $type, 'selectable-values' ) ) {
418 $value = (array) $value;
419
420 if ( $tag->has_option( 'free_text' )
421 and isset( $posted_data[$name . '_free_text'] ) ) {
422 $last_val = array_pop( $value );
423
424 list( $tied_item ) = array_slice(
425 WPCF7_USE_PIPE ? $tag->pipes->collect_afters() : $tag->values,
426 -1, 1
427 );
428
429 list( $last_val, $tied_item ) = array_map(
430 function ( $item ) {
431 return wpcf7_canonicalize( $item, array(
432 'strto' => 'as-is',
433 ) );
434 },
435 array( $last_val, $tied_item )
436 );
437
438 if ( $last_val === $tied_item ) {
439 $value[] = sprintf( '%s %s',
440 $last_val,
441 $posted_data[$name . '_free_text']
442 );
443 } else {
444 $value[] = $last_val;
445 }
446
447 unset( $posted_data[$name . '_free_text'] );
448 }
449 }
450
451 $value = apply_filters( "wpcf7_posted_data_{$type}", $value,
452 $value_orig, $tag
453 );
454
455 $posted_data[$name] = $value;
456
457 if ( $tag->has_option( 'consent_for:storage' )
458 and empty( $posted_data[$name] ) ) {
459 $this->meta['do_not_store'] = true;
460 }
461 }
462
463 $this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data );
464
465 $this->posted_data_hash = $this->create_posted_data_hash();
466
467 return $this->posted_data;
468 }
469
470
471 /**
472 * Sanitizes user input data.
473 */
474 private function sanitize_posted_data( $value ) {
475 if ( is_array( $value ) ) {
476 $value = array_map( array( $this, 'sanitize_posted_data' ), $value );
477 } elseif ( is_string( $value ) ) {
478 $value = wp_check_invalid_utf8( $value );
479 $value = wp_kses_no_null( $value );
480 }
481
482 return $value;
483 }
484
485
486 /**
487 * Returns the time-dependent variable for hash creation.
488 *
489 * @return float Float value rounded up to the next highest integer.
490 */
491 private function posted_data_hash_tick() {
492 return ceil( time() / ( HOUR_IN_SECONDS / 2 ) );
493 }
494
495
496 /**
497 * Creates a hash string based on posted data, the remote IP address,
498 * contact form location, and window of time.
499 *
500 * @param string $tick Optional. If not specified, result of
501 * posted_data_hash_tick() will be used.
502 * @return string The hash.
503 */
504 private function create_posted_data_hash( $tick = '' ) {
505 if ( '' === $tick ) {
506 $tick = $this->posted_data_hash_tick();
507 }
508
509 $hash = wp_hash(
510 wpcf7_flat_join( array_merge(
511 array(
512 $tick,
513 $this->get_meta( 'remote_ip' ),
514 $this->get_meta( 'unit_tag' ),
515 ),
516 $this->posted_data
517 ) ),
518 'wpcf7_submission'
519 );
520
521 return $hash;
522 }
523
524
525 /**
526 * Returns the hash string created for this submission.
527 *
528 * @return string The current hash for the submission.
529 */
530 public function get_posted_data_hash() {
531 return $this->posted_data_hash;
532 }
533
534
535 /**
536 * Verifies that the given string is equivalent to the posted data hash.
537 *
538 * @param string $hash Optional. This value will be compared to the
539 * current posted data hash for the submission. If not
540 * specified, the value of $_POST['_wpcf7_posted_data_hash']
541 * will be used.
542 * @return int|bool 1 if $hash is created 0-30 minutes ago,
543 * 2 if $hash is created 30-60 minutes ago,
544 * false if $hash is invalid.
545 */
546 public function verify_posted_data_hash( $hash = '' ) {
547 if ( '' === $hash and ! empty( $_POST['_wpcf7_posted_data_hash'] ) ) {
548 $hash = trim( $_POST['_wpcf7_posted_data_hash'] );
549 }
550
551 if ( '' === $hash ) {
552 return false;
553 }
554
555 $tick = $this->posted_data_hash_tick();
556
557 // Hash created 0-30 minutes ago.
558 $expected_1 = $this->create_posted_data_hash( $tick );
559
560 if ( hash_equals( $expected_1, $hash ) ) {
561 return 1;
562 }
563
564 // Hash created 30-60 minutes ago.
565 $expected_2 = $this->create_posted_data_hash( $tick - 1 );
566
567 if ( hash_equals( $expected_2, $hash ) ) {
568 return 2;
569 }
570
571 return false;
572 }
573
574
575 /**
576 * Retrieves the remote IP address of this submission.
577 */
578 private function get_remote_ip_addr() {
579 $ip_addr = '';
580
581 if ( isset( $_SERVER['REMOTE_ADDR'] )
582 and WP_Http::is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
583 $ip_addr = $_SERVER['REMOTE_ADDR'];
584 }
585
586 return apply_filters( 'wpcf7_remote_ip_addr', $ip_addr );
587 }
588
589
590 /**
591 * Retrieves the request URL of this submission.
592 */
593 private function get_request_url() {
594 $home_url = untrailingslashit( home_url() );
595
596 if ( self::is_restful() ) {
597 $referer = isset( $_SERVER['HTTP_REFERER'] )
598 ? trim( $_SERVER['HTTP_REFERER'] ) : '';
599
600 if ( $referer
601 and 0 === strpos( $referer, $home_url ) ) {
602 return sanitize_url( $referer );
603 }
604 }
605
606 $url = preg_replace( '%(?<!:|/)/.*$%', '', $home_url )
607 . wpcf7_get_request_uri();
608
609 return $url;
610 }
611
612
613 /**
614 * Runs user input validation.
615 *
616 * @return bool True if no invalid field is found.
617 */
618 private function validate() {
619 if ( $this->invalid_fields ) {
620 return false;
621 }
622
623 $result = new WPCF7_Validation();
624
625 $this->contact_form->validate_schema(
626 array(
627 'text' => true,
628 'file' => false,
629 'field' => array(),
630 ),
631 $result
632 );
633
634 $tags = $this->contact_form->scan_form_tags( array(
635 'feature' => '! file-uploading',
636 ) );
637
638 foreach ( $tags as $tag ) {
639 $type = $tag->type;
640 $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag );
641 }
642
643 $result = apply_filters( 'wpcf7_validate', $result, $tags );
644
645 $this->invalid_fields = $result->get_invalid_fields();
646
647 return $result->is_valid();
648 }
649
650
651 /**
652 * Returns true if user consent is obtained.
653 */
654 private function accepted() {
655 return apply_filters( 'wpcf7_acceptance', true, $this );
656 }
657
658
659 /**
660 * Adds user consent data to this submission.
661 *
662 * @param string $name Field name.
663 * @param string $conditions Conditions of consent.
664 */
665 public function add_consent( $name, $conditions ) {
666 $this->consent[$name] = $conditions;
667 return true;
668 }
669
670
671 /**
672 * Collects user consent data.
673 *
674 * @return array User consent data.
675 */
676 public function collect_consent() {
677 return (array) $this->consent;
678 }
679
680
681 /**
682 * Executes spam protections.
683 *
684 * @return bool True if spam captured.
685 */
686 private function spam() {
687 $spam = false;
688
689 $skip_spam_check = apply_filters( 'wpcf7_skip_spam_check',
690 $this->skip_spam_check,
691 $this
692 );
693
694 if ( $skip_spam_check ) {
695 return $spam;
696 }
697
698 if ( $this->contact_form->is_true( 'subscribers_only' )
699 and current_user_can( 'wpcf7_submit', $this->contact_form->id() ) ) {
700 return $spam;
701 }
702
703 $user_agent = (string) $this->get_meta( 'user_agent' );
704
705 if ( strlen( $user_agent ) < 2 ) {
706 $spam = true;
707
708 $this->add_spam_log( array(
709 'agent' => 'wpcf7',
710 'reason' => __( "User-Agent string is unnaturally short.", 'contact-form-7' ),
711 ) );
712 }
713
714 if ( ! $this->verify_nonce() ) {
715 $spam = true;
716
717 $this->add_spam_log( array(
718 'agent' => 'wpcf7',
719 'reason' => __( "Submitted nonce is invalid.", 'contact-form-7' ),
720 ) );
721 }
722
723 return apply_filters( 'wpcf7_spam', $spam, $this );
724 }
725
726
727 /**
728 * Adds a spam log.
729 *
730 * @link https://contactform7.com/2019/05/31/why-is-this-message-marked-spam/
731 */
732 public function add_spam_log( $args = '' ) {
733 $args = wp_parse_args( $args, array(
734 'agent' => '',
735 'reason' => '',
736 ) );
737
738 $this->spam_log[] = $args;
739 }
740
741
742 /**
743 * Retrieves the spam logging data.
744 *
745 * @return array Spam logging data.
746 */
747 public function get_spam_log() {
748 return $this->spam_log;
749 }
750
751
752 /**
753 * Verifies that a correct security nonce was used.
754 */
755 private function verify_nonce() {
756 if ( ! $this->contact_form->nonce_is_active() or ! is_user_logged_in() ) {
757 return true;
758 }
759
760 $nonce = isset( $_POST['_wpnonce'] ) ? $_POST['_wpnonce'] : '';
761
762 return wpcf7_verify_nonce( $nonce );
763 }
764
765
766 /**
767 * Function called just before sending email.
768 */
769 private function before_send_mail() {
770 $abort = false;
771
772 do_action_ref_array( 'wpcf7_before_send_mail', array(
773 $this->contact_form,
774 &$abort,
775 $this,
776 ) );
777
778 return ! $abort;
779 }
780
781
782 /**
783 * Sends emails based on user input values and contact form email templates.
784 */
785 private function mail() {
786 $contact_form = $this->contact_form;
787
788 $skip_mail = apply_filters( 'wpcf7_skip_mail',
789 $this->skip_mail, $contact_form
790 );
791
792 if ( $skip_mail ) {
793 return true;
794 }
795
796 $result = WPCF7_Mail::send( $contact_form->prop( 'mail' ), 'mail' );
797
798 if ( $result ) {
799 $additional_mail = array();
800
801 if ( $mail_2 = $contact_form->prop( 'mail_2' )
802 and $mail_2['active'] ) {
803 $additional_mail['mail_2'] = $mail_2;
804 }
805
806 $additional_mail = apply_filters( 'wpcf7_additional_mail',
807 $additional_mail, $contact_form
808 );
809
810 foreach ( $additional_mail as $name => $template ) {
811 WPCF7_Mail::send( $template, $name );
812 }
813
814 return true;
815 }
816
817 return false;
818 }
819
820
821 /**
822 * Retrieves files uploaded through this submission.
823 */
824 public function uploaded_files() {
825 return $this->uploaded_files;
826 }
827
828
829 /**
830 * Adds a file to the uploaded files array.
831 *
832 * @param string $name Field name.
833 * @param string|array $file_path File path or array of file paths.
834 */
835 private function add_uploaded_file( $name, $file_path ) {
836 if ( ! wpcf7_is_name( $name ) ) {
837 return false;
838 }
839
840 $paths = (array) $file_path;
841 $uploaded_files = array();
842 $hash_strings = array();
843
844 foreach ( $paths as $path ) {
845 if ( @is_file( $path ) and @is_readable( $path ) ) {
846 $uploaded_files[] = $path;
847 $hash_strings[] = md5_file( $path );
848 }
849 }
850
851 $this->uploaded_files[$name] = $uploaded_files;
852
853 if ( empty( $this->posted_data[$name] ) ) {
854 $this->posted_data[$name] = implode( ' ', $hash_strings );
855 }
856 }
857
858
859 /**
860 * Removes uploaded files.
861 */
862 private function remove_uploaded_files() {
863 foreach ( (array) $this->uploaded_files as $file_path ) {
864 $paths = (array) $file_path;
865
866 foreach ( $paths as $path ) {
867 wpcf7_rmdir_p( $path );
868
869 if ( $dir = dirname( $path )
870 and false !== ( $files = scandir( $dir ) )
871 and ! array_diff( $files, array( '.', '..' ) ) ) {
872 // remove parent dir if it's empty.
873 rmdir( $dir );
874 }
875 }
876 }
877 }
878
879
880 /**
881 * Moves uploaded files to the tmp directory and validates them.
882 *
883 * @return bool True if no invalid file is found.
884 */
885 private function unship_uploaded_files() {
886 $result = new WPCF7_Validation();
887
888 $tags = $this->contact_form->scan_form_tags( array(
889 'feature' => 'file-uploading',
890 ) );
891
892 foreach ( $tags as $tag ) {
893 if ( empty( $_FILES[$tag->name] ) ) {
894 continue;
895 }
896
897 $file = $_FILES[$tag->name];
898
899 $args = array(
900 'tag' => $tag,
901 'name' => $tag->name,
902 'required' => $tag->is_required(),
903 'filetypes' => $tag->get_option( 'filetypes' ),
904 'limit' => $tag->get_limit_option(),
905 'schema' => $this->contact_form->get_schema(),
906 );
907
908 $new_files = wpcf7_unship_uploaded_file( $file, $args );
909
910 if ( is_wp_error( $new_files ) ) {
911 $result->invalidate( $tag, $new_files );
912 } else {
913 $this->add_uploaded_file( $tag->name, $new_files );
914 }
915
916 $result = apply_filters(
917 "wpcf7_validate_{$tag->type}",
918 $result, $tag,
919 array(
920 'uploaded_files' => $new_files,
921 )
922 );
923 }
924
925 $this->invalid_fields = $result->get_invalid_fields();
926
927 return $result->is_valid();
928 }
929
930
931 /**
932 * Adds extra email attachment files that are independent from form fields.
933 *
934 * @param string|array $file_path A file path or an array of file paths.
935 * @param string $template Optional. The name of the template to which
936 * the files are attached.
937 * @return bool True if it succeeds to attach a file at least,
938 * or false otherwise.
939 */
940 public function add_extra_attachments( $file_path, $template = 'mail' ) {
941 if ( ! did_action( 'wpcf7_before_send_mail' ) ) {
942 return false;
943 }
944
945 $extra_attachments = array();
946
947 foreach ( (array) $file_path as $path ) {
948 $path = path_join( WP_CONTENT_DIR, $path );
949
950 if ( file_exists( $path ) ) {
951 $extra_attachments[] = $path;
952 }
953 }
954
955 if ( empty( $extra_attachments ) ) {
956 return false;
957 }
958
959 if ( ! isset( $this->extra_attachments[$template] ) ) {
960 $this->extra_attachments[$template] = array();
961 }
962
963 $this->extra_attachments[$template] = array_merge(
964 $this->extra_attachments[$template],
965 $extra_attachments
966 );
967
968 return true;
969 }
970
971
972 /**
973 * Returns extra email attachment files.
974 *
975 * @param string $template An email template name.
976 * @return array Array of file paths.
977 */
978 public function extra_attachments( $template ) {
979 if ( isset( $this->extra_attachments[$template] ) ) {
980 return (array) $this->extra_attachments[$template];
981 }
982
983 return array();
984 }
985
986 }
987