270 lines
11 KiB
PHP
270 lines
11 KiB
PHP
<?php
|
|
|
|
namespace backend\modules\administrator\controllers;
|
|
|
|
use Yii;
|
|
use yii\web\Controller;
|
|
use yii\web\UploadedFile;
|
|
use yii\helpers\FileHelper;
|
|
use yii\filters\AccessControl;
|
|
use yii\filters\VerbFilter;
|
|
use yii\web\NotFoundHttpException;
|
|
use yii\imagine\Image;
|
|
use Imagine\Image\Box;
|
|
|
|
/**
|
|
* FilemanagerController handles secure file management.
|
|
*/
|
|
class FilemanagerController extends Controller
|
|
{
|
|
// Configuration
|
|
protected $maxFileSize = 20971520; // 20MB
|
|
protected $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'zip', 'rar', '7z', 'txt', 'mp4', 'mp3'];
|
|
protected $allowedMimeTypes = [
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
|
'application/pdf',
|
|
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed',
|
|
'text/plain', 'video/mp4', 'audio/mpeg'
|
|
];
|
|
|
|
public function behaviors()
|
|
{
|
|
return [
|
|
'access' => [
|
|
'class' => AccessControl::class,
|
|
'rules' => [
|
|
[
|
|
'allow' => true,
|
|
'roles' => ['@'],
|
|
],
|
|
],
|
|
],
|
|
'verbs' => [
|
|
'class' => VerbFilter::class,
|
|
'actions' => [
|
|
'create-folder' => ['post'],
|
|
'upload' => ['post'],
|
|
'delete' => ['post'],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
protected function getBaseUploadPath()
|
|
{
|
|
$path = Yii::getAlias('@backend/web/_uploads');
|
|
if (!is_dir($path)) {
|
|
FileHelper::createDirectory($path, 0777);
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
protected function getRequestedPath($path = '')
|
|
{
|
|
$base = realpath($this->getBaseUploadPath());
|
|
if (empty($path)) return $base;
|
|
|
|
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
|
|
$requested = realpath($base . DIRECTORY_SEPARATOR . $path);
|
|
|
|
if ($requested === false) {
|
|
$requested = $base . DIRECTORY_SEPARATOR . $path;
|
|
}
|
|
|
|
if (strpos($requested, $base) !== 0) {
|
|
throw new NotFoundHttpException('Access denied.');
|
|
}
|
|
|
|
return $requested;
|
|
}
|
|
|
|
public function actionIndex($path = '')
|
|
{
|
|
try {
|
|
$currentFullPath = $this->getRequestedPath($path);
|
|
if (!is_dir($currentFullPath)) {
|
|
FileHelper::createDirectory($currentFullPath, 0777);
|
|
}
|
|
|
|
$dirs = [];
|
|
$items = scandir($currentFullPath);
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') continue;
|
|
$fullItem = $currentFullPath . DIRECTORY_SEPARATOR . $item;
|
|
if (is_dir($fullItem)) {
|
|
$dirs[] = [
|
|
'name' => $item,
|
|
'relPath' => trim($path . '/' . $item, '/'),
|
|
'time' => date('Y-m-d H:i:s', filemtime($fullItem)),
|
|
];
|
|
}
|
|
}
|
|
|
|
$fileList = [];
|
|
$files = FileHelper::findFiles($currentFullPath, ['recursive' => false]);
|
|
foreach ($files as $file) {
|
|
$fileList[] = [
|
|
'name' => basename($file),
|
|
'size' => Yii::$app->formatter->asShortSize(filesize($file)),
|
|
'time' => date('Y-m-d H:i:s', filemtime($file)),
|
|
'ext' => pathinfo($file, PATHINFO_EXTENSION),
|
|
'relPath' => trim($path . '/' . basename($file), '/'),
|
|
];
|
|
}
|
|
|
|
usort($fileList, function($a, $b) {
|
|
return strtotime($b['time']) - strtotime($a['time']);
|
|
});
|
|
|
|
return $this->render('index', [
|
|
'dirs' => $dirs,
|
|
'files' => $fileList,
|
|
'currentPath' => $path,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
Yii::$app->session->setFlash('error', "System Error: " . $e->getMessage());
|
|
return $this->redirect(['index']);
|
|
}
|
|
}
|
|
|
|
public function actionCreateFolder($path = '')
|
|
{
|
|
try {
|
|
$parentPath = $this->getRequestedPath($path);
|
|
$folderName = Yii::$app->request->post('folder_name');
|
|
|
|
if (!$folderName) {
|
|
Yii::$app->session->setFlash('error', "กรุณาระบุชื่อโฟลเดอร์");
|
|
return $this->redirect(['index', 'path' => $path]);
|
|
}
|
|
|
|
// Secure folder name
|
|
$folderName = preg_replace('/[^a-zA-Z0-9_\-\x{0E00}-\x{0E7F}]/u', '_', $folderName);
|
|
$newPath = $parentPath . DIRECTORY_SEPARATOR . $folderName;
|
|
|
|
if (!file_exists($newPath)) {
|
|
if (FileHelper::createDirectory($newPath, 0777)) {
|
|
Yii::$app->session->setFlash('success', "สร้างโฟลเดอร์ '$folderName' สำเร็จ");
|
|
}
|
|
} else {
|
|
Yii::$app->session->setFlash('warning', "มีโฟลเดอร์ชื่อนี้อยู่แล้ว");
|
|
}
|
|
} catch (\Exception $e) {
|
|
Yii::$app->session->setFlash('error', "Error: " . $e->getMessage());
|
|
}
|
|
return $this->redirect(['index', 'path' => $path]);
|
|
}
|
|
|
|
public function actionUpload($path = '')
|
|
{
|
|
try {
|
|
$targetFullPath = $this->getRequestedPath($path);
|
|
if (Yii::$app->request->isPost) {
|
|
$files = UploadedFile::getInstancesByName('files');
|
|
$requestedWidth = Yii::$app->request->post('resize_width', '1024');
|
|
|
|
$success = 0;
|
|
$errors = [];
|
|
|
|
foreach ($files as $file) {
|
|
// 1. Check Extension
|
|
if (!in_array(strtolower($file->extension), $this->allowedExtensions)) {
|
|
$errors[] = "{$file->name}: ไม่อนุญาตให้อัปโหลดไฟล์นามสกุลนี้";
|
|
continue;
|
|
}
|
|
|
|
// 2. Check MIME Type (Content sniffing)
|
|
$mimeType = FileHelper::getMimeType($file->tempName);
|
|
if (!in_array($mimeType, $this->allowedMimeTypes)) {
|
|
$errors[] = "{$file->name}: เนื้อหาไฟล์ไม่ถูกต้องตามมาตรฐานความปลอดภัย";
|
|
continue;
|
|
}
|
|
|
|
// 3. Check File Size
|
|
if ($file->size > $this->maxFileSize) {
|
|
$errors[] = "{$file->name}: ขนาดไฟล์เกินขีดจำกัด (20MB)";
|
|
continue;
|
|
}
|
|
|
|
// 4. Sanitize Filename
|
|
$safeName = preg_replace('/[^a-zA-Z0-9_\-\.]/u', '_', $file->baseName);
|
|
$fileName = $safeName . '.' . strtolower($file->extension);
|
|
|
|
if (file_exists($targetFullPath . DIRECTORY_SEPARATOR . $fileName)) {
|
|
$fileName = $safeName . '_' . time() . '.' . strtolower($file->extension);
|
|
}
|
|
|
|
$targetFile = $targetFullPath . DIRECTORY_SEPARATOR . $fileName;
|
|
$isImage = in_array($mimeType, ['image/jpeg', 'image/png', 'image/gif', 'image/webp']);
|
|
|
|
if ($isImage && $requestedWidth !== 'original') {
|
|
$imagine = Image::getImagine();
|
|
$image = $imagine->open($file->tempName);
|
|
$size = $image->getSize();
|
|
$maxWidth = (int)$requestedWidth;
|
|
|
|
if ($size->getWidth() > $maxWidth) {
|
|
Image::thumbnail($file->tempName, $maxWidth, null)
|
|
->save($targetFile, ['quality' => 85]);
|
|
} else {
|
|
$file->saveAs($targetFile);
|
|
}
|
|
$success++;
|
|
} else {
|
|
if ($file->saveAs($targetFile)) {
|
|
$success++;
|
|
} else {
|
|
$errors[] = "{$file->name}: บันทึกไฟล์ล้มเหลว";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($success > 0) Yii::$app->session->setFlash('success', "อัปโหลดและตรวจสอบความปลอดภัยสำเร็จ $success ไฟล์");
|
|
if (!empty($errors)) Yii::$app->session->setFlash('error', "พบข้อผิดพลาด: " . implode('<br>', $errors));
|
|
}
|
|
} catch (\Exception $e) {
|
|
Yii::$app->session->setFlash('error', "Security Error: " . $e->getMessage());
|
|
}
|
|
return $this->redirect(['index', 'path' => $path]);
|
|
}
|
|
|
|
public function actionDelete($relPath)
|
|
{
|
|
try {
|
|
$fullPath = $this->getRequestedPath($relPath);
|
|
$parentRelPath = dirname($relPath);
|
|
if ($parentRelPath === '.') $parentRelPath = '';
|
|
|
|
if (is_dir($fullPath)) {
|
|
$files = array_diff(scandir($fullPath), array('.', '..'));
|
|
if (empty($files)) {
|
|
if (rmdir($fullPath)) Yii::$app->session->setFlash('success', "ลบโฟลเดอร์เรียบร้อยแล้ว");
|
|
} else {
|
|
Yii::$app->session->setFlash('error', "ไม่สามารถลบได้ เนื่องจากในโฟลเดอร์ยังมีไฟล์อยู่");
|
|
}
|
|
} else {
|
|
if (unlink($fullPath)) Yii::$app->session->setFlash('success', "ลบไฟล์เรียบร้อยแล้ว");
|
|
}
|
|
return $this->redirect(['index', 'path' => $parentRelPath]);
|
|
} catch (\Exception $e) {
|
|
Yii::$app->session->setFlash('error', "Delete Error: " . $e->getMessage());
|
|
return $this->redirect(['index']);
|
|
}
|
|
}
|
|
|
|
public function actionDownload($relPath)
|
|
{
|
|
try {
|
|
$fullPath = $this->getRequestedPath($relPath);
|
|
if (file_exists($fullPath) && !is_dir($fullPath)) {
|
|
return Yii::$app->response->sendFile($fullPath);
|
|
}
|
|
} catch (\Exception $e) {
|
|
throw new NotFoundHttpException('Not found.');
|
|
}
|
|
throw new NotFoundHttpException('Not found.');
|
|
}
|
|
}
|