PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 5.7.5
Contact Form 7 v5.7.5
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
982 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 $value_orig = $value = '';
393
394 if ( isset( $posted_data[$name] ) ) {
395 $value_orig = $value = $posted_data[$name];
396 }
397
398 if ( WPCF7_USE_PIPE
399 and $pipes instanceof WPCF7_Pipes
400 and ! $pipes->zero() ) {
401 if ( is_array( $value_orig ) ) {
402 $value = array();
403
404 foreach ( $value_orig as $v ) {
405 $value[] = $pipes->do_pipe( $v );
406 }
407 } else {
408 $value = $pipes->do_pipe( $value_orig );
409 }
410 }
411
412 if ( wpcf7_form_tag_supports( $type, 'selectable-values' ) ) {
413 $value = (array) $value;
414
415 if ( $tag->has_option( 'free_text' )
416 and isset( $posted_data[$name . '_free_text'] ) ) {
417 $last_val = array_pop( $value );
418
419 list( $tied_item ) = array_slice(
420 WPCF7_USE_PIPE ? $tag->pipes->collect_afters() : $tag->values,
421 -1, 1
422 );
423
424 list( $last_val, $tied_item ) = array_map(
425 function ( $item ) {
426 return wpcf7_canonicalize( $item, array(
427 'strto' => 'as-is',
428 ) );
429 },
430 array( $last_val, $tied_item )
431 );
432
433 if ( $last_val === $tied_item ) {
434 $value[] = sprintf( '%s %s',
435 $last_val,
436 $posted_data[$name . '_free_text']
437 );
438 } else {
439 $value[] = $last_val;
440 }
441
442 unset( $posted_data[$name . '_free_text'] );
443 }
444 }
445
446 $value = apply_filters( "wpcf7_posted_data_{$type}", $value,
447 $value_orig, $tag
448 );
449
450 $posted_data[$name] = $value;
451
452 if ( $tag->has_option( 'consent_for:storage' )
453 and empty( $posted_data[$name] ) ) {
454 $this->meta['do_not_store'] = true;
455 }
456 }
457
458 $this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data );
459
460 $this->posted_data_hash = $this->create_posted_data_hash();
461
462 return $this->posted_data;
463 }
464
465
466 /**
467 * Sanitizes user input data.
468 */
469 private function sanitize_posted_data( $value ) {
470 if ( is_array( $value ) ) {
471 $value = array_map( array( $this, 'sanitize_posted_data' ), $value );
472 } elseif ( is_string( $value ) ) {
473 $value = wp_check_invalid_utf8( $value );
474 $value = wp_kses_no_null( $value );
475 }
476
477 return $value;
478 }
479
480
481 /**
482 * Returns the time-dependent variable for hash creation.
483 *
484 * @return float Float value rounded up to the next highest integer.
485 */
486 private function posted_data_hash_tick() {
487 return ceil( time() / ( HOUR_IN_SECONDS / 2 ) );
488 }
489
490
491 /**
492 * Creates a hash string based on posted data, the remote IP address,
493 * contact form location, and window of time.
494 *
495 * @param string $tick Optional. If not specified, result of
496 * posted_data_hash_tick() will be used.
497 * @return string The hash.
498 */
499 private function create_posted_data_hash( $tick = '' ) {
500 if ( '' === $tick ) {
501 $tick = $this->posted_data_hash_tick();
502 }
503
504 $hash = wp_hash(
505 wpcf7_flat_join( array_merge(
506 array(
507 $tick,
508 $this->get_meta( 'remote_ip' ),
509 $this->get_meta( 'unit_tag' ),
510 ),
511 $this->posted_data
512 ) ),
513 'wpcf7_submission'
514 );
515
516 return $hash;
517 }
518
519
520 /**
521 * Returns the hash string created for this submission.
522 *
523 * @return string The current hash for the submission.
524 */
525 public function get_posted_data_hash() {
526 return $this->posted_data_hash;
527 }
528
529
530 /**
531 * Verifies that the given string is equivalent to the posted data hash.
532 *
533 * @param string $hash Optional. This value will be compared to the
534 * current posted data hash for the submission. If not
535 * specified, the value of $_POST['_wpcf7_posted_data_hash']
536 * will be used.
537 * @return int|bool 1 if $hash is created 0-30 minutes ago,
538 * 2 if $hash is created 30-60 minutes ago,
539 * false if $hash is invalid.
540 */
541 public function verify_posted_data_hash( $hash = '' ) {
542 if ( '' === $hash and ! empty( $_POST['_wpcf7_posted_data_hash'] ) ) {
543 $hash = trim( $_POST['_wpcf7_posted_data_hash'] );
544 }
545
546 if ( '' === $hash ) {
547 return false;
548 }
549
550 $tick = $this->posted_data_hash_tick();
551
552 // Hash created 0-30 minutes ago.
553 $expected_1 = $this->create_posted_data_hash( $tick );
554
555 if ( hash_equals( $expected_1, $hash ) ) {
556 return 1;
557 }
558
559 // Hash created 30-60 minutes ago.
560 $expected_2 = $this->create_posted_data_hash( $tick - 1 );
561
562 if ( hash_equals( $expected_2, $hash ) ) {
563 return 2;
564 }
565
566 return false;
567 }
568
569
570 /**
571 * Retrieves the remote IP address of this submission.
572 */
573 private function get_remote_ip_addr() {
574 $ip_addr = '';
575
576 if ( isset( $_SERVER['REMOTE_ADDR'] )
577 and WP_Http::is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
578 $ip_addr = $_SERVER['REMOTE_ADDR'];
579 }
580
581 return apply_filters( 'wpcf7_remote_ip_addr', $ip_addr );
582 }
583
584
585 /**
586 * Retrieves the request URL of this submission.
587 */
588 private function get_request_url() {
589 $home_url = untrailingslashit( home_url() );
590
591 if ( self::is_restful() ) {
592 $referer = isset( $_SERVER['HTTP_REFERER'] )
593 ? trim( $_SERVER['HTTP_REFERER'] ) : '';
594
595 if ( $referer
596 and 0 === strpos( $referer, $home_url ) ) {
597 return sanitize_url( $referer );
598 }
599 }
600
601 $url = preg_replace( '%(?<!:|/)/.*$%', '', $home_url )
602 . wpcf7_get_request_uri();
603
604 return $url;
605 }
606
607
608 /**
609 * Runs user input validation.
610 *
611 * @return bool True if no invalid field is found.
612 */
613 private function validate() {
614 if ( $this->invalid_fields ) {
615 return false;
616 }
617
618 $result = new WPCF7_Validation();
619
620 $this->contact_form->validate_schema(
621 array(
622 'text' => true,
623 'file' => false,
624 'field' => array(),
625 ),
626 $result
627 );
628
629 $tags = $this->contact_form->scan_form_tags( array(
630 'feature' => '! file-uploading',
631 ) );
632
633 foreach ( $tags as $tag ) {
634 $type = $tag->type;
635 $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag );
636 }
637
638 $result = apply_filters( 'wpcf7_validate', $result, $tags );
639
640 $this->invalid_fields = $result->get_invalid_fields();
641
642 return $result->is_valid();
643 }
644
645
646 /**
647 * Returns true if user consent is obtained.
648 */
649 private function accepted() {
650 return apply_filters( 'wpcf7_acceptance', true, $this );
651 }
652
653
654 /**
655 * Adds user consent data to this submission.
656 *
657 * @param string $name Field name.
658 * @param string $conditions Conditions of consent.
659 */
660 public function add_consent( $name, $conditions ) {
661 $this->consent[$name] = $conditions;
662 return true;
663 }
664
665
666 /**
667 * Collects user consent data.
668 *
669 * @return array User consent data.
670 */
671 public function collect_consent() {
672 return (array) $this->consent;
673 }
674
675
676 /**
677 * Executes spam protections.
678 *
679 * @return bool True if spam captured.
680 */
681 private function spam() {
682 $spam = false;
683
684 $skip_spam_check = apply_filters( 'wpcf7_skip_spam_check',
685 $this->skip_spam_check,
686 $this
687 );
688
689 if ( $skip_spam_check ) {
690 return $spam;
691 }
692
693 if ( $this->contact_form->is_true( 'subscribers_only' )
694 and current_user_can( 'wpcf7_submit', $this->contact_form->id() ) ) {
695 return $spam;
696 }
697
698 $user_agent = (string) $this->get_meta( 'user_agent' );
699
700 if ( strlen( $user_agent ) < 2 ) {
701 $spam = true;
702
703 $this->add_spam_log( array(
704 'agent' => 'wpcf7',
705 'reason' => __( "User-Agent string is unnaturally short.", 'contact-form-7' ),
706 ) );
707 }
708
709 if ( ! $this->verify_nonce() ) {
710 $spam = true;
711
712 $this->add_spam_log( array(
713 'agent' => 'wpcf7',
714 'reason' => __( "Submitted nonce is invalid.", 'contact-form-7' ),
715 ) );
716 }
717
718 return apply_filters( 'wpcf7_spam', $spam, $this );
719 }
720
721
722 /**
723 * Adds a spam log.
724 *
725 * @link https://contactform7.com/2019/05/31/why-is-this-message-marked-spam/
726 */
727 public function add_spam_log( $args = '' ) {
728 $args = wp_parse_args( $args, array(
729 'agent' => '',
730 'reason' => '',
731 ) );
732
733 $this->spam_log[] = $args;
734 }
735
736
737 /**
738 * Retrieves the spam logging data.
739 *
740 * @return array Spam logging data.
741 */
742 public function get_spam_log() {
743 return $this->spam_log;
744 }
745
746
747 /**
748 * Verifies that a correct security nonce was used.
749 */
750 private function verify_nonce() {
751 if ( ! $this->contact_form->nonce_is_active() or ! is_user_logged_in() ) {
752 return true;
753 }
754
755 $nonce = isset( $_POST['_wpnonce'] ) ? $_POST['_wpnonce'] : '';
756
757 return wpcf7_verify_nonce( $nonce );
758 }
759
760
761 /**
762 * Function called just before sending email.
763 */
764 private function before_send_mail() {
765 $abort = false;
766
767 do_action_ref_array( 'wpcf7_before_send_mail', array(
768 $this->contact_form,
769 &$abort,
770 $this,
771 ) );
772
773 return ! $abort;
774 }
775
776
777 /**
778 * Sends emails based on user input values and contact form email templates.
779 */
780 private function mail() {
781 $contact_form = $this->contact_form;
782
783 $skip_mail = apply_filters( 'wpcf7_skip_mail',
784 $this->skip_mail, $contact_form
785 );
786
787 if ( $skip_mail ) {
788 return true;
789 }
790
791 $result = WPCF7_Mail::send( $contact_form->prop( 'mail' ), 'mail' );
792
793 if ( $result ) {
794 $additional_mail = array();
795
796 if ( $mail_2 = $contact_form->prop( 'mail_2' )
797 and $mail_2['active'] ) {
798 $additional_mail['mail_2'] = $mail_2;
799 }
800
801 $additional_mail = apply_filters( 'wpcf7_additional_mail',
802 $additional_mail, $contact_form
803 );
804
805 foreach ( $additional_mail as $name => $template ) {
806 WPCF7_Mail::send( $template, $name );
807 }
808
809 return true;
810 }
811
812 return false;
813 }
814
815
816 /**
817 * Retrieves files uploaded through this submission.
818 */
819 public function uploaded_files() {
820 return $this->uploaded_files;
821 }
822
823
824 /**
825 * Adds a file to the uploaded files array.
826 *
827 * @param string $name Field name.
828 * @param string|array $file_path File path or array of file paths.
829 */
830 private function add_uploaded_file( $name, $file_path ) {
831 if ( ! wpcf7_is_name( $name ) ) {
832 return false;
833 }
834
835 $paths = (array) $file_path;
836 $uploaded_files = array();
837 $hash_strings = array();
838
839 foreach ( $paths as $path ) {
840 if ( @is_file( $path ) and @is_readable( $path ) ) {
841 $uploaded_files[] = $path;
842 $hash_strings[] = md5_file( $path );
843 }
844 }
845
846 $this->uploaded_files[$name] = $uploaded_files;
847
848 if ( empty( $this->posted_data[$name] ) ) {
849 $this->posted_data[$name] = implode( ' ', $hash_strings );
850 }
851 }
852
853
854 /**
855 * Removes uploaded files.
856 */
857 private function remove_uploaded_files() {
858 foreach ( (array) $this->uploaded_files as $file_path ) {
859 $paths = (array) $file_path;
860
861 foreach ( $paths as $path ) {
862 wpcf7_rmdir_p( $path );
863
864 if ( $dir = dirname( $path )
865 and false !== ( $files = scandir( $dir ) )
866 and ! array_diff( $files, array( '.', '..' ) ) ) {
867 // remove parent dir if it's empty.
868 rmdir( $dir );
869 }
870 }
871 }
872 }
873
874
875 /**
876 * Moves uploaded files to the tmp directory and validates them.
877 *
878 * @return bool True if no invalid file is found.
879 */
880 private function unship_uploaded_files() {
881 $result = new WPCF7_Validation();
882
883 $tags = $this->contact_form->scan_form_tags( array(
884 'feature' => 'file-uploading',
885 ) );
886
887 foreach ( $tags as $tag ) {
888 if ( empty( $_FILES[$tag->name] ) ) {
889 continue;
890 }
891
892 $file = $_FILES[$tag->name];
893
894 $args = array(
895 'tag' => $tag,
896 'name' => $tag->name,
897 'required' => $tag->is_required(),
898 'filetypes' => $tag->get_option( 'filetypes' ),
899 'limit' => $tag->get_limit_option(),
900 'schema' => $this->contact_form->get_schema(),
901 );
902
903 $new_files = wpcf7_unship_uploaded_file( $file, $args );
904
905 if ( is_wp_error( $new_files ) ) {
906 $result->invalidate( $tag, $new_files );
907 } else {
908 $this->add_uploaded_file( $tag->name, $new_files );
909 }
910
911 $result = apply_filters(
912 "wpcf7_validate_{$tag->type}",
913 $result, $tag,
914 array(
915 'uploaded_files' => $new_files,
916 )
917 );
918 }
919
920 $this->invalid_fields = $result->get_invalid_fields();
921
922 return $result->is_valid();
923 }
924
925
926 /**
927 * Adds extra email attachment files that are independent from form fields.
928 *
929 * @param string|array $file_path A file path or an array of file paths.
930 * @param string $template Optional. The name of the template to which
931 * the files are attached.
932 * @return bool True if it succeeds to attach a file at least,
933 * or false otherwise.
934 */
935 public function add_extra_attachments( $file_path, $template = 'mail' ) {
936 if ( ! did_action( 'wpcf7_before_send_mail' ) ) {
937 return false;
938 }
939
940 $extra_attachments = array();
941
942 foreach ( (array) $file_path as $path ) {
943 $path = path_join( WP_CONTENT_DIR, $path );
944
945 if ( file_exists( $path ) ) {
946 $extra_attachments[] = $path;
947 }
948 }
949
950 if ( empty( $extra_attachments ) ) {
951 return false;
952 }
953
954 if ( ! isset( $this->extra_attachments[$template] ) ) {
955 $this->extra_attachments[$template] = array();
956 }
957
958 $this->extra_attachments[$template] = array_merge(
959 $this->extra_attachments[$template],
960 $extra_attachments
961 );
962
963 return true;
964 }
965
966
967 /**
968 * Returns extra email attachment files.
969 *
970 * @param string $template An email template name.
971 * @return array Array of file paths.
972 */
973 public function extra_attachments( $template ) {
974 if ( isset( $this->extra_attachments[$template] ) ) {
975 return (array) $this->extra_attachments[$template];
976 }
977
978 return array();
979 }
980
981 }
982