上篇文章中我记录了一些html+js+css方面的知识,那这篇文章记录一下主题被修改的地方,以及修改主题的过程中遇到的php方面的问题。
修改后的主题github链接:https://github.com/iyzyi/Aria-amend-by-iyzyi
0x01 相关改动概述
- 添加常驻背景图,可多张随机
- 将首页封面输出站点描述改为输出一言
- 若数据库没有设置文章缩略图,则随机展示/assets/img/article-thumbnail下的图片
- 将文字预览内容的全角空格替换为空、网址替换为###。提取文首400个字节,设定中文2字节英文1字节,将预览文字字节设为200
0x02 常驻背景图
css
<style type="text/css">
body{
background:url(<?php echo Utils::getBackgroundUrl()?>) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
url由php获取,接口由Utils.php提供
php
在Utils.php中,原代码中有一个获取主页背景的函数:
/*excerpts from Utils.php */
public static function getBackground()
{
$options = Helper::options();
if ($options->backgroundUrl) {
$str = $options->backgroundUrl;
$imgs = trim($str);
$urls = explode("\r\n", $imgs);
$n = mt_rand(0, count($urls) - 1);
echo $urls[$n];
} else {
$options->themeUrl('assets/img/background.jpg');
}
}
options->backgroundUrl是在functions.php中获取的:
/*excerpts from functions.php */
$backgroundUrl = new Typecho_Widget_Helper_Form_Element_Textarea('backgroundUrl', null, null, _t('首页背景图片'), _t('需要输入http(s)://,每一行写一个URL,随机展示'));
$form->addInput($backgroundUrl);
在后台的外观设置中填写内容,存入数据库。通过上面的接口,可以获取数据库中的相关内容。
可以类比,将以下代码添加进Utils.php:
public static function getBackgroundUrl()
{
$options = Helper::options();
if ($options->alwaysBackgroundUrl) {
$str = $options->alwaysBackgroundUrl;
$imgs = trim($str);
$urls = explode("\r\n", $imgs);
$n = mt_rand(0, count($urls) - 1);
return $urls[$n];
} else {
return $options->themeUrl . '/assets/img/bg.jpg';
}
}
将以下代码添加进functions.php:
$alwaysBackgroundUrl = new Typecho_Widget_Helper_Form_Element_Textarea('alwaysBackgroundUrl', null, null, _t('常驻背景图片'), _t('真~背景图,一行一个,随机展示,有默认图片'));
$form->addInput($alwaysBackgroundUrl);
0x03 一言
原代码中其实给出了一言的接口,我懒得看明白,直接自己改吧。
同样在functions.php中添加一言的文本框,并提供获取其内容的接口:
$hitokoto = new Typecho_Widget_Helper_Form_Element_Textarea('hitokoto', null, null, _t('一言'), _t('填写一言,一行一句'));
$form->addInput($hitokoto);
在Utils.php中添加随机一言的函数:
public static function getHitokoto()
{
$options = Helper::options();
if ($options->hitokoto) {
$str = $options->hitokoto;
$imgs = trim($str);
$urls = explode("\r\n", $imgs);
$n = mt_rand(0, count($urls) - 1);
echo $urls[$n];
} else {
echo '红楼别夜堪惆怅 香灯半卷流苏帐';
}
}
在header.php中,将本来输出站点概述的代码改为输出一言:
<header id="header" class="clearfix animated fadeInDown">
<div id="site-meta">
<h1 id="site-name"><?php $this->options->title(); ?></h1>
<h2 id="site-description"><?php Utils::getHitokoto(); ?></h2>
</div>
<div id="background"></div>
</header><!-- end #header -->
修改的是h2标签。
0x04 随机展示目录内的图片
若数据库没有设置文章缩略图,则随机展示/assets/img/article-thumbnail下的图片。
原函数在Utils.php内:
public static function getBackground()
{
$options = Helper::options();
if ($options->backgroundUrl) {
$str = $options->backgroundUrl;
$imgs = trim($str);
$urls = explode("\r\n", $imgs);
$n = mt_rand(0, count($urls) - 1);
echo $urls[$n];
} else {
$options->themeUrl('assets/img/background.jpg');
}
}
修改else内的代码:
public static function getThumbnail()
{
$options = Helper::options();
if ($options->defaultThumbnail) {
$str = $options->defaultThumbnail;
$imgs = trim($str);
$urls = explode("\r\n", $imgs);
$n = mt_rand(0, count($urls) - 1);
return $urls[$n];
} else {
$dir = preg_replace('/lib(\/|\\\\)Utils.php/i', 'assets/img/article-thumbnail/', __FILE__);//物理路径
if (is_dir($dir)){
if ($str = opendir($dir)){//这一步坑死我了
while( ($filename = readdir($str)) !== false ) {
if($filename != "." && $filename != ".."){
if (preg_match("/.*\.(jpg|png|jpeg|gif|bmp)$/i", $filename)){
$file_array[]=$filename;
}
}
}
closedir($str);
$n = mt_rand(0, count($file_array) - 1);
return $options->themeUrl . '/assets/img/article-thumbnail/' . $file_array[$n];
}
}
}
}
$dir = preg_replace('/lib(\/|\\\\)Utils.php/i', 'assets/img/article-thumbnail/', __FILE__);
获取Utils.php的物理路径,并将'/lib/Utils.php'
替换为'/assets/img/article-thumbnail/'
即获取图片目录的物理路径。使用正则表达式,使得linux和windows下都可以正确地替换(分隔符一个是/
一个是\
)。windows下的路径中的分隔符为/
或\
,系统都可以识别。但是给出时是给出\
的
注意php中正则表达式在字符串内被两个/包裹,尾端可以跟模式,i是不区分大小写。此外,php不像python中有自然字符串,所以正则表达式中匹配一个\
要用\\\\
$str = opendir($dir)
打开路径流
上面的这一步坑死我了。opendir的参数是本地路径,可以是相对的,也可以是绝对的,但一定要是物理路径。
参考:http://cn.voidcc.com/question/p-esmycsho-xz.html
您无法'打开'一个URL。但是你可以在执行opendir指定相对和绝对路径:
opendir('.') // open current directory
opendir('..') // open parent directory
opendir('childdir') // open the 'childdir' directory which exists in the current directory (.)
opendir('childdir/grandchilddir') // open a grandchild directory down two levels from the current dir
opendir('../brotherdir') // open a directory next to the current one
opendir('../brotherdir/nephewdir') // open a director next to and down one
opendir('../..') // open a grandparent directory
opendir('../../uncle') // yada yada yada
opendir('/') // open the root dir (primordial slime/adam, depending on your viewpoint)
opendir('/cain') // open a dir called "cain" which is housed in the root directory.
注意,倒数第二个读取的是linux的根目录,即/root的上级目录,而不是你想象中的网站的根目录。切记。被坑在这里。
这里我为什么要废大力气拿到绝对的物理路径呢?若是采用相对目录,不同的页面调用时的相对位置不同,导致拿不到图片,如./1.jpg
,对于http://blog2.com/
和http://blog2.com/index.php/category/art/
,路径分别为http://blog2.com/1.jpg
和http://blog.iyzyi.com/index.php/category/art/1.jpg
,根本不是一个位置好吗,所以不可以用相对路径。而绝对路径根本不可能保证模板的可移植性,也被否决了。typecho有几个获取模板url的接口,但那是url,opendir不可以接受url。
目前我的水平只能这样写了,毕竟刚接触的嘛。觉得难看、不简洁的话,日后再改吧。
前端本来就调用了这个函数,所以不需要我们在html插入php代码了。
0x05 文字预览内容的替换
在index.php
和archive.php
的卡片段中,都有以下代码:
<div class="card-body"><?php
if($this->fields->previewContent)
$this->fields->previewContent();
else
$this->excerpt(50, '...');
?></div>
作用是输出文章预览内容。50是50字节。
缺点是输出字太少,也就25个汉字。而且全角空格和网址没有滤过,很难看。
由于$this->excerpt()是typecho内置的一个接口函数,在不修改typecho源代码、只改动主题代码的前提下,似乎无法重定义(反正我是不知道如何重载),所以我的思路是,多输出一些文字,然后js来滤过全角空格和网址后再输出到原来的位置。
将此处代码改为:
<div class="card-body"><?php
if($this->fields->previewContent)
$this->fields->previewContent();
else
$this->excerpt(400,'');//取前400个字节。footer中的js会将其改为最多200个字节
?>
</div>
然后在main.min.js中插入以下的代码:
function excerpts(){
var card_excerpts = document.getElementsByClassName('card-body');
for(var i = 0; i < card_excerpts.length; i++){
var excerpts = card_excerpts[i].innerHTML.replace(/ /g, '').replace(/(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/, '###')
//excerpts = excerpts.substring(0, Math.min(excerpts.length, 100))+'...';
excerpts = cutstr(excerpts, 200);//前200个字节,中文2英文1
card_excerpts[i].innerHTML = excerpts;
}
/*code from https://my.oschina.net/u/2331760/blog/638521 */
function cutstr(str, len) {
var str_length = 0;
var str_len = 0;
str_cut = new String();
str_len = str.length;
for (var i = 0; i < str_len; i++) {
a = str.charAt(i);
str_length++;
if (escape(a).length > 4) {
//中文字符的长度经编码之后大于4
str_length++;
}
str_cut = str_cut.concat(a);
if (str_length >= len) {
str_cut = str_cut.concat("...");
return str_cut;
}
}
//如果给定字符串小于指定长度,则返回源字符串;
if (str_length < len) {
str_cut = str_cut.concat("...");//因为后端接口传进来的字符串无法确定为截取文章部分还是文章全部,所以统统以...结尾吧,不区分了。
return str_cut;
}
}
}
var excerpts = card_excerpts[i].innerHTML.replace(/ /g, '').replace(/(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/, '###')
进行正则表达式替换。
Element.innerHTML
属性设置或获取HTML语法表示的元素的后代。
然后需要运行这个函数。
由于main.min.js
是在footer.php
中被引入的,所以我们在引入main.min.js后插入<script>excerpts();</script>
:
<script src="<?php $this->options->themeUrl('assets/js/main.min.js?v=de446d9d66'); ?>"></script>
<script>excerpts();</script>