2026-02-23 07:57:39 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Additional;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Http\Response;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 추가기능 > PPTX 관리 컨트롤러
|
|
|
|
|
* 파일시스템 스캔 기반으로 PPTX 파일 목록 조회 및 다운로드
|
|
|
|
|
*/
|
|
|
|
|
class PptxController extends Controller
|
|
|
|
|
{
|
|
|
|
|
private array $scanPaths = [
|
|
|
|
|
'/var/www/docs/pptx-output' => ['label' => '산출물', 'source' => 'docs'],
|
|
|
|
|
'/var/www/docs/rules' => ['label' => '정책/규칙', 'source' => 'docs'],
|
|
|
|
|
'/var/www/docs/guides' => ['label' => '가이드', 'source' => 'docs'],
|
|
|
|
|
'/var/www/docs/projects' => ['label' => '프로젝트', 'source' => 'docs'],
|
2026-03-06 15:51:47 +09:00
|
|
|
'/var/www/docs/dev_plans' => ['label' => '계획', 'source' => 'docs'],
|
2026-02-23 07:57:39 +09:00
|
|
|
'/var/www/mng/docs/pptx-output' => ['label' => '산출물', 'source' => 'mng'],
|
|
|
|
|
'/var/www/mng/docs' => ['label' => '교육/문서', 'source' => 'mng'],
|
|
|
|
|
'/var/www/mng/public/docs' => ['label' => '공개 문서', 'source' => 'mng'],
|
2026-02-23 08:08:31 +09:00
|
|
|
'/var/www/sales-docs/pptx-output' => ['label' => '영업 산출물', 'source' => 'sales'],
|
|
|
|
|
'/var/www/sales-docs/plan/pptx' => ['label' => '영업 기획', 'source' => 'sales'],
|
|
|
|
|
'/var/www/sales-docs/plan' => ['label' => '영업 기획', 'source' => 'sales'],
|
2026-02-23 07:57:39 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PPTX 목록 페이지
|
|
|
|
|
*/
|
|
|
|
|
public function index(Request $request): View|Response
|
|
|
|
|
{
|
|
|
|
|
if ($request->header('HX-Request')) {
|
|
|
|
|
return response('', 200)->header('HX-Redirect', route('additional.pptx.index'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$files = $this->scanPptxFiles();
|
|
|
|
|
$categories = collect($files)->pluck('category')->unique()->sort()->values()->all();
|
|
|
|
|
$totalSize = collect($files)->sum('size_bytes');
|
|
|
|
|
|
|
|
|
|
return view('additional.pptx.index', [
|
|
|
|
|
'files' => $files,
|
|
|
|
|
'categories' => $categories,
|
|
|
|
|
'totalCount' => count($files),
|
|
|
|
|
'totalSize' => $this->formatFileSize($totalSize),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PPTX 파일 다운로드
|
|
|
|
|
*/
|
|
|
|
|
public function download(Request $request): BinaryFileResponse|Response
|
|
|
|
|
{
|
|
|
|
|
$filePath = $request->query('file');
|
|
|
|
|
|
|
|
|
|
if (! $filePath) {
|
|
|
|
|
abort(400, '파일 경로가 지정되지 않았습니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 경로 트래버설 방지
|
|
|
|
|
if (str_contains($filePath, '..')) {
|
|
|
|
|
abort(403, '잘못된 파일 경로입니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// .pptx 확장자만 허용
|
|
|
|
|
if (strtolower(pathinfo($filePath, PATHINFO_EXTENSION)) !== 'pptx') {
|
|
|
|
|
abort(403, '허용되지 않는 파일 형식입니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 허용된 base path에 속하는지 검증
|
|
|
|
|
$allowed = false;
|
|
|
|
|
foreach (array_keys($this->scanPaths) as $basePath) {
|
|
|
|
|
if (str_starts_with($filePath, $basePath.'/')) {
|
|
|
|
|
$allowed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! $allowed) {
|
|
|
|
|
abort(403, '허용되지 않는 경로입니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! file_exists($filePath)) {
|
|
|
|
|
abort(404, '파일을 찾을 수 없습니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->download($filePath, basename($filePath), [
|
|
|
|
|
'Content-Type' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 모든 스캔 경로에서 PPTX 파일 수집
|
|
|
|
|
*/
|
|
|
|
|
private function scanPptxFiles(): array
|
|
|
|
|
{
|
|
|
|
|
$files = [];
|
|
|
|
|
$seenPaths = [];
|
|
|
|
|
|
|
|
|
|
foreach ($this->scanPaths as $basePath => $meta) {
|
|
|
|
|
if (! is_dir($basePath)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->findPptxInDirectory($basePath, $basePath, $meta, $files, $seenPaths);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 수정일 내림차순 정렬
|
|
|
|
|
usort($files, fn ($a, $b) => $b['modified_at'] <=> $a['modified_at']);
|
|
|
|
|
|
|
|
|
|
return $files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 디렉토리에서 PPTX 파일 재귀 검색
|
|
|
|
|
*/
|
|
|
|
|
private function findPptxInDirectory(string $dir, string $basePath, array $meta, array &$files, array &$seenPaths): void
|
|
|
|
|
{
|
|
|
|
|
$items = scandir($dir);
|
|
|
|
|
if ($items === false) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
if ($item === '.' || $item === '..') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fullPath = $dir.'/'.$item;
|
|
|
|
|
|
|
|
|
|
if (is_dir($fullPath)) {
|
|
|
|
|
// mng/docs 경로에서 하위 디렉토리 중 다른 scanPath에 해당하는 것은 건너뜀
|
|
|
|
|
if (isset($this->scanPaths[$fullPath])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$this->findPptxInDirectory($fullPath, $basePath, $meta, $files, $seenPaths);
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strtolower(pathinfo($item, PATHINFO_EXTENSION)) !== 'pptx') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 중복 방지 (realpath 기반)
|
|
|
|
|
$realPath = realpath($fullPath);
|
|
|
|
|
if ($realPath && isset($seenPaths[$realPath])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ($realPath) {
|
|
|
|
|
$seenPaths[$realPath] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$relativePath = str_replace($basePath.'/', '', $fullPath);
|
|
|
|
|
$sizeBytes = filesize($fullPath);
|
|
|
|
|
$modifiedAt = filemtime($fullPath);
|
|
|
|
|
|
|
|
|
|
$files[] = [
|
|
|
|
|
'name' => $item,
|
|
|
|
|
'path' => $fullPath,
|
|
|
|
|
'relative_path' => $relativePath,
|
|
|
|
|
'category' => $meta['label'],
|
|
|
|
|
'source' => $meta['source'],
|
|
|
|
|
'source_dir' => str_replace('/var/www/', '', $basePath),
|
|
|
|
|
'size_bytes' => $sizeBytes,
|
|
|
|
|
'size' => $this->formatFileSize($sizeBytes),
|
|
|
|
|
'modified_at' => $modifiedAt,
|
|
|
|
|
'modified_date' => date('Y-m-d H:i', $modifiedAt),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function formatFileSize(int $bytes): string
|
|
|
|
|
{
|
|
|
|
|
if ($bytes >= 1048576) {
|
|
|
|
|
return number_format($bytes / 1048576, 1).' MB';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number_format($bytes / 1024, 0).' KB';
|
|
|
|
|
}
|
|
|
|
|
}
|