typecho / typecho

A PHP Blogging Platform. Simple and Powerful.
http://typecho.org
GNU General Public License v2.0
11.44k stars 2.04k forks source link

无法通过插件注入自定义field更改头图内容 #1642

Open FaceChan opened 1 year ago

FaceChan commented 1 year ago

1. 该问题的重现步骤是什么?

博客使用CDN分流,想通过插件的方式更换头图url为CDN地址

2. 你期待的结果是什么?实际看到的又是什么?

文章可以实现替换效果,但头图无法注入。

尝试直接给$archive赋值,在单个文章页面成功,但在索引页面返回的依然是空值。

3. 问题出现的环境

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * 简易替换插件,可用作镜像类CDN地址转换,方便以后整站迁移。
 *
 * @package SimpleCDN
 * @author Quarkay
 * @version 1.0.0
 * @link https://www.quarkay.com
 */
class SimpleCDN_Plugin implements Typecho_Plugin_Interface
{
    /**
     * 激活插件方法,如果激活失败,直接抛出异常
     *
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function activate(){
        Typecho_Plugin::factory("Widget_Archive")->beforeRender = array('SimpleCDN_Plugin', 'replace_content');
    }

    /**
     * 禁用插件方法,如果禁用失败,直接抛出异常
     *
     * @static
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function deactivate(){
    }

    /**
     * 获取插件配置面板
     *
     * @access public
     * @param Typecho_Widget_Helper_Form $form 配置面板
     * @return void
     */
    public static function config(Typecho_Widget_Helper_Form $form){
        // 设置替换分割符和替换规则
        $sep_flag = new Typecho_Widget_Helper_Form_Element_Text('sep_flag', NULL, '-_-', _t('规则分割符'), _t('规则分割符默认为 -_- ,可根据需要进行设置。'));
        $to_replace = new Typecho_Widget_Helper_Form_Element_Text('to_replace', NULL, '', _t('替换前地址'));
        $replace_to = new Typecho_Widget_Helper_Form_Element_Text('replace_to', NULL, '', _t('替换后地址'));
        $form->addInput($sep_flag);
        $form->addInput($to_replace);
        $form->addInput($replace_to);
        // 设置替换分割符和替换规则
        $form->addInput(new Typecho_Widget_Helper_Form_Element_Select('cdn_type', array('tencent' => '腾讯', 'UPY' => '又拍云'), 'UPY', 'CDN服务商'));
        $cdn_domain = new Typecho_Widget_Helper_Form_Element_Text('cdn_domain', NULL, 'https://static.ezo.biz', _t('CDN域名'), _t('例:https://static.ezo.biz'));
        $pkey = new Typecho_Widget_Helper_Form_Element_Text('pkey', NULL, '', _t('鉴权密钥'), _t('目前仅支持TypeC方式'));
        $lifeTime = new Typecho_Widget_Helper_Form_Element_Text('lifeTime', NULL, '30', _t('生存期限'), _t('单位秒,超过该时间鉴权失效,建议30秒'));
        $form->addInput($cdn_domain);
        $form->addInput($pkey);
        $form->addInput($lifeTime);
    }

    /**
     * 个人用户的配置面板
     *
     * @access public
     * @param Typecho_Widget_Helper_Form $form
     * @return void
     */
    public static function personalConfig(Typecho_Widget_Helper_Form $form){}

    /**
     * 插件实现方法
     *
     * @access public
     * @param Widget_Archive $archive
     * @return void
     */
    public static function replace_content(Widget_Archive $archive)
    {
        // 先得到函数参数避免重复运算
        list($to_replace, $replace_to) = self::prepare_replace_args();

        // 依次对队列中文章的指定内容进行替换
        foreach($archive->stack as $index=>$con){
            // 替换文章标题
            if(array_key_exists('text', $con)){
                $archive->stack[$index]['text'] = preg_replace($to_replace, $replace_to, $con['text']);
            }
            // 替换文章内容
            if(array_key_exists('title', $con)){
                $archive->stack[$index]['title'] = preg_replace($to_replace, $replace_to, $con['title']);
            }
            // 替换头图内容
            if(array_key_exists('banner', $con)){
                $archive->stack[$index]['fields']['banner'] = preg_replace($to_replace, $replace_to, $con['fields']['banner']);
                $archive->stack[$index]['banner'] = $archive->stack[$index]['fields']['banner'];
            }
        }

        // 对 Widget_Archive->row 中对应信息进行更新
        $archive->text = self::ParseImg(preg_replace($to_replace, $replace_to, $archive->text));
        $archive->title = self::ParseImg(preg_replace($to_replace, $replace_to, $archive->title));
        $archive->banner = self::ParseImg(preg_replace($to_replace, $replace_to, $archive->fields->banner));
    }

    /**
     * 准备替换规则参数
     *
     */
    private static function prepare_replace_args()
    {
        // 获取规则分割符
        $sep_flag = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->sep_flag;

        // 分割得到规则并进行预处理
        $to_replace = explode($sep_flag, Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->to_replace);
        $to_replace = array_map(self::sep_callback(), $to_replace);
        $replace_to = explode($sep_flag, Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->replace_to);
        return array($to_replace, $replace_to);
    }

    /**
     * 替换规则预处理
     *
     * @return Callback Function
     */
    private static function sep_callback()
    {
        return function($item){
            // 若本身就是Pattern参数格式,则无需处理
            return preg_match('/^\/.*\/[a-z]*$/i', $item) ? $item : '/'.$item.'/';
        };
    }

    private static function PrivateKeyC($org_url){

        $key = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->pkey;

        $domain = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->cdn_domain;

        $time2 = dechex(time());

        $filename = str_replace($domain, '', $org_url);

        //$sstring=PrivateKeyFileNametime

        $sstring = $key.$filename[0].$time2;

        $md5 = md5($sstring);

        $url = $domain."/".$md5."/".$time2."/".$filename[0];

        return $url;
    }

    private static function PrivateKeyUPY($org_url){

        $key = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->pkey;

        $domain = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->cdn_domain;

        $time1 = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->lifeTime;

        $etime = time() + $time1;

        $filename = str_replace($domain, '', $org_url);
        if(is_array($org_url)){
            $uri = $filename[0];
        }else{
            $uri = $filename;
        }

        $sign_key = $key."&".$etime."&".$uri;
        $sign = md5($sign_key);

        $sign8 = substr($sign,12,8);

        $_upt = $sign8 . $etime;

        return $domain . $uri . "?_upt=" . $_upt;
    }

    private static function ParseImg($content){
        $cdn_type = Typecho_Widget::widget('Widget_Options')->plugin('SimpleCDN')->cdn_type;
        switch ($cdn_type) {
            case 'UPY':
                $cdn_handle = 'self::PrivateKeyUPY';
                break;
            case 'tencent':
                $cdn_handle = 'self::PrivateKeyC';
                break;
            default:
                $cdn_handle = 'self::PrivateKeyUPY';
                break;
        }
        $reg = "/https\:\/\/(.+?)(\.gif|png|jpg|jpeg)/i";
        $content = preg_replace_callback($reg, $cdn_handle, $content);
        return $content;
    }

    public static function cdnIMG($archive){
        $url = self::ParseImg($archive);
        return $url;
    }

}
xiamuguizhi commented 10 months ago

@chatgtp4

你的插件代码看起来已经很接近你想要的功能了。但是,从你的描述来看,你在索引页面无法成功替换头图 URL。这可能是因为在 Typecho 的索引页面中,文章的自定义字段(如头图URL)可能并没有被预加载,所以你在处理索引页面时无法访问这些字段。

你可以尝试在你的主题的索引页面模板中,使用 fields() 方法来预加载文章的自定义字段。以下是一个示例代码:

// 在你的主题的 index.php 文件中
$posts = $this->widget('Widget_Contents_Post_Recent');
while($posts->next()){
    $posts->fields(); // 预加载自定义字段
    // 然后你可以像在文章页面一样访问自定义字段
    echo $posts->fields->banner;
}

然后你可以在你的插件中,使用类似的方法来处理索引页面的文章:

public static function replace_content(Widget_Archive $archive)
{
    // ...
    foreach($archive->stack as $index=>$con){
        // ...
        // 替换头图内容
        if(array_key_exists('fields', $con) && array_key_exists('banner', $con['fields'])){
            $archive->stack[$index]['fields']['banner'] = preg_replace($to_replace, $replace_to, $con['fields']['banner']);
            $archive->stack[$index]['banner'] = $archive->stack[$index]['fields']['banner'];
        }
    }
    // ...
}

这样应该可以解决你的问题。如果还有问题,可能需要更详细地调试你的代码和环境。