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 +