From 738bb1094e33143395b74db0a56ab423e1e42a45 Mon Sep 17 00:00:00 2001
From: HansJack <2310210201@tiangong.edu.cn>
Date: Sun, 25 Jan 2026 11:51:52 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=BB=E9=A2=98=E5=9B=BE?=
=?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E5=A4=87=E4=BB=BD=E6=81=A2=E5=A4=8D=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现了主题图片上传功能,支持logo、favicon等6种类型图片的上传
- 增加了图片上传的前端界面和样式,包含上传按钮和文件选择器
- 完善了后端安全验证,包括管理员权限检查和文件类型限制
- 修复了POST action参数未定义的安全隐患,使用空合并运算符处理
- 优化了AJAX请求URL,避免直接使用location.href可能带来的问题
- 添加了详细的上传错误处理和用户反馈提示
---
functions.php | 67 +++++++++++++++++++++++++++++++++++++++--
static/js/admin.js | 75 ++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 137 insertions(+), 5 deletions(-)
diff --git a/functions.php b/functions.php
index 3afd749..77c7187 100644
--- a/functions.php
+++ b/functions.php
@@ -30,13 +30,14 @@ function themeConfig($form) {
}
$backPath = $absUploadDir . '/BackupSetting_' . $theTheme . '.txt';
$ret = ['success'=>false, 'message'=>'未知错误'];
- if ($_POST['action'] === 'oneblog_theme_backup') {
+ $action = $_POST['action'] ?? '';
+ if ($action === 'oneblog_theme_backup') {
$themeConfStr = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:' . $theTheme))['value'];
$ok = file_put_contents($backPath, $themeConfStr);
$ret = $ok !== false
? ['success'=>true, 'message'=>'备份成功']
: ['success'=>false, 'message'=>'备份失败,uploads 目录不可写'];
- } elseif ($_POST['action'] === 'oneblog_theme_restore') {
+ } elseif ($action === 'oneblog_theme_restore') {
if (file_exists($backPath)) {
$str = file_get_contents($backPath);
$updateThemeConQuery = $db->update('table.options')->rows(['value'=>$str])->where('name=?', 'theme:' . $theTheme);
@@ -47,6 +48,68 @@ function themeConfig($form) {
} else {
$ret = ['success'=>false, 'message'=>'未找到备份文件,无法恢复'];
}
+ } elseif ($action === 'oneblog_theme_upload_image') {
+ $user = Typecho_Widget::widget('Widget_User');
+ if (!$user || !$user->pass('administrator', true)) {
+ $ret = ['success'=>false, 'message'=>'无权限操作'];
+ } else {
+ $allowedKeys = ['logo','logoWhite','Favicon','Tagbg','Webthumb','Weixin'];
+ $key = trim((string)($_POST['key'] ?? ''));
+ if ($key === '' || !in_array($key, $allowedKeys, true)) {
+ $ret = ['success'=>false, 'message'=>'不支持的字段'];
+ } elseif (empty($_FILES['file']) || !is_array($_FILES['file'])) {
+ $ret = ['success'=>false, 'message'=>'未选择文件'];
+ } else {
+ $file = $_FILES['file'];
+ $error = $file['error'] ?? UPLOAD_ERR_NO_FILE;
+ if ($error !== UPLOAD_ERR_OK) {
+ $ret = ['success'=>false, 'message'=>'上传失败(错误码:' . $error . ')'];
+ } else {
+ $tmpName = $file['tmp_name'] ?? '';
+ if (!$tmpName || !is_uploaded_file($tmpName)) {
+ $ret = ['success'=>false, 'message'=>'无效的上传文件'];
+ } else {
+ $origName = (string)($file['name'] ?? '');
+ $ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
+ $allowedExt = ['jpg','jpeg','png','gif','webp','svg','ico'];
+ if ($ext === '' || !in_array($ext, $allowedExt, true)) {
+ $ret = ['success'=>false, 'message'=>'仅支持:' . implode(' / ', $allowedExt)];
+ } else {
+ $nameMap = [
+ 'logo' => 'logo',
+ 'logoWhite' => 'logoWhite',
+ 'Favicon' => 'favicon',
+ 'Tagbg' => 'Tagbg',
+ 'Webthumb' => 'Webthumb',
+ 'Weixin' => 'Weixin',
+ ];
+ $baseName = $nameMap[$key] ?? $key;
+ $baseName = preg_replace('/[^A-Za-z0-9_-]/', '', (string)$baseName);
+ if ($baseName === '') {
+ $ret = ['success'=>false, 'message'=>'文件名不合法'];
+ } else {
+ $themeDir = __DIR__;
+ if (!is_dir($themeDir) || !is_writable($themeDir)) {
+ $ret = ['success'=>false, 'message'=>'主题目录不可写:' . $themeDir];
+ } else {
+ $newName = $baseName . '.' . $ext;
+ $destPath = $themeDir . DIRECTORY_SEPARATOR . $newName;
+ if (file_exists($destPath) && !@unlink($destPath)) {
+ $ret = ['success'=>false, 'message'=>'无法覆盖旧文件:' . $newName];
+ } elseif (!move_uploaded_file($tmpName, $destPath)) {
+ $ret = ['success'=>false, 'message'=>'保存失败,请检查目录权限'];
+ } else {
+ @chmod($destPath, 0644);
+ $url = rtrim((string)Helper::options()->themeUrl, '/\\') . '/' . $newName;
+ $ret = ['success'=>true, 'message'=>'上传成功', 'url'=>$url, 'filename'=>$newName];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
echo json_encode($ret);
exit;
diff --git a/static/js/admin.js b/static/js/admin.js
index 2d0a86c..eafbf7d 100644
--- a/static/js/admin.js
+++ b/static/js/admin.js
@@ -138,6 +138,7 @@ document.addEventListener('DOMContentLoaded', function(){
// 主题设置备份与恢复
jQuery(function($){
+ const ajaxUrl = location.href.split('#')[0];
function showResult(msg, icon, reload){
if (typeof layer !== 'undefined') {
layer.msg(msg, {icon: icon, time: 1200}, function(){
@@ -158,7 +159,7 @@ jQuery(function($){
if(confirm('确定要备份当前主题配置吗?')) doBackup();
}
function doBackup(){
- $.post(location.href, {action:'oneblog_theme_backup', _ajax:1}, function(res){
+ $.post(ajaxUrl, {action:'oneblog_theme_backup', _ajax:1}, function(res){
if(res && res.success){
showResult(res.message, 1, true);
}else{
@@ -180,7 +181,7 @@ jQuery(function($){
if(confirm('确定要恢复主题配置吗?恢复操作不可逆,请谨慎!')) doRestore();
}
function doRestore(){
- $.post(location.href, {action:'oneblog_theme_restore', _ajax:1}, function(res){
+ $.post(ajaxUrl, {action:'oneblog_theme_restore', _ajax:1}, function(res){
if(res && res.success){
showResult(res.message, 1, true);
}else{
@@ -191,5 +192,73 @@ jQuery(function($){
});
}
});
+
+ function ensureUploadStyles(){
+ if (document.getElementById('ob-upload-style')) return;
+ const style = document.createElement('style');
+ style.id = 'ob-upload-style';
+ style.textContent = `
+.ob-upload-input-wrap{position:relative;display:block;width:100%;}
+.ob-upload-input-wrap>input[type="text"]{padding-right:78px;box-sizing:border-box;}
+.ob-upload-btn{position:absolute;right:6px;top:50%;transform:translateY(-50%);height:28px;padding:0 10px;border:1px solid #dcdcdc;background:#f3f3f3;color:#333;border-radius:3px;font-size:12px;line-height:26px;cursor:pointer;}
+.ob-upload-btn:hover{background:#ededed;}
+.ob-upload-btn:disabled{opacity:.6;cursor:not-allowed;}
+ `.trim();
+ document.head.appendChild(style);
+ }
+
+ function bindUpload(fieldName){
+ const $input = $(`input[name="${fieldName}"]`);
+ if (!$input.length) return;
+ if ($input.data('obUploadBind')) return;
+ $input.data('obUploadBind', 1);
+
+ ensureUploadStyles();
+
+ $input.wrap('');
+ const $wrap = $input.parent();
+ const $btn = $('');
+ const $file = $('');
+ $wrap.append($btn, $file);
+
+ $btn.on('click', function(){
+ $file.trigger('click');
+ });
+
+ $file.on('change', function(){
+ const file = this.files && this.files[0];
+ if (!file) return;
+
+ const formData = new FormData();
+ formData.append('action', 'oneblog_theme_upload_image');
+ formData.append('_ajax', '1');
+ formData.append('key', fieldName);
+ formData.append('file', file);
+
+ $btn.prop('disabled', true).text('上传中');
+ $.ajax({
+ url: ajaxUrl,
+ type: 'POST',
+ data: formData,
+ processData: false,
+ contentType: false,
+ dataType: 'json'
+ }).done(function(res){
+ if (res && res.success && res.url) {
+ $input.val(res.url).trigger('change');
+ showResult(res.message || '上传成功', 1);
+ } else {
+ showResult((res && res.message) ? res.message : '上传失败', 2);
+ }
+ }).fail(function(){
+ showResult('请求失败,请检查网络', 2);
+ }).always(function(){
+ $btn.prop('disabled', false).text('上传');
+ $file.val('');
+ });
+ });
+ }
+
+ ['logo','logoWhite','Favicon','Tagbg','Webthumb','Weixin'].forEach(bindUpload);
});
-
\ No newline at end of file
+