WordPress中各种邮件内容及标题的自定义
WordPress有个对于站长十分重要的功能,就是发送
邮件,这些邮件中包括了以下的几种主要的邮件:- 新用户注册通知邮件(为实现更好效果,需要在每次自愿及非自愿更新后手动更改pluggable.php的部分代码,以添加插入点)
- 站长新文章通知邮件(需要自行在publish_post上添加钩子实现)
- 评论回复提醒邮件(需要自行在comment_post上添加钩子实现)
- 用户登录失败提醒邮件(需要自行在wp_login_failed上添加钩子实现 )
- 用户登录成功提醒邮件(需要自行在wp_login上添加钩子实现,适合访问量较少的网站,否则一天之内就能塞满站长的邮箱)
- 文章/评论被评论/回复时提醒发表者(包括TrackBack和PingBack)
- 新评论产生后提醒审核者
- 用户组改动通报(为实现更好效果,需要在每次自愿及非自愿更新后手动更改pluggable.php的部分代码,以添加插入点)
- 找回密码邮件
- 密码更改通知邮件
- 邮箱更改通知邮件
- 自动升级邮件(大致看了一下,有插入点,不过本站暂时没做自定义,目测站长强迫症犯了会去改)
以上所有内容是我通过用FileLocator在WordPress源码中以wp_mail为关键词进行全目录递归全文搜索的结果,稍微有点WP二次开发经验的人应该已经反应过来了,wp_mail是wp中发邮件的核心实现模块,所有其他的模块都是调用的wp_mail实现的邮件发送。基本上涵盖了WordPress中在不增加额外插件的情况下,在不改变WordPress本身用于博客或者CMS等以文章为主的单站点(暂时不准备研究多站点,搜的时候发现在wp-admin下边还有几个用来给多站点子站站长发邮件的接口调用)网站。Po主建议现将本文大致浏览一遍再考虑动手的问题,以免产生不必要的麻烦。
0.理论基础&先决条件
本文接下来将要叙述的内容可能会让完全没有PHP和HTML基础的小白感觉像是在讲玄学
。不过也不需要太过于高水平的PHP,毕竟水平高的不会来看本文,直接自己翻WP源码就能做了。至于本文为什么一直强调PHP和HTML的能力,因为邮件本身的主题和内容都是使用PHP直接生成,如果对PHP的基本语法与字符串拼接(我能想到的最精简的知识储备,只能重新排版邮件内容或者删减内容而不能新增自己希望添加的内容,例如访客的IP并给出归属地信息等功能),而一个漂亮的邮件内容 必然要用HTML这种语言来实现,所以HTML基本的语法结构和标签的使用一定要足够熟练。当然,在改之前,请务必保证自己WordPress处于良好状态并且wp_mail()函数可以正常的发送邮件,如果不能发送邮件,请参照本站文章《万网2年免费虚拟主机无法使用SMTP的解决方案》介绍的方法进行操作和诊断。
1.邮件格式及信纸问题
在WordPress中,默认情况下邮件的MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型,通俗解释就是用来标记邮件主体是什么类型的数据的一个头部字段)会被设定为text/plain,及”文本/真纯文本”类型,于是乎在邮件接收方的邮件浏览器(无论网页版还是用了客户端)中,所有字符将会被原文输出,例如”<b>我是内容</b>”这样一段字符串将会显示为”<b>我是内容</b>”而非像HTML一样输出一个”我是内容“的黑体样式。所以为了保证我们在之后N多过滤器中写的HTML能够正确的表示为一个个精美的页面而不是一坨代码直接堆在接受者眼前,我们需要先将WordPress的默认发信格式转换为text/html,即”文本/超文本标记语言”。如果你用WordPress有一段时间,并且尝试过在度娘那了解过怎么改这个的话,你一定对下面这行代码不陌生:
//改HTML邮件 add_filter('wp_mail_content_type',create_function('','return "text/html";'));
实际上就是创建了个匿名函数然后挂在wp_mail_content_type过滤器上。至此,恭喜你,完成第一步http://xxxxxxx/xx-login.php?xxx=xxx&xxx=xxx>的部分到底想表达个什么意思。同时,HTML中的换行符合和纯文本中换行符作用效果不同也是造成最终邮件内容异常的一个重要原因。
。实际上在网上很多介绍这行代码的博文上都没有注明一个很要命的问题:这行代码有相当大的副作用。副作用很简单,就是把所有邮件内容全部标记为以网页格式解析显示了。可能你会有疑问,我们现在要做的不就是这个么?改HTML确实达到了,但是对于那些本来为了在纯文本模式下看起来好看的设定会变得非常诡异。最为臭名昭著的便是那个新用户注册激活链接失效的问题。很多人遇到这个问题发现是最后边的某个>导致url的截取提前结束,导致放在末尾部分的一次性激活码被损失掉一部分,殊不知是因为自己更改为了html方式解析邮件导致浏览器无法理解那个<在默认的邮件格式下,邮件的背景信纸就是一个白色背景,什么都没有,而在一封能够为网站带来点击的精美邮件中,信纸有着不可或缺的地位。至于信纸从哪里搞,网上一搜一大把,建议直接找那些现成的HTML信纸,用着方便改着也同样很顺手。同时也可以到各大邮件服务商那里,如网易,QQ邮箱,Gmail等等,直接创建一封带有信纸的空白普通邮件,然后点到HTML纯文本模式下拷出来所有的内容,当做信纸亦可。
这里以网易上借鉴
来的一个信纸作为例子:在网易上直接创建这样一个邮件应该不成难事,信纸也选好后,建议在邮件的主体部分键入几个汉字(用于稍后定位邮件主体部分,因为HTML代码长得和英文相似,所以用方块字更好找),随后点击工具栏扩展部分中切换到源代码功能按钮,查看源代码,然后会看到一坨HTML。
中间可以找到我们之前加的那些文字,不过这里要注意,如果有像Po主一样为了截个图调整过字体的话,要把紧挨的带有样式的部分一并算为我们刚刚添加的内容。如果你想继续更改信纸,例如添加页头或者页脚,信纸合法化
等等操作,可以把它粘到HBuilder之类代码编辑器中进行编辑后再继续下面的步骤。通过简单的看上图也能够发现一个现象,本身信纸就是两串html中间包着邮件内容,所以我们要为wp_mail过滤器添加一个函数,在参数传进去生效前把信纸给他套上去。这里以你之前在可视化方式下添加的那点东西为分隔符,暂且称前边的半部分为信纸头,后半部分为信纸尾,添加以下代码:
//改信纸 add_filter('wp_mail',function($args){ $args['message']="信纸头".$args['message']."信纸尾"; return $args; });
至此,可以尝试发送一封邮件,查看信纸的变化。
2.邮件的主题
这部分呢不算是非常显眼的部分,位于邮件主体之外的部分,也用于邮件列表的显示。拥有一个良好的主题能够有效的降低自己网站被接收方邮件网关定为垃圾邮件的概率,提升邮件在网站推广过程中所占比重。邮件的主题在WordPress中有单独的过滤器用来处理,这里先介绍一种通用的做法:
//生成标题 function subDemo($arg1,$arg2,...){ //参数数量根据自己需要的数量定 //生成预期的标题 $newSubject=$arg1; return $newSubject; } add_filter("xxxx_subject","subDemo",null,0); //注意最后一个参数代表了你的过滤器函数接受几个参数
其中xxxx_subject即WordPress中用于修饰邮件主题的过滤器,下面我列出了所有可以用来自定义邮件标题的:
标题名称 | 过滤器名 | 参数列表(按顺序) |
文章/评论被评论/回复时提醒发表者 | comment_notification_subject | 1.WordPress本身生成的邮件主题
2.新评论的ID |
新评论产生后提醒审核者 | comment_moderation_subject | 1.WordPress自动生成的邮件主题
2.新评论的ID |
用户组改动通报(*) | password_change_notification_subject | 1.WordPress自动生成的邮件主题
2.被改密码的用户名 |
新用户注册(管理员)(*) | new_user_notification_for_admin_subject | 1.WordPress自动生成的邮件主题 |
新用户注册(新用户)(*) | new_user_notification_for_user_subject | 1.WordPress自动生成的邮件主题 2.新的用户的ID |
找回密码邮件 | retrieve_password_title | 1.WordPress自动生成的邮件主题 2.被操作用户用户名 3.被操作用户的用户对象(包含大量信息) |
注意:带有*号的一些项目本身WordPress中并不存在提及的过滤器名称,这些过滤器均为Po主在研读上下文代码后决定增加的。官方不添加是因为添加后会给一些恶意插件留空子以收集安装插件的网站中的用户信息(尤其是密码原文信息)。如果希望使用上表中带有(*)号的部分,则需要在每次WordPress更新后根据文末检查并添加代码的部分对pluggable.php进行修改。
3.邮件的主体内容
这部分就比较难受了。因为之前为了实现一个信纸的效果,断然把邮件的格式改成了HTML格式,导致各种邮件多多少少都会有点问题存在,所以我建议只要改了HTML格式,就通过下面的方法将所有的邮件内容都重写一遍,即使不想改变原来邮件的格式排版和内容,也要增加这样一部分内容对纯文本进行HTML特殊字符的转义,之前提到过的那种连接切分异常也就会迎刃而解。如果是更改邮件主体的内容,那么就需要用到大量的字符串拼接来产生一个满意的邮件内容。由于信纸在wp_mail上会自动添加,在编写主体内容时候也就没有必要再去添加信纸了。这里还是只给出一种通用的方法:
function txtDemo($arg1,$arg2,...){ //参数数量根据自己需要的数量定 //生成预期的标题 $newSubject=htmlspecialchars($arg1); //如果保持原样格式,则保留此行,否则删除 return $newSubject; } add_filter("xxxx_text","txtDemo",null,0); //注意最后一个参数代表了你的过滤器函数接受几个参数
与上面改标题相同,其中的xxxx_text也是可以被替换为实际的过滤器名称:
邮件类型 | 过滤器名 | 参数列表(按顺序) |
文章/评论被评论/回复时提醒发表者 | comment_notification_text | 1.WordPress自动生成的邮件内容
2.新评论的ID |
新评论通知审核者 | comment_moderation_text | 1.WordPress自动生成的邮件内容
2.新评论的ID |
用户组改动通报(*) | password_change_notification_text | 1. WordPress自动生成的邮件内容
2.用户对象 |
新用户注册(管理员)(*) | new_user_notification_for_admin_text | 1.WordPress自动生成的邮件内容
2.用户ID |
新用户注册(新用户)(*) | new_user_notification_for_user_text | 1.WordPress自动生成的邮件内容
2.用户ID |
找回密码邮件 | retrieve_password_message | 1.WordPress自动生成的
2.重置密码激活码 3.用户的登录名 4.用户对象 |
邮箱变更通知 | email_change_email | 1.邮件属性数组(详见/wp-includes/user.php-1893行附近)
2.原用户参数数组 3.新用户参数数组 |
注意:带有*号的一些项目本身WordPress中并不存在提及的过滤器名称,这些过滤器均为Po主在研读上下文代码后决定增加的。官方不添加是因为添加后会给一些恶意插件留空子以收集安装插件的网站中的用户信息(尤其是密码原文信息)。如果希望使用上表中带有(*)号的部分,则需要在每次WordPress更新后根据文末检查并添加代码的部分对pluggable.php进行修改。
4.对核心代码的修改
上述很多本身WordPress出于对访客用户隐私的保护,没有设定过滤器,这里为了在每次更新后不必把大段的生成邮件内容代码直接替换到wp-includes/pluggable.php中,只是添加少量代码实现功能,同时也符合WordPress核心在设计过程中遵循的过滤器模式,保持其正常的可扩展性要求。为了实现上述带有星号的功能,按照下面注释中的内容对pluggable.php进行修改添加以下内容,下面的这些提示行号可能会在之后的更新中做小范围内的浮动,关键以删除的那一行为基准:
/** * 用户组改动通报 * pluggable.php: * 1720 +$message=apply_filters("password_change_notification_text",$message,$user); * 1721 +$title = apply_filters("password_change_notification_subject", sprintf(__('[%s] Password Lost/Changed'), $blogname),$user->user_login); * 1722 -wp_mail(get_option('admin_email'), sprintf(__('[%s] Password Lost/Changed'), $blogname), $message); * 1722 +wp_mail(get_option('admin_email'),$title, $message); */ /** * 注册会员通知 * pluggable.php: * 管理员邮件 * 1765 +$message=apply_filters("new_user_notification_for_admin_text",$message,$user_id); * 1766 +$title=apply_filters("new_user_notification_for_admin_subject",sprintf(__('[%s] New User Registration'), $blogname)); * 1767 -@wp_mail(get_option('admin_email'),sprintf(__('[%s] New User Registration'), $blogname), $message); * 1767 +@wp_mail(get_option('admin_email'),$title, $message); * 登陆者邮件 * 1750 +$title = apply_filters("new_user_notification_for_user_subject",sprintf(__('[%s] Your username and password info'),$blogname),$user_id); * 1751 +$message = apply_filters("new_user_notification_for_user_text",$message,$key,$user_id); * 1754 -wp_mail($user->user_email, sprintf(__('[%s] Your username and password info'), $blogname), $message); * 1754 +wp_mail($user->user_email,$title,$message); */
我也建议直接将这几行注释和根据上文所提及的所有内容全部保存为一个functions.mail.php用来保存所有邮件相关的自定义内容,然后再在functions.php中添加一行
include("functions.mail.php");
引入编写的新文件。至于最开始一些提到但是没有介绍的需要额外添加代码实现的邮件功能,直接在百度上找一段改改即可,主要还是注意HTML格式的变化。
2017年5月4日补充
对于上面提到的修改pluggable.php文件,也可以使用下面这段代码在系统更新完成后自动更新pluggable.php的内容:
function autoUpdatePluggablePHP($upgrader,$hookextra){ if($hookextra['type']!='core') return; do_action('update_feedback','正在更新Pluggable.php中关于邮件的过滤器...'); $pluggablePath=get_home_path().'/'.WPINC."/pluggable.php"; $pluggable=file_get_contents($pluggablePath); //new_user_notification_for_user_subject&new_user_notification_for_user_text $codes=[ '//Auto Inject Point 1', ' $message=apply_filters("password_change_notification_text",$message,$user);', ' $title = apply_filters("password_change_notification_subject", sprintf(__("[%s] Password Lost/Changed"), $blogname),$user->user_login);', ' wp_mail(get_option("admin_email"),$title, $message);' ]; $pluggable=str_replace('wp_mail( get_option( \'admin_email\' ), sprintf( __( \'[%s] Password Changed\' ), $blogname ), $message );',implode("\n",$codes),$pluggable); //new_user_notification_for_admin_text&new_user_notification_for_admin_subject $codes=[ '//Auto Inject Point 2', ' $message=apply_filters("new_user_notification_for_admin_text",$message,$user_id);', ' $title=apply_filters("new_user_notification_for_admin_subject",sprintf(__("[%s] New User Registration"), $blogname));', ' @wp_mail(get_option("admin_email"),$title, $message);' ]; $pluggable=str_replace('@wp_mail( get_option( \'admin_email\' ), sprintf( __( \'[%s] New User Registration\' ), $blogname ), $message );',implode("\n",$codes),$pluggable); //new_user_notification_for_user_subject&new_user_notification_for_user_text $codes=[ '//Auto Inject Point 3', ' $title = apply_filters("new_user_notification_for_user_subject",sprintf(__(\'[%s] Your username and password info\'),$blogname),$user_id);', ' $message = apply_filters("new_user_notification_for_user_text",$message,$key,$user_id);', ' wp_mail($user->user_email,$title,$message);', ]; $pluggable=str_replace('wp_mail($user->user_email, sprintf(__(\'[%s] Your username and password info\'), $blogname), $message);',implode("\n",$codes),$pluggable); //检查更新情况 $count=preg_match_all('/Auto Inject Point \d/',$pluggable); if($count<3) do_action('update_feedback',sprintf('<b>警告:pluggable.php关于邮件的代码可能发生变更,代码自动替换仅成功检测并替换%d个检测点!</b>',$count)); //save file_put_contents($pluggablePath,$pluggable); } //在内核更新后更新Pluggable add_action("upgrader_process_complete",'autoUpdatePluggablePHP',10,2);
将这段代码加入主题的functions.php即可.在首次更新后应当确认本段代码的有效性,同时,对于被替换掉的内容可能会在更新过程中变化(更新过程中会产生警告提示),故需要视情况更新代码中str_replace的第一个参数.
2017年11月17日补充:
今天wordpress官方发布了4.9的正式版本。在这个版本中新增加了三个过滤器,能够替换掉本文中手动添加的一些过滤器。
首先是位于pluggable.php 1780行用户密码被修改后触发的wp_password_change_notification_email
$wp_password_change_notification_email = array( 'to' => get_option( 'admin_email' ), /* translators: Password change notification email subject. %s: Site title */ 'subject' => __( '[%s] Password Changed' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the password change notification email sent to the site admin. * * @since 4.9.0 * * @param array $wp_password_change_notification_email {(默认邮件数据对象) * Used to build wp_mail(). * * @type string $to The intended recipient - site admin email address. (管理员邮箱) * @type string $subject The subject of the email.(邮件主题) * @type string $message The body of the email.(邮件内容) * @type string $headers The headers of the email.(邮件头) * } * @param WP_User $user User object for user whose password was changed.(密码变更的用户) * @param string $blogname The site title.(网站标题) */ $wp_password_change_notification_email = apply_filters( 'wp_password_change_notification_email', $wp_password_change_notification_email, $user, $blogname ); wp_mail( $wp_password_change_notification_email['to'], wp_specialchars_decode( sprintf( $wp_password_change_notification_email['subject'], $blogname ) ), $wp_password_change_notification_email['message'], $wp_password_change_notification_email['headers'] );
然后是在新用户注册时对新用户和管理员的邮件,位于pluggable.php的1858行和1924行
$wp_new_user_notification_email_admin = array( 'to' => get_option( 'admin_email' ), /* translators: Password change notification email subject. %s: Site title */ 'subject' => __( '[%s] New User Registration' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the new user notification email sent to the site admin. * * @since 4.9.0 * * @param array $wp_new_user_notification_email {(新用户注册通知邮件数据对象) * Used to build wp_mail(). * * @type string $to The intended recipient - site admin email address.(管理员邮箱地址) * @type string $subject The subject of the email.(邮件主题) * @type string $message The body of the email.(邮件主体内容) * @type string $headers The headers of the email.(邮件头) * } * @param WP_User $user User object for new user.(新注册的用户) * @param string $blogname The site title.(网站标题) */ $wp_new_user_notification_email_admin = apply_filters( 'wp_new_user_notification_email_admin', $wp_new_user_notification_email_admin, $user, $blogname ); @wp_mail( $wp_new_user_notification_email_admin['to'], wp_specialchars_decode( sprintf( $wp_new_user_notification_email_admin['subject'], $blogname ) ), $wp_new_user_notification_email_admin['message'], $wp_new_user_notification_email_admin['headers'] );
$wp_new_user_notification_email = array( 'to' => $user->user_email, /* translators: Password change notification email subject. %s: Site title */ 'subject' => __( '[%s] Your username and password info' ), 'message' => $message, 'headers' => '', ); /** * Filters the contents of the new user notification email sent to the new user. * * @since 4.9.0 * * @param array $wp_new_user_notification_email {(新用户欢迎邮件数据对象) * Used to build wp_mail(). * * @type string $to The intended recipient - New user email address.(新用户邮件地址) * @type string $subject The subject of the email.(邮件主题) * @type string $message The body of the email.(邮件主体内容) * @type string $headers The headers of the email.(邮件头) * } * @param WP_User $user User object for new user.(新注册用户) * @param string $blogname The site title.(网站标题) */ $wp_new_user_notification_email = apply_filters( 'wp_new_user_notification_email', $wp_new_user_notification_email, $user, $blogname ); wp_mail( $wp_new_user_notification_email['to'], wp_specialchars_decode( sprintf( $wp_new_user_notification_email['subject'], $blogname ) ), $wp_new_user_notification_email['message'], $wp_new_user_notification_email['headers'] );
在今天升级后纠正主题中对邮件格式更新的函数时发现,对于wp_new_user_notification_email过滤器貌似官方少传了一个参数$key导致使用此过滤器进行开发的开发者需要从原文中使用正则表达式将这个$key取出,所以这里我建议将pluggable.php的1924行的apply_filter增加一个$key参数,方便我们开发.另外对于这个参数的问题,po主已经向wordpress官方发送一个Issue询问是否能考虑增加这样一个参数以方便开发.
$wp_new_user_notification_email = apply_filters( 'wp_new_user_notification_email', $wp_new_user_notification_email, $user, $blogname );
同时如果升级到4.9版本后,之前设定的每次自动更新pluggable.php内容的代码可以更改为下面的部分并采用新的邮件更新过滤器.
function updatePluggable($upgrader,$hookextra){ if($hookextra['type']!='core') return; do_action('update_feedback','正在更新Pluggable.php中关于邮件的过滤器...'); $pluggablePath=get_home_path().'/'.WPINC."/pluggable.php"; $pluggable=file_get_contents($pluggablePath); //wp_new_user_notification_email $codes=[ '//Auto Inject Point 1', ' $wp_new_user_notification_email = apply_filters( \'wp_new_user_notification_email\', $wp_new_user_notification_email, $user, $blogname,$key);', ]; $pluggable=str_replace('$wp_new_user_notification_email = apply_filters( \'wp_new_user_notification_email\', $wp_new_user_notification_email, $user, $blogname );',implode("\n",$codes),$pluggable); //检查更新情况 $count=preg_match_all('/Auto Inject Point \d/',$pluggable); if($count<1) do_action('update_feedback',sprintf('<b>警告:pluggable.php关于邮件的代码可能发生变更,代码自动替换仅成功检测并替换%d个检测点!</b>',$count)); //save file_put_contents($pluggablePath,$pluggable); } add_action("upgrader_process_complete",'updatePluggable',10,2);
评论
您需要 先登录 才可以回复.