CKEditor 透過 filebrowserImageUploadUrl 指定上傳端點。上傳完成後,伺服器必須回傳特定 JavaScript 片段呼叫 window.parent.CKEDITOR.tools.callFunction() 通知編輯器插入圖片。
config.jsCKEDITOR.editorConfig = function(config) {
// 圖片上傳端點
config.filebrowserImageUploadUrl = '/upload.php?type=img';
// 影音上傳端點 (可選)
config.filebrowserFlashUploadUrl = '/upload.php?type=flash';
// 可選:檔案瀏覽器 (瀏覽伺服器圖片)
// config.filebrowserBrowseUrl = '/browser.php';
};
upload.php<?php
/**
* CKEditor 檔案上傳處理器
* 回傳格式:
*/
// 配置區
$config = [
'allowed_types' => ['img', 'flash'],
'img' => ['ext' => ['jpg','jpeg','png','gif','bmp','webp'], 'max_size' => 2 * 1024 * 1024], // 2MB
'flash' => ['ext' => ['flv','swf','mp4'], 'max_size' => 50 * 1024 * 1024], // 50MB
'upload_dir' => [
'img' => __DIR__ . '/uploads/images',
'flash' => __DIR__ . '/uploads/flash',
],
'url_prefix' => '/uploads', // 公開訪問 URL 前綴
'filename_generator' => function($ext) {
// 命名規則:時間戳_隨機數.副檔名
return date('YmdHis') . '_' . mt_rand(1000, 9999) . '.' . $ext;
},
];
// 確保上傳目錄存在
foreach ($config['upload_dir'] as $dir) {
if (!is_dir($dir)) mkdir($dir, 0755, true);
}
// 輸出 JS 回調並結束
function ckResponse($funcNum, $url = '', $message = '') {
header('Content-Type: text/html; charset=utf-8');
$url = addslashes($url);
$message = addslashes($message);
echo "<script>window.parent.CKEDITOR.tools.callFunction($funcNum, '$url', '$message');</script>";
exit;
}
// 1. 驗證 CKEditor 功能編號
$funcNum = (int)($_GET['CKEditorFuncNum'] ?? 0);
if (!$funcNum) ckResponse(0, '', '錯誤的功能調用請求 (缺少 CKEditorFuncNum)');
// 2. 驗證類型參數
$type = $_GET['type'] ?? '';
if (!in_array($type, $config['allowed_types'])) {
ckResponse($funcNum, '', '不支援的上傳類型: ' . htmlspecialchars($type));
}
$cfg = $config[$type];
$uploadDir = $config['upload_dir'][$type];
// 3. 檢查檔案上傳
if (!isset($_FILES['upload']) || $_FILES['upload']['error'] !== UPLOAD_ERR_OK) {
ckResponse($funcNum, '', '檔案上傳失敗,錯誤代碼: ' . ($_FILES['upload']['error'] ?? '未知'));
}
$file = $_FILES['upload'];
// 4. 驗證副檔名
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $cfg['ext'])) {
ckResponse($funcNum, '', '不允許的檔案類型: .' . $ext . '。允許: ' . implode(', ', $cfg['ext']));
}
// 5. 驗證檔案大小
if ($file['size'] > $cfg['max_size']) {
ckResponse($funcNum, '', '檔案過大: ' . round($file['size']/1024, 1) . 'KB。限制: ' . round($cfg['max_size']/1024, 1) . 'KB');
}
// 6. 驗證 MIME 類型 (防偽裝)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMimes = [
'img' => ['image/jpeg','image/png','image/gif','image/bmp','image/webp'],
'flash' => ['video/x-flv','application/x-shockwave-flash','video/mp4'],
];
if (!in_array($mime, $allowedMimes[$type])) {
ckResponse($funcNum, '', '檔案內容類型不符: ' . $mime);
}
// 7. 生成檔名並移動
$filename = $config['filename_generator']($ext);
$destPath = $uploadDir . '/' . $filename;
$publicUrl = $config['url_prefix'] . '/' . ($type === 'img' ? 'images' : 'flash') . '/' . $filename;
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
ckResponse($funcNum, '', '檔案儲存失敗,請檢查目錄權限');
}
// 8. 成功回調
ckResponse($funcNum, $publicUrl, '上傳成功');
| 防護層 | 實作 |
|---|---|
| 副檔名白名單 | 嚴格限制允許的擴展名 |
| MIME 類型驗證 | finfo_file() 檢測真實內容,防止偽裝副檔名 |
| 檔案大小限制 | 配置 max_size,配合 php.ini upload_max_filesize |
| 隨機檔名 | 時間戳+隨機數,防覆蓋、防猜測、防注入 |
| 儲存目錄非公開執行 | 上傳目錄設定 php_flag engine off 或放在 public 外 |
| 圖片重新處理 | 可選:imagecreatefromstring() 重新編碼,剝離 EXIF/惡意代碼 |
| CSRF Token | 生產環境建議驗證 Token |
// 在 move_uploaded_file 成功後
if ($type === 'img') {
$img = @imagecreatefromstring(file_get_contents($destPath));
if ($img) {
// 限制最大寬度 1200px
$w = imagesx($img); $h = imagesy($img);
if ($w > 1200) {
$newH = intval($h * 1200 / $w);
$thumb = imagescale($img, 1200, $newH);
imagejpeg($thumb, $destPath, 85);
imagedestroy($thumb);
}
imagedestroy($img);
}
}
CKEditor 5 使用 **Simple Upload Adapter**,回傳格式為 JSON:
// CKEditor 5 期望回傳
header('Content-Type: application/json');
echo json_encode([
'url' => $publicUrl,
// 可選
'width' => 800,
'height' => 600,
]);
若同時支援 CKEditor 4/5,可根據 $_GET['CKEditor'] 或 Accept 標頭判斷回傳格式。
CKEditor 圖片上傳核心在於**伺服端嚴格驗證**與**標準回調協議**。本文提供的 upload.php 涵蓋:類型白名單、MIME 檢測、大小限制、隨機命名、安全儲存、標準 JS 回調,可直接用於生產環境。記得配合 Nginx/Apache 禁用上傳目錄腳本執行權限,構建完整防禦纵深。