diff --git a/course/format/classes/activityoverviewbase.php b/course/format/classes/activityoverviewbase.php new file mode 100644 index 00000000000..f5ff78d112d --- /dev/null +++ b/course/format/classes/activityoverviewbase.php @@ -0,0 +1,162 @@ +. + +namespace core_courseformat; + +use cm_info; +use core\context\module as module_context; +use core_completion\cm_completion_details; +use core_courseformat\local\overview\overviewitem; +use core_courseformat\output\local\overview\activityname; +use core_courseformat\base as courseformat; + +/** + * Base class for activity overview. + * + * Plugins must extend this class on their \mod_PLUGINNAME\courseformat\overview + * integration to provide overview items about a course module instance. + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class activityoverviewbase { + /** @var cm_info The course module. */ + protected module_context $context; + + /** @var \stdClass $course */ + protected \stdClass $course; + + /** @var courseformat $format the course format */ + protected courseformat $format; + + /** + * Activity Overview Base class constructor. + * + * Overview integrations are meant to use dependency injection. + * Don't create instances of this class directly, use the factory instead. + * + * \core_courseformat\local\overview\overviewfactory::create($cm); + * + * Plugins can override the constructor adding more dependencies such as: + * + * - protected readonly \moodle_database $db -> To access the database. + * - protected readonly \core\clock $clock -> The clock interface to handle time. + * + * However, it is important to note all original dependencies must be kept. + * + * @param cm_info $cm The course module information (loaded by the factory). + */ + public function __construct( + /** @var cm_info The course module. */ + protected readonly cm_info $cm, + ) { + $this->context = $cm->context; + $this->course = $cm->get_course(); + $this->format = courseformat::instance($this->course); + } + + /** + * Get the plugin specific overview items for the activity. + * + * Plugins can override this method to provide their own overview items. + * + * The resulting array must be indexed by item shortname. + * + * @return overviewitem[] Array of overview items indexed by item shortname. + */ + public function get_extra_overview_items(): array { + return []; + } + + /** + * Get the name of the activity. + */ + final public function get_name_overview(): overviewitem { + return new overviewitem( + name: get_string('name'), + value: $this->cm->name, + content: new activityname($this->cm), + ); + } + + /** + * Retrieves the due date overview for the activity. + * + * @return overviewitem|null null if module does not have a due date. + */ + public function get_due_date_overview(): ?overviewitem { + return null; + } + + /** + * Retrieves the actions overview for the activity. + * + * @return overviewitem|null null if module does not have a main action item. + */ + public function get_actions_overview(): ?overviewitem { + return null; + } + + /** + * Retrieves the completion overview for the activity. + * + * @return overviewitem|null null if completion is not enabled. + */ + public function get_completion_overview(): ?overviewitem { + global $USER; + + $showcompletionconditions = $this->course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS; + + $completiondetails = cm_completion_details::get_instance( + $this->cm, + $USER->id, + $showcompletionconditions + ); + + if (!$completiondetails->is_tracked_user()) { + return null; + } + + $showcompletioninfo = $completiondetails->has_completion() && + ($showcompletionconditions || $completiondetails->show_manual_completion()); + + if (!$showcompletioninfo) { + return new overviewitem( + name: get_string('completion_status', 'completion'), + value: null, + content: '-', + ); + } + + $status = $completiondetails->get_overall_completion(); + + $completionclass = $this->format->get_output_classname('content\\cm\\completion'); + /** @var \core_courseformat\output\local\content\cm\completion $completion */ + $completion = new $completionclass( + $this->format, + $this->cm->get_section_info(), + $this->cm + ); + $completion->set_smallbutton(false); + + return new overviewitem( + name: get_string('completion_status', 'completion'), + value: $status, + content: $completion, + ); + } +} diff --git a/course/format/classes/local/overview/overviewfactory.php b/course/format/classes/local/overview/overviewfactory.php new file mode 100644 index 00000000000..ce1033500b5 --- /dev/null +++ b/course/format/classes/local/overview/overviewfactory.php @@ -0,0 +1,49 @@ +. + +namespace core_courseformat\local\overview; + +use cm_info; +use core_courseformat\activityoverviewbase; + +/** + * Plugin overview instance factory. + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overviewfactory { + /** + * Creates an instance of the appropriate overview class for the given course module. + * + * @param cm_info $cm The course module information. + * @return activityoverviewbase An instance of the appropriate overview class. + */ + public static function create(cm_info $cm): activityoverviewbase { + $classname = "\\mod_{$cm->modname}\\courseformat\\overview"; + if (!class_exists($classname)) { + $classname = resourceoverview::class; + } + + $result = \core\di::get_container()->make($classname, ['cm' => $cm]); + if (!($result instanceof activityoverviewbase)) { + throw new \coding_exception("Class $classname must extend " . activityoverviewbase::class); + } + + return $result; + } +} diff --git a/course/format/classes/local/overview/overviewitem.php b/course/format/classes/local/overview/overviewitem.php new file mode 100644 index 00000000000..d6aea1ff664 --- /dev/null +++ b/course/format/classes/local/overview/overviewitem.php @@ -0,0 +1,146 @@ +. + +namespace core_courseformat\local\overview; + +use core\output\renderable; +use core\output\renderer_base; +use core\output\local\properties\text_align; + +/** + * Class overviewitem + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overviewitem { + /** + * Overview item constructor. + * + * @param string $name The name of the activity. + * @param int|string|bool|null $value The section name. + * @param string|renderable|null $content The item content. + * @param text_align $textalign The preferred text alignment. + */ + public function __construct( + /** @var string the name of the activity */ + protected string $name, + /** @var string the section name */ + protected int|string|bool|null $value, + /** @var string the item content */ + protected string|renderable|null $content = null, + /** @var text_align the preferred text alignment. */ + protected text_align $textalign = text_align::START, + ) { + } + + /** + * Retrieves the name of the overview item. + * + * @return string + */ + public function get_name(): string { + return $this->name; + } + + /** + * Retrieves the value of the overview item. + * + * @return int|string|bool|null + */ + public function get_value(): int|string|bool|null { + return $this->value; + } + + /** + * Gets the content for this item. + * + * Items can utilize either a renderable object or a pre-rendered string as their content. + * + * - For simple items, a plain string is sufficient and can be used in any context. + * - For more complex items, a renderable object is preferable. This allows the item + * to be rendered differently depending on the context, providing greater flexibility. + * + * @return string|\core\output\renderable|null + */ + public function get_content(): string|renderable|null { + return $this->content ?? (string) $this->value ?? null; + } + + /** + * Gets the rendered content for this item. + * + * This method is used when the context does not have any specific requirements + * and could use the default item content rendering. + * + * @param \core\output\renderer_base $output + * @return string + */ + public function get_rendered_content(renderer_base $output): string { + if ($this->content instanceof renderable) { + return $output->render($this->content); + } + return $this->get_content() ?? ''; + } + + /** + * Gets the preferred text alignment of the item. + * + * @return text_align The text alignment. + */ + public function get_text_align(): text_align { + return $this->textalign; + } + + /** + * Sets the content for this item. + * + * Items can utilize either a renderable object or a pre-rendered string as their content. + * + * @param string|renderable|null $content + */ + public function set_content(string|renderable|null $content): void { + $this->content = $content; + } + + /** + * Sets the preferred text alignment of the item. + * + * @param text_align $textalign + */ + public function set_text_align(text_align $textalign): void { + $this->textalign = $textalign; + } + + /** + * Sets the value of the overview item. + * + * @param int|string|bool|null $value + */ + public function set_value(int|string|bool|null $value): void { + $this->value = $value; + } + + /** + * Sets the name of the overview item. + * + * @param string $name + */ + public function set_name(string $name): void { + $this->name = $name; + } +} diff --git a/course/format/classes/local/overview/resourceoverview.php b/course/format/classes/local/overview/resourceoverview.php new file mode 100644 index 00000000000..4889291c435 --- /dev/null +++ b/course/format/classes/local/overview/resourceoverview.php @@ -0,0 +1,46 @@ +. + +namespace core_courseformat\local\overview; + +/** + * Class resourceoverview + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class resourceoverview extends \core_courseformat\activityoverviewbase { + #[\Override] + public function get_extra_overview_items(): array { + return [ + 'type' => $this->get_extra_type_overview(), + ]; + } + + /** + * Retrieves an overview item for the extra type of the resource. + * + * @return overviewitem|null + */ + private function get_extra_type_overview(): ?overviewitem { + return new overviewitem( + name: get_string('resource_type'), + value: $this->cm->modfullname, + content: $this->cm->modfullname, + ); + } +} diff --git a/course/format/classes/output/local/overview/activityname.php b/course/format/classes/output/local/overview/activityname.php new file mode 100644 index 00000000000..6dce126d630 --- /dev/null +++ b/course/format/classes/output/local/overview/activityname.php @@ -0,0 +1,74 @@ +. + +namespace core_courseformat\output\local\overview; + +use cm_info; +use core\output\named_templatable; +use core\output\renderable; +use core\output\renderer_base; +use core_courseformat\base as course_format; +use stdClass; + +/** + * Class activityname + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activityname implements renderable, named_templatable { + /** + * Constructor. + * + * @param cm_info $cm The course module. + */ + public function __construct( + /** @var cm_info The course module. */ + protected cm_info $cm, + ) { + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output Renderer base. + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + $cm = $this->cm; + $section = $this->cm->get_section_info(); + $course = $this->cm->get_course(); + $format = course_format::instance($course); + return (object) [ + 'activityname' => \core_external\util::format_string($cm->name, $cm->context, true), + 'activityurl' => $cm->url, + 'sectiontitle' => $format->get_section_name($section), + 'hidden' => empty($cm->visible), + 'stealth' => $cm->is_stealth(), + ]; + } + + /** + * Get the template name. + * + * @param renderer_base $renderer Renderer base. + * @return string + */ + public function get_template_name(renderer_base $renderer): string { + return 'core_courseformat/local/overview/activityname'; + } +} diff --git a/course/format/templates/local/overview/activityname.mustache b/course/format/templates/local/overview/activityname.mustache new file mode 100644 index 00000000000..7e4b30052f0 --- /dev/null +++ b/course/format/templates/local/overview/activityname.mustache @@ -0,0 +1,46 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_courseformat/local/overview/activityname + + The course overview activity name cell. + + Example context (json): + { + "activityname": "Activity name", + "activityurl": "http://moodle.com", + "sectiontitle": "Section title", + "visible": true, + "stealth": false + } +}} +
+ +
+ {{sectiontitle}} +
+
+ {{#hidden}} + {{#str}}hiddenfromstudents{{/str}} + {{/hidden}} + {{#stealth}} + {{#str}}hiddenoncoursepage{{/str}} + {{/stealth}} +
+
diff --git a/course/format/tests/activityoverviewbase_test.php b/course/format/tests/activityoverviewbase_test.php new file mode 100644 index 00000000000..1c508ffbf68 --- /dev/null +++ b/course/format/tests/activityoverviewbase_test.php @@ -0,0 +1,169 @@ +. + +namespace core_courseformat; + +/** + * Tests for course + * + * @package core_courseformat + * @category test + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_courseformat\activityoverviewbase + */ +final class activityoverviewbase_test extends \advanced_testcase { + #[\Override()] + public static function setUpBeforeClass(): void { + global $CFG; + require_once($CFG->libdir . '/completionlib.php'); + require_once($CFG->dirroot . '/course/format/tests/fixtures/fake_activityoverview.php'); + parent::setUpBeforeClass(); + } + + /** + * Test get_name_overview method. + * + * @covers ::get_name_overview + */ + public function test_get_name_overview(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $activity = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'name' => 'Test!']); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = new \core_courseformat\fake_activityoverview($cm); + + $result = $overview->get_name_overview(); + $this->assertEquals(get_string('name'), $result->get_name()); + $this->assertEquals('Test!', $result->get_value()); + $this->assertInstanceOf(\core_courseformat\output\local\overview\activityname::class, $result->get_content()); + } + + /** + * Test get_completion_overview method. + * + * @covers ::get_completion_overview + * @dataProvider provider_get_completion_overview + * @param int $setcompletion the completion status + */ + public function test_get_completion_overview( + int $setcompletion, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(['enablecompletion' => 1]); + + $user = $generator->create_user(); + $generator->enrol_user($user->id, $course->id); + + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + 'assign', + ['course' => $course->id, 'completion' => \COMPLETION_TRACKING_AUTOMATIC] + ); + + rebuild_course_cache($course->id, true); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = new \core_courseformat\fake_activityoverview($cm); + + $completion = (object) [ + 'coursemoduleid' => $cm->id, + 'timemodified' => time(), + 'viewed' => \COMPLETION_NOT_VIEWED, + 'overrideby' => null, + 'id' => 0, + 'completionstate' => $setcompletion, + 'userid' => $user->id, + ]; + $comletioninfo = new \completion_info($course); + $comletioninfo->internal_set_data($cm, $completion, true); + + $this->setUser($user); + + $result = $overview->get_completion_overview(); + $this->assertEquals(get_string('completion_status', 'completion'), $result->get_name()); + $this->assertEquals($setcompletion, $result->get_value()); + $this->assertInstanceOf(\core_courseformat\output\local\content\cm\completion::class, $result->get_content()); + } + + /** + * Data provider for test_get_completion_overview. + * + * @return array the testing scenarios + */ + public static function provider_get_completion_overview(): array { + return [ + 'complet' => [ + 'setcompletion' => \COMPLETION_COMPLETE, + ], + 'incomplete' => [ + 'setcompletion' => \COMPLETION_INCOMPLETE, + ], + 'complete pass' => [ + 'setcompletion' => \COMPLETION_COMPLETE_PASS, + ], + 'complete fail' => [ + 'setcompletion' => \COMPLETION_COMPLETE_FAIL, + ], + ]; + } + + /** + * Test get_completion_overview method on an activity with no completion. + * + * @covers ::get_completion_overview + */ + public function test_get_completion_overview_no_completion(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(['enablecompletion' => 1]); + + $user = $generator->create_user(); + $generator->enrol_user($user->id, $course->id); + + $this->setAdminUser(); + + $activity = $this->getDataGenerator()->create_module( + 'assign', + ['course' => $course->id] + ); + + rebuild_course_cache($course->id, true); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = new \core_courseformat\fake_activityoverview($cm); + + $this->setUser($user); + + $result = $overview->get_completion_overview(); + $this->assertEquals(get_string('completion_status', 'completion'), $result->get_name()); + $this->assertEquals(null, $result->get_value()); + $this->assertEquals('-', $result->get_content()); + } +} diff --git a/course/format/tests/fixtures/fake_activityoverview.php b/course/format/tests/fixtures/fake_activityoverview.php new file mode 100644 index 00000000000..57040e2c4f9 --- /dev/null +++ b/course/format/tests/fixtures/fake_activityoverview.php @@ -0,0 +1,27 @@ +. + +namespace core_courseformat; + +/** + * This is a mock class for testing the abstract class activityoverviewbase. + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class fake_activityoverview extends activityoverviewbase { +} diff --git a/course/format/tests/fixtures/wrongcm_activityoverview.php b/course/format/tests/fixtures/wrongcm_activityoverview.php new file mode 100644 index 00000000000..b376096947b --- /dev/null +++ b/course/format/tests/fixtures/wrongcm_activityoverview.php @@ -0,0 +1,27 @@ +. + +namespace mod_wrongcm\courseformat; + +/** + * This is a mock to simulate a activity plugin with a wrong overview class. + * + * @package core_courseformat + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class overview { +} diff --git a/course/format/tests/local/overview/overviewfactory_test.php b/course/format/tests/local/overview/overviewfactory_test.php new file mode 100644 index 00000000000..76e845823fb --- /dev/null +++ b/course/format/tests/local/overview/overviewfactory_test.php @@ -0,0 +1,115 @@ +. + +namespace core_courseformat\local\overview; + +/** + * Tests for course + * + * @package core_courseformat + * @category test + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_courseformat\local\overview\overviewfactory + */ +final class overviewfactory_test extends \advanced_testcase { + #[\Override()] + public static function setUpBeforeClass(): void { + global $CFG; + require_once($CFG->dirroot . '/course/format/tests/fixtures/wrongcm_activityoverview.php'); + parent::setUpBeforeClass(); + } + + /** + * Test create method on resource activities. + * + * @covers ::create + * @dataProvider create_resource_provider + * @param string $resourcetype + */ + public function test_create_resource( + string $resourcetype, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $activity = $this->getDataGenerator()->create_module($resourcetype, ['course' => $course->id]); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = overviewfactory::create($cm); + + $this->assertInstanceOf(resourceoverview::class, $overview); + } + + /** + * Data provider for test_create_resource. + * + * @return array + */ + public static function create_resource_provider(): array { + return [ + 'book' => [ + 'resourcetype' => 'book', + ], + 'folder' => [ + 'resourcetype' => 'folder', + ], + 'page' => [ + 'resourcetype' => 'page', + ], + 'resource' => [ + 'resourcetype' => 'resource', + ], + 'url' => [ + 'resourcetype' => 'url', + ], + ]; + } + + /** + * Test create method on a fake activity with a wrong class. + * + * @covers ::create + */ + public function test_create_exception( + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + + $course = $generator->create_course(); + $activity = $this->getDataGenerator()->create_module('page', ['course' => $course->id]); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + // We know the factory will only use the modname to create the overview, + // this is a small trick to make the factory to use a wrong class and + // won't happen in a real code. However, this is the easiest way to test + // the exception. + $reflection = new \ReflectionClass($cm); + $property = $reflection->getProperty('modname'); + $property->setAccessible(true); + $property->setValue($cm, 'wrongcm'); + + $this->expectException(\coding_exception::class); + $this->expectExceptionMessageMatches("/.* must extend core_courseformat\\\\activityoverviewbase.*/"); + overviewfactory::create($cm); + } +} diff --git a/course/format/tests/local/overview/resourceoverview_test.php b/course/format/tests/local/overview/resourceoverview_test.php new file mode 100644 index 00000000000..cf97591a10e --- /dev/null +++ b/course/format/tests/local/overview/resourceoverview_test.php @@ -0,0 +1,106 @@ +. + +namespace core_courseformat\local\overview; + +/** + * Tests for resource overview + * + * @package core_courseformat + * @category test + * @copyright 2025 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_courseformat\local\overview\resourceoverview + */ +final class resourceoverview_test extends \advanced_testcase { + /** + * Test get_extra_overview_items method. + * + * @covers ::get_extra_overview_items + */ + public function test_get_extra_overview_items(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $activity = $this->getDataGenerator()->create_module('url', ['course' => $course->id]); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = overviewfactory::create($cm); + + $result = $overview->get_extra_overview_items(); + $this->assertCount(1, $result); + $this->assertArrayHasKey('type', $result); + $this->assertInstanceOf(\core_courseformat\local\overview\overviewitem::class, $result['type']); + } + + /** + * Test get_extra_type_overview method. + * + * @covers ::get_extra_overview_items + * @covers ::get_extra_type_overview + * @dataProvider get_extra_type_overview_provider + * @param string $resourcetype + */ + public function test_get_extra_type_overview( + string $resourcetype, + ): void { + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $activity = $this->getDataGenerator()->create_module($resourcetype, ['course' => $course->id]); + $modinfo = get_fast_modinfo($course); + $cm = $modinfo->get_cm($activity->cmid); + + $overview = overviewfactory::create($cm); + + $items = $overview->get_extra_overview_items(); + $result = $items['type']; + + $this->assertEquals(get_string('resource_type'), $result->get_name()); + $this->assertEquals($cm->modfullname, $result->get_value()); + $this->assertEquals($cm->modfullname, $result->get_content()); + } + + /** + * Data provider for test_get_extra_type_overview. + * + * @return array + */ + public static function get_extra_type_overview_provider(): array { + return [ + 'book' => [ + 'resourcetype' => 'book', + ], + 'folder' => [ + 'resourcetype' => 'folder', + ], + 'page' => [ + 'resourcetype' => 'page', + ], + 'resource' => [ + 'resourcetype' => 'resource', + ], + 'url' => [ + 'resourcetype' => 'url', + ], + ]; + } +} diff --git a/lang/en/completion.php b/lang/en/completion.php index e7c74af1cbc..613eb0c3e86 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -87,6 +87,7 @@ $string['completion_help'] = 'If enabled, activity completion is tracked, either $string['completion_link'] = 'activity/completion'; $string['completion_manual'] = 'Students must manually mark the activity as done'; $string['completion_none'] = 'None'; +$string['completion_status'] = 'Completion status'; $string['completionactivitydefault'] = 'Use activity default'; $string['completionanygrade_desc'] = 'Any grade'; $string['completiondisabled'] = 'Disabled, not shown in activity settings'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index de13336de7f..7ee066f32b4 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1856,6 +1856,7 @@ $string['resortsubcategoriesby'] = 'Sort subcategories by {$a} ascending'; $string['resortsubcategoriesbyreverse'] = 'Sort subcategories by {$a} descending'; $string['resortcourses'] = 'Sort courses'; $string['resource'] = 'Resource'; +$string['resource_type'] = 'Resource type'; $string['resourcedisplayauto'] = 'Automatic'; $string['resourcedisplaydownload'] = 'Force download'; $string['resourcedisplayembed'] = 'Embed'; diff --git a/lib/apis.json b/lib/apis.json index 6fc0c4e7321..6ee8b57894a 100644 --- a/lib/apis.json +++ b/lib/apis.json @@ -84,10 +84,15 @@ "allowedlevel2": false, "allowedspread": false }, + "courseformat": { + "component": "core_courseformat", + "allowedlevel2": true, + "allowedspread": true + }, "customfield": { - "component": "core_customfield", - "allowedlevel2": true, - "allowedspread": true + "component": "core_customfield", + "allowedlevel2": true, + "allowedspread": true }, "ddl": { "component": "core",