diff --git a/.upgradenotes/MDL-83891-2025061310444666.yml b/.upgradenotes/MDL-83891-2025061310444666.yml new file mode 100644 index 00000000000..94576bf8eec --- /dev/null +++ b/.upgradenotes/MDL-83891-2025061310444666.yml @@ -0,0 +1,8 @@ +issueNumber: MDL-83891 +notes: + mod_data: + - message: >- + New get_approval_requested(), get_all_entries(), + filter_entries_by_user(), filter_entries_by_approval() and + get_comments() functions have been added to mod_data manager class. + type: improved diff --git a/course/format/tests/local/overview/overviewfactory_test.php b/course/format/tests/local/overview/overviewfactory_test.php index 5982887f03c..7fd41381da2 100644 --- a/course/format/tests/local/overview/overviewfactory_test.php +++ b/course/format/tests/local/overview/overviewfactory_test.php @@ -101,7 +101,7 @@ final class overviewfactory_test extends \advanced_testcase { ], 'data' => [ 'resourcetype' => 'data', - 'expected' => resourceoverview::class, + 'expected' => \mod_data\courseformat\overview::class, ], 'feedback' => [ 'resourcetype' => 'feedback', diff --git a/course/format/tests/output/local/overview/missingoverviewnotice_test.php b/course/format/tests/output/local/overview/missingoverviewnotice_test.php index 2b1022550ee..c8214d66365 100644 --- a/course/format/tests/output/local/overview/missingoverviewnotice_test.php +++ b/course/format/tests/output/local/overview/missingoverviewnotice_test.php @@ -67,7 +67,7 @@ final class missingoverviewnotice_test extends \advanced_testcase { 'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'expectempty' => false], 'book' => ['modname' => 'book', 'expectempty' => false], 'choice' => ['modname' => 'choice', 'expectempty' => false], - 'data' => ['modname' => 'data', 'expectempty' => false], + 'data' => ['modname' => 'data', 'expectempty' => true], 'feedback' => ['modname' => 'feedback', 'expectempty' => true], 'folder' => ['modname' => 'folder', 'expectempty' => false], 'forum' => ['modname' => 'forum', 'expectempty' => false], diff --git a/mod/data/classes/courseformat/overview.php b/mod/data/classes/courseformat/overview.php new file mode 100644 index 00000000000..fa6e42ded8a --- /dev/null +++ b/mod/data/classes/courseformat/overview.php @@ -0,0 +1,233 @@ +. + +namespace mod_data\courseformat; + +use core_calendar\output\humandate; +use cm_info; +use core_courseformat\local\overview\overviewitem; +use core\output\action_link; +use core\output\local\properties\text_align; +use core\output\local\properties\button; +use core\url; +use mod_data\dates; +use mod_data\manager; + +/** + * Database activity overview integration. + * + * @package mod_data + * @copyright 2025 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview extends \core_courseformat\activityoverviewbase { + + /** @var manager database activity manager. */ + private $manager; + + /** @var bool whether the user can see pendent entries or not. */ + private $canviewall; + + /** + * Constructor. + * + * @param cm_info $cm the course module instance. + * @param \core\output\renderer_helper $rendererhelper the renderer helper. + * @param \core_string_manager $stringmanager the string manager. + */ + public function __construct( + cm_info $cm, + /** @var \core\output\renderer_helper $rendererhelper the renderer helper */ + protected readonly \core\output\renderer_helper $rendererhelper, + /** @var \core_string_manager $stringmanager the string manager */ + protected readonly \core_string_manager $stringmanager, + ) { + parent::__construct($cm); + + $this->manager = manager::create_from_coursemodule($cm); + $this->canviewall = has_capability('mod/data:approve', $cm->context); + } + + #[\Override] + public function get_due_date_overview(): ?overviewitem { + global $USER; + + $dates = new dates($this->cm, $USER->id); + $duedate = $dates->get_due_date(); + $name = $this->stringmanager->get_string('duedate', 'data'); + + if (empty($duedate)) { + return new overviewitem( + name: $name, + value: null, + content: '-', + ); + } + + $content = humandate::create_from_timestamp($duedate); + + return new overviewitem( + name: $name, + value: $duedate, + content: $content, + ); + } + + #[\Override] + public function get_actions_overview(): ?overviewitem { + if (!$this->canviewall) { + return null; + } + + $text = $this->stringmanager->get_string('view', 'moodle'); + $toapprove = 0; + $alertlabel = $this->stringmanager->get_string('numberofentriestoapprove', 'data'); + if ($this->manager->get_approval_requested()) { + // Let's calculate how many entries need to be approved. + $entries = $this->manager->filter_entries_by_approval($this->manager->get_all_entries(), 0); + $toapprove = count($entries); + + $name = $this->stringmanager->get_string('approve', 'data'); + if ($toapprove > 0) { + $renderer = $this->rendererhelper->get_core_renderer(); + $badge = $renderer->notice_badge( + contents: $toapprove, + title: $alertlabel, + ); + $text = $name . $badge; + } + } + + $content = new action_link( + url: new url('/mod/data/view.php', ['id' => $this->cm->id]), + text: $text, + attributes: ['class' => button::SECONDARY_OUTLINE->classes()], + ); + + return new overviewitem( + name: $this->stringmanager->get_string('actions'), + value: $toapprove, + content: $content, + textalign: text_align::CENTER, + alertcount: $toapprove, + alertlabel: $alertlabel, + ); + } + + #[\Override] + public function get_extra_overview_items(): array { + $columns = []; + // Add entry columns for each view. + if ($this->canviewall) { + $columns['totalentries'] = $this->get_extra_entries_overview(); + } else { + $columns['totalentries'] = $this->get_extra_totalentries_overview(); + $columns['myentries'] = $this->get_extra_myentries_overview(); + } + + // Add comments column for all views. + $columns['comments'] = $this->get_extra_comments_overview(); + + return $columns; + } + + /** + * Get the "Total entries" overview item. + * + * @return overviewitem The overview item. + */ + private function get_extra_totalentries_overview(): overviewitem { + $allentries = $this->manager->get_all_entries(); + if ($this->manager->get_approval_requested()) { + $allentries = $this->manager->filter_entries_by_approval($allentries, 1); + } + $totalentries = count($allentries); + + // Add total entries. + return new overviewitem( + name: $this->stringmanager->get_string('totalentries', 'data'), + value: $totalentries, + content: $totalentries, + textalign: text_align::CENTER, + ); + } + + /** + * Get the "My entries" overview item. + * + * @return overviewitem The overview item. + */ + private function get_extra_myentries_overview(): overviewitem { + global $USER; + + $myentries = $this->manager->filter_entries_by_user($this->manager->get_all_entries(), $USER->id); + $totalmyentries = count($myentries); + + return new overviewitem( + name: $this->stringmanager->get_string('myentries', 'data'), + value: $totalmyentries, + content: $totalmyentries, + textalign: text_align::CENTER, + ); + } + + /** + * Get the "Entries" overview item for teachers. + * + * @return overviewitem The overview item. + */ + private function get_extra_entries_overview(): overviewitem { + $allentries = $this->manager->get_all_entries(); + $totalentries = count($allentries); + + // Add total entries. + return new overviewitem( + name: $this->stringmanager->get_string('entries', 'data'), + value: $totalentries, + content: $totalentries, + textalign: text_align::CENTER, + ); + } + + /** + * Get the "Comments" overview item. + * + * @return overviewitem The overview item. + */ + private function get_extra_comments_overview(): overviewitem { + global $CFG; + + // Add comments column for all views. + if (empty($CFG->usecomments) || (empty($this->manager->get_instance()->comments))) { + return new overviewitem( + name: $this->stringmanager->get_string('comments', 'data'), + value: 0, + content: '-', + textalign: text_align::CENTER, + ); + } + + $approved = ($this->canviewall) ? null : 1; + $comments = $this->manager->get_comments(approved: $approved); + $totalcomments = ($comments) ? count($comments) : 0; + return new overviewitem( + name: $this->stringmanager->get_string('comments', 'data'), + value: $totalcomments, + content: $totalcomments, + textalign: text_align::CENTER, + ); + } +} diff --git a/mod/data/classes/dates.php b/mod/data/classes/dates.php index 248bd30f38d..dd227af0f3d 100644 --- a/mod/data/classes/dates.php +++ b/mod/data/classes/dates.php @@ -36,6 +36,12 @@ use core\activity_dates; */ class dates extends activity_dates { + /** @var int|null timeopen the activity opening date */ + private ?int $timeopen; + + /** @var int|null timeclose the activity closing date */ + private ?int $timeclose; + /** * Returns a list of important dates in mod_data * @@ -44,27 +50,42 @@ class dates extends activity_dates { protected function get_dates(): array { $timeopen = $this->cm->customdata['timeavailablefrom'] ?? null; $timeclose = $this->cm->customdata['timeavailableto'] ?? null; + + $this->timeopen = $timeopen ? (int) $timeopen : null; + $this->timeclose = $timeclose ? (int) $timeclose : null; + $now = time(); $dates = []; - if ($timeopen) { - $openlabelid = $timeopen > $now ? 'activitydate:opens' : 'activitydate:opened'; + if ($this->timeopen) { + $openlabelid = $this->timeopen > $now ? 'activitydate:opens' : 'activitydate:opened'; $dates[] = [ 'dataid' => 'timeavailablefrom', 'label' => get_string($openlabelid, 'course'), - 'timestamp' => (int) $timeopen, + 'timestamp' => (int) $this->timeopen, ]; } - if ($timeclose) { - $closelabelid = $timeclose > $now ? 'activitydate:closes' : 'activitydate:closed'; + if ($this->timeclose) { + $closelabelid = $this->timeclose > $now ? 'activitydate:closes' : 'activitydate:closed'; $dates[] = [ 'dataid' => 'timeavailableto', 'label' => get_string($closelabelid, 'course'), - 'timestamp' => (int) $timeclose, + 'timestamp' => (int) $this->timeclose, ]; } return $dates; } + + /** + * Returns the dues date data, if any. + * @return int|null the close timestamp or null if not set. + */ + public function get_due_date(): ?int { + if (!isset($this->timeclose)) { + $this->get_dates(); + } + return $this->timeclose; + } } diff --git a/mod/data/classes/manager.php b/mod/data/classes/manager.php index 05b0d565a6c..fe5c05e9de4 100644 --- a/mod/data/classes/manager.php +++ b/mod/data/classes/manager.php @@ -69,6 +69,16 @@ class manager { /** @var cm_info course_modules record. */ private $cm; + /** @var array the current data records. + * Do not access this attribute directly, use $this->get_all_entries instead + */ + private $_entries = null; + + /** @var array the current data comments. + * Do not access this attribute directly, use $this->get_all_comments instead + */ + private $_comments = null; + /** @var array the current data_fields records. * Do not access this attribute directly, use $this->get_field_records instead */ @@ -233,6 +243,92 @@ class manager { return !empty($this->_fieldrecords); } + /** + * Return the database entries. + * + * @return [] the data records array. + */ + public function get_all_entries(): array { + global $DB; + + if (empty($this->_entries)) { + $this->_entries = $DB->get_records('data_records', ['dataid' => $this->instance->id]); + } + return $this->_entries; + } + + /** + * Return the database given entries filtered by userid. + * + * @param array $entries Entries to filter from. + * @param int $userid User to filter by. Zero for non-filtering. + * + * @return [] the filtered data records array. + */ + public function filter_entries_by_user(array $entries, int $userid = 0): array { + if ($userid > 0) { + $entries = array_filter($entries, function($entry) use ($userid) { + return $entry->userid == $userid; + }); + } + + return $entries; + } + + /** + * Return the database entries filtered by approved and/or userid. + * + * @param array $entries Entries to filter from. + * @param int $approved Approved value to filter by. + * + * @return [] the filtered data records array. + */ + public function filter_entries_by_approval(array $entries, int $approved): array { + return array_filter($entries, function($entry) use ($approved) { + return $entry->approved == $approved; + }); + } + + /** + * Return the database comments filtered by approved entries. + * + * @param ?int $approved Approved value to filter by. Null for not filtering. + * + * @return [] the filtered data comments array or null if there is no comment. + */ + public function get_comments(?int $approved = null): ?array { + + if ($this->_comments) { + return $this->_comments; + } + + $entries = $this->get_all_entries(); + if (!is_null($approved)) { + $entries = $this->filter_entries_by_approval($entries, $approved); + } + + $this->_comments = []; + + // Initilising comment object. + $args = new stdClass; + $args->context = $this->get_context(); + $args->course = $this->cm->get_course(); + $args->cm = $this->cm; + $args->area = 'database_entry'; + $args->component = 'mod_data'; + + foreach ($entries as $entry) { + $args->itemid = $entry->id; + $comment = new \comment($args); + $morecomments = $comment->get_comments(); + if ($morecomments) { + $this->_comments = array_merge($this->_comments, $morecomments); + } + } + + return $this->_comments; + } + /** * Return the database fields. * @@ -312,6 +408,15 @@ class manager { return new template($this, $templatecontent, $options); } + /** + * Return whether the database module requests entries approval or not. + * + * @return int whether the approval is requested or not + */ + public function get_approval_requested(): int { + return $this->get_instance()->approval; + } + /** Check if the user can manage templates on the current context. * * @param int $userid the user id to check ($USER->id if null). diff --git a/mod/data/lang/en/data.php b/mod/data/lang/en/data.php index f9ebf01b5cb..513f4990965 100644 --- a/mod/data/lang/en/data.php +++ b/mod/data/lang/en/data.php @@ -124,6 +124,7 @@ $string['descending'] = 'Descending'; $string['directorynotapreset'] = '{$a->directory} Not a preset: missing files: {$a->missing_files}'; $string['disapprove'] = 'Undo approval'; $string['download'] = 'Download'; +$string['duedate'] = 'Due date'; $string['edit'] = 'Edit'; $string['editcomment'] = 'Edit comment'; $string['editentry'] = 'Edit entry'; @@ -309,6 +310,7 @@ $string['movezipfailed'] = 'Can\'t move zip'; $string['multientry'] = 'Repeated entry'; $string['multimenu'] = 'Menu (Multi-select)'; $string['multipletags'] = 'Multiple tags found! Template not saved'; +$string['myentries'] = 'My entries'; $string['newentry'] = 'New entry'; $string['newfield'] = 'Create a field'; $string['newfield_help'] = 'A field allows the input of data. Each entry in a database activity can have multiple fields of multiple types such as a date field, which allows participants to select a day, month and year from a drop-down menu, a picture field, which allows participants to upload an image file, or a checkbox field, which allows participants to select one or more options. @@ -330,6 +332,7 @@ $string['notinjectivemap'] = 'Not an injective map'; $string['notemplates'] = 'No templates yet'; $string['notopenyet'] = 'Sorry, this activity is not available until {$a}'; $string['number'] = 'Number'; +$string['numberofentriestoapprove'] = 'Entries to approve: {$a}'; $string['numberrssarticles'] = 'Entries in the RSS feed'; $string['numnotapproved'] = 'Pending'; $string['numrecords'] = '{$a} entries'; @@ -447,6 +450,7 @@ $string['text'] = 'Text'; $string['textarea'] = 'Text area'; $string['timeadded'] = 'Time added'; $string['timemodified'] = 'Time modified'; +$string['totalentries'] = 'Total entries'; $string['type'] = 'Field type'; $string['undefinedprocessactionmethod'] = 'No action method defined in Data_Preset to handle action "{$a}".'; $string['unsupportedfields'] = 'Unsupported fields'; @@ -464,6 +468,7 @@ $string['usepredefinedset'] = 'Use predefined set'; $string['usepreset'] = 'Use this preset'; $string['usestandard'] = 'Use a preset'; $string['usestandard_help'] = 'To use a preset available to the whole site, select it from the list. (If you have added a preset to the list using the save as preset feature then you have the option of deleting it.)'; +$string['viewentries'] = 'View entries'; $string['viewfromdate'] = 'Read only from'; $string['viewnavigation'] = 'View mode tertiary navigation'; $string['viewtemplates'] = 'View templates'; diff --git a/mod/data/tests/behat/overview_report.feature b/mod/data/tests/behat/overview_report.feature new file mode 100644 index 00000000000..c67b0bf6e72 --- /dev/null +++ b/mod/data/tests/behat/overview_report.feature @@ -0,0 +1,123 @@ +@mod @mod_data +Feature: Testing overview integration in database activity + In order to summarize the database activity + As a user + I need to be able to see the database activity overview + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@example.com | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | category | enablecompletion | + | Course 1 | C1 | 0 | 1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And the following "activity" exists: + | course | C1 | + | activity | data | + | name | Database activity | + | intro | description | + | idnumber | data1 | + | approval | 1 | + | completion | 1 | + | comments | 1 | + | timeavailableto | ##1 Jan 2040 08:00## | + And the following "activity" exists: + | course | C1 | + | activity | data | + | name | Without comments | + | intro | description | + | idnumber | data2 | + | approval | 1 | + | completion | 1 | + | comments | 1 | + | timeavailableto | ##1 Jan 2040 08:00## | + And the following "activity" exists: + | course | C1 | + | activity | data | + | name | Empty database | + | intro | empty database | + | idnumber | data3 | + | approval | 0 | + | completion | 0 | + | comments | 0 | + And the following "mod_data > fields" exist: + | database | type | name | description | + | data1 | text | Title field | Title field description | + | data1 | text | Short text field | Short text field description | + | data2 | text | Title field | Title field description | + | data2 | text | Short text field | Short text field description | + And the following "mod_data > templates" exist: + | database | name | + | data1 | singletemplate | + | data1 | listtemplate | + | data1 | addtemplate | + | data1 | asearchtemplate | + | data1 | rsstemplate | + | data2 | singletemplate | + | data2 | listtemplate | + | data2 | addtemplate | + | data2 | asearchtemplate | + | data2 | rsstemplate | + And the following "mod_data > entries" exist: + | database | user | Title field | Short text field | approved | + | data1 | student1 | Student entry | Approved | 1 | + | data1 | student1 | Student second entry | Pending | 0 | + | data1 | teacher1 | Teacher entry | Approved | 1 | + | data2 | teacher1 | Entry no comments | Approved | 1 | + + Scenario: The database activity overview report should generate log events + Given I am on the "Course 1" "course > activities > data" page logged in as "teacher1" + When I am on the "Course 1" "course" page logged in as "teacher1" + And I navigate to "Reports" in current page administration + And I click on "Logs" "link" + And I click on "Get these logs" "button" + Then I should see "Course activities overview page viewed" + And I should see "viewed the instance list for the module 'data'" + + @javascript + Scenario: Students can see relevant columns in the database activity overview + # Add a comment to test the values. + Given I am on the "Database activity" "data activity" page logged in as student1 + And I select "Single view" from the "jump" singleselect + And I click on "Comments (0)" "link" + And I set the following fields to these values: + | Comment | Commenting the entry | + And I click on "Save comment" "link" + When I am on the "Course 1" "course > activities > data" page + # Check columns. + Then I should see "Name" in the "data_overview_collapsible" "region" + And I should see "Status" in the "data_overview_collapsible" "region" + # Check column values. + And the following should exist in the "Table listing all Database activities" table: + | Name | Due date | Total entries | My entries | Comments | + | Database activity | 1 January 2040 | 2 | 2 | 1 | + | Without comments | 1 January 2040 | 1 | 0 | 0 | + | Empty database | - | 0 | 0 | - | + + @javascript + Scenario: Teachers can see relevant columns in the database activity overview + # Add a comment to test the values. + Given I am on the "Database activity" "data activity" page logged in as teacher1 + And I select "Single view" from the "jump" singleselect + And I click on "Comments (0)" "link" + And I set the following fields to these values: + | Comment | Commenting the entry | + And I click on "Save comment" "link" + When I am on the "Course 1" "course > activities > data" page + # Check columns. + And I should not see "My entries" in the "data_overview_collapsible" "region" + And I should not see "Total entries" in the "data_overview_collapsible" "region" + # Check column values. + Then the following should exist in the "Table listing all Database activities" table: + | Name | Due date | Entries | Comments | Actions | + | Database activity | 1 January 2040 | 3 | 1 | Approve (1) | + | Without comments | 1 January 2040 | 1 | 0 | View | + | Empty database | - | 0 | - | View | + # Check the Approve link. + And I click on "Approve" "link" in the "data_overview_collapsible" "region" + And I should see "Pending approval" diff --git a/mod/data/tests/manager_test.php b/mod/data/tests/manager_test.php index 0e2461c424e..713d5b4f583 100644 --- a/mod/data/tests/manager_test.php +++ b/mod/data/tests/manager_test.php @@ -770,4 +770,348 @@ final class manager_test extends \advanced_testcase { ], ]; } + + /** + * Test for get_all_entries(). + * + * @covers ::get_all_entries + */ + public function test_get_all_entries(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $data = $this->getDataGenerator()->create_module(manager::MODULE, ['course' => $course]); + $manager = manager::create_from_instance($data); + + // Empty database should return empty array. + $this->assertEmpty($manager->get_all_entries()); + + // Create data record. + $datarecords = new \stdClass(); + $datarecords->userid = '2'; + $datarecords->dataid = $data->id; + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $data); + $generator->create_entry( + $data, + [$field->field->id => 'Example entry'], + ); + $this->assertCount(1, $manager->get_all_entries()); + } + + /** + * Test filter_entries_by_user. + * + * @covers ::filter_entries_by_user + * @dataProvider provider_test_filter_entries_by_user + * + * @param array $entries + * @param array $myentries + * @param int $expected + * @return void + */ + public function test_filter_entries_by_user( + array $entries, + array $myentries, + int $expected, + ): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => 1], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $currentuser = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $this->setUser($currentuser); + foreach ($myentries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $manager = manager::create_from_instance($activity); + $allentries = $manager->get_all_entries(); + $this->assertCount($expected, $manager->filter_entries_by_user($allentries, $currentuser->id)); + } + + /** + * Data provider for test_filter_entries_by_user. + * + * @return array + */ + public static function provider_test_filter_entries_by_user(): array { + return [ + 'Empty database filtered by current user' => [ + 'entries' => [], + 'myentries' => [], + 'expected' => 0, + ], + 'User without own entries filtered by current user' => [ + 'entries' => [1, 0], + 'myentries' => [], + 'expected' => 0, + ], + 'User with own entries filtered by current user' => [ + 'entries' => [1, 0], + 'myentries' => [1, 0], + 'expected' => 2, + ], + ]; + } + + /** + * Test filter_entries_by_approval. + * + * @covers ::filter_entries_by_approval + * @dataProvider provider_test_filter_entries_by_approval + * + * @param array $entries + * @param int $approvalfilter + * @param int $expected + * @return void + */ + public function test_filter_entries_by_approval( + array $entries, + int $approvalfilter, + int $expected, + ): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => 1], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $manager = manager::create_from_instance($activity); + $allentries = $manager->get_all_entries(); + + $this->assertCount($expected, $manager->filter_entries_by_approval($allentries, $approvalfilter)); + } + + /** + * Data provider for test_filter_entries_by_approval. + * + * @return array + */ + public static function provider_test_filter_entries_by_approval(): array { + return [ + 'Empty database filtered by approved' => [ + 'entries' => [], + 'approvalfilter' => 1, + 'expected' => 0, + ], + 'Database with entries filtered by approved' => [ + 'entries' => [1, 0, 1, 1], + 'approvalfilter' => 1, + 'expected' => 3, + ], + 'Database with entries filtered by non approved' => [ + 'entries' => [1, 0, 1, 1], + 'approvalfilter' => 0, + 'expected' => 1, + ], + ]; + } + + /** + * Test get_comments. + * + * @covers ::get_comments + * @dataProvider provider_test_get_comments + * + * @param array $entries + * @param int $expected + * @param ?int $approvalfilter + * @return void + */ + public function test_get_comments( + array $entries, + int $expected, + ?int $approvalfilter = null, + ): void { + global $CFG; + + $this->resetAfterTest(); + + $CFG->usecomments = true; + + $course = $this->getDataGenerator()->create_course(['enablecomment' => 1]); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => 1, 'comments' => 1], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry) { + $entryid = $generator->create_entry( + $activity, + [$field->field->id => 'Example entry'], + 0, + [], + ['approved' => $entry['approved']], + ); + if ($entry['comments']) { + $commentdata = [ + [ + 'contextlevel' => 'module', + 'instanceid' => $activity->cmid, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $entryid, + 'area' => 'database_entry', + ], + ]; + \core_comment_external::add_comments($commentdata); + } + } + $manager = manager::create_from_instance($activity); + $this->assertCount($expected, $manager->get_comments(approved: $approvalfilter)); + } + + /** + * Data provider for test comments extras. + * + * @return array + */ + public static function provider_test_get_comments(): array { + return [ + 'No comments - no filter' => [ + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + 'approvalfilter' => null, + ], + 'No comments - filtered by approved' => [ + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + 'approvalfilter' => 1, + ], + 'No comments - filtered by pending' => [ + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + 'approvalfilter' => 0, + ], + 'With comments - no filter' => [ + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 2, + 'approvalfilter' => null, + ], + 'With comments - filtered by approved' => [ + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 1, + 'approvalfilter' => 1, + ], + 'With comments - filtered by pending' => [ + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 1, + 'approvalfilter' => 0, + ], + ]; + } + + /** + * Test get_approval_requested. + * + * @covers ::get_approval_requested + */ + public function test_get_approval_requested(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => 1], + ); + $manager = manager::create_from_instance($activity); + $this->assertEquals(1, $manager->get_approval_requested()); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => 0], + ); + $manager = manager::create_from_instance($activity); + $this->assertEquals(0, $manager->get_approval_requested()); + } } diff --git a/mod/data/tests/overview_test.php b/mod/data/tests/overview_test.php new file mode 100644 index 00000000000..0411319af98 --- /dev/null +++ b/mod/data/tests/overview_test.php @@ -0,0 +1,444 @@ +. + +namespace mod_data; + +use core_courseformat\local\overview\overviewfactory; + +/** + * Tests for Database activity overview + * + * @covers \mod_data\courseformat\overview + * @package mod_data + * @category test + * @copyright 2025 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class overview_test extends \advanced_testcase { + /** + * Test get_actions_overview. + * + * @covers ::get_actions_overview + * @dataProvider provider_test_get_actions_overview + * + * @param string $role + * @param bool $needsapproval + * @param array $entries + * @param array|null $expected + * @return void + */ + public function test_get_actions_overview( + string $role, + bool $needsapproval, + array $entries, + ?array $expected + ): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + $this->setUser($currentuser); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => $needsapproval], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + $item = overviewfactory::create($cm)->get_actions_overview(); + + if ($expected === null) { + $this->assertNull($item); + return; + } + + $this->assertEquals($expected['value'], $item->get_value()); + + $content = $item->get_content(); + $this->assertInstanceOf(\action_link::class, $content); + $this->assertStringContainsString($expected['link'], $content->text); + } + + /** + * Data provider for test_get_actions_overview. + * + * @return array + */ + public static function provider_test_get_actions_overview(): array { + return [ + 'Student' => [ + 'role' => 'student', + 'needsapproval' => false, + 'entries' => [1, 0], + 'expected' => null, + ], + 'Teacher with entries (non-require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => false, + 'entries' => [1, 0], + 'expected' => [ + 'link' => get_string('view', 'moodle'), + 'value' => 0, + ], + ], + 'Teacher without entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [], + 'expected' => [ + 'link' => get_string('view', 'moodle'), + 'value' => 0, + ], + ], + 'Teacher with entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [1, 0], + 'expected' => [ + 'link' => get_string('approve', 'data'), + 'value' => 1, + ], + ], + 'Teacher with approved entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [1, 1], + 'expected' => [ + 'link' => get_string('view', 'moodle'), + 'value' => 0, + ], + ], + ]; + } + + /** + * Test get_extra_overview_items. + * + * @covers ::get_extra_overview_items + * @dataProvider provider_test_get_entries_overview + * + * @param string $role + * @param bool $needsapproval + * @param array $entries + * @param array $myentries + * @param array $expected + * @return void + */ + public function test_get_extra_entries_overview( + string $role, + bool $needsapproval, + array $entries, + array $myentries, + array $expected + ): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => $needsapproval], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + $this->setUser($currentuser); + foreach ($myentries as $entry => $approved) { + $generator->create_entry( + $activity, + [$field->field->id => 'Example entry: '.$entry], + 0, + [], + ['approved' => $approved], + ); + } + + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + + if (is_null($expected['totalentries'])) { + $this->assertArrayNotHasKey('totalentries', $items); + } else { + $this->assertEquals($expected['totalentries'], $items['totalentries']->get_value()); + } + + if (is_null($expected['myentries'])) { + $this->assertArrayNotHasKey('myentries', $items); + } else { + $this->assertEquals($expected['myentries'], $items['myentries']->get_value()); + } + } + + /** + * Data provider for test entry related extras. + * + * @return array + */ + public static function provider_test_get_entries_overview(): array { + return [ + 'Student not needing approval' => [ + 'role' => 'student', + 'needsapproval' => false, + 'entries' => [1, 0], + 'myentries' => [1, 0], + 'expected' => [ + 'myentries' => 2, + 'totalentries' => 4, + ], + ], + 'Student needing approval' => [ + 'role' => 'student', + 'needsapproval' => true, + 'entries' => [1, 0], + 'myentries' => [1, 0], + 'expected' => [ + 'myentries' => 2, + 'totalentries' => 2, + ], + ], + 'Teacher with entries (non-require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => false, + 'entries' => [1, 0], + 'myentries' => [1, 0], + 'expected' => [ + 'myentries' => null, + 'totalentries' => 4, + ], + ], + 'Teacher without entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [], + 'myentries' => [], + 'expected' => [ + 'myentries' => null, + 'totalentries' => 0, + ], + ], + 'Teacher with entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [1, 0], + 'myentries' => [1, 0], + 'expected' => [ + 'myentries' => null, + 'totalentries' => 4, + ], + ], + 'Teacher with approved entries (require approval)' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [1, 1], + 'myentries' => [1, 1], + 'expected' => [ + 'myentries' => null, + 'totalentries' => 4, + ], + ], + ]; + } + + /** + * Test get_extra_comments_overview. + * + * @covers ::get_extra_comments_overview + * @dataProvider provider_test_get_comments_overview + * + * @param string $role + * @param bool $needsapproval + * @param array $entries + * @param int $expected + * @return void + */ + public function test_get_extra_comments_overview( + string $role, + bool $needsapproval, + array $entries, + int $expected + ): void { + global $CFG; + + $this->resetAfterTest(); + + $CFG->usecomments = true; + + $course = $this->getDataGenerator()->create_course(['enablecomment' => 1]); + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + manager::MODULE, + ['course' => $course, 'approval' => $needsapproval, 'comments' => 1], + ); + + // Add a field. + /** @var \mod_data_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_data'); + $fieldrecord = (object)[ + 'name' => 'myfield', + 'type' => 'text', + ]; + $field = $generator->create_field($fieldrecord, $activity); + foreach ($entries as $entry) { + $entryid = $generator->create_entry( + $activity, + [$field->field->id => 'Example entry'], + 0, + [], + ['approved' => $entry['approved']], + ); + if ($entry['comments']) { + $commentdata = [ + [ + 'contextlevel' => 'module', + 'instanceid' => $activity->cmid, + 'component' => 'mod_data', + 'content' => 'abc', + 'itemid' => $entryid, + 'area' => 'database_entry', + ], + ]; + \core_comment_external::add_comments($commentdata); + } + } + + $currentuser = $this->getDataGenerator()->create_and_enrol($course, $role); + $this->setUser($currentuser); + + $cm = get_fast_modinfo($course)->get_cm($activity->cmid); + $items = overviewfactory::create($cm)->get_extra_overview_items(); + + $this->assertEquals($expected, $items['comments']->get_value()); + } + + /** + * Data provider for test comments extras. + * + * @return array + */ + public static function provider_test_get_comments_overview(): array { + return [ + 'Student not needing approval with no comments' => [ + 'role' => 'student', + 'needsapproval' => false, + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + ], + 'Student not needing approval with comments' => [ + 'role' => 'student', + 'needsapproval' => false, + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 1, + ], + 'Student needing approval with no comments' => [ + 'role' => 'student', + 'needsapproval' => true, + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + ], + 'Student needing approval with comments' => [ + 'role' => 'student', + 'needsapproval' => true, + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 1, + ], + 'Teacher not needing approval with no comments' => [ + 'role' => 'editingteacher', + 'needsapproval' => false, + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + ], + 'Teacher not needing approval with comments' => [ + 'role' => 'editingteacher', + 'needsapproval' => false, + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 2, + ], + 'Teacher needing approval with no comments' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [ + ['approved' => 1, 'comments' => false], + ['approved' => 0, 'comments' => false], + ], + 'expected' => 0, + ], + 'Teacher needing approval with comments' => [ + 'role' => 'editingteacher', + 'needsapproval' => true, + 'entries' => [ + ['approved' => 1, 'comments' => true], + ['approved' => 0, 'comments' => true], + ], + 'expected' => 2, + ], + ]; + } +}