Typecho博客模板制作手册

文件结构说明

文件名作用必须
style.css主题样式文件
screenshot.png主题缩略图,图片后缀支持 jpg,png,gif,bmp,jpeg
index.php首页以及说明文件
404.php404 页面文件
archive.php通用(分类、搜索、标签、作者)页面文件
category.php分类页面文件
search.php搜索页面文件
tag.php标签页面文件
author.php作者页面文件
comments.php评论页面文件
footer.php底部页面文件
functions.php主题函数文件
header.php头部页面文件
page.php独立页面文件
post.php日志页面文件
sidebar.php侧边栏页面文件

index.php

模板信息

我们先从主文件说起,打开这个文件,首先看到的是注释:

/**
 * 这是typecho系统的一套默认皮肤
 * @package Typecho Default Theme
 * @author typecho
 * @version 1.0.0
 * @link http://typecho.org
 */

这是模板信息存放的地方,格式是PHP 注释,这个备注会被显示在主题切换页面。前两行是简短的介绍,每个“*”表示一个段落开始。

  • @package 表示模板名
  • @author表示作者名
  • @version是模板的版本号
  • @link是作者的网站连接

你会看到这个文件里有三个调用$this->need()方法的地方

<?php
    ...
    $this->need('header.php');
    ...
    $this->need('sidebar.php');
    ...
    $this->need('footer.php');

这些语句用来调用模板的其它 PHP 文件,语法是need(PHP文件相对路径),相对于主题的路径。这个方法实际调用的是require方法,意味着模块是可以重复调用的。

显示文章列表

 <?php while ($this->next()): ?>
        <article class="post" itemscope itemtype="http://schema.org/BlogPosting">
            <h2 class="post-title" itemprop="name headline">
                <a itemprop="url"
                   href="https://www.lvtao.net/url.html?t=Jmx0Oz9waHAgJHRoaXMtJmd0O3Blcm1hbGluaygpID8mZ3Q7" target="_blank"><?php $this->title() ?></a>
            </h2>
            <ul class="post-meta">
                <li itemprop="author" itemscope itemtype="http://schema.org/Person"><?php _e('作者: '); ?><a
                        itemprop="name" href="<?php $this->author->permalink(); ?>"
                        rel="author"><?php $this->author(); ?></a></li>
                <li><?php _e('时间: '); ?>
                    <time datetime="<?php $this->date('c'); ?>" itemprop="datePublished"><?php $this->date(); ?></time>
                </li>
                <li><?php _e('分类: '); ?><?php $this->category(','); ?></li>
                <li itemprop="interactionCount">
                    <a itemprop="discussionUrl"
                       href="<?php $this->permalink() ?>#comments"><?php $this->commentsNum('评论', '1 条评论', '%d 条评论'); ?></a>
                </li>
            </ul>
            <div class="post-content" itemprop="articleBody">
                <?php $this->content('- 阅读剩余部分 -'); ?>
            </div>
        </article>
    <?php endwhile; ?>

实际上在文章数据在$this对象里是以数组存起来的,所以必须用循环语句遍历才可以输出所有文章。

代码解释
<?php if ($this->have()): ?>判断是否有文章
<?php while($this->next()): ?>while 循环语句,PHP 语法,必须与<?php endwhile; ?>对应,$this->next()是判断有没有下篇文章,有返回true,并且让数据下标+1,没有返回false
<?php $this->permalink() ?>当前文章所在的连接
<?php $this->title() ?>文章标题
<?php $this->author(); ?>文章作者
<?php $this->author->permalink(); ?>文章作者地址
<?php $this->date(); ?>文章的发布日期,日期格式可在 Typecho 后台设置->阅读中设置,或者使用参数 1 强制指定日期格式<?php $this->date('Y-m-d'); ?>
<?php $this->category(','); ?>文章所在分类,参数 1 是分隔符
<?php $this->tags(','); ?>文章标签,参数 1 是分隔符
<?php $this->commentsNum('评论', '1 条评论', '%d 条评论'); ?>文章评论数及连接,参数可以指定多个,不指定直接输出数字,指定三个主要是某些语言 0,单数,复数的后缀不一样,有的可能甚至更复杂
<?php $this->content('- 阅读剩余部分 -'); ?>文章内容,其中的“- 阅读剩余部分 -”是显示摘要时隐藏部分的邀请链接,也可使用<?php $this->excerpt(140, '...'); ?>来进行自动截取文字内容,“140”是截取字符数量

文章分页

<?php $this->pageNav('«前一页', '后一页»'); ?>

也可以这样分开写

<?php $this->pageLink('下一页','next'); ?>
<?php $this->pageLink('上一页'); ?>

其他说明

archive.php代码码类似index.php,区别就是index.php是显示首页的,而archive.php是用于显示归档页面(分类,标签,搜索,作者),如果主题中没有没有archive.php则会自动调用index.php作为归档页。


header.php

编码

打开这个文件,见到的第一个 php 代码就是:

<meta charset="<?php $this->options->charset(); ?>">

页面标题

<?php $this->archiveTitle(array(
            'category'  =>  _t('分类 %s 下的文章'),
            'search'    =>  _t('包含关键字 %s 的文章'),
            'tag'       =>  _t('标签 %s 下的文章'),
            'author'    =>  _t('%s 发布的文章')
        ), '', ' - '); ?><?php $this->options->title(); ?>

<?php $this->archiveTitle(); ?>是当前页面的标题,<?php $this->options->title(); ?>是网站的标题。archiveTitle()接收第一个参数用于格式化标题,格式是键值对(PHP 数组就是键值对),键名表示归档页类型,值则是标题模板,Typecho 会自动替换%s为归档关键字。

导入样式

<link rel="stylesheet" href="<?php $this->options->themeUrl('style.css'); ?>">

其中 style.css 是样式表文件相对模板目录的路径和文件名。

其它 HTML 头部信息

<?php $this->header(); ?>

这是 Typecho 的自有函数,会输出 HTML 头部信息;同时这个也是头部插件接口,有了它插件可以向网站头部插入 css 或者 js 代码。

网站名称与 logo

<?php if ($this->options->logoUrl): ?>
<a id="logo" href="<?php $this->options->siteUrl(); ?>">
  <img
    src="<?php $this->options->logoUrl() ?>"
    alt="<?php $this->options->title() ?>"
  />
</a>
<?php else: ?>
<a id="logo" href="<?php $this->options->siteUrl(); ?>"
  ><?php $this->options->title() ?></a
>
<p class="description"><?php $this->options->description() ?></p>
<?php endif; ?>

第一句的 if 判断是判断模板是否通过模板设置设置了 logo 的地址,如果设置了就显示 logo 图片,否则就显示博客标题。
<?php $this->options->siteUrl(); ?>是网站地址
<?php $this->options->title() ?>是网站名字
<?php $this->options->description() ?>是网站描述。
Logo 部分的讲解将会在functions.php章节中详细讲解。

站内搜索

<form id="search" method="post" action="<?php $this->options->siteUrl(); ?>" role="search">
    <label for="s" class="sr-only"><?php _e('搜索关键字'); ?></label>
    <input type="text" id="s" name="s" class="text" placeholder="<?php _e('输入关键字搜索'); ?>"/>
    <button type="submit" class="submit"><?php _e('搜索'); ?></button>
</form>

当你的文章很多很多,这个搜索就必不可少。美化搜索框不要动 form action 和 input name,action 和 name 是必须这么写的。

页面导航

<nav id="nav-menu" class="clearfix" role="navigation">
    <a<?php if ($this->is('index')): ?> class="current"<?php endif; ?>
        href="<?php $this->options->siteUrl(); ?>"><?php _e('首页'); ?></a>
    <?php \Widget\Contents\Page\Rows::alloc()->to($pages); ?>
    <?php while ($pages->next()): ?>
        <a<?php if ($this->is('page', $pages->slug)): ?> class="current"<?php endif; ?>
            href="<?php $pages->permalink(); ?>"
            title="<?php $pages->title(); ?>"><?php $pages->title(); ?></a>
    <?php endwhile; ?>
</nav>

其中<?php $this->options->siteUrl(); ?>是网站地址,
\Widget\Contents\Page\Rows::alloc()是获取所有页面,然后下面的 while 循环是循环输出独立页面的,其中<?php $pages->permalink(); ?>是独立页面的超链接,<?php $pages->title(); ?>是独立页面的标题。


footer.php

页脚文件,推荐大家把一些较大的 js 放在这个文件中最后载入,不会影响阅读。看看我们的 footer 要讲解些什么?

版权声明等

&copy; <?php echo date('Y'); ?> <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>.
    <?php _e('由 <a href="http://www.typecho.org">Typecho</a> 强力驱动'); ?>
  • <?php echo date('Y'); ?>是当前年份
  • <?php $this->options->siteUrl(); ?>是网站地址
  • <?php $this->options->title(); ?>是网站标题。

插件接口

<?php $this->footer(); ?>

用于插件向页脚插入 css,js 文件等。


sidebar.php

最新文章列表

<ul class="widget-list">
  <?php \Widget\Contents\Post\Recent::alloc()
      ->parse('<li><a href="{permalink}">{title}</a></li>'); ?>
</ul>

获取最新的 N 篇文章标题,得到的 html 是

<ul class="widget-list">
  <li>
    <a href="http://example.com/2008/12/31/sample-post-one">文章1的标题</a>
  </li>
  <li>
    <a href="http://example.com/2008/12/31/sample-post-two">文章2的标题</a>
  </li>
  <!-- 省略n个重复 -->
  <li>
    <a href="http://example.com/2008/12/31/sample-post-ten">文章N的标题</a>
  </li>
</ul>

N 的值可以在后台 设置 → 阅读 → 文章列表数目 设置

最新回复列表

<ul class="widget-list">
    <?php \Widget\Comments\Recent::alloc()->to($comments); ?>
    <?php while ($comments->next()): ?>
        <li>
            <a href="<?php $comments->permalink(); ?>"><?php $comments->author(false); ?></a>: <?php $comments->excerpt(35, '...'); ?>
        </li>
    <?php endwhile; ?>
</ul>

获取最新的 N 个回复,得到的 html 是

<ul class="widget-list">
  <li>
    回复人名字:
    <a href="http://example.com/2008/12/31/sample-post#comments-12"
      >回复的内容...</a
    >
  </li>
  <li>
    回复人名字:
    <a href="http://example.com/2008/12/31/sample-post#comments-11"
      >回复的内容...</a
    >
  </li>
  <!-- 省略n个重复 -->
</ul>

其中<?php $comments->excerpt(35, '...'); ?>,“35”代表要回复内容截取的字的个数,“…”代表省略的意思,你可以自行修改。

N 的值可以在后台 设置 → 评论 → 评论列表数目 设置

文章分类列表

<?php \Widget\Metas\Category\Rows::alloc()->listCategories('wrapClass=widget-list'); ?>

效果如下

<ul class="widget-list">
  <li class="category-level-0 category-parent">
    <a href="分类1链接">分类1</a>
  </li>
  <li class="category-level-0 category-parent">
    <a href="分类2链接">分类2</a>
  </li>
  <!-- 省略n个重复 -->
</ul>

如果有个分类 3,分类 4 是上述分类 2 的子分类,那么效果如下

<ul class="widget-list">
  <li class="category-level-0 category-parent">
    <a href="分类1链接">分类1</a>
  </li>
  <li class="category-level-0 category-parent">
    <a href="分类2链接">分类2</a>
    <ul class="widget-list">
      <li class="category-level-1 category-child category-level-odd">
        <a href="分类3链接">分类3</a>
      </li>
      <li class="category-level-1 category-child category-level-odd">
        <a href="分类4链接">分类4</a>
      </li>
    </ul>
  </li>
  <!-- 省略n个重复 -->
</ul>

按月归档

<ul class="widget-list">
    <?php \Widget\Contents\Post\Date::alloc('type=month&format=F Y')
        ->parse('<li><a href="{permalink}">{date}</a></li>'); ?>
</ul>

输出:

<ul class="widget-list">
  <li><a href="http://example.com/2018/11">November 2018</a></li>
  <li><a href="http://example.com/2018/10">October 2018</a></li>
</ul>
  1. format 是日期格式,这是 PHP 日期 format。
  2. type 是归档类型,可选值有:yearmonthday

登陆登出

<?php if($this->user->hasLogin()): ?>
<li class="last">
  <a href="<?php $this->options->adminUrl(); ?>"
    ><?php _e('进入后台'); ?>
    (<?php $this->user->screenName(); ?>)</a
  >
</li>
<li>
  <a href="<?php $this->options->logoutUrl(); ?>"><?php _e('退出'); ?></a>
</li>
<?php else: ?>
<li class="last">
  <a href="<?php $this->options->adminUrl('login.php'); ?>"
    ><?php _e('登录'); ?></a
  >
</li>
<?php endif; ?>

这些是可有可无的,只是为了方便登录登出。

  1. <?php $this->options->adminUrl(); ?>是后台地址
  2. <?php $this->user->screenName(); ?>用户昵称
  3. <?php $this->options->logoutUrl(); ?>登出链接
  4. <?php $this->options->adminUrl('login.php'); ?>登陆链接。

RSS 地址

<a href="<?php $this->options->feedUrl(); ?>"><?php _e('文章 RSS'); ?></a>
<!-- 文章的RSS地址连接 -->
<a href="<?php $this->options->commentsFeedUrl(); ?>"
  ><?php _e('评论 RSS'); ?></a
><!-- 评论的RSS地址连接 -->

post.php

post 页和 index 是差不多的,下面解释下 post.php 里面存在的 php 代码。

代码与说明

代码解释
<?php $this->permalink() ?>文章地址
<?php $this->title() ?>文章标题
<?php $this->author->permalink(); ?>文章作者主页链接
<?php $this->author(); ?>文章作者昵称
<?php $this->date(); ?>文章发布时间
<?php $this->category(','); ?>文章分类,多个分类中间用逗号隔开
<?php $this->content(); ?>文章内容
<?php $this->tags(', ', true, 'none'); ?>文章标签,多个标签间用逗号隔开,标签以带超链接的形式显示,如果不存在标签则显示 none
<?php $this->need('comments.php'); ?>调用评论页
<?php $this->thePrev('%s','没有了'); ?>带有超链接的上一篇文章的标题
<?php $this->theNext('%s','没有了'); ?>带有超链接的下一篇文章的标题
<?php $this->fields->fieldName(); ?>自定义字段fieldName的值,替换fieldName为对应自定义字段名即可

其他说明

page.php 代码同 post.php,区别就是 post 是用来显示文章的,而 page.php 是用来显示独立页面的。


comments.php

评论列表

<?php $this->comments()->to($comments); ?>
<?php if ($comments->have()): ?>
<h3><?php $this->commentsNum(_t('暂无评论'), _t('仅有一条评论'), _t('已有 %d 条评论')); ?></h3>
<?php $comments->listComments(); ?>
<?php $comments->pageNav('&laquo; 前一页', '后一页 &raquo;'); ?>
<?php endif; ?>

判断文章是否存在评论,如果存在就输出评论;其中<?php $comments->listComments(); ?>是评论列表,<?php $comments->pageNav('&laquo; 前一页', '后一页 &raquo;'); ?>是评论翻页按钮。

评论输入表单

<!-- 判断设置是否允许对当前文章进行评论 -->
<?php if($this->allow('comment')): ?>
<div id="<?php $this->respondId(); ?>" class="respond">
  <div class="cancel-comment-reply"><?php $comments->cancelReply(); ?></div>

  <h3 id="response"><?php _e('添加新评论'); ?></h3>
  <!-- 输入表单开始 -->
  <form
    method="post"
    action="<?php $this->commentUrl() ?>"
    id="comment-form"
    role="form"
  >
    <!-- 如果当前用户已经登录 -->
    <?php if($this->user->hasLogin()): ?>
    <!-- 显示当前登录用户的用户名以及登出连接 -->
    <p>
      <?php _e('登录身份: '); ?><a href="<?php $this->options->profileUrl(); ?>"
        ><?php $this->user->screenName(); ?></a
      >.
      <a href="<?php $this->options->logoutUrl(); ?>" title="Logout"
        ><?php _e('退出'); ?>
        &raquo;</a
      >
    </p>
    <!-- 若当前用户未登录 -->
    <?php else: ?>
    <!-- 要求输入名字、邮箱、网址 -->
    <p>
      <label for="author" class="required"><?php _e('称呼'); ?></label>
      <input
        type="text"
        name="author"
        id="author"
        class="text"
        value="<?php $this->remember('author'); ?>"
        required
      />
    </p>
    <p>
      <label for="mail" <?php if ($this-
        >options->commentsRequireMail): ?> class="required"<?php endif; ?>><?php _e('Email'); ?></label
      >
      <input
        type="email"
        name="mail"
        id="mail"
        class="text"
        value="<?php $this->remember('mail'); ?>"
        <?php
        if
        ($this-
      />options->commentsRequireMail): ?> required<?php endif; ?>
      />
    </p>
    <p>
      <label for="url" <?php if ($this-
        >options->commentsRequireURL): ?> class="required"<?php endif; ?>><?php _e('网站'); ?></label
      >
      <input
        type="url"
        name="url"
        id="url"
        class="text"
        placeholder="<?php _e('http://'); ?>"
        value="<?php $this->remember('url'); ?>"
        <?php
        if
        ($this-
      />options->commentsRequireURL): ?> required<?php endif; ?>
      />
    </p>
    <?php endif; ?>
    <!-- 输入要回复的内容 -->
    <p>
      <label for="textarea" class="required"><?php _e('内容'); ?></label>
      <textarea
        rows="8"
        cols="50"
        name="text"
        id="textarea"
        class="textarea"
        required
      >
<?php $this->remember('text'); ?></textarea
      >
    </p>
    <p>
      <button type="submit" class="submit"><?php _e('提交评论'); ?></button>
    </p>
  </form>
</div>
<!-- 若当前文章不允许进行评论 -->
<?php else: ?>
<h3><?php _e('评论已关闭'); ?></h3>
<?php endif; ?>

具体请对应上述代码中注释自行理解。


functions.php

function themeConfig($form) 内的代码是模板设置功能

单行输入框选项:LOGO 设置

$logoUrl = new \Typecho\Widget\Helper\Form\Element\Text(
    'logoUrl',
    null,
    null,
    _t('站点 LOGO 地址'),
    _t('在这里填入一个图片 URL 地址, 以在网站标题前加上一个 LOGO')
);

$form->addInput($logoUrl); // 添加选项到表单

\Typecho\Widget\Helper\Form\Element\Text一共有5个参数,

  1. 第一个参数是选项key,只能是字符串,方便在模板中调用
  2. 第二个填NULL就行
  3. 第三个参数是默认值
  4. 第四个选项是选项标题
  5. 第五个参数是选项附加描述
_t()是 Typecho 中多语言字符处理函数。

上边代码就是在模板设置处添加一个 Logo 设置,可以添加一个图片地址作为 Logo

添加好了通过$this->options->logoUrl调用图片,不过习惯性调用如下:

<!--判断logo已被设置-->
<?php if ($this->options->logoUrl): ?>
<!--给logo图片加上本站超链接-->
<a id="logo" href="<?php $this->options->siteUrl(); ?>">
  <!--显示logo-->
  <img
    src="<?php $this->options->logoUrl() ?>"
    alt="<?php $this->options->title() ?>"
  />
</a>
<?php endif; ?>

此处对应header.php中的 logo 显示。

CHECKBOX选项:显示开关

$sidebarBlock = new \Typecho\Widget\Helper\Form\Element\Checkbox(
    'sidebarBlock',
    [
        'ShowRecentPosts'    => _t('显示最新文章'),
        'ShowRecentComments' => _t('显示最近回复'),
        'ShowCategory'       => _t('显示分类'),
        'ShowArchive'        => _t('显示归档'),
        'ShowOther'          => _t('显示其它杂项')
    ],
    ['ShowRecentPosts', 'ShowRecentComments', 'ShowCategory', 'ShowArchive', 'ShowOther'],
    _t('侧边栏显示')
);

$form->addInput($sidebarBlock->multiMode()); // 添加选项到表单

\Typecho\Widget\Helper\Form\Element\Checkbox同样也有5个参数

  1. 第一个参数是选项key,只能是字符串,方便在模板中调用
  2. 第二个参数是选项列表,必须是数组,key是值,value是显示文本
  3. 第三个参数是默认值,同样必须是数组,存放默认启用的key值数组
  4. 第四个选项是选项标题
  5. 第五个参数是选项附加描述

调用的话,这里拿 ShowCategory 举例,如果勾选它

<?php if (!empty($this->options->sidebarBlock) && in_array('ShowCategory', $this->options->sidebarBlock)): ?>
勾选了就会显示这里的文字
<?php endif; ?>
<!-- 因为 Checkbox 保存的是数组,所以必须使用 in_array 函数判断是否勾选了该选项 -->

这里对应的是sidebar.php中的最新文章,最新评论,文章分类,归档等显示开关。

其他选项

默认主题只提供了两种选项,这里简单介绍其他选项类型。

多行输入框

顾名思义,可以输入多行文本的输入框

$header_html = new \Typecho\Widget\Helper\Form\Element\Text(
    'header_html',
    null,
    null,
    _t('输入头部附加 html 代码'),
    _t('在这里输入的 html 会附加到 header 中,可以填入统计代码,Google 广告代码等')
);

$form->addInput($header_html); // 添加选项到表单

参数功能和单行输入框选项一样。
调用方式

<?php 
if ($this->options->header_html)
  $this->options->header_html();
?>

选项值如何获取?

在 default 主题里,你能搜索到$this的地方,都可以通过$this->options->选项key的方式获取,如果在没有$this的地方,可以通过Helper::options()->选项key这样来获取比如 logo 地址:

<!--判断logo已被设置-->
<?php if (Helper::options()->logoUrl): ?>
<!--给logo图片加上本站超链接-->
<a id="logo" href="<?php Helper::options()->siteUrl(); ?>">
  <!--显示logo-->
  <img
    src="<?php Helper::options()->logoUrl() ?>"
    alt="<?php Helper::options()->title() ?>"
  />
</a>
<?php endif; ?>

其他说明

参考以上代码,照葫芦画瓢,可以增加自己需要的模板设置。


神奇的 is 语法

在前端页面可通过$this-is('xxx')的方式来判定所在页面,比如

$this->is('index');  // 判断首页
$this->is('archive'); //判断 archive
$this->is('single'); // 判断正文页面 page/post
$this->is('page'); // 判断独立页面 page
$this->is('post'); // 判断文章页面 post
$this->is('category'); // 判断分类页面
$this->is('tag'); // 判断标签页面
$this->is('front'); // 判断文章列表页面
$this->is('attachment'); //判断附件页面

以后如果有新增的,可以查看源码自行获取:https://github.com/typecho/typecho/blob/master/var/Widget/Archive.php(搜索$handles)。

warning 注意
当你在Typecho后台设置→阅读中设置将某个独立页面作为首页后,那么原来的首页(文章列表页)就不能用$this->is('index');去判断了,而是使用$this->is('front');进行判断。

分类,页面,文章还可以这样判断

$this->is('category', 'default'); // 判断分类缩略名等于 default
$this->is('page', 'start'); // 判断独立页面缩略名等于 start
$this->is('post', 1); // 判断文章cid等于1

需要注意的是,后面的参数是分类、页面的缩略名或者 id

完整使用实例

<?php if ($this->is('post')) : ?>
如果是文章页面就会显示这里的文字
<?php endif; ?>

文本输出和多语言

在 Typecho 中,输出文字和 PHP 是一样的。

<?php
echo 'Hello World';
echo sprintf('Hello %s', 'World');
?>
直接在<?php ?>标签外编写文字也是可以的

但是如果遇上多语言的话这样是不行的,可以使用 Typecho 内置的多语言函数。

<?php
_e('Hello World');
_e('Hello %s', 'World');
echo _t('Hello %s', 'World');

_t_e的区别是_t会返回一个字符串,而_e会直接输出。

自定义标题

官方默认模板是这么自定义标题的。

<title><?php $this->archiveTitle(array(
            'category'  =>  _t('分类 %s 下的文章'),
            'search'    =>  _t('包含关键字 %s 的文章'),
            'tag'       =>  _t('标签 %s 下的文章'),
            'author'    =>  _t('%s 发布的文章')
        ), '', ' - '); ?><?php $this->options->title(); ?></title>

输出结果:页面标题 - 站点名称

让我们来分解一下其中的语句,后一句大家都很明白,显示站点名称嘛,那前一句呢?其实前一句的标题包含三个参数:

<?php $this->archiveTitle($split, $before, $end); ?>
参数名称默认值简介
$split»多级菜单间的分隔符,如:2009 » 12
$before»title 前显示的字符
$end title 后显示的字符

建议

其实官方默认这样就已经很好了,但是如果有seo优化需要建议在后面再加上页码信息,如:

<?php $this->archiveTitle(array(
            'category'  =>  _t('分类 %s 下的文章'),
            'search'    =>  _t('包含关键字 %s 的文章'),
            'tag'       =>  _t('标签 %s 下的文章'),
            'author'    =>  _t('%s 发布的文章')
        ), '', ' - '); ?><?php $this->options->title(); ?><?php if($this->getCurrentPage()>1) _e("第 %d 页", $this->getCurrentPage()); ?>

注意

本页看到的_t_e都是 Typecho 内置的函数,用于多语言输出文本。详见文本输出和多语言

<?php
$localized_text = _t($format, $arg1, $arg2, ..., $argN);
$localized_text = _e($format, $arg1, $arg2, ..., $argN);

自定义头部信息输出

默认输出

在默认的模板中,头部信息的输出的结果是这样的

<meta name="description" content="Just So So ..." />
<meta name="keywords" content="typecho,php,blog" />
<meta name="generator" content="Typecho 0.8/10.8.15" />
<meta name="template" content="default" />
<link rel="pingback" href=".../action/xmlrpc" />
<link rel="EditURI" type="application/rsd+xml" title="RSD" href=".../action/xmlrpc?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href=".../action/xmlrpc?wlw" />
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href=".../feed/" />
<link rel="alternate" type="application/rdf+xml" title="RSS 1.0" href=".../feed/rss/" />
<link rel="alternate" type="application/atom+xml" title="ATOM 1.0" href=".../feed/atom/" />

操作函数

打开模板中的 header.php 文件,找到下面这句

<?php $this->header(); ?>

加上你要设置的参数即可,比如:

<?php $this->header('keywords=&generator=&template=&pingback=&xmlrpc=&wlw='); ?>

以上代码即可过滤关键词、程序、模板名称、文章引用、离线写作等信息的输出

制作面包屑导航

什么是面包屑导航

面包屑导航是一种网站导航方式,通常在网站顶部显示,用于显示用户当前位置。
面包屑导航通常由导航元素组成,每个元素代表一个网站导航项,通常由链接组成。

tip 面包屑导航示例
首页 » 最新文章\
首页 » 分类名称 » 文章标题\
首页 » 归档年份 » 归档月份\
首页 » 页面名称\
首页 » 分类名称\
首页 » 标签名称\
首页 » 搜索关键词或其他信息

实际上 title 中的内容可以直接移植到我们的面包屑中,在看下面的代码之前,也许你需要先温习神奇的is语法

<div class="crumbs_patch">
    <a href="<?php $this->options->siteUrl(); ?>">Home</a> &raquo;</li>
    <?php if ($this->is('index')): ?><!-- 页面为首页时 -->
        Latest Post
    <?php elseif ($this->is('post')): ?><!-- 页面为文章单页时 -->
        <?php $this->category(); ?> &raquo; <?php $this->title() ?>
    <?php else: ?><!-- 页面为其他页时 -->
        <?php $this->archiveTitle(' &raquo; ','',''); ?>
    <?php endif; ?>
</div>

将上面的代码放在需要显示的页面,例如index.phppost.php,抑或是header.php页面

自定义评论区域

一、自定义单条评论的 HTML 结构

在自定义评论前,我们得先设计好单条评论的 HTML 代码结构,如:

<li id="li-comment-520" class="comment-body comment-parent comment-odd">
    <div id="comment-520">
        <div class="comment-author">
            <img class="avatar" src="avatar.png" alt="" width="40" height="40">
            <cite class="fn"><a href="评论者主页">评论者名字</a></cite>
        </div>
        <div class="comment-meta">
            <a href="评论地址">评论时间</a>
            <span class="comment-reply">回复按钮</span>
        </div>
        <p>我是评论内容</p>
    </div><!-- 单条评论者信息及内容 -->
    <div class="comment-children">
        <!-- 嵌套评论相关 -->
    </div>
</li>

自定义好HTML代码后,将如何去实现呢?首先我们要打开模板文件夹里的comments.php文件,做好修改准备。

二、使用自定义评论函数

打开 comments.php 文件后,我们需要在它的顶部,插入以下函数代码:

<?php function threadedComments($comments, $options) {
    $commentClass = '';
    if ($comments->authorId) {
        if ($comments->authorId == $comments->ownerId) {
            $commentClass .= ' comment-by-author';  //如果是文章作者的评论添加 .comment-by-author 样式
        } else {
            $commentClass .= ' comment-by-user';  //如果是评论作者的添加 .comment-by-user 样式
        }
    } 
    $commentLevelClass = $comments->levels > 0 ? ' comment-child' : ' comment-parent';  //评论层数大于0为子级,否则是父级
?>
/* 自定义评论的代码结构 */
<?php } ?>

其次,将我们刚才自定义好的单条评论的 HTML 代码,放在上面代码里注释的地方,如下:

<?php function threadedComments($comments, $options) {
    $commentClass = '';
    if ($comments->authorId) {
        if ($comments->authorId == $comments->ownerId) {
            $commentClass .= ' comment-by-author';  //如果是文章作者的评论添加 .comment-by-author 样式
        } else {
            $commentClass .= ' comment-by-user';  //如果是评论作者的添加 .comment-by-user 样式
        }
    } 
    $commentLevelClass = $comments->_levels > 0 ? ' comment-child' : ' comment-parent';  //评论层数大于0为子级,否则是父级
?>

/* 自定义评论的代码结构 */
<li id="li-comment-520" class="comment-body comment-parent comment-odd">
    <div id="comment-520">
        <div class="comment-author">
            <img class="avatar" src="avatar.png" alt="" width="40" height="40">
            <cite class="fn"><a href="评论者主页">评论者名字</a></cite>
        </div>
        <div class="comment-meta">
            <a href="评论地址">评论时间</a>
            <span class="comment-reply">回复按钮</span>
        </div>
        <p>我是评论内容</p>
    </div><!-- 单条评论者信息及内容 -->
    <div class="comment-children">
        <!-- 嵌套评论相关 -->
    </div>
</li>
<?php } ?>

三、用系统的评论变量替换HTML中相关属性

把 HTML 里相关的属性,替换成 Typecho 系统中的评论变量,变量的列表可以参考下面。下面的例子,是替换评论的 id:
替换前:

<li id="li-comment-520" class="comment-body comment-parent comment-odd">

替换后:

<li id="li-<?php $comments->theId(); ?>" class="comment-body<?php 
if ($comments->_levels > 0) {
    echo ' comment-child';
    $comments->levelsAlt(' comment-level-odd', ' comment-level-even');
} else {
    echo ' comment-parent';
}
$comments->alt(' comment-odd', ' comment-even');
echo $commentClass; 
?>">

其中,替换ID这里,还需要判断判断当前评论是父级评论还是子级评论,且判断评论 ID 的奇偶数等。

四、添加嵌套评论(子评论)

替换前:

<div class="comment-children">
    <!-- 嵌套评论相关 -->
</div>

替换后:

<?php if ($comments->children) { ?> //是否嵌套评论判断开始
<div class="comment-children">
    <?php $comments->threadedComments($options); ?> //嵌套评论所有内容
</div>
<?php } ?> //是否嵌套评论判断结束

五、相关方法及说明

<?php $comments->gravatar('40', ''); ?> //头像,有两个参数,大小、默认头像?
<?php $comments->author(); ?> //评论作者
<?php $comments->permalink(); ?> //当前评论的连接地址
<?php $comments->date('Y-m-d H:i'); ?>//评论时间,可在括号里设置格式
<?php $comments->reply(); ?> //回复按钮,可在括号里自定义评论按钮的文字
<?php $comments->content(); ?> //评论内容

六、最终得到的代码

当我们把上面所有变量都替换完成之后,最终得到的代码如下:

<?php function threadedComments($comments, $options) {
    $commentClass = '';
    if ($comments->authorId) {
        if ($comments->authorId == $comments->ownerId) {
            $commentClass .= ' comment-by-author';
        } else {
            $commentClass .= ' comment-by-user';
        }
    }

    $commentLevelClass = $comments->levels > 0 ? ' comment-child' : ' comment-parent';
?>

<li id="li-<?php $comments->theId(); ?>" class="comment-body<?php 
if ($comments->levels > 0) {
    echo ' comment-child';
    $comments->levelsAlt(' comment-level-odd', ' comment-level-even');
} else {
    echo ' comment-parent';
}
$comments->alt(' comment-odd', ' comment-even');
echo $commentClass;
?>">
    <div id="<?php $comments->theId(); ?>">
        <div class="comment-author">
            <?php $comments->gravatar('40', ''); ?>
            <cite class="fn"><?php $comments->author(); ?></cite>
        </div>
        <div class="comment-meta">
            <a href="<?php $comments->permalink(); ?>"><?php $comments->date('Y-m-d H:i'); ?></a>
            <span class="comment-reply"><?php $comments->reply(); ?></span>
        </div>
        <?php $comments->content(); ?>
    </div>
<?php if ($comments->children) { ?>
    <div class="comment-children">
        <?php $comments->threadedComments($options); ?>
    </div>
<?php } ?>
</li>
<?php } ?>

注意:上面的自定义评论代码输出的,就是本来评论页里的下面这段代码,所以你就不用对这段代码做任何更改了。

<?php $comments->listComments(); ?>

首次评论审核提示,在自定义评论代码的适当地方添加以下语句,否则将看不到审核提示语句。

<?php if ('waiting' == $comments->status) { ?><span class="text-muted">您的评论需管理员审核后才能显示

分离文章的评论和引用通告

打开模板的comments.php文件,找到通篇的核心语句:

<?php $this->comments()->to($comments); ?>

这条语句控制着评论的类型,他的参数如下:

语句注释
<?php $this→comments()→to($comments); ?> 显示全部(默认)
<?php $this→comments('comment')→to($comments); ?> 只显示 comment
<?php $this→comments('trackback')→to($trackbacks); ?> 只显示 trackback
<?php $this→comments('pingback')→to($pingbacks); ?>只显示 pingback

为了分开,我们开始对comments.php做如下修改,首先只显示评论:

<?php $this->comments('comment')->to($comments); ?><!-- 关键 -->
<?php if ($comments->have()) : ?>
    <ol>
    <?php while ($comments->next()) : ?>
    <li id="<?php $comments->theId() ?>">
        <div class="comment_data">
            <?php $comments->gravatar(32, '', '', 'avatar'); ?>
            <span><?php $comments->author() ?></span> Says:<br />
            <?php $comments->date('F jS, Y'); ?> at <?php $comments->date('h:i a'); ?>
        </div>
        <div class="comment_text"><?php $comments->content() ?></div>
    </li>
    <?php endwhile; ?>
    </ol>
<?php endif; ?>

然后输出 pingback,pingback 并不需要那么多的展示内容,假设只展示标题和日期:

<?php $this->comments('pingback')->to($pingbacks); ?><!-- 关键 -->
<?php if ($pingbacks->have()) : ?>
    <h3>Pingbacks</h3>
    <ol>
    <?php while ($pingbacks->next()) : ?>
        <li id="<?php $pingbacks->theId() ?>">
            <?php $pingbacks->author() ?> <?php $pingbacks->date('F jS, Y'); ?>
        </li>
    <?php endwhile; ?>
    </ol>
<?php endif; ?>

如果你要显示 trackback,也可以按如上的修改。typecho 模板语法很多是通用的,所以当你遇到不清楚的问题时,可以自己试着拼凑一下

自定义字段

字段使用与调用

新建/编辑文章界面时,下方点击添加字段,填写字段名与字段值
模板对应位置(index.php/post.php/page.php/archive.php等)加上如下代码

<!--判断say字段的字段值是否存在-->
<?php if ($this->fields->say): ?> 
他说了:<?php $this->fields->say(); ?> 
<?php endif;?>

::: tip 输出结果

他说了:你好世界

:::

提示用户输入自定义字段

打开主题的functions.php ,填入如下函数(如果函数存在,请自行合并),就可以为你的主题增加一个自动绑定的输入框。

function themeFields($layout) {
    $say = new \Typecho\Widget\Helper\Form\Element\Text('say', NULL, NULL, _t('留言'), _t('输入想说的话)'));
    $layout->addItem($say);
}

模板作者这样设置后,文章字段就不用用户手动增加了,而是默认就加好了,用户只需要提交 字段值(自己想说的话) 就可以了。

只在文章编辑页提示

if($_SERVER['SCRIPT_NAME']=="/admin/write-post.php"){
    function themeFields($layout) {
        $say = new \Typecho\Widget\Helper\Form\Element\Text('say', NULL, NULL, _t('留言'), _t('输入想说的话)'));
        $layout->addItem($say);
    }
}

只在独立页面编辑页显示

if($_SERVER['SCRIPT_NAME']=="/admin/write-page.php"){
    function themeFields($layout) {
        $say = new \Typecho\Widget\Helper\Form\Element\Text('say', NULL, NULL, _t('留言'), _t('输入想说的话)'));
        $layout->addItem($say);
    }
}

调用相关文章

文章页拓展阅读

修改主题的时候一般会在文章页正文下方显示相关文章,拓展阅读,这个功能 Typecho 内置了相关函数,调用即可。
修改 post.php, 将以下内容粘贴至你想加入相关文章的位置(例如我放在文章结束的位置),最后保存即可。

<?php $this->related(5)->to($relatedPosts); ?>
    <ul>
    <?php while ($relatedPosts->next()): ?>
    <li><a href="<?php $relatedPosts->permalink(); ?>" title="<?php $relatedPosts->title(); ?>"><?php $relatedPosts->title(); ?></a></li>
    <?php endwhile; ?>
</ul>

调用方法说明

$this->related($limits, $type);

这个函数有两个参数:

序号参数类型参数默认值说明
1int$limits5相关文章数量
2?string$typeNULL文章关联方式,可接受 tags/author。

独立页面拓展阅读

在 Typecho 中,独立页面是没有标签的,所以只可以调取同作者相关文章。

<?php $relatedPosts = \Widget\Contents\Related\Author::alloc(
    ['cid' => $this->cid, 'type' => 'post', 'author' => $this->author->uid, 'limit' => 5]
); ?>
    <ul>
    <?php while ($relatedPosts->next()): ?>
    <li><a href="<?php $relatedPosts->permalink(); ?>" title="<?php $relatedPosts->title(); ?>"><?php $relatedPosts->title(); ?></a></li>
    <?php endwhile; ?>
</ul>

调用指定分类的文章

根据分类 mid 获取某个分类下的文章列表

<?php \Widget\Archive::allocWithAlias('index', 'pageSize=6&type=category', 'mid=1')->to($new); ?>
<?php while ($new->next()): ?>
<a href="<?php $new->permalink(); ?>"><?php $new->title(); ?></a>
<?php endwhile; ?>

以上就是获取分类mid等于1的最新6篇文章,pageSize=6就是指定调用数量,mid=1指定分类mid,也可以用缩略名方式替换如slug=name其中name就是mid等于1的分类的缩略名。

因为 Typecho 中 Widget 默认是是单例的,你重复获取也只是之前的引用(就是你获取两次都会输出一样的数据,所以必须通过 alias 区分),所以这里需要用\Widget\Archive::allocWithAlias('index-2')来获取新的实例,如下:

<?php \Widget\Archive::allocWithAlias('index-1', 'pageSize=6&type=category', 'mid=1')->to($new); ?>
<?php while ($new->next()): ?>
<a href="<?php $new->permalink(); ?>"><?php $new->title(); ?></a>
<?php endwhile; ?>
<?php \Widget\Archive::allocWithAlias('index-2', 'pageSize=6&type=category', 'mid=2')->to($new); ?>
<?php while ($new->next()): ?>
<a href="<?php $new->permalink(); ?>"><?php $new->title(); ?></a>
<?php endwhile; ?>

文章标签

文章页调用

调用本文标签

在文章页post.php通过下面的代码调用本文的标签:

<?php $this->tags(',', true); ?>
序号参数类型参数默认值说明
1string$split,分隔符,用于分隔标签
2bool$linktrue是否输出带链接的标签
3?string$defaultnull没有标签的时候的输出内容

::: tip 说明
(',', true, 'none') 第一个逗号间的逗号代表标签与标签的间隔用逗号隔开,true是标签以超链接形式输出,第三个参数没提供,说明没有标签不输出内容。
:::

给每个标签套上div

<div><?php $this->tags('</div><div>', true, 'none'); ?></div>

根据文章是否含有标签输出指定内容

比如给含有永久VIP标签的文章输出永久VIP免费提示

<?php if(in_array('永久VIP', $this->tags) ): ?>
永久VIP免费
<?php endif; ?>

输出标签云

<?php \Widget\Metas\Tag\Cloud::alloc('sort=count&desc=1&limit=200')->to($tags); ?>
<?php if($tags->have()): ?>
<ul class="tags-list">
<?php while ($tags->next()): ?>
    <li><a href="<?php $tags->permalink(); ?>" rel="tag" class="size-<?php $tags->split(5, 10, 20, 30); ?>" title="<?php $tags->count(); ?> 个话题"><?php $tags->name(); ?></a></li>
<?php endwhile; ?>
<?php else: ?>
    <li><?php _e('没有任何标签'); ?></li>
<?php endif; ?>
</ul>

::: tip 参数说明

  • sort:排序方式为 mid;
  • ignoreZeroCount:忽略文章数为 0 的;
  • desc:是否降序输出;
  • limit:输出数目。

:::

自定义分页

<style>.page-navigator { display: flex; justify-content: center; align-items: center; gap: 8px;} .page-navigator > li { list-style: none; margin: 0; padding: 0; } .page-navigator > li + li { margin-top: 0 }</style>

自定义分页样式

<?php $this->pageNav('«', '»', 1, '...', array('wrapTag' => 'ol', 'wrapClass' => 'page-navigator', 'itemTag' => 'li', 'textTag' => 'span', 'currentClass' => 'current', 'prevClass' => 'prev', 'nextClass' => 'next',)); ?>

渲染出来的效果是这样的

<ol class="page-navigator"><li class="current"><a href="http://example.com/page/1/">1</a></li><li><a href="http://example.com/page/2/">2</a></li><li><span>...</span></li><li><a href="http://example.com/page/5/">5</a></li><li class="next"><a href="http://example.com/page/2/">»</a></li></ol>

由此可知

  1. «» 分别对应的是上一页按钮和下一页按钮
  2. 数字1是分割范围(分几页),是当前页码附近可现实的页码数量,举个例子,当前页码为1,一共页码为5,那么上述代码输出的效果就是1,2,...5,如果当前页码为2呢,效果就是1,2,3,...5。
  3. ... 是分割字符,就是2中提到的那个省略页码的东西
  4. wrapTag外层包裹标签名,默认olwrapClass外层包裹类名,itemTag内层标签名, 默认litextTag直接输出文字的标签名,currentClass当前聚焦类名,prevClass上一页类名,nextClass下一页类名。

5,itemClass可以给其他页码的标签带上class。【注:第5条为补充内容,例子中没有提到】

页码

当前页码:<?php if($this->getCurrentPage()>1) echo $this->getCurrentPage(); else echo 1;?>
总页码:<?php echo ceil($this->getTotal() / $this->parameter->pageSize); ?>

前台个人资料页

Typecho 中形如/author/1 这样的链接会显示 UID 为 1 的用户的所有文章。

加入你的 UID 是 666 则 /author/666是你的用户页。我们可以自定义author.php在这个页面增加个人资料修改的功能。

<div id="profile">
    <h3><?php _e('个人资料'); ?></h3>
    <section class="card">
        <div class="card-title"><?php _e("用户名"); ?></div>
        <div class="card-body">
            <?php \Widget\Users\Profile::alloc()->profileForm()->render(); ?>
        </div>
    </section>
    <section id="change-password" class="card">
        <div class="card-title"><?php _e('密码修改'); ?></div>
        <div class="card-body">
            <?php \Widget\Users\Profile::alloc()->passwordForm()->render(); ?>
        </div>
    </section>
    <?php \Widget\Users\Profile::alloc()->personalFormList(); ?>
</div>

但是这样有一个问题,所有人点进/author/666都能看见个人资料页面,可以加个判断处理只有当前用户访问所属用户页才显示。

<?php if($this->user->uid==$this->author->uid && $this->user->hasLogin()): ?>
    这里填入上边的代码
<?php endif; ?>

强制显示 404 页面

有的页面比如【个人资料】页面,在用户没登录的时候一般显示为404,那么怎么强制不登录时显示404呢?

修改profile.php(假设profile.php是个人资料独立页面的模板),在最上方加入

if (!$this->user->hasLogin()) {
    throw new Typecho\Router\Exception("Path '{$this->request->getPathInfo()}' not found", 404);
}

常用统计函数

一些例子

<?php Typecho_Widget::widget('Widget_Stat')->to($stat); ?>
文章总数:<?php $stat->publishedPostsNum() ?>篇
分类总数:<?php $stat->categoriesNum() ?>个
评论总数:<?php $stat->publishedCommentsNum() ?>条
页面总数:<?php $stat->publishedPagesNum() ?>个
当前作者的文章总数:<?php $stat->myPublishedPostsNum() ?>篇

其他可用函数

描述来自源码注释,不是特别清晰,有空我会重新梳理下

说明函数名
获取已发布的文章数目publishedPostsNum
获取待审核的文章数目waitingPostsNum
获取草稿文章数目draftPostsNum
获取当前用户已发布的文章数目myPublishedPostsNum
获取当前用户待审核文章数目myWaitingPostsNum
获取当前用户草稿文章数目myDraftPostsNum
获取当前用户已发布的文章数目currentPublishedPostsNum
获取当前用户待审核文章数目currentWaitingPostsNum
获取当前用户草稿文章数目currentDraftPostsNum
获取已发布页面数目publishedPagesNum
获取草稿页面数目draftPagesNum
获取当前显示的评论数目publishedCommentsNum
获取当前待审核的评论数目waitingCommentsNum
获取当前垃圾评论数目spamCommentsNum
获取当前用户显示的评论数目myPublishedCommentsNum
获取当前用户显示的评论数目myWaitingCommentsNum
获取当前用户显示的评论数目mySpamCommentsNum
获取当前文章的评论数目currentCommentsNum
获取当前文章显示的评论数目currentPublishedCommentsNum
获取当前文章显示的评论数目currentWaitingCommentsNum
获取当前文章显示的评论数目currentSpamCommentsNum
获取分类数目categoriesNum

该统计函数来自源码var/Widget/Stat.php

获取建站天数

有可能不准确,因为有人会删掉第一篇文章

<?php
class XStat extends Widget_Stat {
    /**
     * 建站天数(可能不准确,因为有的人把第一篇文章删掉了)
     *
     * @return int
     */
    public static function ___sitePublishedDaysNum(): int
    {
        $db = Typecho_Db::get();
        $count = $db->fetchRow($db->select('MIN(created) as created')->from('table.contents'));
        $diff = time() - $count['created'];
        return intval($diff / 86400);
    }
}

调用方式

<?php Typecho_Widget::widget('XStat')->to($stat); ?>
<?php _t("建站天数:%s天", $stat->sitePublishedDaysNum); ?>

常用模板函数

这些函数都可以放在functions.php方便调用

获取模板版本号

function get_theme_version()
{
     $info = Typecho_Plugin::parseInfo(__DIR__ . '/index.php');
     return $info['version'];
}

全局替换正文内容

themeInit函数必须放在functions.php或者functions.php引用的php文件里

function themeInit($archive) {
    ... // 其他语句
    if ($archive->is('index') || $archive->is('content')) {
        $archive->content = replace_text($archive->content);
    }
    ... // 其他语句
}

function replace_text($content) {
      $content = preg_replace('#<a(.*?) href="([^"]*/)?(([^"/]*)\.[^"]*)"(.*?)>#',
        '<a$1 href="$2$3"$5 target="_blank">', $content); // 强制文章中的链接为新标签打开
      return $content;
}

文章阅读次数统计

使用 views 字段存储数据

放在functions.php

function themeInit($archive) {
    ... // 其他语句
    $db = \Typecho\Db::get();
    $_prefix = $db->getPrefix();
    try {
        if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')->page(1, 1)))) {
            $db->query('ALTER TABLE `' . $_prefix . 'contents` ADD `views` INT DEFAULT 0;');
        }
    } catch (Exception $e) {}
    if ($archive->is('content')) {
        increase_views($archive);
    }
    ... // 其他语句
}

/**
 * 增加浏览次数
 */
function increase_views($archive) {
    $cookie = trim(\Typecho\Cookie::get('__typecho_views') ?? "", " \t\n\r\0\x0B,");
    $viewedIds = !empty($cookie) ? explode(',', $cookie) : [];
    $cid = $archive->cid;
    if (!in_array($cid, $cookie)) {
        $db = \Typecho\Db::get();
        $db->query($db->update('table.contents')
            ->expression('views', 'views + 1')
            ->where('cid = ?', $cid));
        $viewedIds[] = $cid;
        $cookie = implode(',', $viewedIds);
        \Typecho\Cookie::set('__typecho_views', $cookie);
    } 
}

/**
 * 获取浏览次数,调用方式 get_views($archive, "0 times", "1 time", "%d times")
 */
function get_views() {
    $args = func_get_args();
    if (count($args) === 0) {
        throw new \InvalidArgumentException(_t("Parameter#1 \$archive cannot be null"));
    }
    $archive = array_shift($args);
    $row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $archive->cid));
    $num = (isset($row) && isset($row['views']))  ? $row['views'] : 0;
    if (empty($args)) {
        $args[] = '%d';
    }
    return sprintf(array_key_exists($num, $args) ? $args[$num] : array_pop($args), $num);
}

调用举例,在post.php

<li class="meta-item">
    <?php echo get_views($this, _t("无人问津"), _t("1 次浏览"), _t("%d 次浏览")); ?>
</li>

使用自定义字段存储数据

这个方法无需给contents表增加一个字段views,放在functions.php

function themeInit($archive) {
    ... // 其他语句
    if ($archive->is('content')) {
        increase_views($archive);
    }
    ... // 其他语句
}

/**
 * 增加浏览次数
 */
function increase_views($archive) {
    $cookie = trim(\Typecho\Cookie::get('__typecho_views') ?? "", " \t\n\r\0\x0B,");
    $viewedIds = !empty($cookie) ? explode(',', $cookie) : [];
    $cid = $archive->cid;
    if (!in_array($cid, $cookie)) {
        if (!isset($archive->fields->views)) {
            $archive->setField('views', 'int', 1, $cid);
        } else {
            $archive->setField('views', 'int', intval($archive->fields->views) + 1, $cid);
        }
        $viewedIds[] = $cid;
        $cookie = implode(',', $viewedIds);
        \Typecho\Cookie::set('__typecho_views', $cookie);
    } 
}

/**
 * 获取浏览次数,调用方式 get_views($archive, "0 times", "1 time", "%d times")
 */
function get_views() {
    $args = func_get_args();
    if (count($args) === 0) {
        throw new \InvalidArgumentException(_t("Parameter#1 \$archive cannot be null"));
    }
    $archive = array_shift($args);
    $num = intval($archive->fields->views ?? 0);
    if (empty($args)) {
        $args[] = '%d';
    }
    return sprintf(array_key_exists($num, $args) ? $args[$num] : array_pop($args), $num);
}

显示新帖子图标

放在functions.php

function isNewPost($archive){
    $now = new \Typecho\Date(T\Typecho\Date::time());
    return $now->timeStamp - $archive->date->timeStamp < 24*60*60 ? true : false;
}

调用举例,在列表循环里或者post.php里调用

<?php if (isNewPost($this)): ?>
<li class="meta-item">
    新帖子
</li>
<?php endif; ?>

获取文章缩略图

放在functions.php

/**
 * 输出缩略图
 *
 * @param Typecho\Widget|\Widget\Base\Contents|\Widget\Archive $archive 文章对象
 * @param int $quantity 图片数量
 * @param bool $return 是否返回
 * @param bool $parse 是否转换
 * @param string $template 转换模板
 * @return mixed
 */
public static function thumbs($archive, int $quantity = 3, bool $return = false, bool $parse = false, string $template = '<img alt="" src="%s" />')
{
    $thumbs = [];

    // 首先使用自定义字段 thumb
    if (self::fieldExists($archive, 'thumb') && $quantity > 0) {
        $f_thumb = $archive->fields->thumb;
        if (!in_array($f_thumb, $thumbs)) {
            $fieldThumbs = explode("\n", $f_thumb);
            foreach ($fieldThumbs as $thumb) {
                if ($quantity > 0 && !empty(trim($thumb))) {
                    $thumbs[] = $thumb;
                    $quantity -= 1;
                }
            }
        }
    }

    // 然后是正文匹配
    preg_match_all("/<img(?<images>[^>]*?)>/i", $archive->content, $matches);
    foreach ($matches['images'] as $value) {
        if ($quantity <= 0) {
            break;
        }
        $match = '';
        // 2021.09.07 修复下标 src 不存在
        if (XCore::configStr('XLazyLoad', 'on') === 'on') {
            preg_match('/data-src="(?<src>.*?)"/i', $value, $dataSrcMatch);
            if (array_key_exists('src', $dataSrcMatch)) {
                $match = $dataSrcMatch['src'];
            }
        }

        if (empty($match)) {
            preg_match('/src="(?<src>.*?)"/i', $value, $srcMatch);
            if (array_key_exists('src', $srcMatch)) {
                $match = $srcMatch['src'];
            }
        }
        if (!empty($match)) {
            // 2020.03.29 修正输出插件图标的BUG
            if (strpos($match, __TYPECHO_PLUGIN_DIR__ . "/TePass") !== false) {
                continue;
            }
            if (strpos($match, "//") === false) {
                continue;
            }
            if (!in_array($match, $thumbs)) {
                $thumbs[] = $match;
                $quantity -= 1;
            }
        }
    }

    // 接着是附件匹配
    /** @var \Widget\Contents\Attachment\Related $attachments */
    \Widget\Contents\Attachment\Related::allocWithAlias('@content-' . $archive->cid, 'parentId=' . $archive->cid)->to($attachments);
    while ($attachments->next()) {
        if ($quantity <= 0) {
            break;
        }
        if (isset($attachments->isImage) && $attachments->isImage == 1) {
            if (!in_array($attachments->url, $thumbs)) {
                $thumbs[] = $attachments->url;
                $quantity -= 1;
            }
        }
    }

    // 最后是随机
    while ($quantity-- > 0) {
        $thumbs[] = getRandomCover();
    }

    // 转换
    if ($parse && (!empty($template))) {
        for ($i = 0; $i < count($thumbs); $i++) {
            $thumbs[$i] = str_replace("%s", $thumbs[$i], $template);
        }
    }

    // 输出或返回
    if ($return) {
        if (count($thumbs) == 1) {
            return $thumbs[0];
        }
        return $thumbs;
    } else {
        foreach ($thumbs as $thumb) {
            echo $thumb;
        }
        return true;
    }
}

function getRandomCover() {
    // 定义图片路径数组,请根据实际修改
    $images = [
        'path/to/image1.jpg',
        'path/to/image2.jpg',
        'path/to/image3.jpg',
        'path/to/image4.jpg',
        'path/to/image5.jpg',
    ];

    // 从数组中随机选择一张图片
    $randomIndex = array_rand($images);

    // 返回随机选择的图片路径
    return $images[$randomIndex];
}

调用举例,在列表循环里或者post.php里调用

<div class="post-cover">
<?php thumbs($this, 1, false, true); ?>
</div>

标签: PHP, 源代码, Typecho

相关文章

Memcached如何配置分布式使用 并附PHP示例

Memcached是一种高性能的分布式内存对象缓存系统,广泛用于加速动态Web应用程序。通过将数据存储在内存中,Memcached能够显著减少数据库负载,提高应用的响应速度Memcached分布...

使用PHP打造轻量级单文件SQLite数据库管理工具

先声明一下,这是我自己内网使用的一个简单的管理工具,所以安全性方面我肯定是没有测试的~ 如果你要放在公网,请添加相关的权限认证及sql防注入等处理在开发过程中,我们经常需要一个简单易用的数据库管...

PHP 中的 declare 指令

在 PHP 编程中,declare 指令是一个强大的工具,用于控制代码的执行行为。它不仅可以启用严格类型模式,还可以用于其他一些高级功能,如性能监控和字符编码。本文将深入探讨 declare 指...

如何在PHP框架Workerman中实现异步任务处理

在现代Web应用中,处理繁重的业务逻辑时,避免主业务流程被长时间阻塞是非常重要的。Workerman是一个高性能的PHP Socket框架,支持异步任务处理,可以有效地解决这一问题。本文将详细介...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件