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