PluginProbe ʕ •ᴥ•ʔ
Contact Form 7 / 6.1.3
Contact Form 7 v6.1.3
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 11 months 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 11 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
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 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 (
418 empty( $replaced_tags ) or
419 array_filter( $replaced_tags, 'strlen' )
420 ) {
421 $content[$num] = $replaced;
422 } else {
423 unset( $content[$num] ); // Remove a line.
424 }
425 } else {
426 $content[$num] = $replaced;
427 }
428 }
429
430 $content = implode( "\n", $content );
431
432 return $content;
433 }
434
435
436 add_action( 'phpmailer_init', 'wpcf7_phpmailer_init', 10, 1 );
437
438 /**
439 * Adds custom properties to the PHPMailer object.
440 */
441 function wpcf7_phpmailer_init( $phpmailer ) {
442 $custom_headers = $phpmailer->getCustomHeaders();
443 $phpmailer->clearCustomHeaders();
444 $wpcf7_content_type = false;
445
446 foreach ( (array) $custom_headers as $custom_header ) {
447 $name = $custom_header[0];
448 $value = $custom_header[1];
449
450 if ( 'X-WPCF7-Content-Type' === $name ) {
451 $wpcf7_content_type = trim( $value );
452 } else {
453 $phpmailer->addCustomHeader( $name, $value );
454 }
455 }
456
457 if ( 'text/html' === $wpcf7_content_type ) {
458 $phpmailer->msgHTML( $phpmailer->Body );
459 } elseif ( 'text/plain' === $wpcf7_content_type ) {
460 $phpmailer->AltBody = '';
461 }
462 }
463
464
465 /**
466 * Class that represents a single-line text including mail-tags.
467 */
468 class WPCF7_MailTaggedText {
469
470 private $html = false;
471 private $callback = null;
472 private $content = '';
473 private $replaced_tags = array();
474
475
476 /**
477 * The constructor method.
478 */
479 public function __construct( $content, $options = '' ) {
480 $options = wp_parse_args( $options, array(
481 'html' => false,
482 'callback' => null,
483 ) );
484
485 $this->html = (bool) $options['html'];
486
487 if (
488 null !== $options['callback'] and
489 is_callable( $options['callback'] )
490 ) {
491 $this->callback = $options['callback'];
492 } elseif ( $this->html ) {
493 $this->callback = array( $this, 'replace_tags_callback_html' );
494 } else {
495 $this->callback = array( $this, 'replace_tags_callback' );
496 }
497
498 $this->content = $content;
499 }
500
501
502 /**
503 * Retrieves mail-tags that have been replaced by this instance.
504 *
505 * @return array List of mail-tags replaced.
506 */
507 public function get_replaced_tags() {
508 return $this->replaced_tags;
509 }
510
511
512 /**
513 * Replaces mail-tags based on regexp.
514 */
515 public function replace_tags() {
516 $regex = '/(\[?)\[[\t ]*'
517 . '([a-zA-Z_][0-9a-zA-Z:._-]*)' // [2] = name
518 . '((?:[\t ]+"[^"]*"|[\t ]+\'[^\']*\')*)' // [3] = values
519 . '[\t ]*\](\]?)/';
520
521 return preg_replace_callback( $regex, $this->callback, $this->content );
522 }
523
524
525 /**
526 * Callback function for replacement. For HTML message body.
527 */
528 private function replace_tags_callback_html( $matches ) {
529 return $this->replace_tags_callback( $matches, true );
530 }
531
532
533 /**
534 * Callback function for replacement.
535 */
536 private function replace_tags_callback( $matches, $html = false ) {
537 // allow [[foo]] syntax for escaping a tag
538 if ( '[' === $matches[1] 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 = wpcf7_superglobal_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