分析 WordPress `wp_mail()` 函数的源码:如何封装 PHP 的 `mail()` 函数并提供过滤器。

咳咳,各位观众老爷们,晚上好!我是今天的主讲人,咱们今儿个来聊聊WordPress里那个神秘又熟悉的wp_mail()函数。别看它名字简单,肚子里可装了不少乾坤。咱们要做的就是把它扒个精光,看看它到底是怎么把PHP原生的mail()函数包装得如此优雅,还提供了各种过滤器来满足我们这些“挑剔”的开发者。

开场白:mail()函数,爱恨交织的PHP原生函数

在PHP的世界里,mail()函数就像一位老朋友,陪伴我们走过了无数个日日夜夜。它简单直接,只需要几个参数就能发送邮件。但同时,它也像一位让人头疼的老顽固,配置复杂、容易出错、兼容性差,简直让人又爱又恨。

<?php
// PHP原生 mail() 函数的简单用法
$to      = '[email protected]';
$subject = '邮件主题';
$message = '邮件内容';
$headers = 'From: [email protected]' . "rn" .
    'Reply-To: [email protected]' . "rn" .
    'X-Mailer: PHP/' . phpversion();

mail($to, $subject, $message, $headers);
?>

上面这段代码看起来人畜无害,但在实际应用中,你可能会遇到各种各样的问题:

  • 服务器配置问题: mail()函数依赖服务器的邮件配置(通常是sendmail或SMTP),如果配置不正确,邮件就无法发送。
  • 垃圾邮件问题: 如果不设置合适的headers,邮件很容易被标记为垃圾邮件。
  • 编码问题: 如果邮件内容包含中文,可能需要进行编码转换,否则会出现乱码。
  • 安全性问题: 如果不小心,可能会导致邮件头注入攻击。

所以,直接使用mail()函数,需要考虑很多细节,稍有不慎就会踩坑。

wp_mail()函数:WordPress的邮件管家

为了解决这些问题,WordPress提供了一个更高级的函数——wp_mail()。它封装了mail()函数,并提供了一系列过滤器,让我们能够更方便、更安全地发送邮件。

wp_mail()函数的原型如下:

/**
 * Sends an email, similar to PHP's mail function.
 *
 * @since 1.2.1
 *
 * @param string|array $to          Array or comma-separated list of email addresses to send message.
 * @param string       $subject     Email subject.
 * @param string       $message     Message contents.
 * @param string|array $headers     Optional. Additional headers.  Default empty.
 * @param string|array $attachments Optional. Files to attach.  Default empty.
 * @return bool                   Whether the email contents were sent successfully.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // ... 函数体 ...
}

可以看出,wp_mail()函数的参数和mail()函数非常相似,但它做了一些重要的改进:

  • 参数类型更灵活: $to$headers$attachments参数可以是字符串或数组,方便我们处理多个收件人、自定义邮件头和添加附件。
  • 错误处理更完善: wp_mail()函数会返回一个布尔值,指示邮件是否发送成功,方便我们进行错误处理。
  • 过滤器更强大: wp_mail()函数提供了一系列过滤器,让我们能够修改邮件的各个方面,包括收件人、主题、内容、邮件头和附件。

wp_mail()源码解剖:一层层剥开它的内核

要理解wp_mail()函数的工作原理,最好的方法就是阅读它的源码。以下是wp_mail()函数的简化版,去掉了部分不重要的代码,方便我们理解核心逻辑:

function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // 1. 初始化变量
    $defaults = array(
        'to'          => '',
        'subject'     => '',
        'message'     => '',
        'headers'     => '',
        'attachments' => array(),
    );

    $args = wp_parse_args( array(
        'to'          => $to,
        'subject'     => $subject,
        'message'     => $message,
        'headers'     => $headers,
        'attachments' => $attachments,
    ), $defaults );

    // 2. 应用 'wp_mail' 过滤器,允许修改所有参数
    $args = apply_filters( 'wp_mail', $args );

    $to          = $args['to'];
    $subject     = $args['subject'];
    $message     = $args['message'];
    $headers     = $args['headers'];
    $attachments = $args['attachments'];

    // 3. 处理收件人
    if ( ! is_array( $to ) ) {
        $to = explode( ',', $to );
    }

    // 4. 处理邮件头
    if ( ! is_array( $headers ) ) {
        $headers = explode( "n", str_replace( "rn", "n", $headers ) );
    }

    // 5. 构建邮件头
    $headers = apply_filters( 'wp_mail_headers', $headers, $to, $subject, $message );

    // 6. 构建附件
    $attachments = apply_filters( 'wp_mail_attachments', $attachments, $to, $subject, $message );

    // 7. 编码邮件主题
    $subject = wp_encode_mime_header( $subject );

    // 8. 发送邮件
    $result = wp_mail_phpmailer( compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );

    // 9. 返回结果
    return $result;
}

接下来,我们一步步分析这段代码:

1. 初始化变量:

首先,wp_mail()函数初始化了一些变量,并将传入的参数合并到一个数组$args中。这样做的好处是,我们可以使用wp_parse_args()函数来设置默认值,并方便后续的过滤器处理。

2. 应用 ‘wp_mail’ 过滤器:

这是wp_mail()函数最重要的一个特性。它应用了一个名为wp_mail的过滤器,允许我们修改所有传入的参数,包括收件人、主题、内容、邮件头和附件。

$args = apply_filters( 'wp_mail', $args );

这个过滤器非常强大,我们可以使用它来实现各种各样的功能,例如:

  • 修改收件人: 将所有邮件发送到指定的测试邮箱。
  • 修改主题: 在邮件主题中添加前缀,方便区分不同类型的邮件。
  • 修改内容: 在邮件内容中添加签名或免责声明。
  • 添加邮件头: 设置X-Mailer头,防止邮件被标记为垃圾邮件。
  • 添加附件: 自动添加附件,例如网站logo或宣传册。

3. 处理收件人:

wp_mail()函数会将收件人参数转换为数组,方便后续处理。

4. 处理邮件头:

wp_mail()函数会将邮件头参数转换为数组,并统一使用换行符n

5. 构建邮件头:

wp_mail()函数应用了一个名为wp_mail_headers的过滤器,允许我们修改邮件头。

$headers = apply_filters( 'wp_mail_headers', $headers, $to, $subject, $message );

这个过滤器可以用来添加、删除或修改邮件头,例如:

  • 添加Content-Type头: 设置邮件内容的编码方式。
  • 添加Reply-To头: 设置回复邮件的地址。
  • 添加CCBCC头: 添加抄送和密送收件人。

6. 构建附件:

wp_mail()函数应用了一个名为wp_mail_attachments的过滤器,允许我们修改附件。

$attachments = apply_filters( 'wp_mail_attachments', $attachments, $to, $subject, $message );

这个过滤器可以用来添加、删除或修改附件,例如:

  • 添加附件: 从服务器上传文件到邮件中。
  • 修改附件名称: 修改附件的显示名称。
  • 删除附件: 移除不需要的附件。

7. 编码邮件主题:

wp_mail()函数使用wp_encode_mime_header()函数对邮件主题进行编码,防止出现乱码。

8. 发送邮件:

wp_mail()函数调用wp_mail_phpmailer()函数来发送邮件。这个函数使用了PHPMailer库,一个功能强大的邮件发送库,支持SMTP认证、HTML邮件、附件等功能。

9. 返回结果:

wp_mail()函数返回一个布尔值,指示邮件是否发送成功。

wp_mail_phpmailer():PHPMailer的秘密武器

wp_mail_phpmailer()函数是wp_mail()函数的核心,它负责实际发送邮件。这个函数使用了PHPMailer库,一个开源的、功能强大的PHP邮件发送库。

/**
 * Fires the wp_mail action.
 *
 * @since 2.3.0
 *
 * @param array $args Array of wp_mail() arguments.
 * @return bool Whether the email contents were sent successfully.
 */
function wp_mail_phpmailer( $args = array() ) {
    global $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->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();

    $to          = $args['to'];
    $subject     = $args['subject'];
    $message     = $args['message'];
    $headers     = $args['headers'];
    $attachments = $args['attachments'];

    // Set the From address.
    $from_email = apply_filters( 'wp_mail_from', get_option( 'admin_email' ) );
    $from_name = apply_filters( 'wp_mail_from_name', 'WordPress' );

    try {
        $phpmailer->setFrom( $from_email, $from_name, false );
    } catch ( Exception $e ) {
        $mail_error_data['wp_mail_from_error'] = $e->getMessage();
        do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_from_error', $e->getMessage(), $mail_error_data ) );
        return false;
    }

    // Set destination address(es).
    if ( ! is_array( $to ) ) {
        $to = explode( ',', $to );
    }

    foreach ( $to as $recipient ) {
        try {
            $phpmailer->addAddress( trim( $recipient ) );
        } catch ( Exception $e ) {
            $mail_error_data['wp_mail_add_address_error'] = $e->getMessage();
            do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_add_address_error', $e->getMessage(), $mail_error_data ) );
            return false;
        }
    }

    // Set mail's subject and body.
    $phpmailer->Subject = $subject;
    $phpmailer->Body    = $message;

    // Set word wrapping.
    $phpmailer->WordWrap = 78;

    // Set headers.
    if ( ! empty( $headers ) ) {
        foreach ( (array) $headers as $header ) {
            if ( strpos( $header, ':' ) === false ) {
                continue;
            }

            $header_pieces = explode( ':', $header, 2 );
            $header_name   = trim( $header_pieces[0] );
            $header_value  = trim( $header_pieces[1] );

            try {
                $phpmailer->addCustomHeader( $header_name, $header_value );
            } catch ( Exception $e ) {
                $mail_error_data['wp_mail_add_custom_header_error'] = $e->getMessage();
                do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_add_custom_header_error', $e->getMessage(), $mail_error_data ) );
                return false;
            }
        }
    }

    // Set attachments.
    if ( ! empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->addAttachment( $attachment );
            } catch ( Exception $e ) {
                $mail_error_data['wp_mail_add_attachment_error'] = $e->getMessage();
                do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_add_attachment_error', $e->getMessage(), $mail_error_data ) );
                return false;
            }
        }
    }

    // Set SMTP settings.
    if ( defined( 'SMTP_HOST' ) && defined( 'SMTP_PORT' ) ) {
        $phpmailer->isSMTP();
        $phpmailer->Host       = SMTP_HOST;
        $phpmailer->Port       = SMTP_PORT;
        $phpmailer->SMTPAuth   = defined( 'SMTP_AUTH' ) && SMTP_AUTH;
        if ( $phpmailer->SMTPAuth ) {
            $phpmailer->Username = defined( 'SMTP_USER' ) ? SMTP_USER : '';
            $phpmailer->Password = defined( 'SMTP_PASS' ) ? SMTP_PASS : '';
        }
        if ( defined( 'SMTP_SECURE' ) ) {
            $phpmailer->SMTPSecure = SMTP_SECURE;
        }
    }

    // Set HTML message.
    $phpmailer->isHTML( apply_filters( 'wp_mail_content_type', 'text/plain' ) === 'text/html' );

    // Send!
    try {
        $result = $phpmailer->send();
    } catch ( Exception $e ) {
        $mail_error_data['wp_mail_phpmailer_exception'] = $e->getMessage();
        do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_phpmailer_exception', $e->getMessage(), $mail_error_data ) );
        $result = false;
    }

    if ( ! $result ) {
        $mail_error_data['wp_mail_failed'] = $phpmailer->ErrorInfo;
        do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $phpmailer->ErrorInfo, $mail_error_data ) );
    }

    return $result;
}

wp_mail_phpmailer()函数的主要步骤如下:

  1. 初始化PHPMailer: 如果$phpmailer对象不存在,则创建一个新的PHPMailer对象。
  2. 清理之前的邮件数据: 清理PHPMailer对象中的收件人、附件和自定义邮件头。
  3. 设置发件人: 使用wp_mail_fromwp_mail_from_name过滤器来设置发件人地址和名称。
  4. 设置收件人: 将收件人添加到PHPMailer对象中。
  5. 设置邮件主题和内容: 将邮件主题和内容设置到PHPMailer对象中。
  6. 设置邮件头: 将自定义邮件头添加到PHPMailer对象中。
  7. 设置附件: 将附件添加到PHPMailer对象中。
  8. 设置SMTP: 如果定义了SMTP相关的常量,则配置PHPMailer使用SMTP发送邮件。
  9. 设置HTML邮件: 使用wp_mail_content_type过滤器来设置邮件内容的类型,可以是text/plaintext/html
  10. 发送邮件: 调用PHPMailer的send()方法来发送邮件。
  11. 处理错误: 如果发送邮件失败,则触发wp_mail_failed action,并返回false

过滤器大集合:定制你的邮件发送流程

wp_mail()函数提供了一系列过滤器,让我们能够定制邮件发送流程的各个方面。以下是一些常用的过滤器:

过滤器名称 作用 参数
wp_mail 修改所有邮件参数,包括tosubjectmessageheadersattachments $args (array) – 包含所有邮件参数的数组。
wp_mail_from 修改发件人邮箱地址。 $email (string) – 发件人邮箱地址。
wp_mail_from_name 修改发件人名称。 $name (string) – 发件人名称。
wp_mail_headers 修改邮件头。 $headers (array) – 邮件头数组,$to (array) – 收件人数组,$subject (string) – 邮件主题,$message (string) – 邮件内容。
wp_mail_attachments 修改附件。 $attachments (array) – 附件路径数组,$to (array) – 收件人数组,$subject (string) – 邮件主题,$message (string) – 邮件内容。
wp_mail_content_type 修改邮件内容的类型。 $content_type (string) – 邮件内容的类型,可以是text/plaintext/html
phpmailer_init 在PHPMailer对象初始化后,允许修改PHPMailer对象的属性。 $phpmailer (PHPMailer) – PHPMailer对象。

实战演练:使用过滤器定制邮件发送

现在,让我们通过一些实际的例子来演示如何使用过滤器定制邮件发送:

1. 将所有邮件发送到测试邮箱:

add_filter( 'wp_mail', 'my_custom_wp_mail' );

function my_custom_wp_mail( $args ) {
    $args['to'] = '[email protected]'; // 设置测试邮箱
    return $args;
}

2. 在邮件主题中添加前缀:

add_filter( 'wp_mail', 'my_custom_wp_mail_subject' );

function my_custom_wp_mail_subject( $args ) {
    $args['subject'] = '[测试] ' . $args['subject']; // 添加前缀
    return $args;
}

3. 设置邮件内容的类型为HTML:

add_filter( 'wp_mail_content_type', 'my_custom_wp_mail_content_type' );

function my_custom_wp_mail_content_type( $content_type ) {
    return 'text/html'; // 设置为HTML
}

4. 使用SMTP发送邮件:

wp-config.php文件中添加以下代码:

define( 'SMTP_HOST', 'smtp.example.com' ); // SMTP服务器地址
define( 'SMTP_PORT', 587 ); // SMTP端口
define( 'SMTP_AUTH', true ); // 是否需要SMTP认证
define( 'SMTP_USER', 'your_username' ); // SMTP用户名
define( 'SMTP_PASS', 'your_password' ); // SMTP密码
define( 'SMTP_SECURE', 'tls' ); // SMTP加密方式,可以是ssl或tls

总结:wp_mail(),邮件发送的瑞士军刀

wp_mail()函数是WordPress中一个非常重要的函数,它封装了PHP原生的mail()函数,并提供了一系列过滤器,让我们能够更方便、更安全地发送邮件。通过理解wp_mail()函数的源码和灵活运用各种过滤器,我们可以定制邮件发送流程的各个方面,满足各种各样的需求。wp_mail()就像一把瑞士军刀,功能强大,用途广泛,是WordPress开发者必备的工具之一。

好了,今天的讲座就到这里,希望对大家有所帮助!如果有什么问题,欢迎提问,咱们下回再见!

发表回复

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