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 / mail.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
mail.php
681 lines
1 <?php
2
3 add_filter( 'wpcf7_mail_html_body', 'wpcf7_mail_html_body_autop', 10, 1 );
4
5 /**
6 * Filter callback that applies auto-p to HTML email message body.
7 */
8 function wpcf7_mail_html_body_autop( $body ) {
9 if ( wpcf7_autop_or_not( array( 'for' => 'mail' ) ) ) {
10 $body = wpcf7_autop( $body );
11 }
12
13 return $body;
14 }
15
16
17 /**
18 * Class that represents an attempt to compose and send email.
19 */
20 class WPCF7_Mail {
21
22 private static $current = null;
23
24 private $name = '';
25 private $locale = '';
26 private $template = array();
27 private $use_html = false;
28 private $exclude_blank = false;
29
30
31 /**
32 * Returns the singleton instance of this class.
33 */
34 public static function get_current() {
35 return self::$current;
36 }
37
38
39 /**
40 * Composes and sends email based on the specified template.
41 *
42 * @param array $template Array of email template.
43 * @param string $name Optional name of the template, such as
44 * 'mail' or 'mail_2'. Default empty string.
45 * @return bool Whether the email was sent successfully.
46 */
47 public static function send( $template, $name = '' ) {
48 self::$current = new self( $name, $template );
49 return self::$current->compose();
50 }
51
52
53 /**
54 * The constructor method.
55 *
56 * @param string $name The name of the email template.
57 * Such as 'mail' or 'mail_2'.
58 * @param array $template Array of email template.
59 */
60 private function __construct( $name, $template ) {
61 $this->name = trim( $name );
62 $this->use_html = ! empty( $template['use_html'] );
63 $this->exclude_blank = ! empty( $template['exclude_blank'] );
64
65 $this->template = wp_parse_args( $template, array(
66 'subject' => '',
67 'sender' => '',
68 'body' => '',
69 'recipient' => '',
70 'additional_headers' => '',
71 'attachments' => '',
72 ) );
73
74 if ( $submission = WPCF7_Submission::get_instance() ) {
75 $contact_form = $submission->get_contact_form();
76 $this->locale = $contact_form->locale();
77 }
78 }
79
80
81 /**
82 * Returns the name of the email template.
83 */
84 public function name() {
85 return $this->name;
86 }
87
88
89 /**
90 * Retrieves a component from the email template.
91 *
92 * @param string $component The name of the component.
93 * @param bool $replace_tags Whether to replace mail-tags
94 * within the component.
95 * @return string The text representation of the email component.
96 */
97 public function get( $component, $replace_tags = false ) {
98 $use_html = ( $this->use_html && 'body' == $component );
99 $exclude_blank = ( $this->exclude_blank && 'body' == $component );
100
101 $template = $this->template;
102 $component = isset( $template[$component] ) ? $template[$component] : '';
103
104 if ( $replace_tags ) {
105 $component = $this->replace_tags( $component, array(
106 'html' => $use_html,
107 'exclude_blank' => $exclude_blank,
108 ) );
109
110 if ( $use_html ) {
111 // Convert <example@example.com> to &lt;example@example.com&gt;.
112 $component = preg_replace_callback(
113 '/<(.*?)>/',
114 static function ( $matches ) {
115 if ( is_email( $matches[1] ) ) {
116 return sprintf( '&lt;%s&gt;', $matches[1] );
117 } else {
118 return $matches[0];
119 }
120 },
121 $component
122 );
123
124 if ( ! preg_match( '%<html[>\s].*</html>%is', $component ) ) {
125 $component = $this->htmlize( $component );
126 }
127 }
128 }
129
130 return $component;
131 }
132
133
134 /**
135 * Creates HTML message body by adding the header and footer.
136 *
137 * @param string $body The body part of HTML.
138 * @return string Formatted HTML.
139 */
140 private function htmlize( $body ) {
141 if ( $this->locale ) {
142 $lang_atts = sprintf( ' %s',
143 wpcf7_format_atts( array(
144 'dir' => wpcf7_is_rtl( $this->locale ) ? 'rtl' : 'ltr',
145 'lang' => str_replace( '_', '-', $this->locale ),
146 ) )
147 );
148 } else {
149 $lang_atts = '';
150 }
151
152 $header = apply_filters( 'wpcf7_mail_html_header',
153 '<!doctype html>
154 <html xmlns="http://www.w3.org/1999/xhtml"' . $lang_atts . '>
155 <head>
156 <title>' . esc_html( $this->get( 'subject', true ) ) . '</title>
157 </head>
158 <body>
159 ',
160 $this
161 );
162
163 $body = apply_filters( 'wpcf7_mail_html_body', $body, $this );
164
165 $footer = apply_filters( 'wpcf7_mail_html_footer',
166 '</body>
167 </html>',
168 $this
169 );
170
171 return $header . $body . $footer;
172 }
173
174
175 /**
176 * Composes an email message and attempts to send it.
177 *
178 * @param bool $send Whether to attempt to send email. Default true.
179 */
180 private function compose( $send = true ) {
181 $components = array(
182 'subject' => $this->get( 'subject', true ),
183 'sender' => $this->get( 'sender', true ),
184 'body' => $this->get( 'body', true ),
185 'recipient' => $this->get( 'recipient', true ),
186 'additional_headers' => $this->get( 'additional_headers', true ),
187 'attachments' => $this->attachments(),
188 );
189
190 $components = apply_filters( 'wpcf7_mail_components',
191 $components, wpcf7_get_current_contact_form(), $this
192 );
193
194 if ( ! $send ) {
195 return $components;
196 }
197
198 $subject = wpcf7_strip_newline( $components['subject'] );
199 $sender = wpcf7_strip_newline( $components['sender'] );
200 $recipient = wpcf7_strip_newline( $components['recipient'] );
201 $body = $components['body'];
202 $additional_headers = trim( $components['additional_headers'] );
203
204 $headers = "From: $sender\n";
205
206 if ( $this->use_html ) {
207 $headers .= "Content-Type: text/html\n";
208 $headers .= "X-WPCF7-Content-Type: text/html\n";
209 } else {
210 $headers .= "X-WPCF7-Content-Type: text/plain\n";
211 }
212
213 if ( $additional_headers ) {
214 $headers .= $additional_headers . "\n";
215 }
216
217 $attachments = array_filter(
218 (array) $components['attachments'],
219 function ( $attachment ) {
220 $path = path_join( WP_CONTENT_DIR, $attachment );
221
222 if ( ! wpcf7_is_file_path_in_content_dir( $path ) ) {
223 if ( WP_DEBUG ) {
224 trigger_error(
225 sprintf(
226 /* translators: %s: Attachment file path. */
227 __( 'Failed to attach a file. %s is not in the allowed directory.', 'contact-form-7' ),
228 $path
229 ),
230 E_USER_NOTICE
231 );
232 }
233
234 return false;
235 }
236
237 if ( ! is_readable( $path ) or ! is_file( $path ) ) {
238 if ( WP_DEBUG ) {
239 trigger_error(
240 sprintf(
241 /* translators: %s: Attachment file path. */
242 __( 'Failed to attach a file. %s is not a readable file.', 'contact-form-7' ),
243 $path
244 ),
245 E_USER_NOTICE
246 );
247 }
248
249 return false;
250 }
251
252 static $total_size = array();
253
254 if ( ! isset( $total_size[$this->name] ) ) {
255 $total_size[$this->name] = 0;
256 }
257
258 $file_size = (int) @filesize( $path );
259
260 if ( 25 * MB_IN_BYTES < $total_size[$this->name] + $file_size ) {
261 if ( WP_DEBUG ) {
262 trigger_error(
263 __( 'Failed to attach a file. The total file size exceeds the limit of 25 megabytes.', 'contact-form-7' ),
264 E_USER_NOTICE
265 );
266 }
267
268 return false;
269 }
270
271 $total_size[$this->name] += $file_size;
272
273 return true;
274 }
275 );
276
277 return wp_mail( $recipient, $subject, $body, $headers, $attachments );
278 }
279
280
281 /**
282 * Replaces mail-tags within the given text.
283 */
284 public function replace_tags( $content, $args = '' ) {
285 if ( true === $args ) {
286 $args = array( 'html' => true );
287 }
288
289 $args = wp_parse_args( $args, array(
290 'html' => false,
291 'exclude_blank' => false,
292 ) );
293
294 return wpcf7_mail_replace_tags( $content, $args );
295 }
296
297
298 /**
299 * Creates an array of attachments based on uploaded files and local files.
300 */
301 private function attachments( $template = null ) {
302 if ( ! $template ) {
303 $template = $this->get( 'attachments' );
304 }
305
306 $attachments = array();
307
308 if ( $submission = WPCF7_Submission::get_instance() ) {
309 $uploaded_files = $submission->uploaded_files();
310
311 foreach ( (array) $uploaded_files as $name => $paths ) {
312 if ( false !== strpos( $template, "[{$name}]" ) ) {
313 $attachments = array_merge( $attachments, (array) $paths );
314 }
315 }
316 }
317
318 foreach ( explode( "\n", $template ) as $line ) {
319 $line = trim( $line );
320
321 if ( '' === $line or '[' == substr( $line, 0, 1 ) ) {
322 continue;
323 }
324
325 $attachments[] = path_join( WP_CONTENT_DIR, $line );
326 }
327
328 if ( $submission = WPCF7_Submission::get_instance() ) {
329 $attachments = array_merge(
330 $attachments,
331 (array) $submission->extra_attachments( $this->name )
332 );
333 }
334
335 return $attachments;
336 }
337 }
338
339
340 /**
341 * Replaces all mail-tags within the given text content.
342 *
343 * @param string $content Text including mail-tags.
344 * @param string|array $args Optional. Output options.
345 * @return string Result of replacement.
346 */
347 function wpcf7_mail_replace_tags( $content, $args = '' ) {
348 $args = wp_parse_args( $args, array(
349 'html' => false,
350 'exclude_blank' => false,
351 ) );
352
353 if ( is_array( $content ) ) {
354 foreach ( $content as $key => $value ) {
355 $content[$key] = wpcf7_mail_replace_tags( $value, $args );
356 }
357
358 return $content;
359 }
360
361 $content = explode( "\n", $content );
362
363 foreach ( $content as $num => $line ) {
364 $line = new WPCF7_MailTaggedText( $line, $args );
365 $replaced = $line->replace_tags();
366
367 if ( $args['exclude_blank'] ) {
368 $replaced_tags = $line->get_replaced_tags();
369
370 if ( empty( $replaced_tags )
371 or array_filter( $replaced_tags, 'strlen' ) ) {
372 $content[$num] = $replaced;
373 } else {
374 unset( $content[$num] ); // Remove a line.
375 }
376 } else {
377 $content[$num] = $replaced;
378 }
379 }
380
381 $content = implode( "\n", $content );
382
383 return $content;
384 }
385
386
387 add_action( 'phpmailer_init', 'wpcf7_phpmailer_init', 10, 1 );
388
389 /**
390 * Adds custom properties to the PHPMailer object.
391 */
392 function wpcf7_phpmailer_init( $phpmailer ) {
393 $custom_headers = $phpmailer->getCustomHeaders();
394 $phpmailer->clearCustomHeaders();
395 $wpcf7_content_type = false;
396
397 foreach ( (array) $custom_headers as $custom_header ) {
398 $name = $custom_header[0];
399 $value = $custom_header[1];
400
401 if ( 'X-WPCF7-Content-Type' === $name ) {
402 $wpcf7_content_type = trim( $value );
403 } else {
404 $phpmailer->addCustomHeader( $name, $value );
405 }
406 }
407
408 if ( 'text/html' === $wpcf7_content_type ) {
409 $phpmailer->msgHTML( $phpmailer->Body );
410 } elseif ( 'text/plain' === $wpcf7_content_type ) {
411 $phpmailer->AltBody = '';
412 }
413 }
414
415
416 /**
417 * Class that represents a single-line text including mail-tags.
418 */
419 class WPCF7_MailTaggedText {
420
421 private $html = false;
422 private $callback = null;
423 private $content = '';
424 private $replaced_tags = array();
425
426
427 /**
428 * The constructor method.
429 */
430 public function __construct( $content, $args = '' ) {
431 $args = wp_parse_args( $args, array(
432 'html' => false,
433 'callback' => null,
434 ) );
435
436 $this->html = (bool) $args['html'];
437
438 if ( null !== $args['callback']
439 and is_callable( $args['callback'] ) ) {
440 $this->callback = $args['callback'];
441 } elseif ( $this->html ) {
442 $this->callback = array( $this, 'replace_tags_callback_html' );
443 } else {
444 $this->callback = array( $this, 'replace_tags_callback' );
445 }
446
447 $this->content = $content;
448 }
449
450
451 /**
452 * Retrieves mail-tags that have been replaced by this instance.
453 *
454 * @return array List of mail-tags replaced.
455 */
456 public function get_replaced_tags() {
457 return $this->replaced_tags;
458 }
459
460
461 /**
462 * Replaces mail-tags based on regexp.
463 */
464 public function replace_tags() {
465 $regex = '/(\[?)\[[\t ]*'
466 . '([a-zA-Z_][0-9a-zA-Z:._-]*)' // [2] = name
467 . '((?:[\t ]+"[^"]*"|[\t ]+\'[^\']*\')*)' // [3] = values
468 . '[\t ]*\](\]?)/';
469
470 return preg_replace_callback( $regex, $this->callback, $this->content );
471 }
472
473
474 /**
475 * Callback function for replacement. For HTML message body.
476 */
477 private function replace_tags_callback_html( $matches ) {
478 return $this->replace_tags_callback( $matches, true );
479 }
480
481
482 /**
483 * Callback function for replacement.
484 */
485 private function replace_tags_callback( $matches, $html = false ) {
486 // allow [[foo]] syntax for escaping a tag
487 if ( $matches[1] == '['
488 and $matches[4] == ']' ) {
489 return substr( $matches[0], 1, -1 );
490 }
491
492 $tag = $matches[0];
493 $tagname = $matches[2];
494 $values = $matches[3];
495
496 $mail_tag = new WPCF7_MailTag( $tag, $tagname, $values );
497 $field_name = $mail_tag->field_name();
498
499 $submission = WPCF7_Submission::get_instance();
500 $submitted = $submission
501 ? $submission->get_posted_data( $field_name )
502 : null;
503
504 if ( $mail_tag->get_option( 'do_not_heat' ) ) {
505 $submitted = isset( $_POST[$field_name] )
506 ? wp_unslash( $_POST[$field_name] )
507 : '';
508 }
509
510 $replaced = $submitted;
511
512 if ( null !== $replaced ) {
513 if ( $format = $mail_tag->get_option( 'format' ) ) {
514 $replaced = $this->format( $replaced, $format );
515 }
516
517 $replaced = wpcf7_flat_join( $replaced, array(
518 'separator' => wp_get_list_item_separator(),
519 ) );
520
521 if ( $html ) {
522 $replaced = esc_html( $replaced );
523 $replaced = wptexturize( $replaced );
524 }
525 }
526
527 if ( $form_tag = $mail_tag->corresponding_form_tag() ) {
528 $type = $form_tag->type;
529
530 $replaced = apply_filters(
531 "wpcf7_mail_tag_replaced_{$type}", $replaced,
532 $submitted, $html, $mail_tag
533 );
534 }
535
536 $replaced = apply_filters(
537 'wpcf7_mail_tag_replaced', $replaced,
538 $submitted, $html, $mail_tag
539 );
540
541 if ( null !== $replaced ) {
542 $replaced = trim( $replaced );
543
544 $this->replaced_tags[$tag] = $replaced;
545 return $replaced;
546 }
547
548 $special = apply_filters( 'wpcf7_special_mail_tags', null,
549 $mail_tag->tag_name(), $html, $mail_tag
550 );
551
552 if ( null !== $special ) {
553 $this->replaced_tags[$tag] = $special;
554 return $special;
555 }
556
557 return $tag;
558 }
559
560
561 /**
562 * Formats string based on the formatting option in the form-tag.
563 */
564 public function format( $original, $format ) {
565 $original = (array) $original;
566
567 foreach ( $original as $key => $value ) {
568 if ( preg_match( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $value ) ) {
569 $datetime = date_create( $value, wp_timezone() );
570
571 if ( false !== $datetime ) {
572 $original[$key] = wp_date( $format, $datetime->getTimestamp() );
573 }
574 }
575 }
576
577 return $original;
578 }
579
580 }
581
582
583 /**
584 * Class that represents a mail-tag.
585 */
586 class WPCF7_MailTag {
587
588 private $tag;
589 private $tagname = '';
590 private $name = '';
591 private $options = array();
592 private $values = array();
593 private $form_tag = null;
594
595
596 /**
597 * The constructor method.
598 */
599 public function __construct( $tag, $tagname, $values ) {
600 $this->tag = $tag;
601 $this->name = $this->tagname = $tagname;
602
603 $this->options = array(
604 'do_not_heat' => false,
605 'format' => '',
606 );
607
608 if ( ! empty( $values ) ) {
609 preg_match_all( '/"[^"]*"|\'[^\']*\'/', $values, $matches );
610 $this->values = wpcf7_strip_quote_deep( $matches[0] );
611 }
612
613 if ( preg_match( '/^_raw_(.+)$/', $tagname, $matches ) ) {
614 $this->name = trim( $matches[1] );
615 $this->options['do_not_heat'] = true;
616 }
617
618 if ( preg_match( '/^_format_(.+)$/', $tagname, $matches ) ) {
619 $this->name = trim( $matches[1] );
620 $this->options['format'] = $this->values[0];
621 }
622 }
623
624
625 /**
626 * Returns the name part of this mail-tag.
627 */
628 public function tag_name() {
629 return $this->tagname;
630 }
631
632
633 /**
634 * Returns the form field name corresponding to this mail-tag.
635 */
636 public function field_name() {
637 return strtr( $this->name, '.', '_' );
638 }
639
640
641 /**
642 * Returns the value of the specified option.
643 */
644 public function get_option( $option ) {
645 return $this->options[$option];
646 }
647
648
649 /**
650 * Returns the values part of this mail-tag.
651 */
652 public function values() {
653 return $this->values;
654 }
655
656
657 /**
658 * Retrieves the WPCF7_FormTag object that corresponds to this mail-tag.
659 */
660 public function corresponding_form_tag() {
661 if ( $this->form_tag instanceof WPCF7_FormTag ) {
662 return $this->form_tag;
663 }
664
665 if ( $submission = WPCF7_Submission::get_instance() ) {
666 $contact_form = $submission->get_contact_form();
667 $tags = $contact_form->scan_form_tags( array(
668 'name' => $this->field_name(),
669 'feature' => '! zero-controls-container',
670 ) );
671
672 if ( $tags ) {
673 $this->form_tag = $tags[0];
674 }
675 }
676
677 return $this->form_tag;
678 }
679
680 }
681