PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 5.9.5
Contact Form 7 v5.9.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 / 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 2 years ago file.php 2 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 2 years ago l10n.php 3 years ago mail-tag.php 2 years ago mail.php 2 years ago pipe.php 2 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
633 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 $template = $this->template;
150 $component = isset( $template[$component] ) ? $template[$component] : '';
151
152 if ( $replace_tags ) {
153 $component = $this->replace_tags( $component, array(
154 'html' => $use_html,
155 'exclude_blank' => $exclude_blank,
156 ) );
157
158 if ( $use_html ) {
159 // Convert <example@example.com> to &lt;example@example.com&gt;.
160 $component = preg_replace_callback(
161 '/<(.*?)>/',
162 static function ( $matches ) {
163 if ( is_email( $matches[1] ) ) {
164 return sprintf( '&lt;%s&gt;', $matches[1] );
165 } else {
166 return $matches[0];
167 }
168 },
169 $component
170 );
171
172 if ( ! preg_match( '%<html[>\s].*</html>%is', $component ) ) {
173 $component = $this->htmlize( $component );
174 }
175 }
176 }
177
178 $this->component = '';
179
180 return $component;
181 }
182
183
184 /**
185 * Creates HTML message body by adding the header and footer.
186 *
187 * @param string $body The body part of HTML.
188 * @return string Formatted HTML.
189 */
190 private function htmlize( $body ) {
191 if ( $this->locale ) {
192 $lang_atts = sprintf( ' %s',
193 wpcf7_format_atts( array(
194 'dir' => wpcf7_is_rtl( $this->locale ) ? 'rtl' : 'ltr',
195 'lang' => str_replace( '_', '-', $this->locale ),
196 ) )
197 );
198 } else {
199 $lang_atts = '';
200 }
201
202 $header = apply_filters( 'wpcf7_mail_html_header',
203 '<!doctype html>
204 <html xmlns="http://www.w3.org/1999/xhtml"' . $lang_atts . '>
205 <head>
206 <title>' . esc_html( $this->get( 'subject', true ) ) . '</title>
207 </head>
208 <body>
209 ',
210 $this
211 );
212
213 $body = apply_filters( 'wpcf7_mail_html_body', $body, $this );
214
215 $footer = apply_filters( 'wpcf7_mail_html_footer',
216 '</body>
217 </html>',
218 $this
219 );
220
221 return $header . $body . $footer;
222 }
223
224
225 /**
226 * Composes an email message and attempts to send it.
227 *
228 * @param bool $send Whether to attempt to send email. Default true.
229 */
230 private function compose( $send = true ) {
231 $components = array(
232 'subject' => $this->get( 'subject', true ),
233 'sender' => $this->get( 'sender', true ),
234 'body' => $this->get( 'body', true ),
235 'recipient' => $this->get( 'recipient', true ),
236 'additional_headers' => $this->get( 'additional_headers', true ),
237 'attachments' => $this->attachments(),
238 );
239
240 $components = apply_filters( 'wpcf7_mail_components',
241 $components, wpcf7_get_current_contact_form(), $this
242 );
243
244 if ( ! $send ) {
245 return $components;
246 }
247
248 $subject = wpcf7_strip_newline( $components['subject'] );
249 $sender = wpcf7_strip_newline( $components['sender'] );
250 $recipient = wpcf7_strip_newline( $components['recipient'] );
251 $body = $components['body'];
252 $additional_headers = trim( $components['additional_headers'] );
253
254 $headers = "From: $sender\n";
255
256 if ( $this->use_html ) {
257 $headers .= "Content-Type: text/html\n";
258 $headers .= "X-WPCF7-Content-Type: text/html\n";
259 } else {
260 $headers .= "X-WPCF7-Content-Type: text/plain\n";
261 }
262
263 if ( $additional_headers ) {
264 $headers .= $additional_headers . "\n";
265 }
266
267 $attachments = array_filter(
268 (array) $components['attachments'],
269 function ( $attachment ) {
270 $path = path_join( WP_CONTENT_DIR, $attachment );
271
272 if ( ! wpcf7_is_file_path_in_content_dir( $path ) ) {
273 if ( WP_DEBUG ) {
274 trigger_error(
275 sprintf(
276 /* translators: %s: Attachment file path. */
277 __( 'Failed to attach a file. %s is not in the allowed directory.', 'contact-form-7' ),
278 $path
279 ),
280 E_USER_NOTICE
281 );
282 }
283
284 return false;
285 }
286
287 if ( ! is_readable( $path ) or ! is_file( $path ) ) {
288 if ( WP_DEBUG ) {
289 trigger_error(
290 sprintf(
291 /* translators: %s: Attachment file path. */
292 __( 'Failed to attach a file. %s is not a readable file.', 'contact-form-7' ),
293 $path
294 ),
295 E_USER_NOTICE
296 );
297 }
298
299 return false;
300 }
301
302 static $total_size = array();
303
304 if ( ! isset( $total_size[$this->name] ) ) {
305 $total_size[$this->name] = 0;
306 }
307
308 $file_size = (int) @filesize( $path );
309
310 if ( 25 * MB_IN_BYTES < $total_size[$this->name] + $file_size ) {
311 if ( WP_DEBUG ) {
312 trigger_error(
313 __( 'Failed to attach a file. The total file size exceeds the limit of 25 megabytes.', 'contact-form-7' ),
314 E_USER_NOTICE
315 );
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, $args = '' ) {
335 if ( true === $args ) {
336 $args = array( 'html' => true );
337 }
338
339 $args = wp_parse_args( $args, array(
340 'html' => false,
341 'exclude_blank' => false,
342 ) );
343
344 return wpcf7_mail_replace_tags( $content, $args );
345 }
346
347
348 /**
349 * Creates an array of attachments based on uploaded files and local files.
350 */
351 private function attachments( $template = null ) {
352 if ( ! $template ) {
353 $template = $this->get( 'attachments' );
354 }
355
356 $attachments = array();
357
358 if ( $submission = WPCF7_Submission::get_instance() ) {
359 $uploaded_files = $submission->uploaded_files();
360
361 foreach ( (array) $uploaded_files as $name => $paths ) {
362 if ( false !== strpos( $template, "[{$name}]" ) ) {
363 $attachments = array_merge( $attachments, (array) $paths );
364 }
365 }
366 }
367
368 foreach ( explode( "\n", $template ) as $line ) {
369 $line = trim( $line );
370
371 if ( '' === $line or '[' == substr( $line, 0, 1 ) ) {
372 continue;
373 }
374
375 $attachments[] = path_join( WP_CONTENT_DIR, $line );
376 }
377
378 if ( $submission = WPCF7_Submission::get_instance() ) {
379 $attachments = array_merge(
380 $attachments,
381 (array) $submission->extra_attachments( $this->name )
382 );
383 }
384
385 return $attachments;
386 }
387 }
388
389
390 /**
391 * Replaces all mail-tags within the given text content.
392 *
393 * @param string $content Text including mail-tags.
394 * @param string|array $args Optional. Output options.
395 * @return string Result of replacement.
396 */
397 function wpcf7_mail_replace_tags( $content, $args = '' ) {
398 $args = wp_parse_args( $args, array(
399 'html' => false,
400 'exclude_blank' => false,
401 ) );
402
403 if ( is_array( $content ) ) {
404 foreach ( $content as $key => $value ) {
405 $content[$key] = wpcf7_mail_replace_tags( $value, $args );
406 }
407
408 return $content;
409 }
410
411 $content = explode( "\n", $content );
412
413 foreach ( $content as $num => $line ) {
414 $line = new WPCF7_MailTaggedText( $line, $args );
415 $replaced = $line->replace_tags();
416
417 if ( $args['exclude_blank'] ) {
418 $replaced_tags = $line->get_replaced_tags();
419
420 if ( empty( $replaced_tags )
421 or array_filter( $replaced_tags, 'strlen' ) ) {
422 $content[$num] = $replaced;
423 } else {
424 unset( $content[$num] ); // Remove a line.
425 }
426 } else {
427 $content[$num] = $replaced;
428 }
429 }
430
431 $content = implode( "\n", $content );
432
433 return $content;
434 }
435
436
437 add_action( 'phpmailer_init', 'wpcf7_phpmailer_init', 10, 1 );
438
439 /**
440 * Adds custom properties to the PHPMailer object.
441 */
442 function wpcf7_phpmailer_init( $phpmailer ) {
443 $custom_headers = $phpmailer->getCustomHeaders();
444 $phpmailer->clearCustomHeaders();
445 $wpcf7_content_type = false;
446
447 foreach ( (array) $custom_headers as $custom_header ) {
448 $name = $custom_header[0];
449 $value = $custom_header[1];
450
451 if ( 'X-WPCF7-Content-Type' === $name ) {
452 $wpcf7_content_type = trim( $value );
453 } else {
454 $phpmailer->addCustomHeader( $name, $value );
455 }
456 }
457
458 if ( 'text/html' === $wpcf7_content_type ) {
459 $phpmailer->msgHTML( $phpmailer->Body );
460 } elseif ( 'text/plain' === $wpcf7_content_type ) {
461 $phpmailer->AltBody = '';
462 }
463 }
464
465
466 /**
467 * Class that represents a single-line text including mail-tags.
468 */
469 class WPCF7_MailTaggedText {
470
471 private $html = false;
472 private $callback = null;
473 private $content = '';
474 private $replaced_tags = array();
475
476
477 /**
478 * The constructor method.
479 */
480 public function __construct( $content, $args = '' ) {
481 $args = wp_parse_args( $args, array(
482 'html' => false,
483 'callback' => null,
484 ) );
485
486 $this->html = (bool) $args['html'];
487
488 if ( null !== $args['callback']
489 and is_callable( $args['callback'] ) ) {
490 $this->callback = $args['callback'];
491 } elseif ( $this->html ) {
492 $this->callback = array( $this, 'replace_tags_callback_html' );
493 } else {
494 $this->callback = array( $this, 'replace_tags_callback' );
495 }
496
497 $this->content = $content;
498 }
499
500
501 /**
502 * Retrieves mail-tags that have been replaced by this instance.
503 *
504 * @return array List of mail-tags replaced.
505 */
506 public function get_replaced_tags() {
507 return $this->replaced_tags;
508 }
509
510
511 /**
512 * Replaces mail-tags based on regexp.
513 */
514 public function replace_tags() {
515 $regex = '/(\[?)\[[\t ]*'
516 . '([a-zA-Z_][0-9a-zA-Z:._-]*)' // [2] = name
517 . '((?:[\t ]+"[^"]*"|[\t ]+\'[^\']*\')*)' // [3] = values
518 . '[\t ]*\](\]?)/';
519
520 return preg_replace_callback( $regex, $this->callback, $this->content );
521 }
522
523
524 /**
525 * Callback function for replacement. For HTML message body.
526 */
527 private function replace_tags_callback_html( $matches ) {
528 return $this->replace_tags_callback( $matches, true );
529 }
530
531
532 /**
533 * Callback function for replacement.
534 */
535 private function replace_tags_callback( $matches, $html = false ) {
536 // allow [[foo]] syntax for escaping a tag
537 if ( $matches[1] == '['
538 and $matches[4] == ']' ) {
539 return substr( $matches[0], 1, -1 );
540 }
541
542 $tag = $matches[0];
543 $tagname = $matches[2];
544 $values = $matches[3];
545
546 $mail_tag = new WPCF7_MailTag( $tag, $tagname, $values );
547 $field_name = $mail_tag->field_name();
548
549 $submission = WPCF7_Submission::get_instance();
550 $submitted = $submission
551 ? $submission->get_posted_data( $field_name )
552 : null;
553
554 if ( $mail_tag->get_option( 'do_not_heat' ) ) {
555 $submitted = wp_unslash( $_POST[$field_name] ?? '' );
556 }
557
558 $replaced = $submitted;
559
560 if ( null !== $replaced ) {
561 if ( $format = $mail_tag->get_option( 'format' ) ) {
562 $replaced = $this->format( $replaced, $format );
563 }
564
565 $separator = ( 'body' === WPCF7_Mail::get_current_component_name() )
566 ? wp_get_list_item_separator()
567 : ', ';
568
569 $replaced = wpcf7_flat_join( $replaced, array(
570 'separator' => $separator,
571 ) );
572
573 if ( $html ) {
574 $replaced = esc_html( $replaced );
575 $replaced = wptexturize( $replaced );
576 }
577 }
578
579 if ( $form_tag = $mail_tag->corresponding_form_tag() ) {
580 $type = $form_tag->type;
581
582 $replaced = apply_filters(
583 "wpcf7_mail_tag_replaced_{$type}", $replaced,
584 $submitted, $html, $mail_tag
585 );
586 }
587
588 $replaced = apply_filters(
589 'wpcf7_mail_tag_replaced', $replaced,
590 $submitted, $html, $mail_tag
591 );
592
593 if ( null !== $replaced ) {
594 $replaced = trim( $replaced );
595
596 $this->replaced_tags[$tag] = $replaced;
597 return $replaced;
598 }
599
600 $special = apply_filters( 'wpcf7_special_mail_tags', null,
601 $mail_tag->tag_name(), $html, $mail_tag
602 );
603
604 if ( null !== $special ) {
605 $this->replaced_tags[$tag] = $special;
606 return $special;
607 }
608
609 return $tag;
610 }
611
612
613 /**
614 * Formats string based on the formatting option in the form-tag.
615 */
616 public function format( $original, $format ) {
617 $original = (array) $original;
618
619 foreach ( $original as $key => $value ) {
620 if ( preg_match( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $value ) ) {
621 $datetime = date_create( $value, wp_timezone() );
622
623 if ( false !== $datetime ) {
624 $original[$key] = wp_date( $format, $datetime->getTimestamp() );
625 }
626 }
627 }
628
629 return $original;
630 }
631
632 }
633