CommentToMail 是typecho的一个基于 PHPMailer 的评论通知插件, 本文讨论基于1.2.3

===========================
一. 使用PHPMailer 发送邮件:

PHPMailer 包含3个文件:

class.smtp.php 发送邮件用的,php socket 实现smtp协议
class.pop3.php 接受邮件用的
class.phpmailer.php PHPMailer类

有3种邮件发送模式: smtp, mail, sendmail.

从 class.phpmailer.php 文件中

359
/**
360
 * Sets Mailer to send message using SMTP.
361
 * @return void
362
 */
363
public function IsSMTP() {
364
  $this->Mailer = 'smtp';
365
}
366
 
367
/**
368
 * Sets Mailer to send message using PHP mail() function.
369
 * @return void
370
 */
371
public function IsMail() {
372
  $this->Mailer = 'mail';
373
}
374
 
375
/**
376
 * Sets Mailer to send message using the $Sendmail program.
377
 * @return void
378
 */
379
public function IsSendmail() {
380
  if (!stristr(ini_get('sendmail_path'), 'sendmail')) {
381
    $this->Sendmail = '/var/qmail/bin/sendmail';
382
  }
383
  $this->Mailer = 'sendmail';
384
}
385
 
386
/**
387
 * Sets Mailer to send message using the qmail MTA.
388
 * @return void
389
 */
390
public function IsQmail() {
391
  if (stristr(ini_get('sendmail_path'), 'qmail')) {
392
    $this->Sendmail = '/var/qmail/bin/sendmail';
393
  }
394
  $this->Mailer = 'sendmail';
395
}

可知:

smtp 模式最为常用,直接配置好smtp服务器相关参数,就可以发送邮件了

mail 模式调用php自带mail函数来发送邮件,需要运行环境有邮件服务器.

sendmail 模式调用外部二进制程序(sendmail 或 qmail 或php.ini 中 sendmail_path指定)来发送邮件,据
/var/qmail/bin/sendmail 和 /var/qmail/bin/sendmail 可以推测,这个功能一般是在linux服务器上用的,
win服务器就不瞎琢磨了.

windows服务器,又不带邮件服务,综上,只有 smtp模式最靠谱了.

常用smtp邮件服务器:

website ssl host port user pass
mail.yeah.net false smtp.yeah.net 25 user=xxx@yeah.net(或xxx) ***
mail.163.com false smtp.163.com 25 user=xxx@163.com(或xxx) ***
exmail.qq.com false smtp.exmail.qq.com 25 user=xxx@yourdomain ***
(ssl的我没写,因为我测试没成功- -!!)

首先直接写一段代码调用PHPMailer发送邮件,以确定参数配置正确、"最小系统"正常工作.

本地测试正常,拿到服务器发现

1. PHP Warning: set_time_limit() has been disabled for security reasons,

这句意义不是很大,直接@或去掉.

2. SMTP Error: Could not connect to SMTP host.

发现是服务器禁用 fsockopen 所致, 将 class.smtp.php 中 @fsockopen 替换为 @pfscokopen (还好服务器没有禁用这个),发现可以正常发邮件了.

注: CommentToMail 1.2.3 附带的 class.smtp.php 里面就是@fsockopen,遇到此错误可以尝试此法.
还有 Plugin.php 中 SendMail() 中也用到了 fsockopen.

===========================
二. CommentToMail 代码分析:

①何时发送邮件

CommentToMail 插件 添加 finishComment 回调函数, CommentToMail_Plugin::toMail()
即 评论完成时 调用 toMail() 这个函数.

②如何发送邮件

toMail() 这个函数实现的功能就是 根据本条评论的信息 生成邮件信息(发送给谁,内容是什么,等)
并调用 SendMail() 来发送邮件, 之间数据通过临时文件(明白了cache目录的用处了)(gzdeflate,serialize) 和get来传递.

③SendMail() 做了什么?

它通过fsockopen GET 方式把包含邮件内容的临时文件的名字 传递给 /CommentToMail/send_mail.php

④send_mail.php 文件做了什么?

它通过替换 邮件模板 中关键词,产生邮件内容, 并创建 PHPMailer 实例,调用其 Send() 方法把邮件发送出去.

⑤PHPMailer

通过 socket 构造stmp协议,发出邮件.

===========================
三. CommentToMail 调试

因为评论完成时通过get方式调用 send_mail.php 执行, 所以客户端看不到 send_mail.php 的执行结果,不便调试,了解了其原理,可以 注释掉 send_mail.php 中 @unlink($file) 来保存临时文件以供分析和调试.

在typecho中完成一条评论,然后查看cache 目录内临时文件名称,

在 $smtp= unserialize(gzinflate(file_get_contents($file))); 后添加 print_r($smtp) 以查看临时文件内容
浏览器 访问 usr/plugins/CommentToMail/send_mail.php?mail=XXXX (其中XXXX=base64_encode($filename))

又发现服务器gzinflate被禁用- -!!.

于是干脆去掉gzdeflate,即

把 Plugin.php 中

    file_put_contents('./usr/plugins/CommentToMail/cache/'.$filename, gzdeflate(serialize($smtp))); 改为
    file_put_contents('./usr/plugins/CommentToMail/cache/'.$filename, serialize($smtp));

把 send_mail.php 中

    $smtp= unserialize(gzinflate(file_get_contents($file))); 改为
    $smtp= unserialize(file_get_contents($file));

再次在typecho中评论,产生新的临时文件,并在浏览器中访问 send_mail.php?mail=XXXX

发现可以正常读到 临时文件内容了,检查参数配置无误,邮件也发送成功了.

然后又发现当同时发送给被评论者和文章作者时, 邮件发送又不灵了, 先发的邮件总是能够成功,后法的一个总是失败.

调换 send_mail.php 中 向博主发信,向访客发信 的先后次序,仍是后一个失败.

开始想是不是因为服务器禁用 set_time_limit, 后面那个邮件发送超时了?

在send_mail.php 中各处添加时间检查,输出结果如下:

--------------------------

0.000: 将要载入phpmailer.
0.007: 载入phpmailer完毕.

0.009: 将要向访客发信
2012-04-04 18:51:02 向 yyyyy@163.com 发送邮件成功!
0.715: 向访客发信完毕

0.716: 将要向博主发信
SMTP Error: Could not authenticate.
2012-04-04 18:52:02 向 aaaaa@yurenchen.com 发送邮件错误: SMTP Error: Could not authenticate.
60.887: 向博主发信完毕

--------------------------

可以看到第一封邮件基本是秒发出去的,第二封消耗了60秒时间,并最终发送失败.

于是看了下PHPMailer 自带的smtp发送示例 test_db_smtp_basic.php , 大致流程是

new PHPMailer();

设置smtp 参数;

while(){
设置邮件参数;
Send();
ClearAddresses();
ClearAttachments();
}

再对比 send_mail.php, 发现

每次在send函数中创建 PHPMailer 实例, 并用$mail=NULL;销毁实例(插件作者大概也是用惯了JavaScript - -!!),

于是找找 PHPMailer 类的析构方法,发现没有.

于是把 $mail = new PHPMailer(); 拿到 send 函数外面,

并在 send 函数结尾添加

$mail->ClearAddresses();
$mail->ClearAttachments();

以清除接收邮箱地址 和 附件.

再次在浏览器访问 send_mail.php?mail=XXXX

输出结果:

------------------------------

0.000: 将要载入phpmailer.
0.008: 载入phpmailer完毕.

0.009: 将要向访客发信
2012-04-04 19:14:24 向 yyyyy@163.com 发送邮件成功!
0.588: 向访客发信完毕

0.588: 将要向博主发信
2012-04-04 19:14:24 向 aaaaa@yurenchen.com 发送邮件成功!
0.944: 向博主发信完毕

-------------------------------

直接秒发了,内牛满面啊 (T_T)

原来 send_mail.php 中这句 set_time_limit(0); 是这么悲剧来的啊

===========================
四. 关于CommentToMail 1.2.4

相对于1.2.3 的改动主要有以下几点,不过似乎引入的问题比解决的问题还多,但依然感谢带给我们CommentToMail插件的DEFE.

① 在 class.smtp.php 和 Plugin.php 中添加了 fsockopen 容错,

fsockopen 失败则尝试 pfsockopen,

再失败则尝试 stream_socket_client,

再失败 就要报错了:Failed to connect to server

② class.phpmailer.php中 IsSMTP 函数中 'smtp' 修改成了 'SMTP', 这一招似乎是江湖传言,

查看 class.phpmailer.php 代码:

571
// Choose the mailer and send through it
572
switch($this->Mailer) {
573
  case 'sendmail':
574
    return $this->SendmailSend($header, $body);
575
  case 'smtp':
576
    return $this->SmtpSend($header, $body);
577
  default:
578
    return $this->MailSend($header, $body);
579
}

产生的影响就是switch分支总是default,

就是PHPMailer总是用 mail 模式发信,这个在大部分服务器上应该都不能用.

③ send_mail.php 中 引入 class.phpmailer.php 的操作放到了 send 函数中,

不是一上来就载入class.phpmailer.php 文件,而是等到需要时才载入,来提高执行效率.

但是用的是 require 包含文件,当同时发送邮件给博主和被评论者时,两次调用send函数必然报错:

PHP Fatal error: Cannot redeclare class phpmailerException

④ 临时文件动态加密解密函数 mcode 疑似有问题, 没有仔细看加密算法,

直接去掉加密解密过程就可以用,加上则解密时失败.

仍然延续的问题:

① PHPMailer 实例化仍在 send 函数中进行, 导致发送第二封邮件时失败.

好吧,上传了调试后的代码: CommentToMail.rar

转载须注明出处: http://www.yurenchen.com/14.htm