From e9f9902761289fca5614edc85eb1c4b579b2725a Mon Sep 17 00:00:00 2001 From: "choi.jh" Date: Mon, 17 Nov 2025 16:27:04 +0900 Subject: [PATCH] trafficmonitor init...2 --- app/Config/Routes.php | 4 +- app/Controllers/AbstractCrudController.php | 170 ++++ app/Controllers/AbstractWebController.php | 162 +++ app/Controllers/Admin/CollectorController.php | 2 - app/Controllers/Admin/Home.php | 5 +- app/Controllers/Admin/MylogController.php | 2 - app/Controllers/Admin/TrafficController.php | 4 +- app/Controllers/Admin/UserController.php | 2 - app/Controllers/Auth/AuthController.php | 5 +- app/Controllers/CLI/Collector.php | 7 +- app/Controllers/CommonController.php | 946 ++++++------------ app/Controllers/CommonControllerV1.php | 615 ++++++++++++ app/Controllers/Home.php | 2 +- app/Views/admin/api/index.php | 148 +++ app/Views/admin/index.php | 182 +--- .../admin/{welcome/index.php => welcome.php} | 0 16 files changed, 1467 insertions(+), 789 deletions(-) create mode 100644 app/Controllers/AbstractCrudController.php create mode 100644 app/Controllers/AbstractWebController.php create mode 100644 app/Controllers/CommonControllerV1.php create mode 100644 app/Views/admin/api/index.php rename app/Views/admin/{welcome/index.php => welcome.php} (100%) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 18cd32c..9b15743 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -16,7 +16,7 @@ $routes->group('cli', ['namespace' => 'App\Controllers\CLI'], function ($routes) $routes->cli('collect', 'Collector::execute'); }); $routes->group('', ['namespace' => 'App\Controllers'], function ($routes) { - $routes->get('/', 'Home::index'); + $routes->get('/', 'Home::welcome'); $routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) { $routes->get('login', 'LocalController::login_form'); $routes->post('login', 'LocalController::login'); @@ -26,7 +26,7 @@ $routes->group('', ['namespace' => 'App\Controllers'], function ($routes) { }); //Admin 관련 $routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'authFilter:manager'], function ($routes) { - $routes->get('/', 'Home::index'); + $routes->get('/', 'Home::welcome'); $routes->group('user', ['namespace' => 'App\Controllers\Admin'], function ($routes) { $routes->get('/', 'UserController::index'); $routes->get('create', 'UserController::create_form'); diff --git a/app/Controllers/AbstractCrudController.php b/app/Controllers/AbstractCrudController.php new file mode 100644 index 0000000..c8de360 --- /dev/null +++ b/app/Controllers/AbstractCrudController.php @@ -0,0 +1,170 @@ +action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function create_form(): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('formDatas', $this->create_form_process()); + return $this->create_form_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage()); + } + } + + protected function create_process(): CommonEntity + { + // POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.) + $dto = $this->service->createDTO($this->request->getPost()); + return $this->service->create($dto); + } + + protected function create_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다."); + } + + final public function create(): string|RedirectResponse + { + try { + $this->action_init_process(__FUNCTION__); + return $this->create_result_process($this->create_process()); + } catch (ValidationException $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage()); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage()); + } + } + + // --- 수정 (Modify) --- + + protected function modify_form_process($uid): CommonEntity + { + if (!$uid) { + throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); + } + $entity = $this->service->getEntity($uid); + if (!$entity instanceof CommonEntity) { + throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); + } + return $entity; + } + + protected function modify_form_result_process(string $action): string|RedirectResponse + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function modify_form($uid): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('entity', $this->modify_form_process($uid)); + return $this->modify_form_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage()); + } + } + + protected function modify_process($uid): CommonEntity + { + // POST 데이터를 DTO 객체로 변환 + $dto = $this->service->createDTO($this->request->getPost()); + return $this->service->modify($uid, $dto); + } + + protected function modify_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다."); + } + + final public function modify($uid): string|RedirectResponse + { + try { + $this->action_init_process(__FUNCTION__); + return $this->modify_result_process($this->modify_process($uid)); + } catch (ValidationException $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + } + } + + // --- 삭제 (Delete) --- + + protected function delete_process($uid): CommonEntity + { + return $this->service->delete($uid); + } + + protected function delete_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다."); + } + + final public function delete($uid): RedirectResponse + { + try { + return $this->delete_result_process($this->delete_process($uid)); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); + } + } + + // --- 상세보기 (View) --- + + protected function view_process($uid): CommonEntity + { + if (!$uid) { + throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); + } + $entity = $this->service->getEntity($uid); + if (!$entity instanceof CommonEntity) { + throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); + } + return $entity; + } + + protected function view_result_process(string $action): string + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function view($uid): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('entity', $this->view_process($uid)); + return $this->view_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage()); + } + } +} diff --git a/app/Controllers/AbstractWebController.php b/app/Controllers/AbstractWebController.php new file mode 100644 index 0000000..d902d9b --- /dev/null +++ b/app/Controllers/AbstractWebController.php @@ -0,0 +1,162 @@ +getAuthContext(); + } + + protected function getTitle(): string + { + if ($this->_title === null) { + // 이 로직은 하위 클래스에서 service가 초기화되었다고 가정합니다. + $this->_title = lang("{$this->service->getClassPaths(false)}.title"); + } + return $this->_title; + } + + // --- 경로 및 뷰 데이터 관리 --- + + final protected function addActionPaths(string $path) + { + $this->_action_paths[] = $path; + } + + final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string + { + return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths); + } + + final protected function addViewDatas(string $key, mixed $value) + { + $this->_viewDatas[$key] = $value; + } + + final protected function getViewDatas(?string $key = null): mixed + { + if ($key === null) { + return $this->_viewDatas; + } + return $this->_viewDatas[$key] ?? null; + } + + // --- 공통 처리 로직 (Override 가능) --- + + /** + * 모든 액션 실행 전 공통 초기화 작업 + */ + protected function action_init_process(string $action): void + { + $this->addViewDatas('action', $action); + $this->addViewDatas('authContext', $this->getAuthContext()); + // $this->service가 하위 클래스에서 설정되었다고 가정 + $this->addViewDatas('classPath', $this->service->getClassPaths(false)); + } + + /** + * 액션 성공 후 모달을 닫고 부모 창을 리로드하는 스크립트를 반환합니다. + */ + protected function action_modal_process(string $message): string + { + return " + + "; + } + + /** + * 액션 결과에 따라 지정된 메시지와 URL로 리다이렉션합니다. + */ + protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse + { + switch ($type) { + case 'warning': + case 'error': + case 'critical': + case 'alert': + case 'emergency': + log_message($type, $message); + $result = $redirect_url ? redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message); + break; + case 'debug': + case 'info': + case 'notice': + default: + log_message($type, $message); + $redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); + $result = redirect()->to($redirect_url)->with('message', $message); + break; + } + return $result; + } + + /** + * 뷰 경로와 데이터를 이용하여 최종 HTML을 렌더링합니다. + */ + protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string + { + $full_path = implode(DIRECTORY_SEPARATOR, $view_paths); + $final_path = $this->request->getVar('ActionTemplate'); + if ($final_path) { + $full_path .= DIRECTORY_SEPARATOR . $final_path; + } + $view_datas = [ + ...$viewDatas, + 'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []], + ]; + + helper(['form', __FUNCTION__]); + return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]); + } +} diff --git a/app/Controllers/Admin/CollectorController.php b/app/Controllers/Admin/CollectorController.php index e3ad26f..39ffbb3 100644 --- a/app/Controllers/Admin/CollectorController.php +++ b/app/Controllers/Admin/CollectorController.php @@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface; class CollectorController extends AdminController { - public const PATH = 'collector'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); if ($this->service === null) { $this->service = service('collectorservice'); } - $this->addActionPaths(self::PATH); } protected function action_init_process(string $action): void { diff --git a/app/Controllers/Admin/Home.php b/app/Controllers/Admin/Home.php index 91fb965..9c986e7 100644 --- a/app/Controllers/Admin/Home.php +++ b/app/Controllers/Admin/Home.php @@ -2,20 +2,19 @@ namespace App\Controllers\Admin; +use App\Controllers\CommonController; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Psr\Log\LoggerInterface; class Home extends AdminController { - public const PATH = 'welcome'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); - $this->addActionPaths(self::PATH); } //Index,FieldForm관련 - public function index(): string + public function welcome(): string { $dashboards = []; foreach (service('trafficservice')->getEntities(['status' => STATUS['AVAILABLE']]) as $entity) diff --git a/app/Controllers/Admin/MylogController.php b/app/Controllers/Admin/MylogController.php index 55502bf..967dfd4 100644 --- a/app/Controllers/Admin/MylogController.php +++ b/app/Controllers/Admin/MylogController.php @@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface; class MylogController extends AdminController { - public const PATH = 'mylog'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); if ($this->service === null) { $this->service = service('mylogservice'); } - $this->addActionPaths(self::PATH); } protected function action_init_process(string $action): void { diff --git a/app/Controllers/Admin/TrafficController.php b/app/Controllers/Admin/TrafficController.php index b4e0302..139aafc 100644 --- a/app/Controllers/Admin/TrafficController.php +++ b/app/Controllers/Admin/TrafficController.php @@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface; class TrafficController extends AdminController { - public const PATH = 'traffic'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); if ($this->service === null) { $this->service = service('trafficservice'); } - $this->addActionPaths(self::PATH); } protected function action_init_process(string $action): void { @@ -81,7 +79,7 @@ class TrafficController extends AdminController } return $this->action_render_process( $this->getActionPaths(), - self::PATH . DIRECTORY_SEPARATOR . __FUNCTION__, + 'traffic' . DIRECTORY_SEPARATOR . __FUNCTION__, ['entity' => $entity] ); } diff --git a/app/Controllers/Admin/UserController.php b/app/Controllers/Admin/UserController.php index 3a49367..4a8363a 100644 --- a/app/Controllers/Admin/UserController.php +++ b/app/Controllers/Admin/UserController.php @@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface; class UserController extends AdminController { - public const PATH = 'user'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); if ($this->service === null) { $this->service = service('userservice'); } - $this->addActionPaths(self::PATH); } //Action작업관련 protected function action_init_process(string $action): void diff --git a/app/Controllers/Auth/AuthController.php b/app/Controllers/Auth/AuthController.php index 59e4302..b4416cd 100644 --- a/app/Controllers/Auth/AuthController.php +++ b/app/Controllers/Auth/AuthController.php @@ -2,8 +2,7 @@ namespace App\Controllers\Auth; -use App\Controllers\CommonController; - +use App\Controllers\AbstractWebController; use App\Entities\UserEntity; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; @@ -11,7 +10,7 @@ use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Validation\Exceptions\ValidationException; use Psr\Log\LoggerInterface; -abstract class AuthController extends CommonController +abstract class AuthController extends AbstractWebController { public const PATH = 'auth'; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) diff --git a/app/Controllers/CLI/Collector.php b/app/Controllers/CLI/Collector.php index 9fb5b3c..5b1d831 100644 --- a/app/Controllers/CLI/Collector.php +++ b/app/Controllers/CLI/Collector.php @@ -2,16 +2,16 @@ namespace App\Controllers\CLI; -use App\Controllers\CommonController; +use App\Controllers\BaseController; use App\DTOs\CollectorDTO; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Validation\Exceptions\ValidationException; use Psr\Log\LoggerInterface; -class Collector extends CommonController +class Collector extends BaseController { - public const PATH = 'collector'; + private $service = null; public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); @@ -25,7 +25,6 @@ class Collector extends CommonController $this->service->getFormService()->setFormOptions($filters); $this->service->getFormService()->setBatchjobFilters($filters); } - $this->addActionPaths(self::PATH); } public function execute(): void { diff --git a/app/Controllers/CommonController.php b/app/Controllers/CommonController.php index b5c1ef7..78cb4d3 100644 --- a/app/Controllers/CommonController.php +++ b/app/Controllers/CommonController.php @@ -3,668 +3,352 @@ namespace App\Controllers; use App\Entities\CommonEntity; -use App\Controllers\BaseController; -use App\Libraries\AuthContext; -use App\Traits\LogTrait; -use CodeIgniter\HTTP\RedirectResponse; -use CodeIgniter\HTTP\RequestInterface; -use CodeIgniter\HTTP\ResponseInterface; -use Psr\Log\LoggerInterface; -use CodeIgniter\Validation\Exceptions\ValidationException; use CodeIgniter\HTTP\DownloadResponse; +use CodeIgniter\HTTP\RedirectResponse; +use CodeIgniter\Validation\Exceptions\ValidationException; +use CodeIgniter\HTTP\ResponseInterface; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Reader\Html; use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf; -use CodeIgniter\API\ResponseTrait; -abstract class CommonController extends BaseController +/** + * CommonController + * 목록(index), 일괄작업(batchjob), 일괄삭제, 다운로드 로직을 담당합니다. (SRP: Collection Management) + */ +abstract class CommonController extends AbstractCrudController { - use LogTrait; - use ResponseTrait; - protected $request; + // --- 목록 (Index / List) 관련 --- - private array $_action_paths = []; - private array $_viewDatas = []; - protected $service = null; - private ?string $_title = null; + /** + * 조건절(필터, 검색어, 날짜, 정렬)을 처리합니다. (Override 가능) + */ + protected function index_condition_process(string $action): void + { + // Filter조건절 처리 + $index_filters = []; + foreach ($this->service->getFormService()->getFormFilters($action) as $field) { + $value = $this->request->getVar($field) ?? null; + if ($value) { + $this->service->setFilter($field, $value); + $index_filters[$field] = $value; + } + } + $this->addViewDatas('index_filters', $index_filters); - public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) - { - parent::initController($request, $response, $logger); + // 검색어조건절 처리 + $index_word = $this->request->getVar('index_word'); + if ($index_word !== null && $index_word !== '') { + $this->service->setSearchWord($index_word); + } + $this->addViewDatas('index_word', $index_word); + + // 날자검색 + $index_start = $this->request->getVar('index_start'); + $index_end = $this->request->getVar('index_end'); + if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') { + $this->service->setDateFilter($index_start, $index_end); + } + $this->addViewDatas('index_start', $index_start); + $this->addViewDatas('index_end', $index_end); + + // OrderBy처리 + $order_field = $this->request->getVar('order_field'); + $order_value = $this->request->getVar('order_value'); + $this->service->setOrderBy($order_field, $order_value); + $this->addViewDatas('order_field', $order_field); + $this->addViewDatas('order_value', $order_value); + } + + /** + * Pagenation Select Box 옵션을 생성합니다. (Override 가능) + */ + protected function pagenation_options_process(int $index_totalcount, int $perpage): array + { + $page_options = ["" => "줄수선택"]; + // 기존 로직 유지 + for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) { + $page_options[$i] = $i; + } + $page_options[$index_totalcount] = $index_totalcount; + return $page_options; + } + + /** + * PageNation 링크를 생성하고 뷰 데이터에 추가합니다. (Override 가능) + */ + protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed + { + $pager = service("pager"); + $pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group); + $this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group)); + return $pager->links($pager_group, $template); + } + + /** + * Service에서 엔티티 목록을 가져와 처리합니다. (Override 가능) + */ + protected function index_process(array $entities = []): array + { + foreach ($this->service->getEntities() as $entity) { + $entities[] = $entity; + } + return $entities; + } + + /** + * HTML View 출력을 처리합니다. (Override 가능) + */ + protected function index_result_process(string $action): string + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + /** + * 인덱스(목록) 페이지의 메인 로직입니다. + */ + final public function index(): string|ResponseInterface + { + $action = __FUNCTION__; + try { + // 초기화 + $this->action_init_process($action); + $this->addViewDatas('uri', $this->request->getUri()); + + // Paging 설정 + $page = (int) $this->request->getVar('page') ?: 1; + $perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10); + $this->addViewDatas('page', $page); + $this->addViewDatas('perpage', $perpage); + + // 1. Total Count 계산을 위한 조건절 처리 (오버라이드 가능) + $this->index_condition_process($action); + $index_totalcount = $this->service->getTotalCount(); + $this->addViewDatas('index_totalcount', $index_totalcount); + + // Pagination 설정 + $this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage)); + $this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage)); + + // 2. 실제 리스트를 위한 조건절, LIMIT, OFFSET 처리 (오버라이드 가능) + $this->index_condition_process($action); // 조건절을 다시 호출하여 필터/검색어 유지 + $this->service->setLimit($perpage); + $this->service->setOffset(($page - 1) * $perpage); + + // Entities 처리 + $this->addViewDatas('entities', $this->index_process()); + helper(['form']); + $this->addViewDatas('formDatas', $this->request->getVar() ?? []); + } catch (\Exception $e) { + session()->setFlashdata('message', $e->getMessage()); } - final protected function getAuthContext(): AuthContext - { - return service('myauth')->getAuthContext(); + // 현재 URL을 이전 URL 스택에 저장 + $this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : "")); + + // HTML 뷰 렌더링 + return $this->index_result_process($action); + } + + // --- 일괄 작업 (Batch Job) --- + + protected function batchjob_pre_process(array $postDatas = []): array + { + $postDatas = $this->request->getPost(); + + // 1. postDatas에서 선택된 uids 정보 추출 + $uids = $postDatas['batchjob_uids'] ?? []; + if (empty($uids)) { + throw new \Exception("적용할 리스트을 선택하셔야합니다."); } - final protected function addActionPaths(string $path) - { - $this->_action_paths[] = $path; + // 2. 변경할 데이터 추출 및 정리 + unset($postDatas['batchjob_uids'], $postDatas['batchjob_submit']); + + $formDatas = array_filter($postDatas, fn($value) => $value !== "" && $value !== null); + + if (empty($formDatas)) { + throw new \Exception("변경할 조건항목을 선택하셔야합니다."); } - final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string - { - return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths); - } + // 3. 데이터가 있는 필드 추출 + $selectedFields = array_keys($formDatas); + return array($uids, $selectedFields, $formDatas); + } - final protected function addViewDatas(string $key, mixed $value) - { - $this->_viewDatas[$key] = $value; - } + protected function batchjob_process($uid, array $formDatas): CommonEntity + { + // Service 로직 호출 (오버라이드 포인트) + return $this->service->batchjob($uid, $formDatas); + } - final protected function getViewDatas(?string $key = null): mixed - { - if ($key === null) { - return $this->_viewDatas; - } - return $this->_viewDatas[$key] ?? null; - } + protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse + { + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.", + $this->getTitle(), + count($entities), + count($errors), + count($uids) + )); + } - protected function getTitle(): string - { - if ($this->_title === null) { - $this->_title = lang("{$this->service->getClassPaths(false)}.title"); - } - return $this->_title; - } + final public function batchjob(): string|RedirectResponse + { + try { + // 사전작업 및 데이터 추출 + list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process(); - //공통 필수기능 - //필수함수 - //사용자정의 함수 - protected function action_init_process(string $action): void - { - $this->addViewDatas('action', $action); - $this->addViewDatas('authContext', $this->getAuthContext()); - $this->addViewDatas('classPath', $this->service->getClassPaths(false)); - } + // 초기화 + $this->service->getFormService()->setFormFields($selectedFields); + $this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields); + $this->service->getFormService()->setFormFilters($selectedFields); + $this->service->getFormService()->setFormOptions($selectedFields); - protected function action_modal_process(string $message): string - { - return " - - "; - } - - protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse - { - switch ($type) { - case 'warning': - case 'error': - case 'critical': - case 'alert': - case 'emergency': - log_message($type, $message); - $result = $redirect_url ? $result = redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message); - break; - case 'debug': - case 'info': - case 'notice': - default: - log_message($type, $message); - $redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); - $result = redirect()->to($redirect_url)->with('message', $message); - break; - } - return $result; - } - - protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string - { - $lastest_path = array_pop($view_paths); //paths는 마지막을 뺀 앞단까지만 남음 - - $full_path = implode(DIRECTORY_SEPARATOR, $view_paths); - - // GET/POST/JSON 데이터에서 'ActionTemplate' 파라미터를 가져옵니다. - $final_path = $this->request->getVar('ActionTemplate'); - - if ($final_path) { - $full_path .= DIRECTORY_SEPARATOR . $final_path; - } - - $view_datas = [ - ...$viewDatas, - 'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []], - ]; - - helper(['form', __FUNCTION__]); - return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]); - } - - //생성 - protected function create_form_process(array $formDatas = []): array - { - //Form Default값 설정 - return $formDatas; - } - - protected function create_form_result_process(string $action): string|RedirectResponse - { - return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); - } - - final public function create_form(): string|RedirectResponse - { + foreach ($uids as $uid) { try { - $action = __FUNCTION__; - $this->action_init_process($action); - $this->addViewDatas('formDatas', $this->create_form_process()); - return $this->create_form_result_process($action); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage()); - } - } - - protected function create_process(): CommonEntity - { - // POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.) - $dto = $this->service->createDTO($this->request->getPost()); - return $this->service->create($dto); - } - - protected function create_result_process(CommonEntity $entity): string|RedirectResponse - { - return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다."); - } - - final public function create(): string|RedirectResponse - { - try { - $this->action_init_process(__FUNCTION__); - return $this->create_result_process($this->create_process()); + $entities[] = $this->batchjob_process($uid, $formDatas); } catch (ValidationException $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage()); + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage()); + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); } + } + return $this->batchjob_result_process($uids, $entities, $errors); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage()); } + } - //수정 - protected function modify_form_process($uid): CommonEntity - { + // --- 일괄 삭제 (Batch Job Delete) --- + + protected function batchjob_delete_pre_process(): array + { + $postDatas = $this->request->getPost(); + $uids = $postDatas['batchjob_uids'] ?? []; + + if (empty($uids)) { + throw new \Exception("삭제할 리스트을 선택하셔야합니다."); + } + // $uids는 배열로 반환 + return $uids; + } + + protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse + { + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.", + $this->getTitle(), + count($entities), + count($errors), + count($uids) + )); + } + + /** + * 단일 삭제 로직을 재사용 (Override 가능) + */ + protected function batchjob_delete_process($uid): CommonEntity + { + return $this->service->delete($uid); + } + + final public function batchjob_delete(): string|RedirectResponse + { + try { + $uids = $this->batchjob_delete_pre_process(); + $entities = []; + $errors = []; + + foreach ($uids as $uid) { + try { + $entities[] = $this->batchjob_delete_process($uid); + } catch (\Exception $e) { + log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); + } + } + return $this->batchjob_delete_result_process($uids, $entities, $errors); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage()); + } + } + + // --- 문서 다운로드 (Download) --- + + protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array + { + $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download"; + switch ($document_type) { + case 'excel': + $file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); + $writer = IOFactory::createWriter($loaded_data, 'Xlsx'); + $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); + break; + case 'pdf': + $file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); + $writer = new Mpdf($loaded_data); + $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); + break; + default: + throw new \Exception("지원하지 않는 다운로드 타입입니다: {$document_type}"); + } + return array($full_path, $file_name); + } + + protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string + { + switch ($output_type) { + case 'excel': + case 'pdf': + helper(['form']); + // 전체 목록을 다운로드하므로, 목록 조건절을 처리합니다. + $this->index_condition_process($action); + $this->addViewDatas('entities', $this->index_process()); + + // HTML로 렌더링된 내용을 가져옵니다. + $html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + + // HTML을 PhpSpreadsheet 객체로 로드합니다. + $reader = new Html(); + $loaded_data = $reader->loadFromString($html); + + // 파일 저장 및 정보 가져오기 + list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data); + $full_path .= DIRECTORY_SEPARATOR . $file_name; + break; + default: + // 개별 파일 다운로드 로직 if (!$uid) { - throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); + throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다."); } $entity = $this->service->getEntity($uid); - if (!$entity instanceof CommonEntity) { - throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); + if (!$entity) { + throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다."); } - return $entity; + $this->addViewDatas('entity', $entity); + list($file_name, $uploaded_filename) = $entity->getDownlaodFile(); + $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename; + break; } + return $this->response->download($full_path, null)->setFileName($file_name); + } - protected function modify_form_result_process(string $action): string|RedirectResponse - { - return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); - } - - final public function modify_form($uid): string|RedirectResponse - { - try { - $action = __FUNCTION__; - $this->action_init_process($action); - $this->addViewDatas('entity', $this->modify_form_process($uid)); - return $this->modify_form_result_process($action); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage()); - } - } - - protected function modify_process($uid): CommonEntity - { - // POST 데이터를 DTO 객체로 변환 - $dto = $this->service->createDTO($this->request->getPost()); - return $this->service->modify($uid, $dto); - } - - protected function modify_result_process(CommonEntity $entity): string|RedirectResponse - { - return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다."); - } - - final public function modify($uid): string|RedirectResponse - { - try { - $this->action_init_process(__FUNCTION__); - return $this->modify_result_process($this->modify_process($uid)); - } catch (ValidationException $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); - } - } - - //일괄처리 - protected function batchjob_pre_process(array $postDatas = []): array - { - // POST 데이터 전체를 가져옵니다. - foreach ($this->request->getPost() as $field => $value) { - $postDatas[$field] = $value; - } - //1. postDatas에서 선택된 uids 정보 추출 - $uids = []; - if (isset($postDatas['batchjob_uids'])) { - $uids = $postDatas['batchjob_uids']; - unset($postDatas['batchjob_uids']); - } - if (empty($uids)) { - throw new \Exception("적용할 리스트을 선택하셔야합니다."); - } - //2. postDatas에서 필요없는 정보 버림 - unset($postDatas['batchjob_submit']); - //3. 나머지는 $formDatas로 사용할 데이터 추출 - $formDatas = []; - foreach ($postDatas as $field => $value) { - if ($value !== "") { - $formDatas[$field] = $value; - } - } - if (empty($formDatas)) { - throw new \Exception("변경할 조건항목을 선택하셔야합니다."); - } - //4. 데이터가 있는 필드 추출 - $selectedFields = array_keys($formDatas); - return array($uids, $selectedFields, $formDatas); - } - - protected function batchjob_process($uid, array $formDatas): CommonEntity - { - return $this->service->batchjob($uid, $formDatas); - } - - protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse - { - return $this->action_redirect_process('info', sprintf( - "%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.", - $this->getTitle(), - count($entities), - count($errors), - count($uids) - )); - } - - final public function batchjob(): string|RedirectResponse - { - try { - //사전작업 - list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process(); - //초기화 - $this->service->getFormService()->setFormFields($selectedFields); - $this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields); - $this->service->getFormService()->setFormFilters($selectedFields); - $this->service->getFormService()->setFormOptions($selectedFields); - $entities = []; - $errors = []; - foreach ($uids as $uid) { - try { - $entities[] = $this->batchjob_process($uid, $formDatas); - } catch (ValidationException $e) { - log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); - $errors[] = $e->getMessage(); - } catch (\Exception $e) { - log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); - $errors[] = $e->getMessage(); - } - } - return $this->batchjob_result_process($uids, $entities, $errors); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage()); - } - } - - //삭제 - protected function delete_process($uid): CommonEntity - { - return $this->service->delete($uid); - } - - protected function delete_result_process(CommonEntity $entity): string|RedirectResponse - { - return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다."); - } - - final public function delete($uid): RedirectResponse - { - try { - return $this->delete_result_process($this->delete_process($uid)); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); - } - } - - //일괄삭제 - protected function batchjob_delete_pre_process(): array - { - // POST 데이터 전체를 가져옵니다. - $postDatas = $this->request->getPost(); - //1. postDatas에서 선택된 uids 정보 추출 - $uids = []; - if (isset($postDatas['batchjob_uids'])) { - $uids = $postDatas['batchjob_uids']; - unset($postDatas['batchjob_uids']); - } - if (empty($uids)) { - throw new \Exception("삭제할 리스트을 선택하셔야합니다."); - } - return array($uids); - } - - protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse - { - return $this->action_redirect_process('info', sprintf( - "%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.", - $this->getTitle(), - count($entities), - count($errors), - count($uids) - )); - } - - protected function batchjob_delete_process($uid): CommonEntity - { - return $this->service->delete($uid); - } - - final public function batchjob_delete(): string|RedirectResponse - { - try { - $uids = $this->batchjob_delete_pre_process(); - $entities = []; - $errors = []; - foreach ($uids as $uid) { - try { - $entities[] = $this->batchjob_delete_process($uid); - } catch (\Exception $e) { - log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); - $errors[] = $e->getMessage(); - } - } - return $this->batchjob_delete_result_process($uids, $entities, $errors); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage()); - } - } - - //상세보기 - protected function view_process($uid): CommonEntity - { - if (!$uid) { - throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); - } - $entity = $this->service->getEntity($uid); - if (!$entity instanceof CommonEntity) { - throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); - } - return $entity; - } - - protected function view_result_process(string $action): string - { - return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); - } - - final public function view($uid): string|RedirectResponse - { - try { - $action = __FUNCTION__; - $this->action_init_process($action); - $this->addViewDatas('entity', $this->view_process($uid)); - return $this->view_result_process($action); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage()); - } - } - - //리스트관련 - //조건절 처리 - protected function index_condition_process(string $action): void - { - //Filter조건절 처리 - $index_filters = []; - foreach ($this->service->getFormService()->getFormFilters($action) as $field) { - // getVar()를 사용하여 GET, POST, JSON 입력을 통합하여 가져옵니다. - $value = $this->request->getVar($field) ?? null; - if ($value) { - $this->service->setFilter($field, $value); - $index_filters[$field] = $value; - } - } - $this->addViewDatas('index_filters', $index_filters); - - //검색어조건절 처리 - $index_word = $this->request->getVar('index_word'); - if ($index_word !== null && $index_word !== '') { - $this->service->setSearchWord($index_word); - } - $this->addViewDatas('index_word', $index_word); - - //날자검색 - $index_start = $this->request->getVar('index_start'); - $index_end = $this->request->getVar('index_end'); - if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') { - $this->service->setDateFilter($index_start, $index_end); - } - $this->addViewDatas('index_start', $index_start); - $this->addViewDatas('index_end', $index_end); - - //OrcerBy처리 - $order_field = $this->request->getVar('order_field'); - $order_value = $this->request->getVar('order_value'); - $this->service->setOrderBy($order_field, $order_value); - $this->addViewDatas('order_field', $order_field); - $this->addViewDatas('order_value', $order_value); - } - - //Index Option출력용 - protected function pagenation_options_process(int $index_totalcount, int $perpage): array - { - $page_options = ["" => "줄수선택"]; - for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) { - $page_options[$i] = $i; - } - $page_options[$index_totalcount] = $index_totalcount; - return $page_options; - } - - //PageNation 처리 - protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed - { - // 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성 - // 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full', - // 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가 - $pager = service("pager"); - $pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group); - // $page = $pager->getCurrentPage($pager_group); - $this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group)); - return $pager->links($pager_group, $template); - } - - //Entities처리 (하위 클래스에서 오버라이드 가능) - protected function index_process(array $entities = []): array - { - foreach ($this->service->getEntities() as $entity) { - $entities[] = $entity; - } - return $entities; - } - - // HTML View 출력 처리 (기존 로직) - protected function index_result_process(string $action): string - { - return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); - } - - protected function index_api_result_process(string $action): ResponseInterface - { - $viewDatas = $this->getViewDatas(); - - // 필요한 핵심 데이터만 추출 - $data = [ - 'entities' => $viewDatas['entities'] ?? [], - 'total_count' => $viewDatas['index_totalcount'] ?? 0, - 'page' => $viewDatas['page'] ?? 1, - 'perpage' => $viewDatas['perpage'] ?? (DEFAULTS['INDEX_PERPAGE'] ?? 10), // DEFAULTS 확인 필요 - 'filters' => $viewDatas['index_filters'] ?? [], - 'index_word' => $viewDatas['index_word'] ?? null, - 'index_start' => $viewDatas['index_start'] ?? null, - 'index_end' => $viewDatas['index_end'] ?? null, - ]; - - // CodeIgniter의 ResponseTrait의 respond() 메서드를 사용하여 JSON으로 응답 - return $this->respond([ - 'status' => 'success', - 'message' => "{$this->getTitle()} 목록 API 조회 성공", - 'data' => $data, - ]); - } - - public function index(): string|ResponseInterface - { - $action = __FUNCTION__; - - // API 요청 여부를 확인합니다. - // is_api 또는 RequestTemplate 파라미터를 getVar()로 통합하여 확인합니다. - $isApiRequest = $this->request->getVar('is_api') === '1' || - $this->request->getVar('RequestTemplate') === 'api'; - - try { - //초기화 - $this->action_init_process($action); - $this->addViewDatas('uri', $this->request->getUri()); - - // Page, Per_page 설정 - $page = (int) $this->request->getVar('page') ?: 1; - $perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10); - $this->addViewDatas('page', $page); - $this->addViewDatas('perpage', $perpage); - - // index_totalcount를 위한 조건절 처리 (오버라이드 가능) - $this->index_condition_process($action); - $index_totalcount = $this->service->getTotalCount(); - $this->addViewDatas('index_totalcount', $index_totalcount); - - // API 요청이 아닌 경우에만 HTML 뷰를 위한 페이지네이션 링크와 옵션을 준비 - if (!$isApiRequest) { - $this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage)); - $this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage)); - } - - // 조건절 LIMIT , OFFSET 처리 List용 (기존 로직 유지) - $this->index_condition_process($action); - $this->service->setLimit($perpage); - $this->service->setOffset(($page - 1) * $perpage); - - // Entities 처리 (API든 View든 공통적으로 필요하며, 하위 클래스에서 오버라이드 가능) - $this->addViewDatas('entities', $this->index_process()); - - helper(['form']); - - // 인자 없이 getVar() 호출 시, GET, POST, JSON 등 모든 데이터를 가져오며, 없을 경우 빈 배열을 사용합니다. - $this->addViewDatas('formDatas', $this->request->getVar() ?? []); - } catch (\Exception $e) { - // API 요청인 경우 JSON 오류 메시지를 반환 - if ($isApiRequest) { - return $this->failServerError($e->getMessage()); - } - // 일반 요청인 경우 세션 오류 메시지를 설정 - session()->setFlashdata('message', $e->getMessage()); - } - - // --- 결과 분기 처리 --- - - if ($isApiRequest) { - // API 요청에 대한 JSON 렌더링 - return $this->index_api_result_process($action); - } - - // 현재 URL을 세션에 저장 (일반 View 요청 시에만) - $this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : "")); - - // HTML 뷰 렌더링 - return $this->index_result_process($action); - } - - //OUPUT Document 관련 - protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array - { - $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download"; - switch ($document_type) { - case 'excel': - $file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); - $writer = IOFactory::createWriter($loaded_data, 'Xlsx'); - $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); - break; - case 'pdf': - $file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); - $writer = new Mpdf($loaded_data); - $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); - break; - } - return array($full_path, $file_name); - } - - protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string - { - switch ($output_type) { - case 'excel': - case 'pdf': - helper(['form']); - $this->index_condition_process($action); - $this->addViewDatas('entities', $this->index_process()); - $html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); - //data loading - $reader = new Html(); - $loaded_data = $reader->loadFromString($html); - list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data); - $full_path .= DIRECTORY_SEPARATOR . $file_name; - break; - default: - if (!$uid) { - throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다."); - } - $entity = $this->service->getEntity($uid); - if (!$entity) { - throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다."); - } - $this->addViewDatas('entity', $entity); - list($file_name, $uploaded_filename) = $entity->getDownlaodFile(); - $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename; - break; - } - return $this->response->download($full_path, null)->setFileName($file_name); - } - - // Download - final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string - { - $action = __FUNCTION__; - try { - //초기화 - $this->action_init_process($action); - return $this->download_process($action, $output_type, $uid); - } catch (\Exception $e) { - return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage()); - } + /** + * 최종 다운로드 액션입니다. + */ + final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string + { + $action = __FUNCTION__; + try { + $this->action_init_process($action); + return $this->download_process($action, $output_type, $uid); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage()); } + } } diff --git a/app/Controllers/CommonControllerV1.php b/app/Controllers/CommonControllerV1.php new file mode 100644 index 0000000..55da8e9 --- /dev/null +++ b/app/Controllers/CommonControllerV1.php @@ -0,0 +1,615 @@ +getAuthContext(); + } + + final protected function addActionPaths(string $path) + { + $this->_action_paths[] = $path; + } + + final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string + { + return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths); + } + + final protected function addViewDatas(string $key, mixed $value) + { + $this->_viewDatas[$key] = $value; + } + + final protected function getViewDatas(?string $key = null): mixed + { + if ($key === null) { + return $this->_viewDatas; + } + return $this->_viewDatas[$key] ?? null; + } + + protected function getTitle(): string + { + if ($this->_title === null) { + $this->_title = lang("{$this->service->getClassPaths(false)}.title"); + } + return $this->_title; + } + + //공통 필수기능 + //필수함수 + //사용자정의 함수 + protected function action_init_process(string $action): void + { + $this->addViewDatas('action', $action); + $this->addViewDatas('authContext', $this->getAuthContext()); + $this->addViewDatas('classPath', $this->service->getClassPaths(false)); + } + + protected function action_modal_process(string $message): string + { + return " + + "; + } + + protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse + { + switch ($type) { + case 'warning': + case 'error': + case 'critical': + case 'alert': + case 'emergency': + log_message($type, $message); + $result = $redirect_url ? $result = redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message); + break; + case 'debug': + case 'info': + case 'notice': + default: + log_message($type, $message); + $redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); + $result = redirect()->to($redirect_url)->with('message', $message); + break; + } + return $result; + } + + protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string + { + $lastest_path = array_pop($view_paths); //paths는 마지막을 뺀 앞단까지만 남음 + + $full_path = implode(DIRECTORY_SEPARATOR, $view_paths); + + // GET/POST/JSON 데이터에서 'ActionTemplate' 파라미터를 가져옵니다. + $final_path = $this->request->getVar('ActionTemplate'); + + if ($final_path) { + $full_path .= DIRECTORY_SEPARATOR . $final_path; + } + + $view_datas = [ + ...$viewDatas, + 'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []], + ]; + + helper(['form', __FUNCTION__]); + return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]); + } + + //생성 + protected function create_form_process(array $formDatas = []): array + { + //Form Default값 설정 + return $formDatas; + } + + protected function create_form_result_process(string $action): string|RedirectResponse + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function create_form(): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('formDatas', $this->create_form_process()); + return $this->create_form_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage()); + } + } + + protected function create_process(): CommonEntity + { + // POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.) + $dto = $this->service->createDTO($this->request->getPost()); + return $this->service->create($dto); + } + + protected function create_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다."); + } + + final public function create(): string|RedirectResponse + { + try { + $this->action_init_process(__FUNCTION__); + return $this->create_result_process($this->create_process()); + } catch (ValidationException $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage()); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage()); + } + } + + //수정 + protected function modify_form_process($uid): CommonEntity + { + if (!$uid) { + throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); + } + $entity = $this->service->getEntity($uid); + if (!$entity instanceof CommonEntity) { + throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); + } + return $entity; + } + + protected function modify_form_result_process(string $action): string|RedirectResponse + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function modify_form($uid): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('entity', $this->modify_form_process($uid)); + return $this->modify_form_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage()); + } + } + + protected function modify_process($uid): CommonEntity + { + // POST 데이터를 DTO 객체로 변환 + $dto = $this->service->createDTO($this->request->getPost()); + return $this->service->modify($uid, $dto); + } + + protected function modify_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다."); + } + + final public function modify($uid): string|RedirectResponse + { + try { + $this->action_init_process(__FUNCTION__); + return $this->modify_result_process($this->modify_process($uid)); + } catch (ValidationException $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + } + } + + //일괄처리 + protected function batchjob_pre_process(array $postDatas = []): array + { + // POST 데이터 전체를 가져옵니다. + foreach ($this->request->getPost() as $field => $value) { + $postDatas[$field] = $value; + } + //1. postDatas에서 선택된 uids 정보 추출 + $uids = []; + if (isset($postDatas['batchjob_uids'])) { + $uids = $postDatas['batchjob_uids']; + unset($postDatas['batchjob_uids']); + } + if (empty($uids)) { + throw new \Exception("적용할 리스트을 선택하셔야합니다."); + } + //2. postDatas에서 필요없는 정보 버림 + unset($postDatas['batchjob_submit']); + //3. 나머지는 $formDatas로 사용할 데이터 추출 + $formDatas = []; + foreach ($postDatas as $field => $value) { + if ($value !== "") { + $formDatas[$field] = $value; + } + } + if (empty($formDatas)) { + throw new \Exception("변경할 조건항목을 선택하셔야합니다."); + } + //4. 데이터가 있는 필드 추출 + $selectedFields = array_keys($formDatas); + return array($uids, $selectedFields, $formDatas); + } + + protected function batchjob_process($uid, array $formDatas): CommonEntity + { + return $this->service->batchjob($uid, $formDatas); + } + + protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse + { + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.", + $this->getTitle(), + count($entities), + count($errors), + count($uids) + )); + } + + final public function batchjob(): string|RedirectResponse + { + try { + //사전작업 + list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process(); + //초기화 + $this->service->getFormService()->setFormFields($selectedFields); + $this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields); + $this->service->getFormService()->setFormFilters($selectedFields); + $this->service->getFormService()->setFormOptions($selectedFields); + $entities = []; + $errors = []; + foreach ($uids as $uid) { + try { + $entities[] = $this->batchjob_process($uid, $formDatas); + } catch (ValidationException $e) { + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); + } catch (\Exception $e) { + log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); + } + } + return $this->batchjob_result_process($uids, $entities, $errors); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage()); + } + } + + //삭제 + protected function delete_process($uid): CommonEntity + { + return $this->service->delete($uid); + } + + protected function delete_result_process(CommonEntity $entity): string|RedirectResponse + { + return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다."); + } + + final public function delete($uid): RedirectResponse + { + try { + return $this->delete_result_process($this->delete_process($uid)); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); + } + } + + //일괄삭제 + protected function batchjob_delete_pre_process(): array + { + // POST 데이터 전체를 가져옵니다. + $postDatas = $this->request->getPost(); + //1. postDatas에서 선택된 uids 정보 추출 + $uids = []; + if (isset($postDatas['batchjob_uids'])) { + $uids = $postDatas['batchjob_uids']; + unset($postDatas['batchjob_uids']); + } + if (empty($uids)) { + throw new \Exception("삭제할 리스트을 선택하셔야합니다."); + } + return array($uids); + } + + protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse + { + return $this->action_redirect_process('info', sprintf( + "%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.", + $this->getTitle(), + count($entities), + count($errors), + count($uids) + )); + } + + protected function batchjob_delete_process($uid): CommonEntity + { + return $this->service->delete($uid); + } + + final public function batchjob_delete(): string|RedirectResponse + { + try { + $uids = $this->batchjob_delete_pre_process(); + $entities = []; + $errors = []; + foreach ($uids as $uid) { + try { + $entities[] = $this->batchjob_delete_process($uid); + } catch (\Exception $e) { + log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage()); + $errors[] = $e->getMessage(); + } + } + return $this->batchjob_delete_result_process($uids, $entities, $errors); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage()); + } + } + + //상세보기 + protected function view_process($uid): CommonEntity + { + if (!$uid) { + throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다."); + } + $entity = $this->service->getEntity($uid); + if (!$entity instanceof CommonEntity) { + throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다."); + } + return $entity; + } + + protected function view_result_process(string $action): string + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + + final public function view($uid): string|RedirectResponse + { + try { + $action = __FUNCTION__; + $this->action_init_process($action); + $this->addViewDatas('entity', $this->view_process($uid)); + return $this->view_result_process($action); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage()); + } + } + + //리스트관련 + //조건절 처리 + protected function index_condition_process(string $action): void + { + //Filter조건절 처리 + $index_filters = []; + foreach ($this->service->getFormService()->getFormFilters($action) as $field) { + // getVar()를 사용하여 GET, POST, JSON 입력을 통합하여 가져옵니다. + $value = $this->request->getVar($field) ?? null; + if ($value) { + $this->service->setFilter($field, $value); + $index_filters[$field] = $value; + } + } + $this->addViewDatas('index_filters', $index_filters); + + //검색어조건절 처리 + $index_word = $this->request->getVar('index_word'); + if ($index_word !== null && $index_word !== '') { + $this->service->setSearchWord($index_word); + } + $this->addViewDatas('index_word', $index_word); + + //날자검색 + $index_start = $this->request->getVar('index_start'); + $index_end = $this->request->getVar('index_end'); + if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') { + $this->service->setDateFilter($index_start, $index_end); + } + $this->addViewDatas('index_start', $index_start); + $this->addViewDatas('index_end', $index_end); + + //OrcerBy처리 + $order_field = $this->request->getVar('order_field'); + $order_value = $this->request->getVar('order_value'); + $this->service->setOrderBy($order_field, $order_value); + $this->addViewDatas('order_field', $order_field); + $this->addViewDatas('order_value', $order_value); + } + + //Index Option출력용 + protected function pagenation_options_process(int $index_totalcount, int $perpage): array + { + $page_options = ["" => "줄수선택"]; + for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) { + $page_options[$i] = $i; + } + $page_options[$index_totalcount] = $index_totalcount; + return $page_options; + } + + //PageNation 처리 + protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed + { + // 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성 + // 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full', + // 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가 + $pager = service("pager"); + $pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group); + // $page = $pager->getCurrentPage($pager_group); + $this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group)); + return $pager->links($pager_group, $template); + } + + //Entities처리 (하위 클래스에서 오버라이드 가능) + protected function index_process(array $entities = []): array + { + foreach ($this->service->getEntities() as $entity) { + $entities[] = $entity; + } + return $entities; + } + // HTML View 출력 처리 (기존 로직) + protected function index_result_process(string $action): string + { + return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + } + public function index(): string|ResponseInterface + { + $action = __FUNCTION__; + try { + //초기화 + $this->action_init_process($action); + $this->addViewDatas('uri', $this->request->getUri()); + // Page, Per_page 설정 + $page = (int) $this->request->getVar('page') ?: 1; + $perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10); + $this->addViewDatas('page', $page); + $this->addViewDatas('perpage', $perpage); + + // index_totalcount를 위한 조건절 처리 (오버라이드 가능) + $this->index_condition_process($action); + $index_totalcount = $this->service->getTotalCount(); + $this->addViewDatas('index_totalcount', $index_totalcount); + $this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage)); + $this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage)); + + // 조건절 LIMIT , OFFSET 처리 List용 (기존 로직 유지) + $this->index_condition_process($action); + $this->service->setLimit($perpage); + $this->service->setOffset(($page - 1) * $perpage); + + // Entities 처리 (API든 View든 공통적으로 필요하며, 하위 클래스에서 오버라이드 가능) + $this->addViewDatas('entities', $this->index_process()); + helper(['form']); + // 인자 없이 getVar() 호출 시, GET, POST, JSON 등 모든 데이터를 가져오며, 없을 경우 빈 배열을 사용합니다. + $this->addViewDatas('formDatas', $this->request->getVar() ?? []); + } catch (\Exception $e) { + // 일반 요청인 경우 세션 오류 메시지를 설정 + session()->setFlashdata('message', $e->getMessage()); + } + $this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : "")); + // HTML 뷰 렌더링 + return $this->index_result_process($action); + } + + //OUPUT Document 관련 + protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array + { + $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download"; + switch ($document_type) { + case 'excel': + $file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); + $writer = IOFactory::createWriter($loaded_data, 'Xlsx'); + $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); + break; + case 'pdf': + $file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); + $writer = new Mpdf($loaded_data); + $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); + break; + } + return array($full_path, $file_name); + } + + protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string + { + switch ($output_type) { + case 'excel': + case 'pdf': + helper(['form']); + $this->index_condition_process($action); + $this->addViewDatas('entities', $this->index_process()); + $html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas()); + //data loading + $reader = new Html(); + $loaded_data = $reader->loadFromString($html); + list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data); + $full_path .= DIRECTORY_SEPARATOR . $file_name; + break; + default: + if (!$uid) { + throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다."); + } + $entity = $this->service->getEntity($uid); + if (!$entity) { + throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다."); + } + $this->addViewDatas('entity', $entity); + list($file_name, $uploaded_filename) = $entity->getDownlaodFile(); + $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename; + break; + } + return $this->response->download($full_path, null)->setFileName($file_name); + } + + // Download + final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string + { + $action = __FUNCTION__; + try { + //초기화 + $this->action_init_process($action); + return $this->download_process($action, $output_type, $uid); + } catch (\Exception $e) { + return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage()); + } + } +} diff --git a/app/Controllers/Home.php b/app/Controllers/Home.php index 5934333..7aa14fb 100644 --- a/app/Controllers/Home.php +++ b/app/Controllers/Home.php @@ -4,7 +4,7 @@ namespace App\Controllers; class Home extends BaseController { - public function index(): string + public function welcome(): string { return view('welcome_message'); } diff --git a/app/Views/admin/api/index.php b/app/Views/admin/api/index.php new file mode 100644 index 0000000..070703b --- /dev/null +++ b/app/Views/admin/api/index.php @@ -0,0 +1,148 @@ +extend(LAYOUTS[$viewDatas['layout']]['path']) ?> + +section('content') ?> +alertTrait(session('message')) : ""; ?> + +
include("{$layouts}/top"); ?>
+ + + + + +
include("{$layouts}/left_menu"); ?> +
include("{$template}/index_header"); ?>
+
+ + +
+ + include("{$template}/index_content_filter"); ?> + 'batchjob_form', 'method' => "post"]) ?> + + + + + $label): ?> + + + + + + + + + + + +
번호getListLabel($field, $label, $viewDatas) ?>작업
목록 데이터를 로드 중입니다...
+ + +
+ include("{$template}/index_content_bottom"); ?> +
+ + +
+ +
+ +
+
include("{$layouts}/bottom"); ?>
+ + +endSection() ?> \ No newline at end of file diff --git a/app/Views/admin/index.php b/app/Views/admin/index.php index 1c762a3..b1a389b 100644 --- a/app/Views/admin/index.php +++ b/app/Views/admin/index.php @@ -1,148 +1,58 @@ extend(LAYOUTS[$viewDatas['layout']]['path']) ?> section('content') ?> alertTrait(session('message')) : ""; ?> - -
include("{$layouts}/top"); ?>
+
include("{$layouts['path']}/top"); ?>
-   -     -     + + + + + + + + $label): ?> + + + + + +
include("{$layouts}/left_menu"); ?> -      
include("{$template}/index_header"); ?>
-      
-         +
include("{$layouts['path']}/left_menu"); ?> +
include("{$template}/index_header"); ?>
+
-        
-           include("{$template}/index_content_filter"); ?> -           'batchjob_form', 'method' => "post"]) ?> -           -             -               -                 -                 $label): ?> -                   -                 -                 -               -             -             -             -               - +
+ include("{$template}/index_content_filter"); ?> + 'batchjob_form', 'method' => "post"]) ?> +
번호getListLabel($field, $label, $viewDatas) ?>작업
데이터를 불러오는 중입니다...
+ + + + $label): ?> + + + -             -          
번호getListLabel($field, $label, $viewDatas) ?>작업
-           include("{$template}/index_content_bottom"); ?> -           -        
-       + +
getListButton('modify', $num, $viewDatas) ?>getFieldView($field, $entity->$field, $viewDatas) ?> + $label): ?> + getListButton($action, $label, $viewDatas) ?>  + +
+ include("{$template}/index_content_bottom"); ?> + + -       -     -   + + + -
include("{$layouts}/bottom"); ?>
- - - +
include("{$layouts['path']}/bottom"); ?>
endSection() ?> \ No newline at end of file diff --git a/app/Views/admin/welcome/index.php b/app/Views/admin/welcome.php similarity index 100% rename from app/Views/admin/welcome/index.php rename to app/Views/admin/welcome.php