主题移植小记:给 Typecho 添加短代码功能

技术 发布于 2 周前

在移植大叔的 Cherry 主题过程中,发现 WordPress 版本有一项功能是文章内容中插入其他文章的卡片,直接删掉该模块有些可惜,遂研究了下该如何实现,检索后发现已经有短代码插件可以使用,但不太符合需求,所以考虑利用 functions.php 文件来实现,预览效果如下文章卡片。

主题移植小记:Typecho 按年输出文章归档
阅读全文

首先得捋清楚需求,在编辑文章过程中,可以通过插入形如 [post id="10"] 这样的短代码,在文章进行解析时,将匹配到的短代码转化为文章卡片,根据图中的内容可以知道,文章卡片中需要调取 ID 为 10 的文章中的文章标题、文章链接、文章摘要、文章缩略图这些字段,直接贴出完成代码。

// 文章短代码
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ShortcodeParser', 'postCard');

class ShortcodeParser
{
    public static function postCard($content, $widget)
    {
        $pattern = '/\[post\s+id=["\']?(\d+)["\']?\]/i';
        return preg_replace_callback($pattern, function($matches) {
            return self::buildPostCard($matches[1]);
        }, $content);
    }

    private static function buildPostCard($cid)
    {
        try {
            $db = Typecho_Db::get();
            $options = Typecho_Widget::widget('Widget_Options');
    
            $post = $db->fetchRow($db->select('title', 'text', 'slug', 'created', 'modified')
            ->from('table.contents')
            ->where('cid = ?', intval($cid))
            ->where('type = ?', 'post')
            ->where('status = ?', 'publish')
            ->limit(1));
    
            if (!$post) return '文章不存在';
    
            // 根据路由配置动态选择生成方式
            $routeExists = (bool)Typecho_Router::get('post');
            $permalink = $routeExists 
                ? Typecho_Common::url(
                    Typecho_Router::url(
                        'post', 
                        [
                            'slug' => $post['slug'],
                            'category' => self::getFirstCategory($cid)
                        ]
                    ),
                    $options->siteUrl
                )
                : $options->siteUrl . '?cid=' . $cid;

            
            $excerpt = self::getExcerpt($post['text'], 50);
            $thumb = $options->Thumb . '?url=' . self::getPostThumb($cid, $options) . '&w=200&h=140';
    
            return <<<HTML
            <div class="post_go">
                <div class="post_left">
                    <img decoding="async" src="{$thumb}" alt="{$post['title']}">
                    <div class="post_info">
                        <div class="post_name_h2">{$post['title']}</div>
                        <p>{$excerpt}</p>
                    </div>
                </div>
                <a class="post_url" href="{$permalink}">阅读全文
                    <i class="bi bi-arrow-right ms-2"></i>
                </a>
            </div>
            HTML;
        } 
        catch (Exception $e) {
            return '文章加载失败:'.$e->getMessage();
        }
    }    

    private static function getFirstCategory($cid)
    {
        $db = Typecho_Db::get();
        return $db->fetchRow($db->select('slug')
            ->from('table.metas')
            ->join('table.relationships', 'table.relationships.mid = table.metas.mid')
            ->where('table.relationships.cid = ?', $cid)
            ->where('table.metas.type = ?', 'category')
            ->limit(1))['slug'] ?? '';
    }
    
    private static function getExcerpt($text, $length)
    {
        $cleanText = trim(strip_tags($text));
        return Typecho_Common::subStr($cleanText, 0, $length, '...');
    }

    private static function getPostThumb($cid, $options)
    {

        $uploadBase = defined('__TYPECHO_UPLOAD_URL__') 
        ? rtrim(__TYPECHO_UPLOAD_URL__, '/') 
        : $options->siteUrl;
        
        $default = $options->Image ?? '';
    
        $db = Typecho_Db::get();
        $attachments = $db->fetchAll($db->select('text')
            ->from('table.contents')
            ->where('parent = ?', $cid)
            ->where('type = ?', 'attachment'));
    
        foreach ($attachments as $attach) {
            $meta = unserialize($attach['text']);
            if (isset($meta['mime']) && strpos($meta['mime'], 'image/') === 0) {
                return Typecho_Common::url($meta['path'], $uploadBase);
            }
        } 
        return $default;
    }    
}

上面是 functions.php 中的代码,将该段代码放入主题的 functions.php 文件中,当文章内容中有类似 [post id="10"] 的内容就会被解析成如下 html 结构。

<div class="post_go">
    <div class="post_left">
        <img decoding="async" src="{$thumb}" alt="{$post['title']}">
        <div class="post_info">
            <div class="post_name_h2">{$post['title']}</div>
            <p>{$excerpt}</p>
        </div>
    </div>
    <a class="post_url" href="{$widget->permalink}">阅读全文
        <i class="bi bi-arrow-right ms-2"></i>
    </a>
</div>

注意:在文章缩略图的函数中,因为个人使用的原因,是通过 getPostThumb() 从获取文章第一个图片附件,若附件中无图片则调用主题设置中的 Image 字段设置作为默认缩略图,你可能需要在主题中加入如下配置项。

$Image = new Typecho_Widget_Helper_Form_Element_Text('Image', NULL, NULL, _t('默认缩略图'));
$form->addInput($Image);  

实际使用该段代码时,你可能会发现文章列表中使用 <?php $this->excerpt(100, '...'); ?> 输出的摘要部分会存在形如 [post id="10"] 的短代码,如果介意的话可以将下面代码加入 functions.php 中,并在文章列表循环中使用 <?php echo clearExcerpt($this->excerpt); ?> 来输出摘要。

// 摘要过滤
function clearExcerpt($content, $max_length = 150) {
    $clean = preg_replace('/\[[^]]+\]/', '', $content);
    $clean = strip_tags($clean);
    
    $clean = htmlspecialchars_decode($clean);
    $clean = str_replace(['“', '”'], '"', $clean);
    
    $clean = preg_replace('/\s+/', ' ', $clean);
    return Typecho_Common::subStr($clean, 0, $max_length, '...');
}

评论(0)

发布评论

相关文章