602 lines
21 KiB
PHP
602 lines
21 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* 七牛云存储+CDN+WebP扩展+图片质量+EXIF信息
|
|||
|
|
*
|
|||
|
|
* @package Qiniu
|
|||
|
|
* @author 石头厝
|
|||
|
|
* @version 2.6.0
|
|||
|
|
* @link https://www.shitoucuo.com
|
|||
|
|
* @date 2024-01-04
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
require __DIR__ . '/vendor/autoload.php';
|
|||
|
|
|
|||
|
|
use Qiniu\Auth;
|
|||
|
|
use Qiniu\Storage\UploadManager;
|
|||
|
|
use Qiniu\Storage\BucketManager;
|
|||
|
|
|
|||
|
|
class Qiniu_Plugin implements Typecho_Plugin_Interface
|
|||
|
|
{
|
|||
|
|
// 使用静态变量确保CSS只加载一次
|
|||
|
|
private static $_cssLoaded = false;
|
|||
|
|
|
|||
|
|
// 激活插件
|
|||
|
|
public static function activate()
|
|||
|
|
{
|
|||
|
|
Typecho_Plugin::factory('Widget_Upload')->uploadHandle = array('Qiniu_Plugin', 'uploadHandle');
|
|||
|
|
Typecho_Plugin::factory('Widget_Upload')->modifyHandle = array('Qiniu_Plugin', 'modifyHandle');
|
|||
|
|
Typecho_Plugin::factory('Widget_Upload')->deleteHandle = array('Qiniu_Plugin', 'deleteHandle');
|
|||
|
|
Typecho_Plugin::factory('Widget_Upload')->attachmentHandle = array('Qiniu_Plugin', 'attachmentHandle');
|
|||
|
|
|
|||
|
|
// 添加文章内容输出时的EXIF信息显示
|
|||
|
|
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('Qiniu_Plugin', 'parseContent');
|
|||
|
|
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('Qiniu_Plugin', 'parseContent');
|
|||
|
|
|
|||
|
|
// 使用安全的CSS加载方式
|
|||
|
|
Typecho_Plugin::factory('Widget_Archive')->footer = array('Qiniu_Plugin', 'footer');
|
|||
|
|
|
|||
|
|
return _t('插件已激活,请先配置七牛云信息!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 禁用插件
|
|||
|
|
public static function deactivate()
|
|||
|
|
{
|
|||
|
|
return _t('插件已禁用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 插件配置面板
|
|||
|
|
public static function config(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
$bucket = new Typecho_Widget_Helper_Form_Element_Text('bucket', null, null, _t('空间名称:'), _t('七牛云存储空间名称'));
|
|||
|
|
$bucket->addRule('required', _t('"空间名称"不能为空!'));
|
|||
|
|
$form->addInput($bucket);
|
|||
|
|
|
|||
|
|
$accesskey = new Typecho_Widget_Helper_Form_Element_Text('accesskey', null, null, _t('AccessKey:'), _t('从七牛云控制台获取'));
|
|||
|
|
$form->addInput($accesskey->addRule('required', _t('AccessKey不能为空!')));
|
|||
|
|
|
|||
|
|
$secretkey = new Typecho_Widget_Helper_Form_Element_Text('secretkey', null, null, _t('SecretKey:'), _t('从七牛云控制台获取'));
|
|||
|
|
$form->addInput($secretkey->addRule('required', _t('SecretKey不能为空!')));
|
|||
|
|
|
|||
|
|
$domain = new Typecho_Widget_Helper_Form_Element_Text('domain', null, 'https://', _t('绑定域名:'), _t('空间绑定的域名,如:https://cdn.example.com'));
|
|||
|
|
$form->addInput($domain->addRule('required', _t('请填写空间绑定的域名!'))->addRule('url', _t('您输入的域名格式错误!')));
|
|||
|
|
|
|||
|
|
$savepath = new Typecho_Widget_Helper_Form_Element_Text('savepath', null, 'typecho/{year}/{month}/', _t('保存路径前缀'), _t('可使用变量:{year}, {month}, {day}, {random}'));
|
|||
|
|
$form->addInput($savepath);
|
|||
|
|
|
|||
|
|
// WebP转换开关
|
|||
|
|
$webp = new Typecho_Widget_Helper_Form_Element_Radio('webp',
|
|||
|
|
array('0' => _t('关闭'), '1' => _t('开启')),
|
|||
|
|
'0',
|
|||
|
|
_t('WebP自动转换:'),
|
|||
|
|
_t('开启后上传的JPEG/PNG图片将自动转换为WebP格式'));
|
|||
|
|
$form->addInput($webp);
|
|||
|
|
|
|||
|
|
// 图片质量设置
|
|||
|
|
$quality = new Typecho_Widget_Helper_Form_Element_Text('quality', null, '85', _t('图片质量:'),
|
|||
|
|
_t('设置图片压缩质量(1-100),仅对JPEG/PNG/WEBP格式有效。85为推荐值'));
|
|||
|
|
$quality->addRule('required', _t('图片质量不能为空!'))
|
|||
|
|
->addRule('isInteger', _t('必须输入数字!'))
|
|||
|
|
->addRule('range', _t('请输入1-100之间的数字!'), array(1, 100));
|
|||
|
|
$form->addInput($quality);
|
|||
|
|
|
|||
|
|
// EXIF信息开关
|
|||
|
|
$exif = new Typecho_Widget_Helper_Form_Element_Radio('exif',
|
|||
|
|
array('0' => _t('关闭'), '1' => _t('开启')),
|
|||
|
|
'0',
|
|||
|
|
_t('EXIF信息显示:'),
|
|||
|
|
_t('开启后文章图片悬停时会显示拍摄信息(相机型号、光圈、快门等)'));
|
|||
|
|
$form->addInput($exif);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 个人用户配置面板
|
|||
|
|
public static function personalConfig(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获得插件配置信息
|
|||
|
|
public static function getConfig()
|
|||
|
|
{
|
|||
|
|
return Typecho_Widget::widget('Widget_Options')->plugin('Qiniu');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 安全的CSS加载方式
|
|||
|
|
public static function footer()
|
|||
|
|
{
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
if ($option->exif && !self::$_cssLoaded) {
|
|||
|
|
// 只在文章页面加载CSS
|
|||
|
|
$widget = Typecho_Widget::widget('Widget_Archive');
|
|||
|
|
if ($widget->is('single')) {
|
|||
|
|
echo '<script>
|
|||
|
|
if (document.readyState === "loading") {
|
|||
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|||
|
|
var style = document.createElement("style");
|
|||
|
|
style.type = "text/css";
|
|||
|
|
style.textContent = `';
|
|||
|
|
self::echoCss();
|
|||
|
|
echo '`;
|
|||
|
|
document.head.appendChild(style);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
var style = document.createElement("style");
|
|||
|
|
style.type = "text/css";
|
|||
|
|
style.textContent = `';
|
|||
|
|
self::echoCss();
|
|||
|
|
echo '`;
|
|||
|
|
document.head.appendChild(style);
|
|||
|
|
}
|
|||
|
|
</script>';
|
|||
|
|
self::$_cssLoaded = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 输出CSS内容
|
|||
|
|
private static function echoCss()
|
|||
|
|
{
|
|||
|
|
echo '.qiniu-exif-container {
|
|||
|
|
display: inline-block;
|
|||
|
|
position: relative;
|
|||
|
|
max-width: 100%;
|
|||
|
|
}
|
|||
|
|
.qiniu-exif-info {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 0;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
background: rgba(0, 0, 0, 0.85);
|
|||
|
|
color: white;
|
|||
|
|
padding: 8px 10px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
line-height: 1.4;
|
|||
|
|
opacity: 0;
|
|||
|
|
visibility: hidden;
|
|||
|
|
transition: opacity 0.3s ease, visibility 0.3s ease;
|
|||
|
|
z-index: 9999;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|||
|
|
pointer-events: none;
|
|||
|
|
}
|
|||
|
|
.qiniu-exif-container:hover .qiniu-exif-info {
|
|||
|
|
opacity: 1;
|
|||
|
|
visibility: visible;
|
|||
|
|
}
|
|||
|
|
.qiniu-exif-info div {
|
|||
|
|
margin: 3px 0;
|
|||
|
|
}
|
|||
|
|
.qiniu-exif-info strong {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #fff;
|
|||
|
|
}';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析文章内容,为图片添加EXIF容器
|
|||
|
|
*/
|
|||
|
|
public static function parseContent($content, $widget, $lastResult)
|
|||
|
|
{
|
|||
|
|
$content = $lastResult ? $lastResult : $content;
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
|
|||
|
|
if (!$option->exif || !$widget->is('single')) {
|
|||
|
|
return $content;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$pattern = '/<img\s+[^>]*src=["\']([^"\']+)["\'][^>]*>/i';
|
|||
|
|
return preg_replace_callback($pattern, function($matches) {
|
|||
|
|
$imgTag = $matches[0];
|
|||
|
|
$src = $matches[1];
|
|||
|
|
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
$domain = rtrim($option->domain, '/');
|
|||
|
|
|
|||
|
|
// 检查是否是七牛云的图片
|
|||
|
|
if (strpos($src, $domain) === false) {
|
|||
|
|
return $imgTag;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从URL中提取文件路径
|
|||
|
|
$path = str_replace($domain, '', $src);
|
|||
|
|
$path = ltrim(parse_url($path, PHP_URL_PATH), '/');
|
|||
|
|
|
|||
|
|
// 去除图片处理参数
|
|||
|
|
if (strpos($path, '?') !== false) {
|
|||
|
|
$path = substr($path, 0, strpos($path, '?'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从数据库获取EXIF信息
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$row = $db->fetchRow($db->select()->from('table.contents')
|
|||
|
|
->where('type = ?', 'attachment')
|
|||
|
|
->where('text LIKE ?', '%"path":"' . $path . '"%')
|
|||
|
|
->limit(1));
|
|||
|
|
|
|||
|
|
if ($row) {
|
|||
|
|
$attachment = unserialize($row['text']);
|
|||
|
|
|
|||
|
|
if (isset($attachment['exif']) && !empty($attachment['exif'])) {
|
|||
|
|
$exifData = $attachment['exif'];
|
|||
|
|
|
|||
|
|
// 生成简化的EXIF信息HTML
|
|||
|
|
$exifHtml = '<div class="qiniu-exif-info">';
|
|||
|
|
|
|||
|
|
if (!empty($exifData['camera'])) {
|
|||
|
|
$exifHtml .= '<div><strong>' . htmlspecialchars($exifData['camera']) . '</strong></div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$details = array();
|
|||
|
|
if (!empty($exifData['exposure'])) {
|
|||
|
|
$details[] = '快门:' . htmlspecialchars($exifData['exposure']);
|
|||
|
|
}
|
|||
|
|
if (!empty($exifData['aperture'])) {
|
|||
|
|
$details[] = '光圈:f/' . htmlspecialchars($exifData['aperture']);
|
|||
|
|
}
|
|||
|
|
if (!empty($exifData['iso'])) {
|
|||
|
|
$details[] = 'ISO:' . htmlspecialchars($exifData['iso']);
|
|||
|
|
}
|
|||
|
|
if (!empty($exifData['focal_length'])) {
|
|||
|
|
$details[] = '焦距:' . htmlspecialchars($exifData['focal_length']) . 'mm';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!empty($details)) {
|
|||
|
|
$exifHtml .= '<div>' . implode(' ', $details) . '</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$exifHtml .= '</div>';
|
|||
|
|
|
|||
|
|
return '<div class="qiniu-exif-container">' . $imgTag . $exifHtml . '</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $imgTag;
|
|||
|
|
}, $content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 提取EXIF信息
|
|||
|
|
*/
|
|||
|
|
private static function extractExif($filePath)
|
|||
|
|
{
|
|||
|
|
if (!function_exists('exif_read_data')) {
|
|||
|
|
return array();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$exif = @exif_read_data($filePath);
|
|||
|
|
if (!$exif) {
|
|||
|
|
return array();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$result = array();
|
|||
|
|
|
|||
|
|
// 相机型号
|
|||
|
|
if (!empty($exif['Make']) || !empty($exif['Model'])) {
|
|||
|
|
$make = !empty($exif['Make']) ? trim($exif['Make']) : '';
|
|||
|
|
$model = !empty($exif['Model']) ? trim($exif['Model']) : '';
|
|||
|
|
$result['camera'] = trim($make . ' ' . $model);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 曝光时间
|
|||
|
|
if (!empty($exif['ExposureTime'])) {
|
|||
|
|
$result['exposure'] = $exif['ExposureTime'];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 光圈值
|
|||
|
|
if (!empty($exif['FNumber'])) {
|
|||
|
|
$fnumber = is_array($exif['FNumber']) ? $exif['FNumber'][0] / $exif['FNumber'][1] : $exif['FNumber'];
|
|||
|
|
$result['aperture'] = number_format($fnumber, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ISO
|
|||
|
|
if (!empty($exif['ISOSpeedRatings'])) {
|
|||
|
|
$result['iso'] = $exif['ISOSpeedRatings'];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 焦距
|
|||
|
|
if (!empty($exif['FocalLength'])) {
|
|||
|
|
if (is_array($exif['FocalLength'])) {
|
|||
|
|
$focal = $exif['FocalLength'][0] / $exif['FocalLength'][1];
|
|||
|
|
} else {
|
|||
|
|
$focal = $exif['FocalLength'];
|
|||
|
|
}
|
|||
|
|
$result['focal_length'] = number_format($focal, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 拍摄时间
|
|||
|
|
if (!empty($exif['DateTimeOriginal'])) {
|
|||
|
|
$result['date'] = date('Y-m-d H:i', strtotime($exif['DateTimeOriginal']));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检测是否支持WebP转换
|
|||
|
|
*/
|
|||
|
|
private static function canConvertWebp()
|
|||
|
|
{
|
|||
|
|
if (!extension_loaded('gd')) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$gdInfo = gd_info();
|
|||
|
|
return isset($gdInfo['WebP Support']) && $gdInfo['WebP Support'];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 压缩图片并转换格式
|
|||
|
|
*/
|
|||
|
|
private static function compressAndConvert($sourcePath, $targetExt, $quality)
|
|||
|
|
{
|
|||
|
|
$imageInfo = @getimagesize($sourcePath);
|
|||
|
|
if (!$imageInfo) {
|
|||
|
|
return $sourcePath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$mime = $imageInfo['mime'];
|
|||
|
|
|
|||
|
|
// 创建图像资源
|
|||
|
|
switch ($mime) {
|
|||
|
|
case 'image/jpeg':
|
|||
|
|
$image = imagecreatefromjpeg($sourcePath);
|
|||
|
|
break;
|
|||
|
|
case 'image/png':
|
|||
|
|
$image = imagecreatefrompng($sourcePath);
|
|||
|
|
imagepalettetotruecolor($image);
|
|||
|
|
imagealphablending($image, false);
|
|||
|
|
imagesavealpha($image, true);
|
|||
|
|
break;
|
|||
|
|
case 'image/gif':
|
|||
|
|
$image = imagecreatefromgif($sourcePath);
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
return $sourcePath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!$image) {
|
|||
|
|
return $sourcePath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'img_') . '.' . $targetExt;
|
|||
|
|
|
|||
|
|
$success = false;
|
|||
|
|
switch ($targetExt) {
|
|||
|
|
case 'jpg':
|
|||
|
|
case 'jpeg':
|
|||
|
|
$success = imagejpeg($image, $tempFile, $quality);
|
|||
|
|
break;
|
|||
|
|
case 'png':
|
|||
|
|
$pngQuality = 9 - round(($quality / 100) * 9);
|
|||
|
|
$success = imagepng($image, $tempFile, $pngQuality);
|
|||
|
|
break;
|
|||
|
|
case 'webp':
|
|||
|
|
if (self::canConvertWebp()) {
|
|||
|
|
$success = imagewebp($image, $tempFile, $quality);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
imagedestroy($image);
|
|||
|
|
|
|||
|
|
if ($success && file_exists($tempFile)) {
|
|||
|
|
return $tempFile;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $sourcePath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 使用七牛基本图片处理
|
|||
|
|
*/
|
|||
|
|
private static function applyQiniuStyle($url, $option)
|
|||
|
|
{
|
|||
|
|
$pathInfo = pathinfo($url);
|
|||
|
|
$currentExt = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : '';
|
|||
|
|
|
|||
|
|
$needQuality = $option->quality && $option->quality != 85;
|
|||
|
|
$needWebP = $option->webp && $currentExt != 'webp';
|
|||
|
|
|
|||
|
|
if (!$needQuality && !$needWebP) {
|
|||
|
|
return $url;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$params = array();
|
|||
|
|
|
|||
|
|
if ($needQuality) {
|
|||
|
|
$params[] = 'q/' . intval($option->quality);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($needWebP) {
|
|||
|
|
$params[] = 'format/webp';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!empty($params)) {
|
|||
|
|
$separator = (strpos($url, '?') === false) ? '?' : '&';
|
|||
|
|
$url .= $separator . 'imageView2/2/' . implode('/', $params);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $url;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除文件
|
|||
|
|
*/
|
|||
|
|
public static function deleteFile($filepath)
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
$auth = new Auth($option->accesskey, $option->secretkey);
|
|||
|
|
$bucketMgr = new BucketManager($auth);
|
|||
|
|
|
|||
|
|
$err = $bucketMgr->delete($option->bucket, $filepath);
|
|||
|
|
|
|||
|
|
return $err === null;
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 上传文件到七牛云 - 修复中文文件名问题
|
|||
|
|
*/
|
|||
|
|
public static function uploadFile($file, $content = null)
|
|||
|
|
{
|
|||
|
|
error_reporting(0);
|
|||
|
|
|
|||
|
|
if (empty($file['name']) || !isset($file['tmp_name'])) {
|
|||
|
|
return array('error' => 1, 'message' => '上传文件无效');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取原始文件名
|
|||
|
|
$originalName = basename($file['name']);
|
|||
|
|
|
|||
|
|
// 使用pathinfo正确处理中文字符
|
|||
|
|
$pathInfo = pathinfo($originalName);
|
|||
|
|
$originalExt = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : '';
|
|||
|
|
$baseName = isset($pathInfo['filename']) ? $pathInfo['filename'] : '';
|
|||
|
|
|
|||
|
|
// 校验扩展名
|
|||
|
|
if (!Widget_Upload::checkFileType($originalExt)) {
|
|||
|
|
return array('error' => 1, 'message' => '不允许上传此类型文件');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
|
|||
|
|
if (empty($option->bucket) || empty($option->accesskey) || empty($option->secretkey) || empty($option->domain)) {
|
|||
|
|
return array('error' => 1, 'message' => '请先正确配置七牛云插件');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$date = new Typecho_Date(Typecho_Widget::widget('Widget_Options')->gmtTime);
|
|||
|
|
|
|||
|
|
// 提取EXIF信息
|
|||
|
|
$exifData = array();
|
|||
|
|
if ($option->exif && in_array($originalExt, array('jpg', 'jpeg'))) {
|
|||
|
|
$exifData = self::extractExif($file['tmp_name']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理图片压缩和转换
|
|||
|
|
$tempFile = null;
|
|||
|
|
$uploadPath = $file['tmp_name'];
|
|||
|
|
$finalExt = $originalExt;
|
|||
|
|
|
|||
|
|
$supportedImages = array('jpg', 'jpeg', 'png', 'gif');
|
|||
|
|
if (in_array($originalExt, $supportedImages)) {
|
|||
|
|
$quality = isset($option->quality) ? intval($option->quality) : 85;
|
|||
|
|
$quality = max(1, min(100, $quality));
|
|||
|
|
|
|||
|
|
$targetExt = $originalExt;
|
|||
|
|
if ($option->webp && self::canConvertWebp() && $originalExt != 'gif') {
|
|||
|
|
$targetExt = 'webp';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($targetExt != $originalExt || $quality != 85) {
|
|||
|
|
$processedPath = self::compressAndConvert($file['tmp_name'], $targetExt, $quality);
|
|||
|
|
|
|||
|
|
if ($processedPath != $file['tmp_name']) {
|
|||
|
|
$uploadPath = $processedPath;
|
|||
|
|
$finalExt = $targetExt;
|
|||
|
|
$tempFile = $processedPath;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成存储路径 - 保持原始文件名,只替换文件系统不允许的字符
|
|||
|
|
// 处理文件名,保留中文字符,只替换特殊字符
|
|||
|
|
$safeName = $baseName;
|
|||
|
|
|
|||
|
|
// 替换文件系统不允许的字符,但保留中文字符
|
|||
|
|
$safeName = preg_replace('/[<>:"\/\\|?*]/', '_', $safeName);
|
|||
|
|
// 移除首尾空格
|
|||
|
|
$safeName = trim($safeName);
|
|||
|
|
// 如果名称为空,使用时间戳
|
|||
|
|
if (empty($safeName)) {
|
|||
|
|
$safeName = 'image_' . time();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isset($content)) {
|
|||
|
|
$filePath = $content['attachment']->path;
|
|||
|
|
self::deleteFile($filePath);
|
|||
|
|
} else {
|
|||
|
|
$savepath = preg_replace(array('/\{year\}/', '/\{month\}/', '/\{day\}/', '/\{random\}/'),
|
|||
|
|
array($date->year, $date->month, $date->day, uniqid()),
|
|||
|
|
$option->savepath);
|
|||
|
|
$filePath = rtrim($savepath, '/') . '/' . $safeName . '.' . $finalExt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!file_exists($uploadPath)) {
|
|||
|
|
return array('error' => 1, 'message' => '上传文件不存在');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$upManager = new UploadManager();
|
|||
|
|
$auth = new Auth($option->accesskey, $option->secretkey);
|
|||
|
|
$token = $auth->uploadToken($option->bucket);
|
|||
|
|
|
|||
|
|
// 七牛云SDK会自动处理文件名编码
|
|||
|
|
list($ret, $error) = $upManager->putFile($token, $filePath, $uploadPath);
|
|||
|
|
|
|||
|
|
if ($tempFile && file_exists($tempFile) && $tempFile != $file['tmp_name']) {
|
|||
|
|
@unlink($tempFile);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($error == null) {
|
|||
|
|
$rawUrl = Typecho_Common::url($filePath, $option->domain);
|
|||
|
|
$processedUrl = self::applyQiniuStyle($rawUrl, $option);
|
|||
|
|
|
|||
|
|
$fileSize = @filesize($uploadPath);
|
|||
|
|
|
|||
|
|
$result = array(
|
|||
|
|
'name' => $originalName, // 使用原始文件名
|
|||
|
|
'path' => $filePath,
|
|||
|
|
'size' => $fileSize ? (int)$fileSize : 0,
|
|||
|
|
'type' => $finalExt,
|
|||
|
|
'mime' => $finalExt == 'webp' ? 'image/webp' : Typecho_Common::mimeContentType($uploadPath),
|
|||
|
|
'url' => $processedUrl
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!empty($exifData)) {
|
|||
|
|
$result['exif'] = $exifData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
} else {
|
|||
|
|
return array('error' => 1, 'message' => '七牛云上传失败: ' . $error->message());
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
if ($tempFile && file_exists($tempFile) && $tempFile != $file['tmp_name']) {
|
|||
|
|
@unlink($tempFile);
|
|||
|
|
}
|
|||
|
|
return array('error' => 1, 'message' => '上传异常: ' . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上传文件处理函数
|
|||
|
|
public static function uploadHandle($file)
|
|||
|
|
{
|
|||
|
|
return self::uploadFile($file);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 修改文件处理函数
|
|||
|
|
public static function modifyHandle($content, $file)
|
|||
|
|
{
|
|||
|
|
return self::uploadFile($file, $content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除文件处理函数
|
|||
|
|
public static function deleteHandle(array $content)
|
|||
|
|
{
|
|||
|
|
if (isset($content['attachment'])) {
|
|||
|
|
self::deleteFile($content['attachment']->path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取实际文件绝对访问路径
|
|||
|
|
public static function attachmentHandle(array $content)
|
|||
|
|
{
|
|||
|
|
$option = self::getConfig();
|
|||
|
|
if (isset($content['attachment'])) {
|
|||
|
|
$rawUrl = Typecho_Common::url($content['attachment']->path, $option->domain);
|
|||
|
|
return self::applyQiniuStyle($rawUrl, $option);
|
|||
|
|
}
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
}
|