Merge branch 'MDL-85405_501_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_501_STABLE

This commit is contained in:
Mihail Geshoski
2025-12-02 14:19:39 +08:00
13 changed files with 222 additions and 113 deletions
@@ -0,0 +1,9 @@
issueNumber: MDL-85405
notes:
core_course:
- message: >-
The external function `core_course_get_course_contents` now includes the
`candisplay` property for each returned module. If this is false, the
module should not be displayed on the course page (for example, for
question banks).
type: improved
+5 -1
View File
@@ -746,7 +746,11 @@ abstract class info {
$modulename = format_string($cm->get_name(), true, ['context' => $context]);
// We make sure that we add a data attribute to the name so we can change it later if the
// original module name changes.
if ($cm->has_view() && $cm->get_user_visible()) {
if (
\course_modinfo::is_mod_type_visible_on_course($cm->modname)
&& $cm->has_view()
&& $cm->get_user_visible()
) {
// Help student by providing a link to the module which is preventing availability.
return \html_writer::link($cm->get_url(), $modulename, ['data-cm-name-for' => $cm->id]);
} else {
@@ -53,7 +53,11 @@ class block_activity_modules extends block_list {
foreach($modinfo->cms as $cm) {
// Exclude activities that aren't visible or have no view link (e.g. label). Account for folder being displayed inline.
if (!$cm->uservisible || (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)) {
if (
!\course_modinfo::is_mod_type_visible_on_course($cm->modname)
|| !$cm->uservisible
|| (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)
) {
continue;
}
if (array_key_exists($cm->modname, $modfullnames)) {
@@ -26,6 +26,7 @@ Feature: Block activity modules
| url | Frontpage url name | Frontpage url description | Acceptance test site | url0 |
| wiki | Frontpage wiki name | Frontpage wiki description | Acceptance test site | wiki0 |
| workshop | Frontpage workshop name | Frontpage workshop description | Acceptance test site | workshop0 |
| qbank | Frontpage qbank name | Frontpage qbank description | Acceptance test site | qbank0 |
When I log in as "admin"
And I am on site homepage
@@ -74,6 +75,8 @@ Feature: Block activity modules
And I should see "Frontpage imscp name"
And I should see "Frontpage folder name"
And I should see "Frontpage url name"
And I am on site homepage
And "Question banks" "link" should not exist in the "Activities" "block"
Scenario: Add activities block in a course
Given the following "courses" exist:
@@ -100,6 +103,7 @@ Feature: Block activity modules
| url | Test url name | Test url description | C1 | url1 |
| wiki | Test wiki name | Test wiki description | C1 | wiki1 |
| workshop | Test workshop name | Test workshop description | C1 | workshop1 |
| qbank | Test qbank name | Test qbank description | C1 | qbank1 |
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
@@ -147,3 +151,5 @@ Feature: Block activity modules
And I should see "Test imscp name"
And I should see "Test folder name"
And I should see "Test url name"
And I am on "Course 1" course homepage
And "Question banks" "link" should not exist in the "Activities" "block"
+158 -102
View File
@@ -287,6 +287,7 @@ class core_course_external extends external_api {
$module['completion'] = $cm->completion;
$module['downloadcontent'] = $cm->downloadcontent;
$module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
$module['candisplay'] = plugin_supports('mod', $cm->modname, FEATURE_CAN_DISPLAY, true);
$module['dates'] = $activitydates;
$module['groupmode'] = $cm->groupmode;
@@ -442,119 +443,174 @@ class core_course_external extends external_api {
return new external_multiple_structure(
new external_single_structure(
array(
[
'id' => new external_value(PARAM_INT, 'Section ID'),
'name' => new external_value(PARAM_RAW, 'Section name'),
'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
'summary' => new external_value(PARAM_RAW, 'Section description'),
'summaryformat' => new external_format_value('summary'),
'section' => new external_value(PARAM_INT, 'Section number inside the course', VALUE_OPTIONAL),
'hiddenbynumsections' => new external_value(PARAM_INT, 'Whether is a section hidden in the course format',
VALUE_OPTIONAL),
'hiddenbynumsections' => new external_value(
PARAM_INT,
'Whether is a section hidden in the course format',
VALUE_OPTIONAL,
),
'uservisible' => new external_value(PARAM_BOOL, 'Is the section visible for the user?', VALUE_OPTIONAL),
'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.', VALUE_OPTIONAL),
'component' => new external_value(PARAM_COMPONENT, 'The delegate component of this section if any.',
VALUE_OPTIONAL),
'itemid' => new external_value(PARAM_INT,
'The optional item id delegate component can use to identify its instance.', VALUE_OPTIONAL),
'component' => new external_value(
PARAM_COMPONENT,
'The delegate component of this section if any.',
VALUE_OPTIONAL,
),
'itemid' => new external_value(
PARAM_INT,
'The optional item id delegate component can use to identify its instance.',
VALUE_OPTIONAL,
),
'modules' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'activity id'),
'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
'name' => new external_value(PARAM_RAW, 'activity module name'),
'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
'uservisible' => new external_value(PARAM_BOOL, 'Is the module visible for the user?',
VALUE_OPTIONAL),
'availabilityinfo' => new external_value(PARAM_RAW, 'Availability information.',
VALUE_OPTIONAL),
'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
VALUE_OPTIONAL),
'modicon' => new external_value(PARAM_URL, 'activity icon url'),
'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
'purpose' => new external_value(PARAM_ALPHA, 'the module purpose'),
'branded' => new external_value(PARAM_BOOL, 'Whether the module is branded or not',
VALUE_OPTIONAL),
'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
'afterlink' => new external_value(PARAM_RAW, 'After link info to be displayed.',
VALUE_OPTIONAL),
'activitybadge' => self::get_activitybadge_structure(),
'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
'noviewlink' => new external_value(PARAM_BOOL, 'Whether the module has no view page',
VALUE_OPTIONAL),
'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
'completiondata' => $completiondefinition,
'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
'dates' => new external_multiple_structure(
new external_single_structure(
array(
'label' => new external_value(PARAM_TEXT, 'date label'),
'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
'relativeto' => new external_value(PARAM_INT, 'relative date timestamp',
VALUE_OPTIONAL),
'dataid' => new external_value(PARAM_NOTAGS, 'cm data id', VALUE_OPTIONAL),
)
),
'Course dates',
VALUE_DEFAULT,
[]
),
'groupmode' => new external_value(PARAM_INT, 'Group mode value', VALUE_OPTIONAL),
'contents' => new external_multiple_structure(
new external_single_structure(
array(
// content info
'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
'filename'=> new external_value(PARAM_FILE, 'filename'),
'filepath'=> new external_value(PARAM_PATH, 'filepath'),
'filesize'=> new external_value(PARAM_INT, 'filesize'),
'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
'timecreated' => new external_value(PARAM_INT, 'Time created'),
'timemodified' => new external_value(PARAM_INT, 'Time modified'),
'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
'isexternalfile' => new external_value(PARAM_BOOL, 'Whether is an external file.',
VALUE_OPTIONAL),
'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for external files.',
VALUE_OPTIONAL),
// copyright related info
'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
'author' => new external_value(PARAM_TEXT, 'Content owner'),
'license' => new external_value(PARAM_TEXT, 'Content license'),
'tags' => new external_multiple_structure(
\core_tag\external\tag_item_exporter::get_read_structure(), 'Tags',
VALUE_OPTIONAL
),
)
), 'Course contents', VALUE_DEFAULT, array()
),
'contentsinfo' => new external_single_structure(
array(
'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
'filessize' => new external_value(PARAM_INT, 'Total files size.'),
'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
'mimetypes' => new external_multiple_structure(
new external_value(PARAM_RAW, 'File mime type.'),
'Files mime types.'
new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'activity id'),
'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
'name' => new external_value(PARAM_RAW, 'activity module name'),
'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
'contextid' => new external_value(PARAM_INT, 'Activity context id.', VALUE_OPTIONAL),
'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
'uservisible' => new external_value(
PARAM_BOOL,
'Is the module visible for the user?',
VALUE_OPTIONAL,
),
'availabilityinfo' => new external_value(
PARAM_RAW,
'Availability information.',
VALUE_OPTIONAL,
),
'visibleoncoursepage' => new external_value(
PARAM_INT,
'is the module visible on course page',
VALUE_OPTIONAL,
),
'modicon' => new external_value(PARAM_URL, 'activity icon url'),
'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
'purpose' => new external_value(PARAM_ALPHA, 'the module purpose'),
'branded' => new external_value(
PARAM_BOOL,
'Whether the module is branded or not',
VALUE_OPTIONAL,
),
'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
'availability' => new external_value(PARAM_RAW, 'module availability settings', VALUE_OPTIONAL),
'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
'onclick' => new external_value(PARAM_RAW, 'Onclick action.', VALUE_OPTIONAL),
'afterlink' => new external_value(
PARAM_RAW,
'After link info to be displayed.',
VALUE_OPTIONAL,
),
'activitybadge' => self::get_activitybadge_structure(),
'customdata' => new external_value(PARAM_RAW, 'Custom data (JSON encoded).', VALUE_OPTIONAL),
'noviewlink' => new external_value(
PARAM_BOOL,
'Whether the module has no view page',
VALUE_OPTIONAL,
),
'candisplay' => new external_value(
PARAM_BOOL,
'Whether the module should be displayed on the course page',
VALUE_OPTIONAL,
),
'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
'completiondata' => $completiondefinition,
'downloadcontent' => new external_value(
PARAM_INT,
'The download content value',
VALUE_OPTIONAL,
),
'dates' => new external_multiple_structure(
new external_single_structure(
[
'label' => new external_value(PARAM_TEXT, 'date label'),
'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
'relativeto' => new external_value(
PARAM_INT,
'relative date timestamp',
VALUE_OPTIONAL,
),
'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
the main file.', VALUE_OPTIONAL),
), 'Contents summary information.', VALUE_OPTIONAL
'dataid' => new external_value(PARAM_NOTAGS, 'cm data id', VALUE_OPTIONAL),
]
),
)
), 'list of module'
)
)
)
'Course dates',
VALUE_DEFAULT,
[]
),
'groupmode' => new external_value(PARAM_INT, 'Group mode value', VALUE_OPTIONAL),
'contents' => new external_multiple_structure(
new external_single_structure(
[
// Content info.
'type' => new external_value(PARAM_TEXT, 'a file or a folder or external link'),
'filename' => new external_value(PARAM_FILE, 'filename'),
'filepath' => new external_value(PARAM_PATH, 'filepath'),
'filesize' => new external_value(PARAM_INT, 'filesize'),
'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
'content' => new external_value(
PARAM_RAW,
'Raw content, will be used when type is content',
VALUE_OPTIONAL
),
'timecreated' => new external_value(PARAM_INT, 'Time created'),
'timemodified' => new external_value(PARAM_INT, 'Time modified'),
'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
'mimetype' => new external_value(PARAM_RAW, 'File mime type.', VALUE_OPTIONAL),
'isexternalfile' => new external_value(
PARAM_BOOL,
'Whether is an external file.',
VALUE_OPTIONAL,
),
'repositorytype' => new external_value(
PARAM_PLUGIN,
'The repository type for external files.',
VALUE_OPTIONAL,
),
// Copyright related info.
'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
'author' => new external_value(PARAM_TEXT, 'Content owner'),
'license' => new external_value(PARAM_TEXT, 'Content license'),
'tags' => new external_multiple_structure(
\core_tag\external\tag_item_exporter::get_read_structure(),
'Tags',
VALUE_OPTIONAL,
),
]
),
'Course contents',
VALUE_DEFAULT,
[],
),
'contentsinfo' => new external_single_structure(
[
'filescount' => new external_value(PARAM_INT, 'Total number of files.'),
'filessize' => new external_value(PARAM_INT, 'Total files size.'),
'lastmodified' => new external_value(PARAM_INT, 'Last time files were modified.'),
'mimetypes' => new external_multiple_structure(
new external_value(PARAM_RAW, 'File mime type.'),
'Files mime types.'
),
'repositorytype' => new external_value(PARAM_PLUGIN, 'The repository type for
the main file.', VALUE_OPTIONAL),
],
'Contents summary information.',
VALUE_OPTIONAL,
),
],
),
'list of modules',
),
],
),
);
}
@@ -92,9 +92,14 @@ class overviewpage implements renderable, named_templatable {
$archetypes = [];
foreach ($modinfo->cms as $cm) {
// Exclude activities that aren't visible or have no view link (e.g. label).
// Exclude activities that are not displayed on the course page,
// aren't visible or have no view link (e.g. label).
// Account for folder being displayed inline.
if (!$cm->uservisible || (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)) {
if (
!\course_modinfo::is_mod_type_visible_on_course($cm->modname)
|| !$cm->uservisible
|| (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)
) {
continue;
}
if (array_key_exists($cm->modname, $modfullnames)) {
@@ -221,7 +221,9 @@ class overviewtable implements externable, named_templatable, renderable {
private function is_cm_displayable(cm_info $cm): bool {
// Folder is an exception because it has settings to be displayed in the course
// page without having a view link.
return $cm->uservisible && ($cm->has_view() || strcmp($cm->modname, 'folder') === 0);
return $cm->uservisible
&& ($cm->has_view() || strcmp($cm->modname, 'folder') === 0)
&& \course_modinfo::is_mod_type_visible_on_course($cm->modname);
}
/**
+5 -4
View File
@@ -225,10 +225,11 @@ class format_singleactivity extends core_courseformat\base implements core_cours
public static function get_supported_activities() {
$availabletypes = get_module_types_names();
foreach ($availabletypes as $module => $name) {
if (plugin_supports('mod', $module, FEATURE_NO_VIEW_LINK, false)) {
unset($availabletypes[$module]);
}
if (sectiondelegate::has_delegate_class('mod_' . $module)) {
if (
plugin_supports('mod', $module, FEATURE_NO_VIEW_LINK, false)
|| sectiondelegate::has_delegate_class('mod_' . $module)
|| !course_modinfo::is_mod_type_visible_on_course($module)
) {
unset($availabletypes[$module]);
}
}
@@ -34,6 +34,7 @@ Feature: Courses can be created in Single Activity mode
# Check that not all the activity types are in the dropdown.
And I should not see "Text and media" in the "Type of activity" "field"
And I should not see "Subsection" in the "Type of activity" "field"
And I should not see "Question bank" in the "Type of activity" "field"
And I set the field "Type of activity" to "Assignment"
And I press "Save and display"
And I should see "New Assignment"
@@ -109,6 +109,7 @@ Feature: Users can access the course activities overview page
| url | C1 | Activity 18 |
| wiki | C1 | Activity 19 |
| workshop | C1 | Activity 20 |
| qbank | C1 | Activity 21 |
Given I am on the "Course 1" "course > activities" page logged in as "teacher1"
And I should see "Assignments" in the "assign_overview_collapsible" "region"
And I should see "Choices" in the "choice_overview_collapsible" "region"
@@ -124,6 +125,9 @@ Feature: Users can access the course activities overview page
And I should see "Workshops" in the "workshop_overview_collapsible" "region"
# All resources are grouped.
And I should see "Resources" in the "resource_overview_collapsible" "region"
# Qbanks and labels are not shown.
And I should not see "Labels" in the "course-overview-page" "region"
And I should not see "Question banks" in the "course-overview-page" "region"
@javascript
Scenario: The resources overview is loaded at the moment the section is expanded via Ajax
@@ -20,6 +20,7 @@ use cache;
use cache_store;
use core\output\html_writer;
use core_collator;
use course_modinfo;
use filterobject;
/**
@@ -124,7 +125,12 @@ class text_filter extends \core_filters\text_filter {
$sortedactivities = [];
foreach ($modinfo->cms as $cm) {
// Use normal access control and visibility, but exclude labels and hidden activities.
if ($cm->visible && $cm->has_view() && $cm->uservisible) {
if (
$cm->visible
&& $cm->has_view()
&& $cm->uservisible
&& course_modinfo::is_mod_type_visible_on_course($cm->modname)
) {
$sortedactivities[] = (object)[
'name' => $cm->name,
'url' => $cm->url,
@@ -41,9 +41,18 @@ final class text_filter_test extends \advanced_testcase {
'page',
['course' => $course->id, 'name' => 'Test (2)']
);
// Create a label and question bank that should not be linked to.
$this->getDataGenerator()->create_module(
'label',
['course' => $course->id, 'name' => 'Label 1', 'intro' => 'Label 1']
);
$this->getDataGenerator()->create_module(
'qbank',
['course' => $course->id, 'name' => 'Question bank 1']
);
// Format text with all three entries in HTML.
$html = '<p>Please read the two pages Test 1 and <i>Test (2)</i>.</p>';
$html = '<p>Please read the two pages Test 1 and <i>Test (2)</i>, but not Label 1 or Question bank 1.</p>';
$filtered = format_text($html, FORMAT_HTML, ['context' => $context]);
// Find all the glossary links in the result.
+2
View File
@@ -1369,6 +1369,8 @@ $string['missingteacher'] = 'Must choose something';
$string['missingurl'] = 'Missing URL';
$string['missingusername'] = 'Missing username';
$string['moddoesnotsupporttype'] = 'Module {$a->modname} does not support uploads of type {$a->type}';
$string['modhidden'] = 'Availability';
$string['modhidden_help'] = '* Hide on course page: Not available to students. This module cannot be shown to students.';
$string['modhide'] = 'Hide';
$string['modshow'] = 'Show';
$string['modvisible'] = 'Availability';