From 0721df974b9525918f1d6bf0221317d5ed0953ab Mon Sep 17 00:00:00 2001 From: gakigaki Date: Tue, 13 May 2025 06:41:28 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Refactor:=20CSV=E3=82=A8=E3=82=AF=E3=82=B9?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=88=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSVインポート処理に合わせて実装を変更 --- app/Enums/LearningtaskExportType.php | 19 + .../Contracts/CsvDataProviderInterface.php | 36 ++ .../Csv/LearningtasksCsvExporter.php | 145 ++++++ .../DataProviders/ReportCsvDataProvider.php | 177 ++++++++ .../Factories/ColumnDefinitionFactory.php | 31 +- .../Factories/CsvDataProviderFactory.php | 39 ++ .../Learningtasks/LearningtasksPlugin.php | 73 ++- .../LearningtasksReportCsvExporter.php | 159 ------- .../LearningtasksServiceProvider.php | 2 + .../Learningtasks/LearningtaskPostTrait.php | 130 ------ config/app.php | 1 + .../default/learningtasks_show.blade.php | 5 +- .../LearningtasksPluginExportCsvTest.php | 421 ++++++++++++++++++ .../Csv/LearningtasksCsvExporterTest.php | 250 +++++++++++ .../ReportCsvDataProviderTest.php | 280 ++++++++++++ .../Factories/ColumnDefinitionFactoryTest.php | 39 +- .../Factories/CsvDataProviderFactoryTest.php | 117 +++++ .../LearningtasksReportCsvExporterTest.php | 417 ----------------- 18 files changed, 1591 insertions(+), 750 deletions(-) create mode 100644 app/Enums/LearningtaskExportType.php create mode 100644 app/Plugins/User/Learningtasks/Contracts/CsvDataProviderInterface.php create mode 100644 app/Plugins/User/Learningtasks/Csv/LearningtasksCsvExporter.php create mode 100644 app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php create mode 100644 app/Plugins/User/Learningtasks/Factories/CsvDataProviderFactory.php delete mode 100644 app/Plugins/User/Learningtasks/LearningtasksReportCsvExporter.php delete mode 100644 app/Traits/Learningtasks/LearningtaskPostTrait.php create mode 100644 tests/Feature/Plugins/User/Learningtasks/LearningtasksPluginExportCsvTest.php create mode 100644 tests/Unit/Plugins/User/Learningtasks/Csv/LearningtasksCsvExporterTest.php create mode 100644 tests/Unit/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProviderTest.php create mode 100644 tests/Unit/Plugins/User/Learningtasks/Factories/CsvDataProviderFactoryTest.php delete mode 100644 tests/Unit/Plugins/User/Learningtasks/LearningtasksReportCsvExporterTest.php diff --git a/app/Enums/LearningtaskExportType.php b/app/Enums/LearningtaskExportType.php new file mode 100644 index 000000000..b76aa6296 --- /dev/null +++ b/app/Enums/LearningtaskExportType.php @@ -0,0 +1,19 @@ + 'レポート提出', + ]; +} diff --git a/app/Plugins/User/Learningtasks/Contracts/CsvDataProviderInterface.php b/app/Plugins/User/Learningtasks/Contracts/CsvDataProviderInterface.php new file mode 100644 index 000000000..ab5617faa --- /dev/null +++ b/app/Plugins/User/Learningtasks/Contracts/CsvDataProviderInterface.php @@ -0,0 +1,36 @@ +getHeaders() で返されるヘッダーの + * 順序に対応した「値の配列」であること。 + * + * @param ColumnDefinitionInterface $column_definition カラム定義 (ヘッダー順序等の参照用) + * @param LearningtasksPosts $post 課題投稿コンテキスト + * @param Page $page ページコンテキスト + * @param string $site_url サイトURL (ファイルURL等生成用) + * @return iterable> データ行の iterable (各行は値の配列) + */ + public function getRows( + ColumnDefinitionInterface $column_definition, + LearningtasksPosts $post, + Page $page, + string $site_url + ): iterable; +} diff --git a/app/Plugins/User/Learningtasks/Csv/LearningtasksCsvExporter.php b/app/Plugins/User/Learningtasks/Csv/LearningtasksCsvExporter.php new file mode 100644 index 000000000..696d71f78 --- /dev/null +++ b/app/Plugins/User/Learningtasks/Csv/LearningtasksCsvExporter.php @@ -0,0 +1,145 @@ +learningtask_post = $learningtask_post; + $this->page = $page; + $this->column_definition = $column_definition; + $this->data_provider = $data_provider; + $this->user_repository = $user_repository; + } + + /** + * CSVエクスポートを実行し、HTTPレスポンスを返す + */ + public function export(string $site_url, string $character_code): StreamedResponse + { + // 1. ヘッダー行を取得 + $header_row = $this->column_definition->getHeaders(); + + // 2. ファイル名生成 + $filename = FileUtils::toValidFilename($this->learningtask_post->post_title . '_Export.csv'); + + // 3. レスポンスヘッダー (streamDownload が Content-Disposition を主に設定) + // Content-Type は必要に応じて明示的に指定する + $headers = [ + 'Content-Type' => 'text/csv; charset='. $character_code, + ]; + + // 4. ストリーミング処理のコールバックを定義 + $callback = function() use ($site_url, $character_code, $header_row) { + // 出力ストリーム 'php://output' を書き込みモードで開く + $handle = fopen('php://output', 'w'); + if ($handle === false) { + Log::error("CSV Export Streaming: Failed to open php://output."); + // ここで処理を中断する(例外を投げるなど) + return; + } + + // ロケール設定 (fputcsv の挙動のため念のため) + CsvUtils::setLocale(); + + // 文字コードに応じた処理: BOM 追加 (UTF-8の場合) + if ($character_code === CsvCharacterCode::utf_8) { + // Excel での互換性のため UTF-8 BOM を先頭に書き込む + fwrite($handle, CsvUtils::bom); + } + + // 文字コードに応じた処理: ヘッダー行のエンコーディング変換 + if ($character_code === CsvCharacterCode::sjis_win) { + // ヘッダー行を Shift-JIS に変換 + $header_row = array_map( + fn($value) => mb_convert_encoding((string)$value, CsvCharacterCode::sjis_win, 'UTF-8'), + $header_row + ); + } + // ヘッダー行を CSV として書き込み + fputcsv($handle, $header_row); + // データ行を取得 (DataProvider から iterable で) + $data_rows_iterable = $this->data_provider->getRows( + $this->column_definition, + $this->learningtask_post, + $this->page, + $site_url + ); + + // データ行を一件ずつ処理して出力ストリームに書き込み + foreach ($data_rows_iterable as $row_array) { + // Shift-JIS で出力する場合の変換 + if ($character_code === CsvCharacterCode::sjis_win) { + $row_array = array_map(fn($value) => mb_convert_encoding((string)$value, 'SJIS-win', 'UTF-8'), $row_array); + } + + // RFC4180 準拠: fputcsv は基本的なダブルクォートのエスケープは行うが、 + // 改行コード等の扱いでより厳密な処理が必要な場合は、自前でエスケープ処理を追加検討。 + // (CsvUtils::getResponseCsvData にあった str_replace('"', '""', ...) の処理は fputcsv が行う) + fputcsv($handle, $row_array); + } + // php://output は fclose 不要 + }; + + // 5. ストリーミングダウンロードレスポンスを生成して返す + return response()->streamDownload($callback, $filename, $headers); + } + + /** + * ユーザーがCSVエクスポート可能か判定 + * + * @param User $user + * @return bool + */ + public function canExport(User $user): bool + { + if ($user->can('role_article_admin')) { + return true; + } + $teachers = $this->user_repository->getTeachers($this->learningtask_post, $this->page); + if ($teachers->contains('id', $user->id)) { + return true; + } + return false; + } +} diff --git a/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php b/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php new file mode 100644 index 000000000..ba611b3e6 --- /dev/null +++ b/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php @@ -0,0 +1,177 @@ +user_repository = $user_repository; + } + + /** + * レポート課題のCSVデータ行を生成して yield する + * (CsvDataProviderInterface の実装) + * + * @param ColumnDefinitionInterface $column_definition カラム定義 + * @param LearningtasksPosts $post 課題投稿コンテキスト + * @param Page $page ページコンテキスト + * @param string $site_url サイトURL + * @return Generator> Generator オブジェクトを返す + */ + public function getRows( + ColumnDefinitionInterface $column_definition, + LearningtasksPosts $post, + Page $page, + string $site_url + ): Generator + { + // 1. ヘッダーを取得 (順序の参照用に内部で使う) + $header_columns = $column_definition->getHeaders(); + if (empty($header_columns)) { + // ヘッダーがなければ何も yield しない + return; + } + + // 2. 対象学生を取得 + $students = $this->user_repository->getStudents($post, $page); + if ($students->isEmpty()){ + // 対象がいなければ何も yield しない + return; + } + + // 3. 関連ステータスを一括取得・グループ化 + $statuses_by_user = $this->fetchAllStatusesGroupedByUser($students, $post); + + // 4. 学生ごとにループして行データを yield + foreach ($students as $student) { + $student_statuses = $statuses_by_user->get($student->id, collect()); + $submission_eval_pair = $this->findLastSubmissionAndEvaluation($student_statuses); + $submission_count = $student_statuses->where('task_status', 1)->count(); + + // 一行分のデータを生成 (ヘルパーメソッド利用) + $row_values = $this->generateRowForStudent( + $student, + $submission_eval_pair['last_submission'], + $submission_eval_pair['last_evaluation'], + $submission_count, + $header_columns, // 要求ヘッダーリスト + $site_url + ); + // 配列に追加する代わりに yield で返す + yield $row_values; + } + } + + /** + * 対象学生全員の関連ステータス(提出・評価)を一括取得し、ユーザーIDでグループ化する + * + * @param Collection $students 対象学生のコレクション + * @param LearningtasksPosts $post 課題投稿コンテキスト + * @return Collection グループ化されたステータスのコレクション + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + private function fetchAllStatusesGroupedByUser(Collection $students, LearningtasksPosts $post): Collection + { + return LearningtasksUsersStatuses::where('post_id', $post->id) + ->whereIn('task_status', [1, 2]) + ->whereIn('user_id', $students->pluck('id')) + ->orderBy('id', 'desc') + ->get() + ->groupBy('user_id'); + } + + /** + * 学生一人のステータスコレクションから、最新の提出と対応する評価を見つける + * + * @param Collection $student_statuses 学生のステータスコレクション + * @return array 最新の提出と評価を含む配列 + */ + private function findLastSubmissionAndEvaluation(Collection $student_statuses): array + { + $student_submissions = $student_statuses->where('task_status', 1); + $student_evaluations = $student_statuses->where('task_status', 2); + $last_submission = $student_submissions->first(); + $last_evaluation = null; + if ($last_submission && $student_submissions->count() === $student_evaluations->count()) { + $last_evaluation = $student_evaluations->first(); + } + return ['last_submission' => $last_submission, 'last_evaluation' => $last_evaluation]; + } + + /** + * 学生一人分のCSV行データを生成 (データ生成マップ利用) + */ + private function generateRowForStudent( + User $student, + ?LearningtasksUsersStatuses $last_submission, + ?LearningtasksUsersStatuses $last_evaluation, + int $submit_count, + array $required_headers, + string $site_url + ): array { + $row_values = []; + $data_generators = $this->getColumnDataGenerators( + $student, $last_submission, $last_evaluation, $submit_count, $site_url + ); + foreach ($required_headers as $header) { + if (isset($data_generators[$header])) { + $row_values[] = $data_generators[$header](); + } else { + Log::warning("ReportCsvDataProvider: Unknown header '{$header}' requested."); + $row_values[] = null; + } + } + return $row_values; // 値のみの配列 + } + + /** + * 各カラムのデータ生成ロジック(クロージャ)を連想配列で返すヘルパー + */ + private function getColumnDataGenerators( + User $student, + ?LearningtasksUsersStatuses $last_submission, + ?LearningtasksUsersStatuses $last_evaluation, + int $submit_count, + string $site_url + ): array { + return [ + 'ログインID' => fn() => $student->userid, + 'ユーザ名' => fn() => $student->name, + '提出日時' => fn() => optional($last_submission)->created_at, + '提出回数' => fn() => $submit_count, + '本文' => fn() => optional($last_submission)->comment, + 'ファイルURL' => function() use ($last_submission, $site_url) { + $upload_id = optional($last_submission)->upload_id; + return $upload_id ? $site_url . '/file/' . $upload_id : null; + }, + '評価' => fn() => optional($last_evaluation)->grade, + '評価コメント' => fn() => optional($last_evaluation)->comment, + ]; + } +} diff --git a/app/Plugins/User/Learningtasks/Factories/ColumnDefinitionFactory.php b/app/Plugins/User/Learningtasks/Factories/ColumnDefinitionFactory.php index ec99001b7..5f41a9d3b 100644 --- a/app/Plugins/User/Learningtasks/Factories/ColumnDefinitionFactory.php +++ b/app/Plugins/User/Learningtasks/Factories/ColumnDefinitionFactory.php @@ -2,6 +2,7 @@ namespace App\Plugins\User\Learningtasks\Factories; +use App\Enums\LearningtaskExportType; use App\Enums\LearningtaskImportType; use App\Enums\LearningtaskUseFunction; use App\Models\User\Learningtasks\LearningtasksPosts; @@ -12,38 +13,36 @@ use InvalidArgumentException; /** - * インポートタイプに基づいて ColumnDefinition インスタンスを生成する Factory クラス + * オペレーションタイプに基づいて ColumnDefinition インスタンスを生成する Factory クラス */ class ColumnDefinitionFactory { /** - * 指定されたインポートタイプに対応する ColumnDefinition オブジェクトを生成する。 + * 指定されたオペレーションタイプに対応する ColumnDefinition オブジェクトを生成する。 * - * @param string $import_type インポート種別を示す文字列 ('report', 'exam' など) - * @param LearningtasksPosts $post カラム定義のコンテキストとなる課題投稿オブジェクト + * @param string $type オペレーション種別を示す文字列 ('report', 'exam' など) + * @param LearningtaskSettingChecker $checker 設定有効性を判定するサービス * @return ColumnDefinitionInterface 生成された ColumnDefinition 実装インスタンス - * @throws InvalidArgumentException 未知のインポートタイプが指定された場合 + * @throws InvalidArgumentException 未知のオペレーションタイプが指定された場合 */ - public function make(string $import_type, LearningtasksPosts $post): ColumnDefinitionInterface + public function make(string $type, LearningtaskSettingChecker $checker): ColumnDefinitionInterface { - // ColumnDefinition (例: Report) は SettingChecker を必要とし、 - // SettingChecker は $post を必要とするため、ここでまず SettingChecker を生成する。 - $setting_checker = new LearningtaskSettingChecker($post); - - // インポートタイプに応じて適切な ColumnDefinition を生成 - switch ($import_type) { + // タイプに応じて適切な ColumnDefinition を生成 + switch ($type) { case LearningtaskImportType::report: // レポート評価設定が有効かチェック - if (!$setting_checker->isEnabled(LearningtaskUseFunction::use_report_evaluate)) { + if (!$checker->isEnabled(LearningtaskUseFunction::use_report_evaluate)) { // 無効なら専用例外をスロー throw new FeatureDisabledException("この課題ではレポート評価機能が有効になっていません。"); } // 有効ならインスタンスを返す (SettingChecker は渡す必要がある) - return new LearningtaskReportColumnDefinition($setting_checker); + return new LearningtaskReportColumnDefinition($checker); + case LearningtaskExportType::report: + return new LearningtaskReportColumnDefinition($checker); // case 'exam': タイプが追加された場合の例 (実装クラスはまだない) - // return new LearningtaskExamColumnDefinition($setting_checker); // 将来追加 + // return new LearningtaskExamColumnDefinition($checker); // 将来追加 default: - throw new InvalidArgumentException("未知のカラム定義タイプ(インポートタイプ)です: {$import_type}"); + throw new InvalidArgumentException("未知のカラム定義タイプ(インポートタイプ)です: {$type}"); } } } diff --git a/app/Plugins/User/Learningtasks/Factories/CsvDataProviderFactory.php b/app/Plugins/User/Learningtasks/Factories/CsvDataProviderFactory.php new file mode 100644 index 000000000..f74452fce --- /dev/null +++ b/app/Plugins/User/Learningtasks/Factories/CsvDataProviderFactory.php @@ -0,0 +1,39 @@ +getPost($post_id); - if (empty($learning_post->id)) { - return $this->viewError("404_inframe", null, 'post_idがありません。'); - } + // モデル取得 + $page = $this->page; + // SettingChecker や DataProvider がリレーションを使う可能性を考慮し Eager Load + $post = LearningtasksPosts::with(['learningtask', 'post_settings'])->findOrFail($post_id); - $exporter = new LearningtasksReportCsvExporter($learning_post->id, $page_id); + // バリデーション (文字コードのみ) + $validated = $request->validate([ + 'export_type' => ['required', Rule::in(LearningtaskExportType::getMemberKeys())], + 'character_code' => ['required', Rule::in(CsvCharacterCode::getMemberKeys())], + ]); + $character_code = $validated['character_code']; + $export_type = $validated['export_type']; - // 教員か管理者のみダウンロード可能 + try { + // 依存性を解決/生成 (Factory利用) + $user_repository = app(LearningtaskUserRepository::class); + $column_definition_factory = app(ColumnDefinitionFactory::class); + $data_provider_factory = app(CsvDataProviderFactory::class); + $setting_checker = new LearningtaskSettingChecker($post); + + // Factory を使って ColumnDefinition と DataProvider を取得 + // ColumnDefinitionFactory が内部で SettingChecker を使う + $column_definition = $column_definition_factory->make($export_type, $setting_checker); + $data_provider = $data_provider_factory->make($export_type); + // Exporter 生成 (汎用 Exporter を使用) + $exporter = new LearningtasksCsvExporter( + $post, + $page, + $column_definition, + $data_provider, + $user_repository + ); + } catch (InvalidArgumentException $e) { + // Factory で未知のタイプが指定された場合など + Log::warning("CSV Export - Invalid argument for factory: " . $e->getMessage()); + return back()->with('error', '無効なエクスポートタイプが指定されたか、処理の準備に失敗しました。'); + } catch (Exception $e) { + Log::error("CSV Export - Service instantiation failed for post ID {$post->id}: " . $e->getMessage()); + return back()->with('error', 'エクスポート処理の準備に失敗しました。'); + } + + // 権限チェック if (!$exporter->canExport(Auth::user())) { - return $this->viewError("403_inframe", null, 'CSVエクスポートの権限がありません。'); + Log::error("CSV Export - Permission check failed for post ID {$post->id} by user ID " . Auth::id()); + abort(403, 'CSVファイルをエクスポートする権限がありません。'); } - // CSV出力 - return $exporter->export(url('/'), $request->character_code); + // エクスポート実行 + try { + $site_url = url('/'); + return $exporter->export($site_url, $character_code); + } catch (Exception $e) { + Log::error("CSV Export - Export process failed for post ID {$post->id}: " . $e->getMessage() . "\n" . $e->getTraceAsString()); + return back()->with('error', 'CSVエクスポート処理中にエラーが発生しました。'); + } } /** diff --git a/app/Plugins/User/Learningtasks/LearningtasksReportCsvExporter.php b/app/Plugins/User/Learningtasks/LearningtasksReportCsvExporter.php deleted file mode 100644 index db239b25a..000000000 --- a/app/Plugins/User/Learningtasks/LearningtasksReportCsvExporter.php +++ /dev/null @@ -1,159 +0,0 @@ -learningtask_post = LearningtasksPosts::findOrFail($learningtask_post_id); - $this->page = Page::findOrFail($page_id); - } - - /** - * CSVエクスポート - * @param string $site_url サイトURL - * @param string $character_code 文字コード - * @return \Illuminate\Http\Response CSVレスポンス - */ - public function export($site_url, $character_code): \Illuminate\Http\Response - { - $header = $this->getHeaderColumns(); - $rows = $this->getRows($site_url); - $csv_data = array_merge([$header], $rows); - - $filename = FileUtils::toValidFilename($this->learningtask_post->post_title . '_レポート.csv'); - $response_headers = [ - 'Content-Type' => 'text/csv', - 'Content-Disposition' => 'attachment; filename="'.$filename.'"', - ]; - - // データ - $csv_data = CsvUtils::getResponseCsvData($csv_data, $character_code); - - return response()->make($csv_data, 200, $response_headers); - } - - /** - * ヘッダーの項目を取得する - * @return array ヘッダーの項目 - */ - public function getHeaderColumns(): array - { - $header_columns = ['ログインID', 'ユーザ名', '提出日時', '提出回数']; - - // 本文 - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_comment)) { - $header_columns[] = '本文'; - } - - // 提出ファイルのURL - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_file)) { - $header_columns[] = 'ファイルURL'; - } - - // 評価 - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_evaluate)) { - $header_columns[] = '評価'; - } - - // 評価コメント - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_evaluate_comment)) { - $header_columns[] = '評価コメント'; - } - - return $header_columns; - } - - /** - * 行データを取得する - * @param string $site_url サイトURL - * @return array 行データ - */ - public function getRows(string $site_url): array - { - $rows = []; - - $students = $this->fetchStudentUsers(); - $submits = LearningtasksUsersStatuses::where('post_id', $this->learningtask_post->id) - ->where('task_status', 1) // 提出 - ->get(); - $evaluations = LearningtasksUsersStatuses::where('post_id', $this->learningtask_post->id) - ->where('task_status', 2) // 評価 - ->get(); - - foreach ($students as $student) { - $row = [ - 'ログインID' => $student->userid, - 'ユーザ名' => $student->name, - ]; - - $student_submits = $submits->where('user_id', $student->id)->sortByDesc('id'); - $student_evaluations = $evaluations->where('user_id', $student->id)->sortByDesc('id'); - - // 最後の提出と評価の組み合わせを出力する - $last_submit = $student_submits->first(); - // 提出と評価で数が合わないということは、最後の提出の評価がまだされていないということ - $last_evaluation = null; - if ($student_submits->count() === $student_evaluations->count()) { - $last_evaluation = $student_evaluations->first(); - } - $row['提出日時'] = optional($last_submit)->created_at; - $row['提出回数'] = $student_submits->count(); - - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_comment)) { - $row['本文'] = optional($last_submit)->comment; - } - - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_file)) { - $row['ファイルURL'] = optional($last_submit)->upload_id ? $site_url . '/file/' . optional($last_submit)->upload_id : null; - } - - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_evaluate)) { - $row['評価'] = optional($last_evaluation)->grade; - } - - if ($this->isSettingEnabled(LearningtaskUseFunction::use_report_evaluate_comment)) { - $row['評価コメント'] = optional($last_evaluation)->comment; - } - - $rows[] = $row; - } - - return $rows; - } - - /** - * ユーザーがCSVエクスポート可能かどうかを判定する - * @param User $user ユーザ - * @return bool エクスポート可能な場合はtrue、そうでない場合はfalse - */ - public function canExport(User $user): bool - { - // 管理者であればエクスポート可能 - if ($user->can('role_article_admin')) { - return true; - } - - // 教員であればエクスポート可能 - if ($this->fetchTeacherUsers()->contains($user)) { - return true; - } - - return false; - } -} diff --git a/app/Plugins/User/Learningtasks/Providers/LearningtasksServiceProvider.php b/app/Plugins/User/Learningtasks/Providers/LearningtasksServiceProvider.php index 46357d8a0..8337e42e1 100644 --- a/app/Plugins/User/Learningtasks/Providers/LearningtasksServiceProvider.php +++ b/app/Plugins/User/Learningtasks/Providers/LearningtasksServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Support\ServiceProvider; use App\Plugins\User\Learningtasks\Contracts\RowProcessorInterface; +use App\Plugins\User\Learningtasks\DataProviders\ReportCsvDataProvider; use App\Plugins\User\Learningtasks\Handlers\ReportExceptionHandler; use App\Plugins\User\Learningtasks\Services\LearningtaskEvaluationRowProcessor; use App\Plugins\User\Learningtasks\Repositories\LearningtaskUserRepository; @@ -25,6 +26,7 @@ public function register() LearningtaskEvaluationRowProcessor::class, ); $this->app->bind(ReportExceptionHandler::class); + $this->app->bind(ReportCsvDataProvider::class); // ステートレスなリポジトリをシングルトンとして登録 $this->app->singleton(LearningtaskUserRepository::class); diff --git a/app/Traits/Learningtasks/LearningtaskPostTrait.php b/app/Traits/Learningtasks/LearningtaskPostTrait.php deleted file mode 100644 index e57279ff0..000000000 --- a/app/Traits/Learningtasks/LearningtaskPostTrait.php +++ /dev/null @@ -1,130 +0,0 @@ -learningtask_post->post_settings->where('use_function', $setting_name)->first(); - if ($setting) { - return $setting->value === 'on'; - } - - // 科目の設定がない場合は、課題管理の設定を参照する - $setting = $this->learningtask_post->learningtask->learningtask_settings->where('use_function', $setting_name)->first(); - if ($setting) { - return $setting->value === 'on'; - } - return false; - } - - /** - * 科目に設定されている受講生を取得する - * @return \Illuminate\Database\Eloquent\Collection - */ - public function fetchStudentUsers(): \Illuminate\Database\Eloquent\Collection - { - // student_join_flag = 0: 未使用 - // student_join_flag = 1: 未使用 - // student_join_flag = 2: 配置ページのメンバーシップユーザ全員 - // student_join_flag = 3: 配置ページのメンバーシップユーザから選ぶ - - // 配置ページのメンバーシップユーザ全員 - if ($this->learningtask_post->student_join_flag == 2) { - return $this->fetchMembershipUsers(RoleName::student); - } - - return $this->fetchLearningtaskPostStudents(); - } - - /** - * 科目に設定されている教員を取得する - * @return \Illuminate\Database\Eloquent\Collection - */ - public function fetchTeacherUsers(): \Illuminate\Database\Eloquent\Collection - { - // teacher_join_flag = 0: 未使用 - // teacher_join_flag = 1: 未使用 - // teacher_join_flag = 2: 配置ページのメンバーシップユーザ全員 - // teacher_join_flag = 3: 配置ページのメンバーシップユーザから選ぶ - - // 配置ページのメンバーシップユーザ全員 - if ($this->learningtask_post->teacher_join_flag == 2) { - return $this->fetchMembershipUsers(RoleName::teacher); - } - - return $this->fetchLearningtaskPostTeachers(); - } - - /** - * 配置ページのメンバーシップユーザの受講生を全員を取得する - * @param string $role_name 役割名 - * @return \Illuminate\Database\Eloquent\Collection - */ - private function fetchMembershipUsers(string $role_name): \Illuminate\Database\Eloquent\Collection - { - // メンバーシップ設定は親ページから継承している場合がある - $membership_page = $this->page->getInheritMembershipPage(); - $group_ids = PageRole::select('group_id') - ->where('page_id', optional($membership_page)->id) - ->groupBy('group_id') - ->get() - ->pluck('group_id'); - return User::query() - ->whereHas('group_users', function ($query) use ($group_ids) { - $query->whereIn('group_id', $group_ids); - }) - ->whereExists(function ($query) use ($role_name) { - $query->select(\DB::raw(1)) - ->from('users_roles') - ->whereRaw('users_roles.users_id = users.id') - ->where('users_roles.target', '=', 'original_role') - ->where('users_roles.role_name', '=', $role_name); - }) - ->orderBy('users.id') - ->get(); - } - - /** - * 科目に設定された受講生を取得する - * @return \Illuminate\Database\Eloquent\Collection - */ - private function fetchLearningtaskPostStudents(): \Illuminate\Database\Eloquent\Collection - { - return User::whereIn('id', $this->learningtask_post->students->pluck('user_id'))->orderBy('id')->get(); - } - - /** - * 科目に設定された教員を取得する - * @return \Illuminate\Database\Eloquent\Collection - */ - private function fetchLearningtaskPostTeachers(): \Illuminate\Database\Eloquent\Collection - { - return User::whereIn('id', $this->learningtask_post->teachers->pluck('user_id'))->orderBy('id')->get(); - } -} diff --git a/config/app.php b/config/app.php index 3f8c73869..350560431 100644 --- a/config/app.php +++ b/config/app.php @@ -277,6 +277,7 @@ 'RoleName' => \App\Enums\RoleName::class, 'LearningtaskUserJoinFlag' => \App\Enums\LearningtaskUserJoinFlag::class, 'LearningtasksExaminationColumn' => \App\Enums\LearningtasksExaminationColumn::class, + 'LearningtaskExportType' => \App\Enums\LearningtaskExportType::class, 'LearningtaskImportType' => \App\Enums\LearningtaskImportType::class, 'CounterDesignType' => \App\Enums\CounterDesignType::class, 'BaseLoginRedirectPage' => \App\Enums\BaseLoginRedirectPage::class, diff --git a/resources/views/plugins/user/learningtasks/default/learningtasks_show.blade.php b/resources/views/plugins/user/learningtasks/default/learningtasks_show.blade.php index 34b46d28d..b47f0c8c7 100644 --- a/resources/views/plugins/user/learningtasks/default/learningtasks_show.blade.php +++ b/resources/views/plugins/user/learningtasks/default/learningtasks_show.blade.php @@ -52,9 +52,10 @@
{{-- CSV出力 --}}
- +
-
+ +