站长帮忙给我的新域名配套了WordPress框架后,自由度比起先前在chron.boo项目下面时高了许多,可以自由下载插件(我这里使用的系统语言是台湾所以称之为外挂)和主题,也有布景主题和外挂的档案编辑器可以直接修改文件。于是我就心动了:所以能不能实现在别的博客上看到的一些功能?我想实现的功能主要有单篇文章展示字数和预计阅读时间;footer展示全站文章篇数和总字数、建站至今经历的天数。从我暗搓搓的观察来看,是不是Hugo框架有主题(比如Stack)可以自动实现这个功能?然而我用的WordPress,审美上喜欢的一些主题并没有自带这些功能,所以一切都要自己捣腾。注意:本人纯业余,本文比起教程更多是探索的记录,包含了各种试错过程和外行的思考。

单篇文章展示目录

虽然文章目录并不在我明确想增加的功能里,但最先实现的功能却是这个。原因很简单:一个插件就搞定了……插件名称:Easy Table of Contents。这是我在搜索字数统计插件时无意中发现的,做得真的很易用,甚至有中文版,于是我就很容易地把外观改成了符合本站主题的配色。

美中不足之一:目录一开始是靠左显示的,但是我设置项调着调着就莫名其妙变成居中了……“内容目录外观→对齐方式”设置项怎么改都没反应,插件删除了重装还是保持在居中状态,不知道怎么重新弄成靠左。

美中不足之二:这个就是吹毛求疵了,目录是有个按钮可以收起展开的,但是那个按钮图标有点不合我的主题画风,加上点击后出现的虚线方框和图标是错位的,就有点丑,我就把这个按钮给取消了。其实这些通过外挂档案编辑器应该都是可以改的,但是需求不是很急迫,我就还没研究。

单篇文章展示字数和阅读时间

这个我一开始也是想找个可以直接实现的插件,然而没有,或者说,各有问题。WP Word Count和我的系统不兼容,一启用就打不开外观编辑器;其他可以用的插件全是按照英文来计算的,中文没有计算在内。偌大的WordPress插件社区,竟没有一个把中文也计算在内的字数统计插件?

然后我通过搜索引擎找到了一篇文章里提供的代码,说是可以不使用任何插件就可以实现这个功能。方法是,打开你的布景主题档案编辑器,在functions.php中插入函数:

function zzb_reading_time() {
    $post = get_post();
    $content = $post->post_content;
    $wpm = 300; // 每分钟阅读字数设定,可根据需求修改
    $clean_content = strip_shortcodes( $content );
    $clean_content = strip_tags( $clean_content );
    $word_count = mb_strlen( $clean_content,'UTF8'); //按UTF8编码统计字数,一个汉字只算1个字
    $time = ceil( $word_count / $wpm );
    return '字数' .$word_count . '个  阅读全文: ' .$time . ' 分钟';
}

然后在single.php中插入输出部分:

<?php echo zzb_reading_time(); ?>

我找到了functions.php,然而本主题中并没有single.php文件。想了想,打开我的老相好Magpaper主题,嗯,这里是有的。所以就是新版的Blocks主题编辑器没有single.php文件。直觉告诉我新版应该可以通过短代码来实现,然而我并不知道具体怎么实现。那咋整?

于是我继续搜,找到了一篇繁中文章,这里给出的代码是:

function reading_time() {
    $content = get_post_field( 'post_content', $post->ID );
    $str_mblen = mb_strlen(preg_replace('/\s/','',html_entity_decode(strip_tags($content))),'UTF-8');
    $readingtime = ceil($str_mblen / 400); //此處以每分鐘平均閱讀400字為例
    $totalreadingtime = "閱讀時間". " "  . $readingtime . " " . "分鐘";
    return $totalreadingtime;
}
add_shortcode( 'estimated_reading_time', 'reading_time' );

划重点,add_shortcode()。知道怎么弄了。虽然这里的代码只生成了阅读时间,但是阅读时间也是通过字数来计算的嘛,找出字数变量在return行改改就是了。

总之,两种方案我都试了一下,具体计算出的字数都比我在纯纯写作上的原稿计字多一些,但是方案二字数更接近,于是就采纳方案二了。在遥远的未来我可能会把字数计算改得更准确一些,但这就不着急了。我看WordPress在文章编辑时显示的那个字数是准的,但不知道怎么把那个字数提取出来。

于是最终我在functions.php文件中增加的代码是:

function reading_time() {
    $content = get_post_field( 'post_content',  );
	$str_mblen = mb_strlen(preg_replace('/\s/','',html_entity_decode(strip_tags($content))),'UTF-8');
    $readingtime = ceil($str_mblen / 400); //此處以每分鐘平均閱讀400字為例
	$totalreadingtime = $str_mblen. " 字丨阅读时间". " "  . $readingtime . " " . "分钟";
    return $totalreadingtime;
}
add_shortcode( 'estimated_reading_time', 'reading_time' );

用短代码[ estimated_reading_time ](去掉[]中空格)实现到前台就是:

5940 字丨预计阅读 15 分钟

大功告成,朕很满意。

footer展示全站文章数和建站天数

学会怎么把功能封装进短代码里之后接下来其实就很简单了,就是搜一下WordPress站点统计功能代码,好些文章都有提供。不止我要的这两项,给出的统计函数有文章数、草稿数、评论数、建站天数、标签数、页面数、分类数、链接数、用户数和最后更新时间,不过我只需要文章数和建站天数两项。可惜没有总字数,预计需要写个循环做加法,缓缓打开PHP教程……后文再讲,先讲这个。

总之,为了实现全站文章数统计,我在functions.php文件中增加的代码是:

function count_posts() {
	$count_posts = wp_count_posts();
	$published_posts = $count_posts->publish;
	return $published_posts. ' 座礁石';
}
add_shortcode( 'published_posts', 'count_posts');

我这里礁石就是代表文章数,你们可以自行改字符串。用短代码[ published_posts ]实现到前台就是:

47 座礁石

为了实现建站天数计算,我在functions.php文件中增加的代码是:

function established_days() {
	$days = floor((time()-strtotime("2023-12-28"))/86400);
	return '经历 '. $days. ' 次潮汐';
}
add_shortcode( 'established_days', 'established_days');

潮汐就代表了天数,多数滩涂是半日潮,潮水一天涨落两次,早潮为潮,晚潮为汐。用短代码[ established_days ]实现到前台就是:

经历 285 次潮汐

大功告成,朕很满意。×2

footer展示全站总字数

这个是最难弄的,搜教程也搜不到,但逻辑还是蛮简单的,就是把所有文章都调出来然后写个循环统计字数嘛。怎么把所有文章都调出来呢,我了解到有个get_posts()函数,直觉应该是用这个。

于是我就想先用get_posts()函数把文章内容调出来看看,但是总是给我报错。查了教程,应该要给这个函数提供一个数组,它会返回一个文章数组,然后用foreach循环可以遍历。PHP语法的数组和循环格式我还不了解,于是缓缓打开PHP入门教程准备生啃一下。看了两个分p后,福至心灵的我忽然想到:为什么不问问神奇海螺对话式AI呢?

但是写这个代码需要WordPress框架的一些专有统计函数,我不知道AI知不知道。抱着试试的心理,我就在poe.com问了AI:

为WordPress的functions.php文件写一段代码,使得它显示网站内所有文章的总字数。

分别问了ChatGPT和Claude-instant,都是旧版本的,GPT不是4,Claude也不是2,因为升级版现在poe不给我试用额度了,要花钱。

没想到真的都能写。GPT回给了我一个结尾是echo行的函数,并让我在主题模板文件里加入这个函数的调用;Claude回给了我一个结尾是return行的函数,并且给我add_shortcode()注册了短代码。也就是说,GPT给的代码在老版WordPress主题才能用,Claude给的代码Blocks主题能用。看起来Claude更聪明一点,于是我接着用Claude。

但是Claude回给我的代码用了SQL查询而不是get_posts()函数,而我也没学过SQL,为了不让事情超出掌控,我让它不使用SQL查询。于是它就用get_posts()了。试了一下,反馈给我总字数5w5,而我手动加了一下目前全站总字数是5w2,同时我知道这肯定没按UTF8编码来计算汉字,我又让它按照UTF8编码来显示。

结果新代码给我报出来总字数1w8,给我整愣了。多是因为汉字被按两个字节来计算了以及各种不必要的符号也被算进去了,少是什么道理?

我先猜是不是文章类型没有计算全。于是我先让Claude再给我写一段代码来获取网站全部的post type。结果一下还给我报错,排查问题发现是数据类型不对,短代码似乎只能输出字符串,于是又转换数据类型来看,但看完发现应该不是文章类型的问题,因为给get_posts()函数的数组代码里也写了‘post type’ => ‘post’

然后我盯着这个1w8和这个5w2,终于想到了是不是没有把设置的发布时间早于网站建立时间的文章计算在内。验证猜想:

不使用SQL查询,为WordPress的functions.php文件写一段代码,使得它按照UTF8编码显示网站内所有文章的总字数,包含发布日期早于网站建立日期的文章。

结果在代码里填网站建立日期的时候一上来还填错了,英文-给打成中文-了,又绕了个圈才验证成功,使用get_posts()函数的话发布日期早于建站日期的文章字数给我显示是0。还挺快的,两下我就给找到原因了……

那怎么整?没辙。问了Claude,除非你改文章发布日期或者网站建立日期,不然光用get_posts()似乎就是没法算。

沉默。

于是,缓缓转向SQL……

使用SQL查询,为WordPress的functions.php文件写一段代码,使得它按照UTF8编码显示网站内所有文章的总字数。

结果,它给的代码没给我返回任何东西。又把我给整蒙了。中间略过一堆排查问题的过程和跟SQL打架的过程,简单来讲就是我好像意识到问题是什么了,但是又具体说不清楚。反正肯定是代码有问题。

于是又重新问了两遍,它给了我不同方案,终于有个方案给我返回确切数字了。

是,

12w……

我猜测这肯定是5w2汉字按字节计算翻倍,再加上了什么乱七八糟的符号的结果。那怎么整?我让它用UTF8算,结果数值又不显示了。显示出12w的那个代码很简洁,SELECT部分只用了CONVERT()CHAR_LENTH()CONVERT(),对比不能工作的代码,估计肯定不是REPLACE()的问题就是CAST()的问题。能说得上来的,其中一个用了CAST(post_content AS BINARY),你说你把文章转二进制干嘛呀!

加上还有那些过量的多余符号也被计算在内的问题。我不了解REPLACE()的正则,但是根据统计单篇文章字数的经验,WordPress里的PHP函数那些preg_replace()strip_tags()呀还是有用的,加上SQL内处理UTF8编码总是弄不好,但其实mb_strlen()就可以很清楚算明白,那就不如出来弄。于是向Claude的最终请求:

使用SQL查询所有文章内容,再使用mb_strlen计算总字数。

然后它给我的代码,运行成功了。结果是5w多。加油,已经很接近了!

再优化一下,把foreach循环里的str_replace(” “, “”, $content)换成preg_replace(“/\s+/”, “”, $content),最后出来:52042。

和我把前台显示的单篇文章字数手动全加起来的数值一模一样。

嗯。

大功告成,朕很满意。×3,但是是熬夜疲惫版。此时已经是夜里三点了,我弄这项功能一下弄了起码六个小时……

最后记录一下我的宝贝代码:

function total_words() {

	global $wpdb;

	$sql = "SELECT post_content 
          FROM {$wpdb->posts}
          WHERE post_type = 'post' AND post_status = 'publish'";
          
	$results = $wpdb->get_results($sql);

	$count = 0;

	foreach ($results as $result) {
		$content = $result->post_content;
		$content = strip_tags($content);
		$content = preg_replace("/\s+/", "", $content);
		$count += mb_strlen($content, 'UTF-8'); 
	}

	return '留下 '. $count. ' 道痕迹';

}

add_shortcode('total_words', 'total_words');

呈现出的效果是:

留下 137114 道痕迹

本文参考

包括但不限于单章字数方案一单章字数方案二站点统计功能代码。还包括我随手搜到的很多PHP和SQL函数解释、bilibili的PHP入门教程以及对话式AI……

朕睡了,88。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


无际的旅途里,我将我借来的一一归还。我归还琴,归还剑,归还战马,归还厮杀。归还流云,归还雅逸,归还骄傲,归还感伤。最终,流水将我和我的尸块扑到了月儿荒凉的野地上;离渭城远了,我听到渭水的波声也就渐渐地小了。