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