EP128. “核心功能实现:字数统计与预估阅读时间”
🔒 登录后可标记已读把之前搭好的设置页面真正用起来:给博客文章正文挂一个 the_content 过滤器,只在「文章详情页 + 主查询 + 至少勾选了一项统计」时才触发;具体拼 HTML 的逻辑拆到独立的 createHTML() 方法里,依次判断字数、字符数、预估阅读时间三个开关,各自拼接对应文字,最后根据「显示位置」设置决定是拼在正文前面还是后面。字数只算一次、字数和阅读时间共用同一个变量,避免重复计算。
涉及文件
wp-content/plugins/our-first-unique-plugin/our-first-unique-plugin.php(修改)
代码实现
__construct():新增 the_content 过滤器:
add_filter('the_content', array($this, 'ifWrap'));
ifWrap():判断是否需要处理,是则转给 createHTML():
function ifWrap($content) {
if (is_main_query() AND is_single() AND
(
get_option('wcp_wordcount', '1') OR
get_option('wcp_charactercount', '1') OR
get_option('wcp_readtime', '1')
)) {
return $this->createHTML($content);
}
return $content;
}
createHTML():真正拼出统计信息 HTML,按设置决定拼在正文前还是后:
function createHTML($content) {
$html = '<h3>' . esc_html(get_option('wcp_headline', 'Post Statistics')) . '</h3><p>';
// get word count once because both wordcount and read time will need it.
if (get_option('wcp_wordcount', '1') OR get_option('wcp_readtime', '1')) {
$wordCount = str_word_count(strip_tags($content));
}
if (get_option('wcp_wordcount', '1')) {
$html .= 'This post has ' . $wordCount . ' words.<br>';
}
if (get_option('wcp_charactercount', '1')) {
$html .= 'This post has ' . strlen(strip_tags($content)) . ' characters.<br>';
}
if (get_option('wcp_readtime', '1')) {
$html .= 'This post will take about ' . round($wordCount/225) . ' minute(s) to read.<br>';
}
$html .= '</p>';
if (get_option('wcp_location', '0') == '0') {
return $html . $content;
}
return $content . $html;
}
关键改动点:
ifWrap()判断条件三部分都要满足:is_main_query()(排除侧边栏等次要循环)、is_single()(只在文章详情页生效,is_page()只匹配 Page 类型,is_single()才是匹配 Post 详情页的正确函数)、以及「三个开关至少有一个是开的」——get_option()传第二个参数作为默认值,插件刚安装、用户还没保存过设置时,也能按预期表现(默认全部开启)- 判断分支的具体处理逻辑刻意拆到独立的
createHTML($content)方法里,而不是全塞进ifWrap()一个函数——保持每个方法只负责一件事,ifWrap只管「要不要处理」,createHTML只管「怎么处理」 $wordCount只计算一次、存进变量复用:字数统计和阅读时间预估都需要用到词数,str_word_count(strip_tags($content))是有实际开销的运算(尤其文章很长时),没必要算两遍——用一个if提前判断「只要字数统计或阅读时间任一开启,就算一次」,避免在只勾了字符统计时做无意义的字数运算strip_tags($content)在统计前先去掉 HTML 标签,确保统计的是纯文字内容,不会把<p>、<strong>这类标签本身也算进字数/字符数里str_word_count()统计词数,strlen()统计字符长度,都是 PHP 内建函数,不需要自己写字符串遍历逻辑- 阅读时间用「平均每分钟阅读 225 词」这个经验值粗略估算:
round($wordCount / 225),round()取整避免出现「2.7 分钟」这种不自然的小数结果 - 最终决定拼接顺序:
get_option('wcp_location', '0') == '0'为真就是「拼在正文前面」($html . $content),否则拼在后面($content . $html)——对应后台设置里「Beginning of post」/「End of post」两个选项 - 标题文字输出前用
esc_html()转义——即使是从后台设置里读出来的、理论上只有管理员能改的值,依然坚持「凡是动态内容输出到前台页面就要转义」的安全习惯,防止万一数据库被篡改注入恶意脚本
[截图:前台博客文章详情页,正文前/后出现的 Post Statistics 字数/字符数/预估阅读时间统计区块]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
is_single() | WP 内建 function | 判断当前是否为 Post(或其他非 Page/attachment 的普通文章类型)的详情页 |
str_word_count($string) | PHP 内建 function | 统计字符串中的单词数量 |
strlen($string) | PHP 内建 function | 统计字符串长度(字符数) |
strip_tags($string) | PHP 内建 function | 去除字符串中的 HTML/PHP 标签 |
round($number) | PHP 内建 function | 四舍五入取整 |
常见坑
- 统计字数/字符数前不先
strip_tags()——会把 HTML 标签本身的字符也算进统计结果,导致数字偏大且不准确 - 字数和阅读时间各自独立计算一遍
str_word_count()——没必要的重复运算,尤其文章内容很长时会有实际的性能损耗,应该只算一次存进变量复用 get_option()不传默认值——插件刚装上、用户还没主动保存过任何设置时,数据库里对应的选项行根本不存在,不传默认值会导致返回false,一系列判断逻辑的结果会不符合预期(比如新装用户看不到任何统计信息,而不是符合预期的「默认全部显示」)
延伸 / 后续讲座会用到
第一个插件(字数统计)到此完工。下一讲会暂停功能开发,专门讲「怎么让插件方便被翻译成其他语言(国际化/本地化)」,再往后开始做第二个插件——敏感词过滤器。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 23, EP128