分析 WordPress `wp_mail()` 函数的源码:它如何封装 PHP 的 `mail()` 函数并处理邮件头。

各位观众老爷,今天老夫就来给大家扒一扒 WordPress 里那个神神秘秘的 wp_mail() 函数的底裤,看看它到底是怎么把 PHP 原生的 mail() 函数给包装起来,又是怎么处理那些让人头大的邮件头的。保证让大家听得懂,看得明白,还能笑出声。

开场白:PHP 原生 mail() 的尴尬

话说 PHP 自带的 mail() 函数,简直就是个“直男癌晚期患者”。它简单粗暴,功能单一,用起来让人抓狂。

举个例子,你想发一封 HTML 格式的邮件,还得自己吭哧吭哧地拼凑邮件头,一不小心就出错,导致邮件乱码、无法显示等等问题。而且,安全性也是个大问题,很容易被垃圾邮件发送者利用。

所以,WordPress 为了方便开发者,就封装了一个 wp_mail() 函数,让大家可以更轻松、更安全地发送邮件。

wp_mail() 函数的庐山真面目

wp_mail() 函数的代码藏在 wp-includes/pluggable.php 文件里。咱们先来看看它的基本结构:

function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
  // 1. 参数处理与过滤
  // 2. 构建邮件头
  // 3. 处理附件
  // 4. 发送邮件
  // 5. 返回结果
}

可以看到,wp_mail() 函数接收五个参数:

参数 类型 描述
$to string/array 收件人邮箱地址,可以是字符串或数组。
$subject string 邮件主题。
$message string 邮件正文内容。
$headers string/array 邮件头信息,可以是字符串或数组。
$attachments array 附件列表,是一个包含附件文件路径的数组。

接下来,咱们就一步一步地深入分析 wp_mail() 函数的源码。

第一步:参数处理与过滤

wp_mail() 函数的第一步就是对传入的参数进行处理和过滤,确保数据的有效性和安全性。

  • 处理收件人地址 ($to):

    wp_mail() 函数会检查 $to 参数的类型,如果它是数组,则将其转换为逗号分隔的字符串。这样做是为了兼容不同的使用场景。

    if ( is_array( $to ) ) {
      $to = implode( ',', $to );
    }

    同时,wp_mail() 还会使用 wp_strip_all_tags() 函数过滤收件人地址,防止 XSS 攻击。

  • 应用过滤器 (wp_mail):

    wp_mail() 函数会应用 wp_mail 过滤器,允许开发者在邮件发送之前修改所有参数。

    $atts = array( 'to' => $to, 'subject' => $subject, 'message' => $message, 'headers' => $headers, 'attachments' => $attachments );
    
    /**
     * Filters the array of email arguments used when sending mail.
     *
     * @since 2.2.0
     *
     * @param array $atts {
     *     Array of email arguments.
     *
     *     @type string|array $to          Array or comma-separated string of email addresses to send message.
     *     @type string       $subject     Email subject.
     *     @type string       $message     Email message.
     *     @type string|array $headers     Optional. Additional headers. Use an empty array to set none.
     *     @type string|array $attachments Optional. Files to attach. Use an empty array to set none.
     * }
     */
    $atts = apply_filters( 'wp_mail', $atts );
    
    $to          = $atts['to'];
    $subject     = $atts['subject'];
    $message     = $atts['message'];
    $headers     = $atts['headers'];
    $attachments = $atts['attachments'];

    这个过滤器非常有用,开发者可以通过它来实现自定义的邮件处理逻辑,比如添加额外的邮件头、修改邮件内容等等。

第二步:构建邮件头

邮件头是邮件的重要组成部分,它包含了邮件的各种元数据,比如发件人、收件人、邮件类型等等。wp_mail() 函数会根据传入的 $headers 参数,构建最终的邮件头。

  • 标准化邮件头:

    wp_mail() 函数首先会将 $headers 参数标准化为一个数组,方便后续处理。如果 $headers 是字符串,则将其拆分成数组。

    if ( ! is_array( $headers ) ) {
      $headers = explode( "n", str_replace( "rn", "n", $headers ) );
    }
  • 设置默认邮件头:

    wp_mail() 函数会设置一些默认的邮件头,比如 Content-TypeFromContent-Type 决定了邮件的类型,通常设置为 text/plaintext/htmlFrom 决定了发件人的邮箱地址。

    // Set Content-Type if not already set.
    $content_type = 'Content-Type: text/plain; charset=' . get_bloginfo( 'charset' );
    $found = false;
    foreach ( (array) $headers as $header ) {
      if ( stripos( $header, 'Content-Type:' ) !== false ) {
        $found = true;
        break;
      }
    }
    if ( ! $found ) {
      $headers[] = $content_type;
    }
    
    // Set From: header
    // Get the site domain and get rid of www.
    $sitename = strtolower( $_SERVER['SERVER_NAME'] );
    if ( substr( $sitename, 0, 4 ) == 'www.' ) {
      $sitename = substr( $sitename, 4 );
    }
    
    $from_email = 'wordpress@' . $sitename;
    
    $switched_locale = switch_to_locale( get_locale(), SWT_FILTER );
    
    $from_name = get_option( 'blogname' );
    if ( '[email protected]' === $from_email ) {
      $from_email = sanitize_email( get_option( 'admin_email' ) );
    }
    
    $from = "From: "" . wp_specialchars_decode( $from_name, ENT_QUOTES ) . "" <$from_email>";
    
    if ( $switched_locale ) {
      restore_previous_locale();
    }
    
    // Add it to the headers if it isn't already there.
    $found = false;
    foreach ( (array) $headers as $header ) {
      if ( strpos( $header, 'From:' ) !== false ) {
        $found = true;
        break;
      }
    }
    if ( ! $found ) {
      $headers[] = $from;
    }

    这里需要注意的是,wp_mail() 会尝试获取站点的域名和管理员邮箱地址,作为默认的发件人信息。如果管理员邮箱地址未设置,则使用 [email protected] 作为默认值。

  • 应用过滤器 (wp_mail_from, wp_mail_from_name, wp_mail_content_type, wp_mail_charset):

    wp_mail() 函数还提供了一系列的过滤器,允许开发者自定义邮件头中的各个字段。

    • wp_mail_from: 用于修改发件人邮箱地址。
    • wp_mail_from_name: 用于修改发件人姓名。
    • wp_mail_content_type: 用于修改邮件类型。
    • wp_mail_charset: 用于修改邮件字符集。
    /**
     * Filters the email address to send from.
     *
     * @since 2.2.0
     *
     * @param string $from_email Email address to send from.
     */
    $from_email = apply_filters( 'wp_mail_from', $from_email );
    
    /**
     * Filters the name to associate with the "from" email address.
     *
     * @since 2.3.0
     *
     * @param string $from_name Name to associate with the "from" email address.
     */
    $from_name = apply_filters( 'wp_mail_from_name', $from_name );
    
    $from = "From: "" . wp_specialchars_decode( $from_name, ENT_QUOTES ) . "" <$from_email>";
    
    /**
     * Filters the content type of the email.
     *
     * @since 2.3.0
     *
     * @param string $content_type Content type of the email.
     */
    $content_type = apply_filters( 'wp_mail_content_type', $content_type );
    
    /**
     * Filters the charset of the email.
     *
     * @since 2.3.0
     *
     * @param string $charset Email charset.
     */
    $charset = apply_filters( 'wp_mail_charset', get_bloginfo( 'charset' ) );

    这些过滤器为开发者提供了极大的灵活性,可以根据实际需求定制邮件的各个方面。

  • 构建最终邮件头:

    最后,wp_mail() 函数将所有邮件头信息拼接成一个字符串,作为 mail() 函数的参数。

    $headers = implode( "n", $headers );

第三步:处理附件

如果 $attachments 参数不为空,wp_mail() 函数会处理附件,并将它们添加到邮件中。

  • 检查附件文件是否存在:

    wp_mail() 函数会遍历 $attachments 数组,检查每个附件文件是否存在。如果文件不存在,则跳过该附件。

    if ( ! empty( $attachments ) ) {
      foreach ( $attachments as $attachment ) {
        if ( ! file_exists( $attachment ) ) {
          continue;
        }
      }
    }
  • 使用 PHPMailer 处理附件:

    wp_mail() 函数使用 PHPMailer 类来处理附件。PHPMailer 是一个强大的邮件发送类库,可以方便地添加附件、设置邮件格式等等。

    // (Re)create it, if it's gone missing.
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
      require_once ABSPATH . WPINC . '/class-phpmailer.php';
      require_once ABSPATH . WPINC . '/class-smtp.php';
      $phpmailer = new PHPMailer( true );
    }
    
    // Clear previous email data.
    $phpmailer->ClearAddresses();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();
    
    // Set the From address.
    $phpmailer->From = $from_email;
    $phpmailer->FromName = $from_name;
    
    if ( version_compare( phpversion(), '5.5', '>=' ) ) {
      $phpmailer->CharSet = $charset;
    }
    
    // Set destination address(es).
    foreach ( explode( ',', $to ) as $recipient ) {
      try {
        $phpmailer->addAddress( trim( $recipient ) );
      } catch ( phpmailerException $e ) {
        continue;
      }
    }
    
    // Set the subject.
    $phpmailer->Subject = $subject;
    
    // Set the message body.
    $phpmailer->Body = $message;
    
    // Set Content-Type and charset.
    foreach ( (array) $headers as $header ) {
      if ( stripos( $header, 'Content-Type:' ) !== false ) {
        if ( stripos( $header, 'text/html' ) !== false ) {
          $phpmailer->isHTML( true );
        }
      } else {
        // Add custom headers.
        $name = substr( $header, 0, strpos( $header, ':' ) );
        $value = trim( substr( $header, strpos( $header, ':' ) + 1 ) );
        $phpmailer->addCustomHeader( $name, $value );
      }
    }
    
    if ( ! $phpmailer->isHTML() ) {
      $phpmailer->Body = wp_strip_all_tags( $phpmailer->Body );
    }
    
    // Add attachments.
    foreach ( $attachments as $attachment ) {
      try {
        $phpmailer->addAttachment( $attachment );
      } catch ( phpmailerException $e ) {
        continue;
      }
    }

    wp_mail() 函数会先创建一个 PHPMailer 实例,然后设置发件人、收件人、主题、正文等信息。最后,遍历 $attachments 数组,将每个附件添加到 PHPMailer 实例中。

第四步:发送邮件

wp_mail() 函数最终会调用 PHPMailer 的 send() 方法来发送邮件。

try {
  $sent = $phpmailer->send();
} catch ( phpmailerException $e ) {
  $sent = false;
}

如果发送成功,send() 方法会返回 true,否则返回 false

第五步:返回结果

wp_mail() 函数会将邮件发送的结果返回。

return $sent;

总结:wp_mail() 的价值

wp_mail() 函数是对 PHP 原生 mail() 函数的封装和增强,它提供了以下价值:

  • 易用性: wp_mail() 函数提供了更简洁的 API,方便开发者发送邮件。
  • 安全性: wp_mail() 函数对输入参数进行过滤,防止 XSS 攻击。
  • 灵活性: wp_mail() 函数提供了丰富的过滤器,允许开发者自定义邮件的各个方面。
  • 可扩展性: wp_mail() 函数使用 PHPMailer 类来处理附件,支持各种邮件格式和附件类型。

wp_mail() 函数源码分析流程图

graph TD
    A[开始] --> B(参数处理与过滤);
    B --> C(构建邮件头);
    C --> D{是否有附件?};
    D -- 是 --> E(处理附件);
    E --> F(发送邮件);
    D -- 否 --> F;
    F --> G(返回结果);
    G --> H[结束];

彩蛋:如何修改 wp_mail() 的默认发件人地址

有时候,我们希望修改 wp_mail() 函数的默认发件人地址,比如将其设置为站点的名称或管理员邮箱地址。

可以通过以下代码实现:

add_filter( 'wp_mail_from', 'my_custom_mail_from' );
function my_custom_mail_from( $original_email_address ) {
  return '[email protected]'; // 修改为你想要的发件人邮箱地址
}

add_filter( 'wp_mail_from_name', 'my_custom_mail_from_name' );
function my_custom_mail_from_name( $original_email_from ) {
  return 'Your Website Name'; // 修改为你想要的发件人姓名
}

将以上代码添加到主题的 functions.php 文件中即可。

好了,今天的讲座就到这里。希望大家通过这次学习,对 wp_mail() 函数有了更深入的了解。记住,掌握了 wp_mail() 函数,你就掌握了 WordPress 邮件发送的钥匙!如果还有什么疑问,欢迎在评论区留言,老夫会尽力解答。 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注