false, 'error' => lang('api_method_not_allowed'), ], JSON_UNESCAPED_UNICODE); exit; } // 요청 본문 파싱 $input = json_decode(file_get_contents('php://input'), true); if (json_last_error() !== JSON_ERROR_NONE) { http_response_code(400); echo installer_json_encode([ 'success' => false, 'error' => lang('api_invalid_request'), ], JSON_UNESCAPED_UNICODE); exit; } // 필드 검증 및 기본값 설정 $adminTemplates = isset($input['admin_templates']) && is_array($input['admin_templates']) ? $input['admin_templates'] : []; $userTemplates = isset($input['user_templates']) && is_array($input['user_templates']) ? $input['user_templates'] : []; $modules = isset($input['modules']) && is_array($input['modules']) ? $input['modules'] : []; $plugins = isset($input['plugins']) && is_array($input['plugins']) ? $input['plugins'] : []; $languagePacks = isset($input['language_packs']) && is_array($input['language_packs']) ? $input['language_packs'] : []; // 확장 이름 매핑 (identifier → {ko: '...', en: '...'}) $extensionNames = isset($input['extension_names']) && is_array($input['extension_names']) ? $input['extension_names'] : []; // 관리자 템플릿은 최소 1개 필수 if (empty($adminTemplates)) { http_response_code(400); echo installer_json_encode([ 'success' => false, 'error' => lang('error_admin_template_required'), ], JSON_UNESCAPED_UNICODE); exit; } // 문자열 배열로 정리 (보안) $adminTemplates = array_values(array_filter(array_map('strval', $adminTemplates))); $userTemplates = array_values(array_filter(array_map('strval', $userTemplates))); $modules = array_values(array_filter(array_map('strval', $modules))); $plugins = array_values(array_filter(array_map('strval', $plugins))); $languagePacks = array_values(array_filter(array_map('strval', $languagePacks))); // 식별자 형식 검증: 셸 메타문자/경로 조합 차단 후 artisan 명령에 안전하게 보간되도록 한다. // 실제 번들 식별자 예시: sirsoft-board, gnuboard7-hello_module, g7-module-sirsoft-board-ja $identifierPattern = '/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/'; $identifierGroups = [ 'admin_templates' => $adminTemplates, 'user_templates' => $userTemplates, 'modules' => $modules, 'plugins' => $plugins, 'language_packs' => $languagePacks, ]; foreach ($identifierGroups as $groupKey => $ids) { foreach ($ids as $id) { if (! preg_match($identifierPattern, $id)) { http_response_code(400); echo installer_json_encode([ 'success' => false, 'error' => lang('error_invalid_extension_identifier', ['identifier' => $id, 'group' => $groupKey]), ], JSON_UNESCAPED_UNICODE); exit; } } } try { // 현재 상태 가져오기 $state = getInstallationState(); // selected_extensions 업데이트 $state['selected_extensions'] = [ 'admin_templates' => $adminTemplates, 'user_templates' => $userTemplates, 'modules' => $modules, 'plugins' => $plugins, 'language_packs' => $languagePacks, ]; // 확장 이름 매핑 저장 (설치 시 표시용) $state['extension_names'] = $extensionNames; // Step 4 완료 표시 $state['step_status']['4'] = 'completed'; // 다음 스텝으로 이동 $state['current_step'] = 5; // 세션도 동기화 (Step 5로 이동 허용) $_SESSION['installer_current_step'] = 5; // 상태 저장 $saved = saveInstallationState($state); if (! $saved) { throw new Exception(lang('state_save_failed')); } // 로그 기록 addLog(lang('log_extensions_selected', [ 'admin' => count($adminTemplates), 'user' => count($userTemplates), 'modules' => count($modules), 'plugins' => count($plugins), 'language_packs' => count($languagePacks), ])); // 성공 응답 echo installer_json_encode([ 'success' => true, 'message' => lang('log_extensions_saved'), 'data' => [ 'selected_extensions' => $state['selected_extensions'], 'next_step' => 5, ], ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo installer_json_encode([ 'success' => false, 'error' => $e->getMessage(), ], JSON_UNESCAPED_UNICODE); }