Typecho自定义上传文件名、图片相对路径插件

本文提供一种修改Typecho对上传文件随机命名的方式,并通过相对路径支持多域名访问Typecho实例。

背景

Typecho的图片附件上传主要存在以下痛点:

  1. 上传后的文件名被随机命名修改,并在图片URL中暴露,不利于后期维护,可读性差
  2. 在编辑器上传后的文件点击插入正文时,图片URL会附带完整域名为绝对路径,不支持多域名访问的网站,会产生跨域错误

问题定位

通过对Typecho源码排查,确认文件名随机,以及存储路径包含年/月文件夹,均为Widget\Upload::uploadHandle()实现的逻辑

//获取文件名
$fileName = sprintf('%u', crc32(uniqid())) . '.' . $ext;
$path = $path . '/' . $fileName;

而绝对路径计算的逻辑在Widget\Upload::attachmentHandle()实现

public static function attachmentHandle(Config $attachment): string
{
    $result = Plugin::factory(Upload::class)->trigger($hasPlugged)->call('attachmentHandle', $attachment);
    if ($hasPlugged) {
        return $result;
    }

    $options = Options::alloc();
    return Common::url(
        $attachment->path,
        defined('__TYPECHO_UPLOAD_URL__') ? __TYPECHO_UPLOAD_URL__ : $options->siteUrl
    );
}

插件实现

通过插件hook对应的接口,基于原代码实现,微调相关逻辑即可实现:

  1. 上传后保持原文件名

    注意没有删除路径上的年/月目录,如有需要可自行精简删除
  2. 插入的图片URL默认为相对路径
<?php

class UploadForTypecho_Plugin implements Typecho_Plugin_Interface
{
    const UPLOAD_DIR = '/usr/uploads';

    public static function activate()
    {
        Typecho_Plugin::factory('Widget_Upload')->uploadHandle = array(__CLASS__, 'uploadHandle');
        Typecho_Plugin::factory('Widget_Upload')->attachmentHandle = array(__CLASS__, 'attachmentHandle');
        return _t('插件已激活,请尝试再次上传附件,现有附件不作变动');
    }

    public static function deactivate()
    {
        return _t('插件已禁用');
    }

    public static function uploadHandle($file)
    {
        if (empty($file['name'])) {
            return false;
        }

        $safeName = self::getSafeName($file['name']);

        if (!Widget_Upload::checkFileType($safeName['extension'])) {
            return false;
        }

        if (empty($safeName['prefix'])) {
            return false;
        }

        $date = new Typecho_Date(Typecho_Date::gmtTime());
        $path = Typecho_Common::url(
            defined('__TYPECHO_UPLOAD_DIR__') ? __TYPECHO_UPLOAD_DIR__ : self::UPLOAD_DIR,
            defined('__TYPECHO_UPLOAD_ROOT_DIR__') ? __TYPECHO_UPLOAD_ROOT_DIR__ : __TYPECHO_ROOT_DIR__
        ) . '/' . $date->year . '/' . $date->month;

        if (!is_dir($path)) {
            if (!self::makeUploadDir($path)) {
                return false;
            }
        }

        $fileName = $safeName['prefix'] . '.' . $safeName['extension'];
        $path = $path . '/' . $fileName;

        if (isset($file['tmp_name'])) {
            if (!@move_uploaded_file($file['tmp_name'], $path)) {
                return false;
            }
        } elseif (isset($file['bytes'])) {
            if (!file_put_contents($path, $file['bytes'])) {
                return false;
            }
        } elseif (isset($file['bits'])) {
            if (!file_put_contents($path, $file['bits'])) {
                return false;
            }
        } else {
            return false;
        }

        if (!isset($file['size'])) {
            $file['size'] = filesize($path);
        }

        return [
            'name' => $file['name'],
            'path' => (defined('__TYPECHO_UPLOAD_DIR__') ? __TYPECHO_UPLOAD_DIR__ : self::UPLOAD_DIR)
                . '/' . $date->year . '/' . $date->month . '/' . $fileName,
            'size' => $file['size'],
            'type' => $safeName['extension'],
            'mime' => Typecho_Common::mimeContentType($path)
        ];
    }

    public static function config(Typecho_Widget_Helper_Form $form)
    {
    }

    public static function personalConfig(Typecho_Widget_Helper_Form $form)
    {
    }

    private static function getSafeName(string &$name): array
    {
        $name = str_replace(['"', '<', '>'], '', $name);
        $name = str_replace('\\', '/', $name);
        $info = pathinfo($name);

        $prefix = $info['basename'];
        if (isset($info['extension'])) {
            $prefix = substr($prefix, 0, -strlen($info['extension']) - 1);
        }

        return [
            'extension' => isset($info['extension']) ? strtolower($info['extension']) : '',
            'prefix' => $prefix ?? ''
        ];
    }

    private static function makeUploadDir(string $path): bool
    {
        $path = preg_replace("/\\\+/", '/', $path);
        $current = rtrim($path, '/');
        $last = $current;

        while (!is_dir($current) && false !== strpos($path, '/')) {
            $last = $current;
            $current = dirname($current);
        }

        if ($last == $current) {
            return true;
        }

        if (!@mkdir($last, 0755)) {
            return false;
        }

        return self::makeUploadDir($path);
    }

    public static function attachmentHandle($attachment)
    {
        return $attachment->path;
    }
}

将其保存到UploadForTypecho插件目录下,启用后即可对后续的上传逻辑进行修改,不影响已上传的图片。

/app
└── usr
    └── plugins
        └── UploadForTypecho
            └── Plugin.php
 喜欢文章
头像