本文提供一种修改Typecho对上传文件随机命名的方式,并通过相对路径支持多域名访问Typecho实例。
背景
Typecho的图片附件上传主要存在以下痛点:
- 上传后的文件名被随机命名修改,并在图片URL中暴露,不利于后期维护,可读性差
- 在编辑器上传后的文件点击插入正文时,图片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对应的接口,基于原代码实现,微调相关逻辑即可实现:
上传后保持原文件名
注意没有删除路径上的年/月目录,如有需要可自行精简删除
- 插入的图片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