S3upload/Plugin.php

365 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
// 包含 Composer 自动加载器
require_once __DIR__ . '/vendor/autoload.php';
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
/**
* S3 协议上传插件
*
* @package S3Upload
* @author 老孙
* @version 1.0.4
* @link https://www.imsun.org
*/
class S3Upload_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function activate()
{
Typecho_Plugin::factory('Widget_Upload')->uploadHandle = array('S3Upload_Plugin', 'uploadHandle');
Typecho_Plugin::factory('Widget_Upload')->modifyHandle = array('S3Upload_Plugin', 'modifyHandle');
Typecho_Plugin::factory('Widget_Upload')->deleteHandle = array('S3Upload_Plugin', 'deleteHandle');
Typecho_Plugin::factory('Widget_Upload')->attachmentHandle = array('S3Upload_Plugin', 'attachmentHandle');
Typecho_Plugin::factory('Widget_Upload')->attachmentDataHandle = array('S3Upload_Plugin', 'attachmentDataHandle');
Typecho_Plugin::factory('Widget_Archive')->beforeRender = array('S3Upload_Plugin', 'Widget_Archive_beforeRender');
return _t('插件已经激活,请设置 S3 配置信息');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function deactivate()
{
return _t('插件已被禁用');
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
$endpoint = new Typecho_Widget_Helper_Form_Element_Text('endpoint', null, 's3.amazonaws.com', _t('S3 Endpoint'));
$form->addInput($endpoint->addRule('required', _t('必须填写 S3 Endpoint')));
$bucket = new Typecho_Widget_Helper_Form_Element_Text('bucket', null, '', _t('Bucket 名称'));
$form->addInput($bucket->addRule('required', _t('必须填写 Bucket 名称')));
$accessKey = new Typecho_Widget_Helper_Form_Element_Text('accessKey', null, '', _t('Access Key'));
$form->addInput($accessKey->addRule('required', _t('必须填写 Access Key')));
$secretKey = new Typecho_Widget_Helper_Form_Element_Text('secretKey', null, '', _t('Secret Key'));
$form->addInput($secretKey->addRule('required', _t('必须填写 Secret Key')));
$region = new Typecho_Widget_Helper_Form_Element_Text('region', null, 'us-east-1', _t('区域'));
$form->addInput($region->addRule('required', _t('必须填写区域')));
$customDomain = new Typecho_Widget_Helper_Form_Element_Text(
'customDomain',
null,
'',
_t('自定义域名 (可选)'),
_t('如果您使用自定义域名访问 S3 存储请在此填写。例如https://cdn.yourdomain.com')
);
$form->addInput($customDomain);
$sign = new Typecho_Widget_Helper_Form_Element_Radio(
'sign',
array('open' => _t('开启'), 'close' => _t('关闭')),
'open',
_t('签名开关'),
_t('是否对 URL 进行签名')
);
$form->addInput($sign);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form)
{
}
/**
* 上传文件处理函数
*
* @access public
* @param array $file 上传的文件
* @return mixed
*/
public static function uploadHandle($file)
{
if (empty($file['name'])) {
return false;
}
$ext = self::getSafeName($file['name']);
if (!Widget_Upload::checkFileType($ext)) {
return false;
}
$options = Helper::options()->plugin('S3Upload');
$date = new Typecho_Date($options->gmtTime);
$path = self::getUploadDir() . $date->year . '/' . $date->month;
$fileName = sprintf('%u', crc32(uniqid())) . '.' . $ext;
$uploadPath = $path . '/' . $fileName;
$s3Client = self::getS3Client();
try {
// 上传到S3
$s3Client->putObject([
'Bucket' => $options->bucket,
'Key' => $uploadPath,
'Body' => fopen($file['tmp_name'], 'rb'),
'ACL' => 'public-read',
]);
// 保存到本地
$uploadDir = __TYPECHO_ROOT_DIR__ . '/usr/uploads/';
$localPath = $uploadDir . $uploadPath;
$localDir = dirname($localPath);
if (!is_dir($localDir)) {
mkdir($localDir, 0755, true);
}
if (!copy($file['tmp_name'], $localPath)) {
error_log("Failed to save file locally: " . $localPath);
}
return [
'name' => $file['name'],
'path' => $uploadPath,
'size' => $file['size'],
'type' => $ext,
'mime' => Typecho_Common::mimeContentType($uploadPath)
];
} catch (AwsException $e) {
throw new Typecho_Plugin_Exception(_t('文件上传失败:' . $e->getMessage()));
}
}
/**
* 修改文件处理函数
*
* @access public
* @param array $content 老文件
* @param array $file 新上传的文件
* @return mixed
*/
public static function modifyHandle($content, $file)
{
if (empty($file['name'])) {
return false;
}
$fileInfo = self::uploadHandle($file);
if ($fileInfo) {
self::deleteHandle($content);
return $fileInfo;
}
return false;
}
/**
* 删除文件
*
* @access public
* @param array $content 文件相关信息
* @return boolean
*/
public static function deleteHandle(array $content)
{
$options = Helper::options()->plugin('S3Upload');
$s3Client = self::getS3Client();
try {
$s3Client->deleteObject([
'Bucket' => $options->bucket,
'Key' => $content['attachment']->path,
]);
// 删除本地文件
$localPath = __TYPECHO_ROOT_DIR__ . '/usr/uploads/' . $content['attachment']->path;
if (file_exists($localPath)) {
unlink($localPath);
}
return true;
} catch (AwsException $e) {
error_log("Failed to delete file: " . $e->getMessage());
return false;
}
}
/**
* 获取实际文件在 S3 上的绝对访问路径
*
* @access public
* @param array $content 文件相关信息
* @return string
*/
public static function attachmentHandle($content)
{
return self::attachmentDataHandle($content);
}
public static function attachmentDataHandle($content)
{
$opt = Helper::options()->plugin('S3Upload');
$s3Client = self::getS3Client();
$path = str_replace('/usr/uploads/', self::getUploadDir(), $content['attachment']->path);
if ($opt->sign == 'open') {
$command = $s3Client->getCommand('GetObject', [
'Bucket' => $opt->bucket,
'Key' => $path
]);
$request = $s3Client->createPresignedRequest($command, '+60 minutes');
$url = (string) $request->getUri();
} else {
$url = $s3Client->getObjectUrl($opt->bucket, $path);
}
return self::setDomain($url);
}
public static function Widget_Archive_beforeRender($archive)
{
$options = Helper::options()->plugin('S3Upload');
$s3Client = self::getS3Client();
if ($options->sign == 'open') {
if ($archive->is('single')) {
$archive->text = self::refresh_cdn_url($options, $s3Client, $archive->text);
}
}
}
private static function refresh_cdn_url($options, $s3Client, $text)
{
$domain = self::getDomain();
return preg_replace_callback(
'/<img.*?src=[\'\"](.*?)[\'\"].*?>/i',
function ($matches) use ($options, $s3Client, $domain) {
$url = $matches[1];
if (strpos($url, $domain) !== false) {
$path = str_replace($domain . '/', '', $url);
$path = explode('?', $path)[0]; // Remove query string
$command = $s3Client->getCommand('GetObject', [
'Bucket' => $options->bucket,
'Key' => $path
]);
$request = $s3Client->createPresignedRequest($command, '+10 minutes');
$newUrl = (string) $request->getUri();
$newUrl = self::setDomain($newUrl);
return str_replace($url, $newUrl, $matches[0]);
}
return $matches[0];
},
$text
);
}
private static function getS3Client()
{
$options = Helper::options()->plugin('S3Upload');
return new S3Client([
'version' => 'latest',
'region' => $options->region,
'endpoint' => 'https://' . $options->endpoint,
'use_path_style_endpoint' => false,
'credentials' => [
'key' => $options->accessKey,
'secret' => $options->secretKey,
],
]);
}
private static function getDomain()
{
$opt = Helper::options()->plugin('S3Upload');
$domain = $opt->customDomain;
if (empty($domain)) {
$domain = 'https://' . $opt->bucket . '.' . $opt->endpoint;
}
return rtrim($domain, '/'); // 移除末尾的 '/'
}
private static function setDomain($url)
{
$opt = Helper::options()->plugin('S3Upload');
$s3_url = 'https://' . $opt->bucket . '.' . $opt->endpoint;
$cdn_domain = $opt->customDomain;
error_log("setDomain - Original URL: " . $url);
error_log("setDomain - S3 URL base: " . $s3_url);
error_log("setDomain - CDN Domain: " . $cdn_domain);
if (!empty($cdn_domain)) {
// 使用 parse_url 来分析 URL
$parsed_url = parse_url($url);
$s3_host = $opt->bucket . '.' . $opt->endpoint;
if (isset($parsed_url['host']) && $parsed_url['host'] === $s3_host) {
$new_url = $cdn_domain . $parsed_url['path'];
if (isset($parsed_url['query'])) {
$new_url .= '?' . $parsed_url['query'];
}
error_log("setDomain - URL replaced with CDN domain: " . $new_url);
return $new_url;
} else {
error_log("setDomain - URL doesn't match expected S3 format. Using original URL.");
}
} else {
error_log("setDomain - No CDN domain set, using original S3 URL");
}
error_log("setDomain - Final URL: " . $url);
return $url;
}
private static function getUploadDir()
{
return 'usr/uploads/';
}
private static function getSafeName($name)
{
$name = str_replace(array('"', '<', '>'), '', $name);
$name = str_replace('\\', '/', $name);
$name = false === strpos($name, '/') ? ('a' . $name) : str_replace('/', '/a', $name);
$info = pathinfo($name);
$name = substr($info['basename'], 1);
return isset($info['extension']) ? strtolower($info['extension']) : '';
}
}