[ '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('
', $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.'); } }