MDL-85391 course: Handle null course urls
Course urls may be null when navigation is set to true. The return value from course_get_url being null was not being handled before this change, which could lead to an exception causing the page not to load. It also handles null urls in the course index in Boost so that they are not a link to what ever the current page is. If either the first or last section in a format did not a return a url under the Classic theme there would still be a link displayed with a link to the current page the user is on. After this change no link would be displayed in this case.
This commit is contained in:
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/section",["exports","core_courseform
|
||||
* @class core_courseformat/local/courseindex/section
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_sectiontitle=_interopRequireDefault(_sectiontitle),_dndsection=_interopRequireDefault(_dndsection),_log=_interopRequireDefault(_log);class Component extends _dndsection.default{create(){this.name="courseindex_section",this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_item']",SECTION_TITLE:"[data-for='section_title']",CM_LAST:'[data-for="cm"]:last-child',DND_ALLOWED:"[data-courseindexdndallowed='true']"},this.classes={SECTIONHIDDEN:"dimmed",SECTIONCURRENT:"current",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",OVERLAYBORDERS:"overlay-preview-borders"},this.id=this.element.dataset.id,this.isPageItem=!1}static init(target,selectors){let element=document.querySelector(target);return element||(_log.default.debug("Init component with id is deprecated, use a query selector instead."),element=document.getElementById(target)),new this({element:element,selectors:selectors})}stateReady(state){this.configState(state);const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(this.reactive.isEditing&&this.reactive.supportComponents&&document.querySelector(this.selectors.DND_ALLOWED)){const titleitem=new _sectiontitle.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(titleitem)}const section=state.section.get(this.id);window.location.href==section.sectionurl.replace(/&/g,"&")&&(this.reactive.dispatch("setPageItem","section",this.id),sectionItem.scrollIntoView())}getWatchers(){return[{watch:"section[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}getLastCm(){return this.getElement(this.selectors.CM_LAST)}_refreshSection(_ref){var _element$hasrestricti,_element$dragging,_element$locked;let{element:element}=_ref;const sectionItem=this.getElement(this.selectors.SECTION_ITEM);sectionItem.classList.toggle(this.classes.SECTIONHIDDEN,!element.visible),sectionItem.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hasrestricti=element.hasrestrictions)&&void 0!==_element$hasrestricti&&_element$hasrestricti),this.element.classList.toggle(this.classes.SECTIONCURRENT,element.current),this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked,this.getElement(this.selectors.SECTION_TITLE).innerHTML=element.title}_refreshPageItem(_ref2){var _element$pageItem,_this$pageItem;let{element:element,state:state}=_ref2;if(!element.pageItem)return;const containsPageItem=this._isPageItemInThisSection(element.pageItem);if(!containsPageItem||this._isParentSectionIndexCollapsed(state))return this.pageItem=!1,void this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM);!state.section.get(this.id).indexcollapsed||null!==(_element$pageItem=element.pageItem)&&void 0!==_element$pageItem&&_element$pageItem.isStatic?this.pageItem="section"==element.pageItem.type&&element.pageItem.id==this.id:this.pageItem=containsPageItem;this.getElement(this.selectors.SECTION_ITEM).classList.toggle(this.classes.PAGEITEM,null!==(_this$pageItem=this.pageItem)&&void 0!==_this$pageItem&&_this$pageItem),this.pageItem&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}_isPageItemInThisSection(pageItem){if(pageItem.sectionId==this.id)return!0;return!!this.element.querySelector("".concat(this.selectors.SECTION,'[data-id="').concat(pageItem.sectionId,'"]'))}_isParentSectionIndexCollapsed(state){const parentElement=this.element.parentElement.closest(this.selectors.SECTION);if(!parentElement||!parentElement.dataset.id)return null;return!!state.section.get(parentElement.dataset.id).indexcollapsed}async addOverlay(){this.element.classList.add(this.classes.OVERLAYBORDERS)}removeOverlay(){this.element.classList.remove(this.classes.OVERLAYBORDERS)}}return _exports.default=Component,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_sectiontitle=_interopRequireDefault(_sectiontitle),_dndsection=_interopRequireDefault(_dndsection),_log=_interopRequireDefault(_log);class Component extends _dndsection.default{create(){this.name="courseindex_section",this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_item']",SECTION_TITLE:"[data-for='section_title']",CM_LAST:'[data-for="cm"]:last-child',DND_ALLOWED:"[data-courseindexdndallowed='true']"},this.classes={SECTIONHIDDEN:"dimmed",SECTIONCURRENT:"current",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",OVERLAYBORDERS:"overlay-preview-borders"},this.id=this.element.dataset.id,this.isPageItem=!1}static init(target,selectors){let element=document.querySelector(target);return element||(_log.default.debug("Init component with id is deprecated, use a query selector instead."),element=document.getElementById(target)),new this({element:element,selectors:selectors})}stateReady(state){var _section$sectionurl;this.configState(state);const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(this.reactive.isEditing&&this.reactive.supportComponents&&document.querySelector(this.selectors.DND_ALLOWED)){const titleitem=new _sectiontitle.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(titleitem)}const section=state.section.get(this.id);window.location.href==(null===(_section$sectionurl=section.sectionurl)||void 0===_section$sectionurl?void 0:_section$sectionurl.replace(/&/g,"&"))&&(this.reactive.dispatch("setPageItem","section",this.id),sectionItem.scrollIntoView())}getWatchers(){return[{watch:"section[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}getLastCm(){return this.getElement(this.selectors.CM_LAST)}_refreshSection(_ref){var _element$hasrestricti,_element$dragging,_element$locked;let{element:element}=_ref;const sectionItem=this.getElement(this.selectors.SECTION_ITEM);sectionItem.classList.toggle(this.classes.SECTIONHIDDEN,!element.visible),sectionItem.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hasrestricti=element.hasrestrictions)&&void 0!==_element$hasrestricti&&_element$hasrestricti),this.element.classList.toggle(this.classes.SECTIONCURRENT,element.current),this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked,this.getElement(this.selectors.SECTION_TITLE).innerHTML=element.title}_refreshPageItem(_ref2){var _element$pageItem,_this$pageItem;let{element:element,state:state}=_ref2;if(!element.pageItem)return;const containsPageItem=this._isPageItemInThisSection(element.pageItem);if(!containsPageItem||this._isParentSectionIndexCollapsed(state))return this.pageItem=!1,void this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM);!state.section.get(this.id).indexcollapsed||null!==(_element$pageItem=element.pageItem)&&void 0!==_element$pageItem&&_element$pageItem.isStatic?this.pageItem="section"==element.pageItem.type&&element.pageItem.id==this.id:this.pageItem=containsPageItem;this.getElement(this.selectors.SECTION_ITEM).classList.toggle(this.classes.PAGEITEM,null!==(_this$pageItem=this.pageItem)&&void 0!==_this$pageItem&&_this$pageItem),this.pageItem&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}_isPageItemInThisSection(pageItem){if(pageItem.sectionId==this.id)return!0;return!!this.element.querySelector("".concat(this.selectors.SECTION,'[data-id="').concat(pageItem.sectionId,'"]'))}_isParentSectionIndexCollapsed(state){const parentElement=this.element.parentElement.closest(this.selectors.SECTION);if(!parentElement||!parentElement.dataset.id)return null;return!!state.section.get(parentElement.dataset.id).indexcollapsed}async addOverlay(){this.element.classList.add(this.classes.OVERLAYBORDERS)}removeOverlay(){this.element.classList.remove(this.classes.OVERLAYBORDERS)}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=section.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -99,7 +99,8 @@ export default class Component extends DndSection {
|
||||
}
|
||||
// Check if the current url is the section url.
|
||||
const section = state.section.get(this.id);
|
||||
if (window.location.href == section.sectionurl.replace(/&/g, "&")) {
|
||||
// Sections urls may be null.
|
||||
if (window.location.href == section.sectionurl?.replace(/&/g, "&")) {
|
||||
this.reactive.dispatch('setPageItem', 'section', this.id);
|
||||
sectionItem.scrollIntoView();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ class sectionnavigation implements named_templatable, renderable {
|
||||
}
|
||||
$data->previousname = get_section_name($course, $sections[$back]);
|
||||
$data->previousurl = course_get_url($course, $back, ['navigation' => true]);
|
||||
$data->hasprevious = true;
|
||||
// If there is no url for the section the link should not be displayed.
|
||||
$data->hasprevious = !empty($data->previousurl);
|
||||
}
|
||||
$back--;
|
||||
}
|
||||
@@ -115,7 +116,8 @@ class sectionnavigation implements named_templatable, renderable {
|
||||
}
|
||||
$data->nextname = get_section_name($course, $sections[$forward]);
|
||||
$data->nexturl = course_get_url($course, $forward, ['navigation' => true]);
|
||||
$data->hasnext = true;
|
||||
// If there is no url for the section the link should not be displayed.
|
||||
$data->hasnext = !empty($data->nexturl);
|
||||
}
|
||||
$forward++;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,9 @@ class sectionselector implements named_templatable, renderable {
|
||||
nothing: ['' => get_string('jumpto')],
|
||||
);
|
||||
// Disable the current section.
|
||||
$select->set_option_disabled($disabledlink);
|
||||
if (!is_null($disabledlink)) {
|
||||
$select->set_option_disabled($disabledlink);
|
||||
}
|
||||
$select->class = 'jumpmenu';
|
||||
$select->formid = 'sectionmenu';
|
||||
|
||||
@@ -143,17 +145,19 @@ class sectionselector implements named_templatable, renderable {
|
||||
bool $indent = false
|
||||
) {
|
||||
$url = $this->get_section_url($course, $section);
|
||||
$indentation = $indent ? self::INDENTER : '';
|
||||
$this->sectionmenu[$url] = $indentation . $format->get_section_name($section);
|
||||
if (!is_null($url)) {
|
||||
$indentation = $indent ? self::INDENTER : '';
|
||||
$this->sectionmenu[$url] = $indentation . $format->get_section_name($section);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the section url.
|
||||
* @param stdClass $course
|
||||
* @param section_info $section
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_section_url(stdClass $course, section_info $section): string {
|
||||
return course_get_url($course, (object) $section, ['navigation' => true])->out(false);
|
||||
private function get_section_url(stdClass $course, section_info $section): ?string {
|
||||
return course_get_url($course, (object) $section, ['navigation' => true])?->out(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class section implements renderable {
|
||||
'rawtitle' => $section->name,
|
||||
'cmlist' => [],
|
||||
'visible' => !empty($section->visible),
|
||||
'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])->out(false),
|
||||
'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])?->out(false),
|
||||
'current' => $format->is_section_current($section),
|
||||
'indexcollapsed' => $indexcollapsed,
|
||||
'contentcollapsed' => $contentcollapsed,
|
||||
|
||||
@@ -41,7 +41,7 @@ class format_singleactivity extends core_courseformat\base implements core_cours
|
||||
* @param array $options options for view URL. At the moment core uses:
|
||||
* 'navigation' (bool) ignored by this format
|
||||
* 'sr' (int) ignored by this format
|
||||
* @return null|moodle_url
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_url($section, $options = []) {
|
||||
return new moodle_url('/course/view.php', ['id' => $this->courseid]);
|
||||
|
||||
@@ -43,7 +43,7 @@ class format_social extends core_courseformat\base {
|
||||
* @param array $options options for view URL. At the moment core uses:
|
||||
* 'navigation' (bool) ignored by this format
|
||||
* 'sr' (int) ignored by this format
|
||||
* @return null|moodle_url
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_url($section, $options = array()) {
|
||||
return new moodle_url('/course/view.php', ['id' => $this->courseid]);
|
||||
|
||||
@@ -91,14 +91,27 @@
|
||||
<span class="visually-hidden">{{#str}} collapse, core {{/str}}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="{{{sectionurl}}}"
|
||||
class="courseindex-link text-truncate"
|
||||
data-action="togglecourseindexsection"
|
||||
data-for="section_title"
|
||||
tabindex="-1"
|
||||
>
|
||||
{{{title}}}
|
||||
</a>
|
||||
{{! We display a link when there is a sectionurl, otherwise a span. The data-for attribute is required by JS. }}
|
||||
{{#sectionurl}}
|
||||
<a href="{{{sectionurl}}}"
|
||||
class="courseindex-link text-truncate"
|
||||
data-action="togglecourseindexsection"
|
||||
data-for="section_title"
|
||||
tabindex="-1"
|
||||
>
|
||||
{{{title}}}
|
||||
</a>
|
||||
{{/sectionurl}}
|
||||
{{^sectionurl}}
|
||||
<span
|
||||
class="text-truncate"
|
||||
data-action="togglecourseindexsection"
|
||||
data-for="section_title"
|
||||
tabindex="-1"
|
||||
>
|
||||
{{{title}}}
|
||||
</span>
|
||||
{{/sectionurl}}
|
||||
<span class="current-badge badge bg-primary text-white ms-2 px-2 rounded-pill">
|
||||
{{highlighted}}
|
||||
</span>
|
||||
|
||||
@@ -107,7 +107,7 @@ class format_topics extends core_courseformat\base {
|
||||
* @param array $options options for view URL. At the moment core uses:
|
||||
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
|
||||
* 'sr' (int) used by course formats to specify to which section to return
|
||||
* @return null|moodle_url
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_url($section, $options = []) {
|
||||
$course = $this->get_course();
|
||||
|
||||
@@ -124,7 +124,7 @@ class format_weeks extends core_courseformat\base {
|
||||
* @param array $options options for view URL. At the moment core uses:
|
||||
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
|
||||
* 'sr' (int) used by course formats to specify to which section to return
|
||||
* @return null|moodle_url
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_view_url($section, $options = array()) {
|
||||
$course = $this->get_course();
|
||||
|
||||
@@ -2578,7 +2578,7 @@ function get_sorted_course_formats($enabledonly = false) {
|
||||
* @param array $options options for view URL. At the moment core uses:
|
||||
* 'navigation' (bool) if true and section has no separate page, the function returns null
|
||||
* 'sr' (int) used by multipage formats to specify to which section to return
|
||||
* @return moodle_url The url of course
|
||||
* @return moodle_url|null The url of course
|
||||
*/
|
||||
function course_get_url($courseorid, $section = null, $options = array()) {
|
||||
return course_get_format($courseorid)->get_view_url($section, $options);
|
||||
|
||||
Reference in New Issue
Block a user