diff --git a/app/Enums/LearningtaskUseFunction.php b/app/Enums/LearningtaskUseFunction.php index ceace37d0..f68242616 100644 --- a/app/Enums/LearningtaskUseFunction.php +++ b/app/Enums/LearningtaskUseFunction.php @@ -43,6 +43,8 @@ final class LearningtaskUseFunction extends EnumsBase const use_report_reference_mail = 'use_'.self::report.'_reference_mail'; // [表示方法] const use_report_status_collapse = 'use_'.self::report.'_status_collapse'; + const use_report_show_word_count = 'use_'.self::report.'_show_word_count'; + const use_report_show_char_count = 'use_'.self::report.'_show_char_count'; // --- 試験設定 // [利用する試験提出機能] @@ -110,6 +112,8 @@ final class LearningtaskUseFunction extends EnumsBase self::use_report_reference_mail => 'メール送信(受講者宛)', // 表示方法 self::use_report_status_collapse => '履歴を開閉する', + self::use_report_show_word_count => '文字数を表示する', + self::use_report_show_char_count => '字数を表示する', // --- 試験設定 // 利用する試験提出機能 self::use_examination => '提出', diff --git a/app/Models/User/Learningtasks/LearningtasksUsersStatuses.php b/app/Models/User/Learningtasks/LearningtasksUsersStatuses.php index c551d96ec..430cd56b5 100644 --- a/app/Models/User/Learningtasks/LearningtasksUsersStatuses.php +++ b/app/Models/User/Learningtasks/LearningtasksUsersStatuses.php @@ -192,4 +192,23 @@ public function upload() // withDefault() を指定しておくことで、Uploads がないときに空のオブジェクトが返ってくるので、null po 防止。 return $this->hasOne(Uploads::class, 'id', 'upload_id')->withDefault(); } + + /** + * 単語数を取得する + */ + public function getWordCountAttribute() + { + // マルチバイト文字などは適切な値を取得できない + // 必要になれば形態素解析などを行う必要がある + return $this->comment ? str_word_count($this->comment) : 0; + } + + /** + * 字数を取得する + * 文字数は、全角文字も半角文字も1文字としてカウントする。 + */ + public function getCharCountAttribute() + { + return $this->comment ? mb_strlen($this->comment) : 0; + } } diff --git a/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php b/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php index 32c0cc555..d1e513c35 100644 --- a/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php +++ b/app/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProvider.php @@ -171,6 +171,8 @@ private function getColumnDataGenerators( }, '評価' => fn() => optional($last_evaluation)->grade, '評価コメント' => fn() => optional($last_evaluation)->comment, + '単語数' => fn() => optional($last_submission)->word_count, + '字数' => fn() => optional($last_submission)->char_count, ]; } } diff --git a/app/Plugins/User/Learningtasks/Services/LearningtaskReportColumnDefinition.php b/app/Plugins/User/Learningtasks/Services/LearningtaskReportColumnDefinition.php index 4fadcc79f..5ee9bc09c 100644 --- a/app/Plugins/User/Learningtasks/Services/LearningtaskReportColumnDefinition.php +++ b/app/Plugins/User/Learningtasks/Services/LearningtaskReportColumnDefinition.php @@ -28,6 +28,8 @@ class LearningtaskReportColumnDefinition implements ColumnDefinitionInterface // 'ファイルURL' => 'file_url', '評価' => 'grade', '評価コメント' => 'comment', + '単語数' => 'word_count', + '字数' => 'char_count', ]; /** @@ -58,6 +60,12 @@ public function getHeaders(): array if ($this->setting_checker->isEnabled(LearningtaskUseFunction::use_report_comment)) { $header_columns[] = '本文'; } + if ($this->setting_checker->isEnabled(LearningtaskUseFunction::use_report_show_word_count)) { + $header_columns[] = '単語数'; + } + if ($this->setting_checker->isEnabled(LearningtaskUseFunction::use_report_show_char_count)) { + $header_columns[] = '字数'; + } if ($this->setting_checker->isEnabled(LearningtaskUseFunction::use_report_file)) { $header_columns[] = 'ファイルURL'; } diff --git a/resources/views/plugins/user/learningtasks/default/learningtasks_edit_learningtasks.blade.php b/resources/views/plugins/user/learningtasks/default/learningtasks_edit_learningtasks.blade.php index 175c5d190..ee62e8abd 100644 --- a/resources/views/plugins/user/learningtasks/default/learningtasks_edit_learningtasks.blade.php +++ b/resources/views/plugins/user/learningtasks/default/learningtasks_edit_learningtasks.blade.php @@ -340,11 +340,20 @@ class="custom-control-input"
-
-
+
+
getFunction('use_report_status_collapse')) == 'on') checked=checked @endif>
+
+ getFunction('use_report_show_word_count')) == 'on') checked=checked @endif> + +
+
+ getFunction('use_report_show_char_count')) == 'on') checked=checked @endif> + +
+ 単語数の表示は、日本語などのマルチバイト文字に対応していません。
diff --git a/resources/views/plugins/user/learningtasks/default/learningtasks_edit_report.blade.php b/resources/views/plugins/user/learningtasks/default/learningtasks_edit_report.blade.php index a30fbdf19..fa3e615c7 100644 --- a/resources/views/plugins/user/learningtasks/default/learningtasks_edit_report.blade.php +++ b/resources/views/plugins/user/learningtasks/default/learningtasks_edit_report.blade.php @@ -231,11 +231,20 @@ class="custom-control-input"
-
-
+
+
getFunction('use_report_status_collapse', true)) == 'on') checked=checked @endif>
+
+ getFunction('use_report_show_word_count', true)) == 'on') checked=checked @endif> + +
+
+ getFunction('use_report_show_char_count', true)) == 'on') checked=checked @endif> + +
+ 単語数の表示は、日本語などのマルチバイト文字に対応していません。
diff --git a/resources/views/plugins/user/learningtasks/default/learningtasks_show_report_status.blade.php b/resources/views/plugins/user/learningtasks/default/learningtasks_show_report_status.blade.php index 9fe8ae583..9bb9f69e9 100644 --- a/resources/views/plugins/user/learningtasks/default/learningtasks_show_report_status.blade.php +++ b/resources/views/plugins/user/learningtasks/default/learningtasks_show_report_status.blade.php @@ -31,7 +31,28 @@ @if ($tool->isUseFunction($user_status->task_status, 'comment')) コメント - {!!nl2br(e($user_status->comment))!!} + {!!nl2br(e($user_status->comment))!!} + {{-- 文字数、単語数の表示 --}} + @php + $should_show_word_count = $tool->isUseFunction($user_status->task_status, 'show_word_count'); + $should_show_char_count = $tool->isUseFunction($user_status->task_status, 'show_char_count'); + $word_count = $user_status->word_count; + $char_count = $user_status->char_count; + @endphp + @if ($should_show_word_count || $should_show_char_count) +
+ @if ($should_show_word_count) + {{ $word_count }}単語 + @endif + @if ($should_show_word_count && $should_show_char_count) + / + @endif + @if ($should_show_char_count) + {{ $char_count }}文字 + @endif +
+ @endif + @endif diff --git a/tests/Unit/Models/User/Learningtasks/LearningtasksUsersStatusesTest.php b/tests/Unit/Models/User/Learningtasks/LearningtasksUsersStatusesTest.php new file mode 100644 index 000000000..ee853f0d4 --- /dev/null +++ b/tests/Unit/Models/User/Learningtasks/LearningtasksUsersStatusesTest.php @@ -0,0 +1,54 @@ + null]); + $this->assertSame(0, $model->word_count); + $model = new LearningtasksUsersStatuses(['comment' => '']); + $this->assertSame(0, $model->word_count); + } + + /** + * commentの内容に応じてword_countが正しい値を返すことをテスト + */ + public function testWordCountReturnsExpectedValue(): void + { + $model = new LearningtasksUsersStatuses(['comment' => 'This is a test']); + $this->assertSame(4, $model->word_count); + } + + /** + * commentがnullまたは空文字の場合、char_countは0を返すことをテスト + */ + public function testCharCountIsZeroWhenCommentIsNullOrEmpty(): void + { + $model = new LearningtasksUsersStatuses(['comment' => null]); + $this->assertSame(0, $model->char_count); + $model = new LearningtasksUsersStatuses(['comment' => '']); + $this->assertSame(0, $model->char_count); + } + + /** + * commentの内容に応じてchar_countが正しい値を返すことをテスト + */ + public function testCharCountReturnsExpectedValue(): void + { + $model = new LearningtasksUsersStatuses(['comment' => 'abc']); + $this->assertSame(3, $model->char_count); + $model = new LearningtasksUsersStatuses(['comment' => 'テストword']); + $this->assertSame(7, $model->char_count); + } +} diff --git a/tests/Unit/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProviderTest.php b/tests/Unit/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProviderTest.php index b244eef83..db68f98c8 100644 --- a/tests/Unit/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProviderTest.php +++ b/tests/Unit/Plugins/User/Learningtasks/DataProviders/ReportCsvDataProviderTest.php @@ -277,4 +277,57 @@ public function getRowsYieldsDataWithEmptyFieldsWhenNoStatuses(): void ]; $this->assertEquals($expected_row_data, $result_rows_array[0], '提出/評価がない学生のデータが期待通りであること'); } + + /** + * 単語数・字数カラムが有効な場合に正しい値が出力されるテスト + * @test + * @covers ::getRows + * @group learningtasks + * @group learningtasks-dataprovider + */ + public function getRowsYieldsWordAndCharCountColumns(): void + { + // Arrange + $student = User::factory()->create(['userid' => 's003', 'name' => '学生 丙']); + $submission_comment = 'word テスト 123'; // 半角2単語+全角1単語+数字1単語 → str_word_count=3, mb_strlen=11 + $submission_created_at_str = now()->subHour()->toDateTimeString(); + $submission = LearningtasksUsersStatuses::factory()->create([ + 'post_id' => $this->post->id, 'user_id' => $student->id, 'task_status' => 1, + 'comment' => $submission_comment, 'upload_id' => 888, 'created_at' => $submission_created_at_str, + ]); + // 評価データも追加(値は使わない) + LearningtasksUsersStatuses::factory()->create([ + 'post_id' => $this->post->id, 'user_id' => $student->id, 'task_status' => 2, + 'grade' => '優', 'comment' => '評価コメント', + ]); + + // ヘッダーに単語数・字数を含める + $headers = ['ログインID', 'ユーザ名', '提出日時', '提出回数', '単語数', '字数']; + $this->mock_column_definition->shouldReceive('getHeaders')->once()->andReturn($headers); + $this->mock_user_repository->shouldReceive('getStudents') + ->with($this->post, $this->page) + ->once() + ->andReturn(new EloquentCollection([$student])); + + // Act + $result_iterable = $this->data_provider->getRows( + $this->mock_column_definition, + $this->post, + $this->page, + $this->site_url + ); + $result_rows_array = iterator_to_array($result_iterable); + + // Assert + $this->assertCount(1, $result_rows_array, '学生1名分のデータが yield されるべき'); + $expected_row = [ + $student->userid, + $student->name, + $submission_created_at_str, + '1', // 提出回数 + str_word_count($submission_comment), // 単語数(PHPのstr_word_count仕様) + mb_strlen($submission_comment), // 字数(全角・半角問わず) + ]; + $this->assertEquals($expected_row, $result_rows_array[0], '単語数・字数カラムの値が正しく出力されること'); + } }