PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 6.0.6
Contact Form 7 v6.0.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 1 year ago css 2 years ago js 1 year ago swv 1 year ago capabilities.php 7 years ago contact-form-functions.php 1 year ago contact-form-template.php 1 year ago contact-form.php 1 year ago controller.php 1 year ago file.php 1 year ago form-tag.php 1 year ago form-tags-manager.php 1 year ago formatting.php 1 year ago functions.php 1 year ago html-formatter.php 1 year ago integration.php 1 year ago l10n.php 1 year ago mail-tag.php 2 years ago mail.php 1 year ago pipe.php 1 year ago pocket-holder.php 3 years ago rest-api.php 1 year ago shortcodes.php 1 year ago special-mail-tags.php 1 year ago submission.php 1 year ago upgrade.php 2 years ago validation-functions.php 1 year ago validation.php 1 year ago
mail.php
629 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 wp_trigger_error(
274 '',
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 return false;
284 }
285
286 if ( ! is_readable( $path ) or ! is_file( $path ) ) {
287 wp_trigger_error(
288 '',
289 sprintf(
290 /* translators: %s: Attachment file path. */
291 __( 'Failed to attach a file. %s is not a readable file.', 'contact-form-7' ),
292 $path
293 ),
294 E_USER_NOTICE
295 );
296
297 return false;
298 }
299
300 static $total_size = array();
301
302 if ( ! isset( $total_size[$this->name] ) ) {
303 $total_size[$this->name] = 0;
304 }
305
306 $file_size = (int) @filesize( $path );
307
308 if ( 25 * MB_IN_BYTES < $total_size[$this->name] + $file_size ) {
309 wp_trigger_error(
310 '',
311 __( 'Failed to attach a file. The total file size exceeds the limit of 25 megabytes.', 'contact-form-7' ),
312 E_USER_NOTICE
313 );
314
315 return false;
316 }
317
318 $total_size[$this->name] += $file_size;
319
320 return true;
321 }
322 );
323
324 return wp_mail( $recipient, $subject, $body, $headers, $attachments );
325 }
326
327
328 /**
329 * Replaces mail-tags within the given text.
330 */
331 public function replace_tags( $content, $options = '' ) {
332 if ( true === $options ) {
333 $options = array( 'html' => true );
334 }
335
336 $options = wp_parse_args( $options, array(
337 'html' => false,
338 'exclude_blank' => false,
339 ) );
340
341 return wpcf7_mail_replace_tags( $content, $options );
342 }
343
344
345 /**
346 * Creates an array of attachments based on uploaded files and local files.
347 */
348 private function attachments( $template = null ) {
349 if ( ! $template ) {
350 $template = $this->get( 'attachments' );
351 }
352
353 $attachments = array();
354
355 if ( $submission = WPCF7_Submission::get_instance() ) {
356 $uploaded_files = $submission->uploaded_files();
357
358 foreach ( (array) $uploaded_files as $name => $paths ) {
359 if ( false !== strpos( $template, "[{$name}]" ) ) {
360 $attachments = array_merge( $attachments, (array) $paths );
361 }
362 }
363 }
364
365 foreach ( explode( "\n", $template ) as $line ) {
366 $line = trim( $line );
367
368 if ( '' === $line or '[' === substr( $line, 0, 1 ) ) {
369 continue;
370 }
371
372 $attachments[] = path_join( WP_CONTENT_DIR, $line );
373 }
374
375 if ( $submission = WPCF7_Submission::get_instance() ) {
376 $attachments = array_merge(
377 $attachments,
378 (array) $submission->extra_attachments( $this->name )
379 );
380 }
381
382 return $attachments;
383 }
384 }
385
386
387 /**
388 * Replaces all mail-tags within the given text content.
389 *
390 * @param string $content Text including mail-tags.
391 * @param string|array $options Optional. Output options.
392 * @return string Result of replacement.
393 */
394 function wpcf7_mail_replace_tags( $content, $options = '' ) {
395 $options = wp_parse_args( $options, array(
396 'html' => false,
397 'exclude_blank' => false,
398 ) );
399
400 if ( is_array( $content ) ) {
401 foreach ( $content as $key => $value ) {
402 $content[$key] = wpcf7_mail_replace_tags( $value, $options );
403 }
404
405 return $content;
406 }
407
408 $content = explode( "\n", $content );
409
410 foreach ( $content as $num => $line ) {
411 $line = new WPCF7_MailTaggedText( $line, $options );
412 $replaced = $line->replace_tags();
413
414 if ( $options['exclude_blank'] ) {
415 $replaced_tags = $line->get_replaced_tags();
416
417 if ( empty( $replaced_tags )
418 or array_filter( $replaced_tags, 'strlen' ) ) {
419 $content[$num] = $replaced;
420 } else {
421 unset( $content[$num] ); // Remove a line.
422 }
423 } else {
424 $content[$num] = $replaced;
425 }
426 }
427
428 $content = implode( "\n", $content );
429
430 return $content;
431 }
432
433
434 add_action( 'phpmailer_init', 'wpcf7_phpmailer_init', 10, 1 );
435
436 /**
437 * Adds custom properties to the PHPMailer object.
438 */
439 function wpcf7_phpmailer_init( $phpmailer ) {
440 $custom_headers = $phpmailer->getCustomHeaders();
441 $phpmailer->clearCustomHeaders();
442 $wpcf7_content_type = false;
443
444 foreach ( (array) $custom_headers as $custom_header ) {
445 $name = $custom_header[0];
446 $value = $custom_header[1];
447
448 if ( 'X-WPCF7-Content-Type' === $name ) {
449 $wpcf7_content_type = trim( $value );
450 } else {
451 $phpmailer->addCustomHeader( $name, $value );
452 }
453 }
454
455 if ( 'text/html' === $wpcf7_content_type ) {
456 $phpmailer->msgHTML( $phpmailer->Body );
457 } elseif ( 'text/plain' === $wpcf7_content_type ) {
458 $phpmailer->AltBody = '';
459 }
460 }
461
462
463 /**
464 * Class that represents a single-line text including mail-tags.
465 */
466 class WPCF7_MailTaggedText {
467
468 private $html = false;
469 private $callback = null;
470 private $content = '';
471 private $replaced_tags = array();
472
473
474 /**
475 * The constructor method.
476 */
477 public function __construct( $content, $options = '' ) {
478 $options = wp_parse_args( $options, array(
479 'html' => false,
480 'callback' => null,
481 ) );
482
483 $this->html = (bool) $options['html'];
484
485 if ( null !== $options['callback']
486 and is_callable( $options['callback'] ) ) {
487 $this->callback = $options['callback'];
488 } elseif ( $this->html ) {
489 $this->callback = array( $this, 'replace_tags_callback_html' );
490 } else {
491 $this->callback = array( $this, 'replace_tags_callback' );
492 }
493
494 $this->content = $content;
495 }
496
497
498 /**
499 * Retrieves mail-tags that have been replaced by this instance.
500 *
501 * @return array List of mail-tags replaced.
502 */
503 public function get_replaced_tags() {
504 return $this->replaced_tags;
505 }
506
507
508 /**
509 * Replaces mail-tags based on regexp.
510 */
511 public function replace_tags() {
512 $regex = '/(\[?)\[[\t ]*'
513 . '([a-zA-Z_][0-9a-zA-Z:._-]*)' // [2] = name
514 . '((?:[\t ]+"[^"]*"|[\t ]+\'[^\']*\')*)' // [3] = values
515 . '[\t ]*\](\]?)/';
516
517 return preg_replace_callback( $regex, $this->callback, $this->content );
518 }
519
520
521 /**
522 * Callback function for replacement. For HTML message body.
523 */
524 private function replace_tags_callback_html( $matches ) {
525 return $this->replace_tags_callback( $matches, true );
526 }
527
528
529 /**
530 * Callback function for replacement.
531 */
532 private function replace_tags_callback( $matches, $html = false ) {
533 // allow [[foo]] syntax for escaping a tag
534 if ( '[' === $matches[1] and ']' === $matches[4] ) {
535 return substr( $matches[0], 1, -1 );
536 }
537
538 $tag = $matches[0];
539 $tagname = $matches[2];
540 $values = $matches[3];
541
542 $mail_tag = new WPCF7_MailTag( $tag, $tagname, $values );
543 $field_name = $mail_tag->field_name();
544
545 $submission = WPCF7_Submission::get_instance();
546 $submitted = $submission
547 ? $submission->get_posted_data( $field_name )
548 : null;
549
550 if ( $mail_tag->get_option( 'do_not_heat' ) ) {
551 $submitted = wp_unslash( $_POST[$field_name] ?? '' );
552 }
553
554 $replaced = $submitted;
555
556 if ( null !== $replaced ) {
557 if ( $format = $mail_tag->get_option( 'format' ) ) {
558 $replaced = $this->format( $replaced, $format );
559 }
560
561 $separator = ( 'body' === WPCF7_Mail::get_current_component_name() )
562 ? wp_get_list_item_separator()
563 : ', ';
564
565 $replaced = wpcf7_flat_join( $replaced, array(
566 'separator' => $separator,
567 ) );
568
569 if ( $html ) {
570 $replaced = esc_html( $replaced );
571 $replaced = wptexturize( $replaced );
572 }
573 }
574
575 if ( $form_tag = $mail_tag->corresponding_form_tag() ) {
576 $type = $form_tag->type;
577
578 $replaced = apply_filters(
579 "wpcf7_mail_tag_replaced_{$type}", $replaced,
580 $submitted, $html, $mail_tag
581 );
582 }
583
584 $replaced = apply_filters(
585 'wpcf7_mail_tag_replaced', $replaced,
586 $submitted, $html, $mail_tag
587 );
588
589 if ( null !== $replaced ) {
590 $replaced = trim( $replaced );
591
592 $this->replaced_tags[$tag] = $replaced;
593 return $replaced;
594 }
595
596 $special = apply_filters( 'wpcf7_special_mail_tags', null,
597 $mail_tag->tag_name(), $html, $mail_tag
598 );
599
600 if ( null !== $special ) {
601 $this->replaced_tags[$tag] = $special;
602 return $special;
603 }
604
605 return $tag;
606 }
607
608
609 /**
610 * Formats string based on the formatting option in the form-tag.
611 */
612 public function format( $original, $format ) {
613 $original = (array) $original;
614
615 foreach ( $original as $key => $value ) {
616 if ( preg_match( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $value ) ) {
617 $datetime = date_create( $value, wp_timezone() );
618
619 if ( false !== $datetime ) {
620 $original[$key] = wp_date( $format, $datetime->getTimestamp() );
621 }
622 }
623 }
624
625 return $original;
626 }
627
628 }
629