first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
define("format_topics/mutations",["exports","core_courseformat/courseeditor","core_courseformat/local/courseeditor/mutations","core_courseformat/local/content/actions"],(function(_exports,_courseeditor,_mutations,_actions){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_mutations=_interopRequireDefault(_mutations),_actions=_interopRequireDefault(_actions);class TopicsMutations extends _mutations.default{constructor(){super(...arguments),_defineProperty(this,"sectionHighlight",(async function(stateManager,sectionIds){const logEntry=this._getLoggerEntry(stateManager,"section_highlight",sectionIds,{component:"format_topics"}),course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_highlight",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1),stateManager.addLoggerEntry(await logEntry)})),_defineProperty(this,"sectionUnhighlight",(async function(stateManager,sectionIds){const logEntry=this._getLoggerEntry(stateManager,"section_unhighlight",sectionIds,{component:"format_topics"}),course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_unhighlight",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1),stateManager.addLoggerEntry(await logEntry)}))}}_exports.init=()=>{(0,_courseeditor.getCurrentCourseEditor)().addMutations(new TopicsMutations),_actions.default.addActions({sectionHighlight:"sectionHighlight",sectionUnhighlight:"sectionUnhighlight"})}}));
//# sourceMappingURL=mutations.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"mutations.min.js","sources":["../src/mutations.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Format topics mutations.\n *\n * An instance of this class will be used to add custom mutations to the course editor.\n * To make sure the addMutations method find the proper functions, all functions must\n * be declared as class attributes, not a simple methods. The reason is because many\n * plugins can add extra mutations to the course editor.\n *\n * @module format_topics/mutations\n * @copyright 2022 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseActions from 'core_courseformat/local/content/actions';\n\nclass TopicsMutations extends DefaultMutations {\n\n /**\n * Highlight sections.\n *\n * It is important to note this mutation method is declared as a class attribute,\n * See the class jsdoc for more details on why.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n sectionHighlight = async function(stateManager, sectionIds) {\n const logEntry = this._getLoggerEntry(\n stateManager,\n 'section_highlight',\n sectionIds,\n {component: 'format_topics'}\n );\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_highlight', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n stateManager.addLoggerEntry(await logEntry);\n\n };\n\n /**\n * Unhighlight sections.\n *\n * It is important to note this mutation method is declared as a class attribute,\n * See the class jsdoc for more details on why.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n sectionUnhighlight = async function(stateManager, sectionIds) {\n const logEntry = this._getLoggerEntry(\n stateManager,\n 'section_unhighlight',\n sectionIds,\n {component: 'format_topics'}\n );\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_unhighlight', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n stateManager.addLoggerEntry(await logEntry);\n };\n}\n\nexport const init = () => {\n const courseEditor = getCurrentCourseEditor();\n // Some plugin (activity or block) may have their own mutations already registered.\n // This is why we use addMutations instead of setMutations here.\n courseEditor.addMutations(new TopicsMutations());\n // Add direct mutation content actions.\n CourseActions.addActions({\n sectionHighlight: 'sectionHighlight',\n sectionUnhighlight: 'sectionUnhighlight',\n });\n};\n"],"names":["TopicsMutations","DefaultMutations","async","stateManager","sectionIds","logEntry","this","_getLoggerEntry","component","course","get","sectionLock","updates","_callEditWebservice","id","processUpdates","addLoggerEntry","addMutations","addActions","sectionHighlight","sectionUnhighlight"],"mappings":"goBAgCMA,wBAAwBC,8FAWPC,eAAeC,aAAcC,kBACtCC,SAAWC,KAAKC,gBAClBJ,aACA,oBACAC,WACA,CAACI,UAAW,kBAEVC,OAASN,aAAaO,IAAI,eAC3BC,YAAYR,aAAcC,YAAY,SACrCQ,cAAgBN,KAAKO,oBAAoB,oBAAqBJ,OAAOK,GAAIV,YAC/ED,aAAaY,eAAeH,cACvBD,YAAYR,aAAcC,YAAY,GAC3CD,aAAaa,qBAAqBX,wDAajBH,eAAeC,aAAcC,kBACxCC,SAAWC,KAAKC,gBAClBJ,aACA,sBACAC,WACA,CAACI,UAAW,kBAEVC,OAASN,aAAaO,IAAI,eAC3BC,YAAYR,aAAcC,YAAY,SACrCQ,cAAgBN,KAAKO,oBAAoB,sBAAuBJ,OAAOK,GAAIV,YACjFD,aAAaY,eAAeH,cACvBD,YAAYR,aAAcC,YAAY,GAC3CD,aAAaa,qBAAqBX,4BAItB,MACK,0CAGRY,aAAa,IAAIjB,kCAEhBkB,WAAW,CACrBC,iBAAkB,mBAClBC,mBAAoB"}
+10
View File
@@ -0,0 +1,10 @@
define("format_topics/section",["exports","core/reactive","core_courseformat/courseeditor","core/templates"],(function(_exports,_reactive,_courseeditor,_templates){var obj;
/**
* Format topics section extra logic component.
*
* @module format_topics/section
* @copyright 2022 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.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};class HighlightSection extends _reactive.BaseComponent{create(){this.name="format_topics_section",this.selectors={SECTION:"[data-for='section']",SETMARKER:'[data-action="sectionHighlight"]',REMOVEMARKER:'[data-action="sectionUnhighlight"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={HIDE:"d-none"},this.formatActions={HIGHLIGHT:"sectionHighlight",UNHIGHLIGHT:"sectionUnhighlight"}}getWatchers(){return[{watch:"section.current:updated",handler:this._refreshHighlight}]}async _refreshHighlight(_ref){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction,{element:element}=_ref;element.current?(selector=this.selectors.SETMARKER,newAction=this.formatActions.UNHIGHLIGHT):(selector=this.selectors.REMOVEMARKER,newAction=this.formatActions.HIGHLIGHT);const affectedAction=this.getElement("".concat(this.selectors.SECTION," ").concat(selector),element.id);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,""),affectedAction.dataset.swapicon=affectedAction.dataset.icon,affectedAction.dataset.icon=newIcon}}}}_exports.init=()=>{const courseEditor=(0,_courseeditor.getCurrentCourseEditor)();courseEditor.supportComponents&&courseEditor.isEditing&&new HighlightSection({element:document.getElementById("region-main"),reactive:courseEditor})}}));
//# sourceMappingURL=section.min.js.map
File diff suppressed because one or more lines are too long
+95
View File
@@ -0,0 +1,95 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Format topics mutations.
*
* An instance of this class will be used to add custom mutations to the course editor.
* To make sure the addMutations method find the proper functions, all functions must
* be declared as class attributes, not a simple methods. The reason is because many
* plugins can add extra mutations to the course editor.
*
* @module format_topics/mutations
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import DefaultMutations from 'core_courseformat/local/courseeditor/mutations';
import CourseActions from 'core_courseformat/local/content/actions';
class TopicsMutations extends DefaultMutations {
/**
* Highlight sections.
*
* It is important to note this mutation method is declared as a class attribute,
* See the class jsdoc for more details on why.
*
* @param {StateManager} stateManager the current state manager
* @param {array} sectionIds the list of section ids
*/
sectionHighlight = async function(stateManager, sectionIds) {
const logEntry = this._getLoggerEntry(
stateManager,
'section_highlight',
sectionIds,
{component: 'format_topics'}
);
const course = stateManager.get('course');
this.sectionLock(stateManager, sectionIds, true);
const updates = await this._callEditWebservice('section_highlight', course.id, sectionIds);
stateManager.processUpdates(updates);
this.sectionLock(stateManager, sectionIds, false);
stateManager.addLoggerEntry(await logEntry);
};
/**
* Unhighlight sections.
*
* It is important to note this mutation method is declared as a class attribute,
* See the class jsdoc for more details on why.
*
* @param {StateManager} stateManager the current state manager
* @param {array} sectionIds the list of section ids
*/
sectionUnhighlight = async function(stateManager, sectionIds) {
const logEntry = this._getLoggerEntry(
stateManager,
'section_unhighlight',
sectionIds,
{component: 'format_topics'}
);
const course = stateManager.get('course');
this.sectionLock(stateManager, sectionIds, true);
const updates = await this._callEditWebservice('section_unhighlight', course.id, sectionIds);
stateManager.processUpdates(updates);
this.sectionLock(stateManager, sectionIds, false);
stateManager.addLoggerEntry(await logEntry);
};
}
export const init = () => {
const courseEditor = getCurrentCourseEditor();
// Some plugin (activity or block) may have their own mutations already registered.
// This is why we use addMutations instead of setMutations here.
courseEditor.addMutations(new TopicsMutations());
// Add direct mutation content actions.
CourseActions.addActions({
sectionHighlight: 'sectionHighlight',
sectionUnhighlight: 'sectionUnhighlight',
});
};
+117
View File
@@ -0,0 +1,117 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Format topics section extra logic component.
*
* @module format_topics/section
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {BaseComponent} from 'core/reactive';
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import Templates from 'core/templates';
class HighlightSection extends BaseComponent {
/**
* Constructor hook.
*/
create() {
// Optional component name for debugging.
this.name = 'format_topics_section';
// Default query selectors.
this.selectors = {
SECTION: `[data-for='section']`,
SETMARKER: `[data-action="sectionHighlight"]`,
REMOVEMARKER: `[data-action="sectionUnhighlight"]`,
ACTIONTEXT: `.menu-action-text`,
ICON: `.icon`,
};
// Default classes to toggle on refresh.
this.classes = {
HIDE: 'd-none',
};
// The topics format section specific actions.
this.formatActions = {
HIGHLIGHT: 'sectionHighlight',
UNHIGHLIGHT: 'sectionUnhighlight',
};
}
/**
* Component watchers.
*
* @returns {Array} of watchers
*/
getWatchers() {
return [
{watch: `section.current:updated`, handler: this._refreshHighlight},
];
}
/**
* Update a content section using the state information.
*
* @param {object} param
* @param {Object} param.element details the update details.
*/
async _refreshHighlight({element}) {
let selector;
let newAction;
if (element.current) {
selector = this.selectors.SETMARKER;
newAction = this.formatActions.UNHIGHLIGHT;
} else {
selector = this.selectors.REMOVEMARKER;
newAction = this.formatActions.HIGHLIGHT;
}
// Find the affected action.
const affectedAction = this.getElement(`${this.selectors.SECTION} ${selector}`, element.id);
if (!affectedAction) {
return;
}
// Change action, text and icon.
affectedAction.dataset.action = newAction;
const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);
if (affectedAction.dataset?.swapname && actionText) {
const oldText = actionText?.innerText;
actionText.innerText = affectedAction.dataset.swapname;
affectedAction.dataset.swapname = oldText;
}
const icon = affectedAction.querySelector(this.selectors.ICON);
if (affectedAction.dataset?.swapicon && icon) {
const newIcon = affectedAction.dataset.swapicon;
if (newIcon) {
const pixHtml = await Templates.renderPix(newIcon, 'core');
Templates.replaceNode(icon, pixHtml, '');
affectedAction.dataset.swapicon = affectedAction.dataset.icon;
affectedAction.dataset.icon = newIcon;
}
}
}
}
export const init = () => {
// Add component to the section.
const courseEditor = getCurrentCourseEditor();
if (courseEditor.supportComponents && courseEditor.isEditing) {
new HighlightSection({
element: document.getElementById('region-main'),
reactive: courseEditor,
});
}
};
@@ -0,0 +1,125 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Specialised restore for Topics course format.
*
* @package format_topics
* @category backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Specialised restore for Topics course format.
*
* Processes 'numsections' from the old backup files and hides sections that used to be "orphaned".
*
* @package format_topics
* @category backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_format_topics_plugin extends restore_format_plugin {
/** @var int */
protected $originalnumsections = 0;
/**
* Checks if backup file was made on Moodle before 3.3 and we should respect the 'numsections'
* and potential "orphaned" sections in the end of the course.
*
* @return bool
*/
protected function need_restore_numsections() {
$backupinfo = $this->step->get_task()->get_info();
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
return version_compare($backuprelease, '3.3', '<');
}
/**
* Creates a dummy path element in order to be able to execute code after restore.
*
* @return restore_path_element[]
*/
public function define_course_plugin_structure() {
global $DB;
// Since this method is executed before the restore we can do some pre-checks here.
// In case of merging backup into existing course find the current number of sections.
$target = $this->step->get_task()->get_target();
if (($target == backup::TARGET_CURRENT_ADDING || $target == backup::TARGET_EXISTING_ADDING) &&
$this->need_restore_numsections()) {
$maxsection = $DB->get_field_sql(
'SELECT max(section) FROM {course_sections} WHERE course = ?',
[$this->step->get_task()->get_courseid()]);
$this->originalnumsections = (int)$maxsection;
}
// Dummy path element is needed in order for after_restore_course() to be called.
return [new restore_path_element('dummy_course', $this->get_pathfor('/dummycourse'))];
}
/**
* Dummy process method.
*
* @return void
*/
public function process_dummy_course() {
}
/**
* Executed after course restore is complete.
*
* This method is only executed if course configuration was overridden.
*
* @return void
*/
public function after_restore_course() {
global $DB;
if (!$this->need_restore_numsections()) {
// Backup file was made in Moodle 3.3 or later, we don't need to process 'numsecitons'.
return;
}
$data = $this->connectionpoint->get_data();
$backupinfo = $this->step->get_task()->get_info();
if ($backupinfo->original_course_format !== 'topics' || !isset($data['tags']['numsections'])) {
// Backup from another course format or backup file does not even have 'numsections'.
return;
}
$numsections = (int)$data['tags']['numsections'];
foreach ($backupinfo->sections as $key => $section) {
// For each section from the backup file check if it was restored and if was "orphaned" in the original
// course and mark it as hidden. This will leave all activities in it visible and available just as it was
// in the original course.
// Exception is when we restore with merging and the course already had a section with this section number,
// in this case we don't modify the visibility.
if ($this->step->get_task()->get_setting_value($key . '_included')) {
$sectionnum = (int)$section->title;
if ($sectionnum > $numsections && $sectionnum > $this->originalnumsections) {
$DB->execute("UPDATE {course_sections} SET visible = 0 WHERE course = ? AND section = ?",
[$this->step->get_task()->get_courseid(), $sectionnum]);
}
}
}
}
}
@@ -0,0 +1,117 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace format_topics\courseformat;
use core_courseformat\stateupdates;
use core_courseformat\stateactions as stateactions_base;
use core\event\course_module_updated;
use cm_info;
use section_info;
use stdClass;
use course_modinfo;
use moodle_exception;
use context_module;
use context_course;
/**
* Contains the core course state actions specific to topics format.
*
* @package format_topics
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stateactions extends stateactions_base {
/**
* Highlight course section.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids section ids (only ther first one will be highlighted)
* @param int $targetsectionid not used
* @param int $targetcmid not used
*/
public function section_highlight(
stateupdates $updates,
stdClass $course,
array $ids = [],
?int $targetsectionid = null,
?int $targetcmid = null
): void {
global $DB;
$this->validate_sections($course, $ids, __FUNCTION__);
$coursecontext = context_course::instance($course->id);
require_capability('moodle/course:setcurrentsection', $coursecontext);
// Get the previous marked section.
$modinfo = get_fast_modinfo($course);
$previousmarker = $DB->get_field("course", "marker", ['id' => $course->id]);
$section = $modinfo->get_section_info_by_id(reset($ids), MUST_EXIST);
if ($section->section == $previousmarker) {
return;
}
// Mark the new one.
course_set_marker($course->id, $section->section);
$updates->add_section_put($section->id);
if ($previousmarker) {
$section = $modinfo->get_section_info($previousmarker);
$updates->add_section_put($section->id);
}
}
/**
* Remove highlight from a course sections.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids optional extra section ids to refresh
* @param int $targetsectionid not used
* @param int $targetcmid not used
*/
public function section_unhighlight(
stateupdates $updates,
stdClass $course,
array $ids = [],
?int $targetsectionid = null,
?int $targetcmid = null
): void {
global $DB;
$this->validate_sections($course, $ids, __FUNCTION__);
$coursecontext = context_course::instance($course->id);
require_capability('moodle/course:setcurrentsection', $coursecontext);
$affectedsections = [];
// Get the previous marked section and unmark it.
$modinfo = get_fast_modinfo($course);
$previousmarker = $DB->get_field("course", "marker", ['id' => $course->id]);
course_set_marker($course->id, 0);
$section = $modinfo->get_section_info($previousmarker, MUST_EXIST);
$updates->add_section_put($section->id);
foreach ($ids as $sectionid) {
$section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST);
if ($section->section != $previousmarker) {
$updates->add_section_put($section->id);
}
}
}
}
@@ -0,0 +1,57 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Contains the default content output class.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_topics\output\courseformat;
use core_courseformat\output\local\content as content_base;
use renderer_base;
/**
* Base class to render a course content.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content extends content_base {
/**
* @var bool Topic format has also add section after each topic.
*/
protected $hasaddsection = true;
/**
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
$PAGE->requires->js_call_amd('format_topics/mutations', 'init');
$PAGE->requires->js_call_amd('format_topics/section', 'init');
return parent::export_for_template($output);
}
}
@@ -0,0 +1,57 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Contains the default section controls output class.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_topics\output\courseformat\content;
use core_courseformat\base as course_format;
use core_courseformat\output\local\content\section as section_base;
use stdClass;
/**
* Base class to render a course section.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class section extends section_base {
/** @var course_format the course format */
protected $format;
public function export_for_template(\renderer_base $output): stdClass {
$format = $this->format;
$data = parent::export_for_template($output);
if (!$this->format->get_sectionnum()) {
$addsectionclass = $format->get_output_classname('content\\addsection');
$addsection = new $addsectionclass($format);
$data->numsections = $addsection->export_for_template($output);
$data->insertafter = true;
}
return $data;
}
}
@@ -0,0 +1,162 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Contains the default section controls output class.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_topics\output\courseformat\content\section;
use core_courseformat\output\local\content\section\controlmenu as controlmenu_base;
use moodle_url;
/**
* Base class to render a course section menu.
*
* @package format_topics
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class controlmenu extends controlmenu_base {
/** @var \core_courseformat\base the course format class */
protected $format;
/** @var \section_info the course section class */
protected $section;
/**
* Generate the edit control items of a section.
*
* This method must remain public until the final deprecation of section_edit_control_items.
*
* @return array of edit control items
*/
public function section_control_items() {
$format = $this->format;
$section = $this->section;
$coursecontext = $format->get_context();
$controls = [];
if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) {
$controls['highlight'] = $this->get_highlight_control();
}
$parentcontrols = parent::section_control_items();
// If the edit key exists, we are going to insert our controls after it.
if (array_key_exists("edit", $parentcontrols)) {
$merged = [];
// We can't use splice because we are using associative arrays.
// Step through the array and merge the arrays.
foreach ($parentcontrols as $key => $action) {
$merged[$key] = $action;
if ($key == "edit") {
// If we have come to the edit key, merge these controls here.
$merged = array_merge($merged, $controls);
}
}
return $merged;
} else {
return array_merge($controls, $parentcontrols);
}
}
/**
* Return the course url.
*
* @return moodle_url
*/
protected function get_course_url(): moodle_url {
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$sectionreturn = $format->get_sectionnum();
if ($sectionreturn) {
$url = course_get_url($course, $section->section);
} else {
$url = course_get_url($course);
}
$url->param('sesskey', sesskey());
return $url;
}
/**
* Return the specific section highlight action.
*
* @return array the action element.
*/
protected function get_highlight_control(): array {
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$sectionreturn = $format->get_sectionnum();
$url = $this->get_course_url();
if (!is_null($sectionreturn)) {
$url->param('sectionid', $format->get_sectionid());
}
$highlightoff = get_string('highlightoff');
$highlightofficon = 'i/marked';
$highlighton = get_string('highlight');
$highlightonicon = 'i/marker';
if ($course->marker == $section->section) { // Show the "light globe" on/off.
$url->param('marker', 0);
$result = [
'url' => $url,
'icon' => $highlightofficon,
'name' => $highlightoff,
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'editing_highlight',
'data-action' => 'sectionUnhighlight',
'data-sectionreturn' => $sectionreturn,
'data-id' => $section->id,
'data-icon' => $highlightofficon,
'data-swapname' => $highlighton,
'data-swapicon' => $highlightonicon,
],
];
} else {
$url->param('marker', $section->section);
$result = [
'url' => $url,
'icon' => $highlightonicon,
'name' => $highlighton,
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'editing_highlight',
'data-action' => 'sectionHighlight',
'data-sectionreturn' => $sectionreturn,
'data-id' => $section->id,
'data-icon' => $highlightonicon,
'data-swapname' => $highlightoff,
'data-swapicon' => $highlightofficon,
],
];
}
return $result;
}
}
@@ -0,0 +1,66 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace format_topics\output;
use core_courseformat\output\section_renderer;
use moodle_page;
/**
* Basic renderer for topics format.
*
* @copyright 2012 Dan Poltawski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends section_renderer {
/**
* Constructor method, calls the parent constructor.
*
* @param moodle_page $page
* @param string $target one of rendering target constants
*/
public function __construct(moodle_page $page, $target) {
parent::__construct($page, $target);
// Since format_topics_renderer::section_edit_control_items() only displays the 'Highlight' control
// when editing mode is on we need to be sure that the link 'Turn editing mode on' is available for a user
// who does not have any other managing capability.
$page->set_other_editing_capability('moodle/course:setcurrentsection');
}
/**
* Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page.
*
* @param section_info|stdClass $section The course_section entry from DB
* @param stdClass $course The course entry from DB
* @return string HTML to output.
*/
public function section_title($section, $course) {
return $this->render(course_get_format($course)->inplace_editable_render_section_name($section));
}
/**
* Generate the section title to be displayed on the section page, without a link.
*
* @param section_info|stdClass $section The course_section entry from DB
* @param int|stdClass $course The course entry from DB
* @return string HTML to output.
*/
public function section_title_without_link($section, $course) {
return $this->render(course_get_format($course)->inplace_editable_render_section_name($section, false));
}
}
@@ -0,0 +1,48 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for Topics course format.
*
* @package format_topics
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_topics\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\null_provider;
/**
* Privacy Subsystem for Topics course format implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Upgrade scripts for Topics course format.
*
* @package format_topics
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade script for Topics course format.
*
* @param int|float $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_format_topics_upgrade($oldversion) {
global $DB;
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2023030700) {
// For sites migrating from 4.0.x or 4.1.x where the indentation was removed,
// we are disabling 'indentation' value by default.
if ($oldversion >= 2022041900) {
set_config('indentation', 0, 'format_topics');
} else {
set_config('indentation', 1, 'format_topics');
}
upgrade_plugin_savepoint(true, 2023030700, 'format', 'topics');
}
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2023100901) {
// During the migration to version 4.4, ensure that sections with null names are renamed to their corresponding
// previous 'Topic X' for continuity.
$newsectionname = $DB->sql_concat("'Topic '", 'section');
$sql = <<<EOF
UPDATE {course_sections}
SET name = $newsectionname
WHERE section > 0 AND (name IS NULL OR name = '')
AND course IN (SELECT id FROM {course} WHERE format = 'topics')
EOF;
$DB->execute(
sql: $sql,
);
// Main savepoint reached.
upgrade_plugin_savepoint(true, 2023100901, 'format', 'topics');
}
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+60
View File
@@ -0,0 +1,60 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Topics course format. Display the whole course as "topics" made of modules.
*
* @package format_topics
* @copyright 2006 The Open University
* @author N.D.Freear@open.ac.uk, and others.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/filelib.php');
require_once($CFG->libdir.'/completionlib.php');
// Horrible backwards compatible parameter aliasing.
if ($topic = optional_param('topic', 0, PARAM_INT)) {
$url = $PAGE->url;
$url->param('section', $topic);
debugging('Outdated topic param passed to course/view.php', DEBUG_DEVELOPER);
redirect($url);
}
// End backwards-compatible aliasing.
// Retrieve course format option fields and add them to the $course object.
$format = course_get_format($course);
$course = $format->get_course();
$context = context_course::instance($course->id);
if (($marker >= 0) && has_capability('moodle/course:setcurrentsection', $context) && confirm_sesskey()) {
$course->marker = $marker;
course_set_marker($course->id, $marker);
}
// Make sure section 0 is created.
course_create_sections_if_missing($course, 0);
$renderer = $PAGE->get_renderer('format_topics');
if (!is_null($displaysection)) {
$format->set_sectionnum($displaysection);
}
$outputclass = $format->get_output_classname('content');
$widget = new $outputclass($format);
echo $renderer->render($widget);
@@ -0,0 +1,40 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Strings for component Custom sections course format.
*
* @package format_topics
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['currentsection'] = 'Current section';
$string['hidefromothers'] = 'Hide';
$string['legacysectionname'] = 'Topic';
$string['newsection'] = 'New section';
$string['page-course-view-topics'] = 'Any course main page in custom sections format';
$string['page-course-view-topics-x'] = 'Any course page in custom sections format';
$string['pluginname'] = 'Custom sections';
$string['plugin_description'] = 'The course is divided into customisable sections.';
$string['privacy:metadata'] = 'The Custom sections format plugin does not store any personal data.';
$string['indentation'] = 'Allow indentation on course page';
$string['indentation_help'] = 'Allow teachers, and other users with the manage activities capability, to indent items on the course page.';
$string['section_highlight_feedback'] = 'Section {$a->name} highlighted.';
$string['section_unhighlight_feedback'] = 'Highlighting removed from section {$a->name}.';
$string['section0name'] = 'General';
$string['sectionname'] = 'Section';
$string['showfromothers'] = 'Show';
+444
View File
@@ -0,0 +1,444 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* This file contains main class for Topics course format.
*
* @since Moodle 2.0
* @package format_topics
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot. '/course/format/lib.php');
use core\output\inplace_editable;
/**
* Main class for the Topics course format.
*
* @package format_topics
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format_topics extends core_courseformat\base {
/**
* Returns true if this course format uses sections.
*
* @return bool
*/
public function uses_sections() {
return true;
}
public function uses_course_index() {
return true;
}
public function uses_indentation(): bool {
return (get_config('format_topics', 'indentation')) ? true : false;
}
/**
* Returns the display name of the given section that the course prefers.
*
* Use section name is specified by user. Otherwise use default ("Topic #").
*
* @param int|stdClass $section Section object from database or just field section.section
* @return string Display name that the course format prefers, e.g. "Topic 2"
*/
public function get_section_name($section) {
$section = $this->get_section($section);
if ((string)$section->name !== '') {
return format_string($section->name, true,
['context' => context_course::instance($this->courseid)]);
} else {
return $this->get_default_section_name($section);
}
}
/**
* Returns the default section name for the topics course format.
*
* If the section number is 0, it will use the string with key = section0name from the course format's lang file.
* If the section number is not 0, it will consistently return the name 'newsection', disregarding the specific section number.
*
* @param int|stdClass $section Section object from database or just field course_sections section
* @return string The default value for the section name.
*/
public function get_default_section_name($section) {
$section = $this->get_section($section);
if ($section->sectionnum == 0) {
return get_string('section0name', 'format_topics');
}
return get_string('newsection', 'format_topics');
}
/**
* Generate the title for this section page.
*
* @return string the page title
*/
public function page_title(): string {
return get_string('sectionoutline');
}
/**
* The URL to use for the specified course (with section).
*
* @param int|stdClass $section Section object from database or just field course_sections.section
* if omitted the course view page is returned
* @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
*/
public function get_view_url($section, $options = []) {
$course = $this->get_course();
if (array_key_exists('sr', $options) && !is_null($options['sr'])) {
$sectionno = $options['sr'];
} else if (is_object($section)) {
$sectionno = $section->section;
} else {
$sectionno = $section;
}
if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {
// Display section on separate page.
$sectioninfo = $this->get_section($sectionno);
return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);
}
return new moodle_url('/course/view.php', ['id' => $course->id]);
}
/**
* Returns the information about the ajax support in the given source format.
*
* The returned object's property (boolean)capable indicates that
* the course format supports Moodle course ajax features.
*
* @return stdClass
*/
public function supports_ajax() {
$ajaxsupport = new stdClass();
$ajaxsupport->capable = true;
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation.
*
* @param global_navigation $navigation
* @param navigation_node $node The course node within the navigation
* @return void
*/
public function extend_course_navigation($navigation, navigation_node $node) {
global $PAGE;
// If section is specified in course/view.php, make sure it is expanded in navigation.
if ($navigation->includesectionnum === false) {
$selectedsection = optional_param('section', null, PARAM_INT);
if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
$PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
$navigation->includesectionnum = $selectedsection;
}
}
// Check if there are callbacks to extend course navigation.
parent::extend_course_navigation($navigation, $node);
// We want to remove the general section if it is empty.
$modinfo = get_fast_modinfo($this->get_course());
$sections = $modinfo->get_sections();
if (!isset($sections[0])) {
// The general section is empty to find the navigation node for it we need to get its ID.
$section = $modinfo->get_section_info(0);
$generalsection = $node->get($section->id, navigation_node::TYPE_SECTION);
if ($generalsection) {
// We found the node - now remove it.
$generalsection->remove();
}
}
}
/**
* Custom action after section has been moved in AJAX mode.
*
* Used in course/rest.php
*
* @return array This will be passed in ajax respose
*/
public function ajax_section_move() {
global $PAGE;
$titles = [];
$course = $this->get_course();
$modinfo = get_fast_modinfo($course);
$renderer = $this->get_renderer($PAGE);
if ($renderer && ($sections = $modinfo->get_section_info_all())) {
foreach ($sections as $number => $section) {
$titles[$number] = $renderer->section_title($section, $course);
}
}
return ['sectiontitles' => $titles, 'action' => 'move'];
}
/**
* Returns the list of blocks to be automatically added for the newly created course.
*
* @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
* each of values is an array of block names (for left and right side columns)
*/
public function get_default_blocks() {
return [
BLOCK_POS_LEFT => [],
BLOCK_POS_RIGHT => [],
];
}
/**
* Definitions of the additional options that this course format uses for course.
*
* Topics format uses the following options:
* - coursedisplay
* - hiddensections
*
* @param bool $foreditform
* @return array of options
*/
public function course_format_options($foreditform = false) {
static $courseformatoptions = false;
if ($courseformatoptions === false) {
$courseconfig = get_config('moodlecourse');
$courseformatoptions = [
'hiddensections' => [
'default' => $courseconfig->hiddensections,
'type' => PARAM_INT,
],
'coursedisplay' => [
'default' => $courseconfig->coursedisplay,
'type' => PARAM_INT,
],
];
}
if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) {
$courseformatoptionsedit = [
'hiddensections' => [
'label' => new lang_string('hiddensections'),
'help' => 'hiddensections',
'help_component' => 'moodle',
'element_type' => 'select',
'element_attributes' => [
[
0 => new lang_string('hiddensectionscollapsed'),
1 => new lang_string('hiddensectionsinvisible')
],
],
],
'coursedisplay' => [
'label' => new lang_string('coursedisplay'),
'element_type' => 'select',
'element_attributes' => [
[
COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'),
COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi'),
],
],
'help' => 'coursedisplay',
'help_component' => 'moodle',
],
];
$courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
}
return $courseformatoptions;
}
/**
* Adds format options elements to the course/section edit form.
*
* This function is called from {@link course_edit_form::definition_after_data()}.
*
* @param MoodleQuickForm $mform form the elements are added to.
* @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form.
* @return array array of references to the added form elements.
*/
public function create_edit_form_elements(&$mform, $forsection = false) {
global $COURSE;
$elements = parent::create_edit_form_elements($mform, $forsection);
if (!$forsection && (empty($COURSE->id) || $COURSE->id == SITEID)) {
// Add "numsections" element to the create course form - it will force new course to be prepopulated
// with empty sections.
// The "Number of sections" option is no longer available when editing course, instead teachers should
// delete and add sections when needed.
$courseconfig = get_config('moodlecourse');
$max = (int)$courseconfig->maxsections;
$element = $mform->addElement('select', 'numsections', get_string('numberweeks'), range(0, $max ?: 52));
$mform->setType('numsections', PARAM_INT);
if (is_null($mform->getElementValue('numsections'))) {
$mform->setDefault('numsections', $courseconfig->numsections);
}
array_unshift($elements, $element);
}
return $elements;
}
/**
* Updates format options for a course.
*
* In case if course format was changed to 'topics', we try to copy options
* 'coursedisplay' and 'hiddensections' from the previous format.
*
* @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
* @param stdClass $oldcourse if this function is called from {@link update_course()}
* this object contains information about the course before update
* @return bool whether there were any changes to the options values
*/
public function update_course_format_options($data, $oldcourse = null) {
$data = (array)$data;
if ($oldcourse !== null) {
$oldcourse = (array)$oldcourse;
$options = $this->course_format_options();
foreach ($options as $key => $unused) {
if (!array_key_exists($key, $data)) {
if (array_key_exists($key, $oldcourse)) {
$data[$key] = $oldcourse[$key];
}
}
}
}
return $this->update_format_options($data);
}
/**
* Whether this format allows to delete sections.
*
* Do not call this function directly, instead use {@link course_can_delete_section()}
*
* @param int|stdClass|section_info $section
* @return bool
*/
public function can_delete_section($section) {
return true;
}
/**
* Indicates whether the course format supports the creation of a news forum.
*
* @return bool
*/
public function supports_news() {
return true;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
// Allow the third visibility state inside visible sections or in section 0.
return !$section->section || $section->visible;
}
/**
* Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide).
*
* Access to the course is already validated in the WS but the callback has to make sure
* that particular action is allowed by checking capabilities
*
* Course formats should register.
*
* @param section_info|stdClass $section
* @param string $action
* @param int $sr
* @return null|array any data for the Javascript post-processor (must be json-encodeable)
*/
public function section_action($section, $action, $sr) {
global $PAGE;
if ($section->section && ($action === 'setmarker' || $action === 'removemarker')) {
// Format 'topics' allows to set and remove markers in addition to common section actions.
require_capability('moodle/course:setcurrentsection', context_course::instance($this->courseid));
course_set_marker($this->courseid, ($action === 'setmarker') ? $section->section : 0);
return null;
}
// For show/hide actions call the parent method and return the new content for .section_availability element.
$rv = parent::section_action($section, $action, $sr);
$renderer = $PAGE->get_renderer('format_topics');
if (!($section instanceof section_info)) {
$modinfo = course_modinfo::instance($this->courseid);
$section = $modinfo->get_section_info($section->section);
}
$elementclass = $this->get_output_classname('content\\section\\availability');
$availability = new $elementclass($this, $section);
$rv['section_availability'] = $renderer->render($availability);
return $rv;
}
/**
* Return the plugin configs for external functions.
*
* @return array the list of configuration settings
* @since Moodle 3.5
*/
public function get_config_for_external() {
// Return everything (nothing to hide).
$formatoptions = $this->get_format_options();
$formatoptions['indentation'] = get_config('format_topics', 'indentation');
return $formatoptions;
}
/**
* Get the required javascript files for the course format.
*
* @return array The list of javascript files required by the course format.
*/
public function get_required_jsfiles(): array {
return [];
}
}
/**
* Implements callback inplace_editable() allowing to edit values in-place.
*
* @param string $itemtype
* @param int $itemid
* @param mixed $newvalue
* @return inplace_editable
*/
function format_topics_inplace_editable($itemtype, $itemid, $newvalue) {
global $DB, $CFG;
require_once($CFG->dirroot . '/course/lib.php');
if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
$section = $DB->get_record_sql(
'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?',
[$itemid, 'topics'], MUST_EXIST);
return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Settings for format_topics
*
* @package format_topics
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
$url = new moodle_url('/admin/course/resetindentation.php', ['format' => 'topics']);
$link = html_writer::link($url, get_string('resetindentation', 'admin'));
$settings->add(new admin_setting_configcheckbox(
'format_topics/indentation',
new lang_string('indentation', 'format_topics'),
new lang_string('indentation_help', 'format_topics').'<br />'.$link,
1
));
}
@@ -0,0 +1,118 @@
@format @format_topics
Feature: Sections can be edited and deleted in custom sections format
In order to rearrange my course contents
As a teacher
I need to edit and delete sections
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format | coursedisplay | numsections | initsections |
| Course 1 | C1 | topics | 0 | 5 | 1 |
| Course 2 | C2 | topics | 0 | 1 | 0 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Test assignment name | Test assignment description | C1 | assign1 | 0 |
| book | Test book name | | C1 | book1 | 1 |
| lesson | Test lesson name | Test lesson description | C1 | lesson1 | 4 |
| choice | Test choice name | Test choice description | C1 | choice1 | 5 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And I log in as "teacher1"
Scenario: View the default name of the general section in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I edit the section "0"
Then the field "Section name" matches value ""
And I should see "General"
Scenario: Edit the default name of the general section in custom sections format
Given I am on "Course 1" course homepage with editing mode on
And I should see "General" in the "General" "section"
When I edit the section "0" and I fill the form with:
| Section name | This is the general section |
Then I should see "This is the general section" in the "page" "region"
Scenario: View the default name of the second section in custom sections format
Given I am on "Course 2" course homepage with editing mode on
When I edit the section "1"
Then the field "Section name" matches value ""
And I should see "New section"
Scenario: Edit section summary in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I edit the section "2" and I fill the form with:
| Description | Welcome to section 2 |
Then I should see "Welcome to section 2" in the "page" "region"
Scenario: Edit section default name in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I edit the section "2" and I fill the form with:
| Section name | This is the second section |
Then I should see "This is the second section" in the "page" "region"
And I should not see "Section 2" in the "region-main" "region"
@javascript
Scenario: Inline edit section name in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I set the field "Edit section name" in the "Section 1" "section" to "Midterm evaluation"
Then I should not see "Section 1" in the "region-main" "region"
And "New name for section" "field" should not exist
And I should see "Midterm evaluation" in the "Midterm evaluation" "section"
And I am on "Course 1" course homepage
And I should not see "Section 1" in the "region-main" "region"
And I should see "Midterm evaluation" in the "Midterm evaluation" "section"
Scenario: Deleting the last section in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I delete section "5"
Then I should see "Are you absolutely sure you want to completely delete \"Section 5\" and all the activities it contains?"
And I press "Delete"
And I should not see "Section 5"
And I should see "Section 4"
Scenario: Deleting the middle section in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I delete section "4"
And I press "Delete"
Then I should not see "Section 4"
And I should see "Section 5"
And I should not see "Test lesson name"
And I should see "Test choice name" in the "Section 5" "section"
@javascript
Scenario: Adding sections at the end of a custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I click on "Add section" "link" in the "course-addsection" "region"
Then I should see "New section" in the "New section" "section"
And I should see "Test choice name" in the "Section 5" "section"
@javascript
Scenario: Adding sections between in custom sections format
Given I am on "Course 1" course homepage with editing mode on
When I hover over the "Add section" "link" in the "Section 4" "section"
And I click on "Add section" "link" in the "Section 4" "section"
Then I should see "New section" in the "New section" "section"
And I should see "Test choice name" in the "Section 5" "section"
And I should not see "Test choice name" in the "New section" "section"
@javascript
Scenario: Add a section and then add an activity in it
Given I am on "Course 1" course homepage with editing mode on
When I click on "Add section" "link" in the "course-addsection" "region"
And I add an assign activity to course "Course 1" section "6" and I fill the form with:
| Assignment name | Very new activity |
| Description | Test |
Then I should see "Very new activity" in the "New section" "section"
@javascript
Scenario: Copy section permalink URL to clipboard
Given I am on "Course 1" course homepage with editing mode on
When I open section "1" edit menu
And I click on "Permalink" "link" in the "Section 1" "section"
And I click on "Copy to clipboard" "link" in the "Permalink" "dialogue"
Then I should see "Text copied to clipboard"
@@ -0,0 +1,53 @@
@format @format_topics
Feature: Sections can be highlighted
In order to mark sections
As a teacher
I need to highlight and unhighlight sections
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| format | topics |
| coursedisplay | 0 |
| numsections | 5 |
| initsections | 1 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Test assignment name | Test assignment description | C1 | assign1 | 0 |
| book | Test book name | Test book description | C1 | book1 | 1 |
| lesson | Test lesson name | Test lesson description | C1 | lesson1 | 4 |
| choice | Test choice name | Test choice description | C1 | choice1 | 5 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
@javascript
Scenario: Highlight a section
When I open section "2" edit menu
And I click on "Highlight" "link" in the "Section 2" "section"
Then I should see "Highlighted" in the "Section 2" "section"
@javascript
Scenario: Highlight a section when another section is already highlighted
Given I open section "3" edit menu
And I click on "Highlight" "link" in the "Section 3" "section"
And I should see "Highlighted" in the "Section 3" "section"
When I open section "2" edit menu
And I click on "Highlight" "link" in the "Section 2" "section"
Then I should see "Highlighted" in the "Section 2" "section"
And I should not see "Highlighted" in the "Section 3" "section"
@javascript
Scenario: Unhighlight a section
Given I open section "3" edit menu
And I click on "Highlight" "link" in the "Section 3" "section"
And I should see "Highlighted" in the "Section 3" "section"
When I open section "3" edit menu
And I click on "Unhighlight" "link" in the "Section 3" "section"
Then I should not see "Highlighted" in the "Section 3" "section"
@@ -0,0 +1,222 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace format_topics\courseformat;
use core_courseformat\stateupdates;
use moodle_exception;
use stdClass;
/**
* Topics course format related unit tests.
*
* @package format_topics
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stateactions_test extends \advanced_testcase {
/**
* Enrol a user into a course and login as this user.
*
* @param stdClass $course the course object
* @param string $rolename the rolename
*/
private function enrol_user(stdClass $course, string $rolename): void {
// Create and enrol user using given role.
if ($rolename == 'admin') {
$this->setAdminUser();
} else {
$user = $this->getDataGenerator()->create_user();
if ($rolename != 'unenroled') {
$this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
}
$this->setUser($user);
}
}
/**
* Tests for section_highlight method.
*
* @dataProvider basic_role_provider
* @covers ::section_highlight
* @param string $rolename The role of the user that will execute the method.
* @param bool $expectedexception If this call will raise an exception.
*/
public function test_section_highlight(string $rolename, bool $expectedexception = false): void {
global $DB;
$this->resetAfterTest(true);
$generator = $this->getDataGenerator();
$course = $generator->create_course(
['numsections' => 4, 'format' => 'topics'],
['createsections' => true]
);
$this->enrol_user($course, $rolename);
$sectionrecords = $DB->get_records('course_sections', ['course' => $course->id], 'section');
$sectionids = [];
foreach ($sectionrecords as $section) {
$sectionids[] = $section->id;
}
// Initialise stateupdates.
$courseformat = course_get_format($course->id);
$updates = new stateupdates($courseformat);
// All state actions accepts batch editing (an array of sections in this case). However,
// only one course section can be marked as highlighted. This means that if we send more
// than one section id only the first one will be highlighted and the rest will be ignored.
$methodparam = [
$sectionids[1],
$sectionids[2],
$sectionids[3],
];
// Actions have an array of ids as param but only the first one will be highlighted.
$highlightid = reset($methodparam);
$highlight = $sectionrecords[$highlightid];
if ($expectedexception) {
$this->expectException(moodle_exception::class);
}
// Execute given method.
$actions = new stateactions();
$actions->section_highlight(
$updates,
$course,
$methodparam
);
// Check state returned after executing given action.
$updatelist = $updates->jsonSerialize();
$this->assertCount(1, $updatelist);
$update = reset($updatelist);
$this->assertEquals('section', $update->name);
$this->assertEquals('put', $update->action);
$this->assertEquals($highlightid, $update->fields->id);
$this->assertEquals(1, $update->fields->current);
// Check DB sections.
$this->assertEquals($highlight->section, $DB->get_field("course", "marker", ['id' => $course->id]));
}
/**
* Tests for section_unhighlight method.
*
* @dataProvider basic_role_provider
* @covers ::section_unhighlight
* @param string $rolename The role of the user that will execute the method.
* @param bool $expectedexception If this call will raise an exception.
*/
public function test_section_unhighlight(string $rolename, bool $expectedexception = false): void {
global $DB;
$this->resetAfterTest(true);
$generator = $this->getDataGenerator();
$course = $generator->create_course(
['numsections' => 4, 'format' => 'topics'],
['createsections' => true]
);
// Highlight section 1.
course_set_marker($course->id, 1);
$this->enrol_user($course, $rolename);
$sectionrecords = $DB->get_records('course_sections', ['course' => $course->id], 'section');
$sectionids = [];
foreach ($sectionrecords as $section) {
$sectionids[] = $section->id;
}
// Initialise stateupdates.
$courseformat = course_get_format($course->id);
$updates = new stateupdates($courseformat);
// The section_unhighlight accepts extra sections to refresh the state data.
$methodparam = [
$sectionids[3],
$sectionids[4],
];
if ($expectedexception) {
$this->expectException(moodle_exception::class);
}
// Execute given method.
$actions = new stateactions();
$actions->section_unhighlight(
$updates,
$course,
$methodparam
);
// The Unhilight mutation always return the previous highlighted
// section (1) and all the extra sections passed (3, and 4) to ensure
// all of them are updated.
$returnedsectionnumbers = [1, 3, 4];
// Check state returned after executing given action.
$updatelist = $updates->jsonSerialize();
$this->assertCount(3, $updatelist);
foreach ($updatelist as $update) {
$this->assertEquals('section', $update->name);
$this->assertEquals('put', $update->action);
$this->assertContains($update->fields->number, $returnedsectionnumbers);
$this->assertEquals(0, $update->fields->current);
}
// Check DB sections.
$this->assertEquals(0, $DB->get_field("course", "marker", ['id' => $course->id]));
}
/**
* Data provider for basic role tests.
*
* @return array the testing scenarios
*/
public function basic_role_provider(): array {
return [
'admin' => [
'role' => 'admin',
'expectedexception' => false,
],
'editingteacher' => [
'role' => 'editingteacher',
'expectedexception' => false,
],
'teacher' => [
'role' => 'teacher',
'expectedexception' => true,
],
'student' => [
'role' => 'student',
'expectedexception' => true,
],
'guest' => [
'role' => 'guest',
'expectedexception' => true,
],
'unenroled' => [
'role' => 'unenroled',
'expectedexception' => true,
],
];
}
}
@@ -0,0 +1,284 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace format_topics;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
/**
* Topics course format related unit tests.
*
* @package format_topics
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_topics
*/
class format_topics_test extends \advanced_testcase {
/**
* Tests for format_topics::get_section_name method with default section names.
*
* @return void
*/
public function test_get_section_name(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(['numsections' => $numsections, 'format' => 'topics'],
['createsections' => true]);
// Get section names for course.
$coursesections = $DB->get_records('course_sections', ['course' => $course->id]);
// Test get_section_name with default section names.
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
// Assert that with unmodified section names, get_section_name returns the same result as get_default_section_name.
$this->assertEquals($courseformat->get_default_section_name($section), $courseformat->get_section_name($section));
}
}
/**
* Tests for format_topics::get_section_name method with modified section names.
*
* @return void
*/
public function test_get_section_name_customised(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(['numsections' => $numsections, 'format' => 'topics'],
['createsections' => true]);
// Get section names for course.
$coursesections = $DB->get_records('course_sections', ['course' => $course->id]);
// Modify section names.
$customname = "Custom Section";
foreach ($coursesections as $section) {
$section->name = "$customname $section->section";
$DB->update_record('course_sections', $section);
}
// Requery updated section names then test get_section_name.
$coursesections = $DB->get_records('course_sections', ['course' => $course->id]);
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
// Assert that with modified section names, get_section_name returns the modified section name.
$this->assertEquals($section->name, $courseformat->get_section_name($section));
}
}
/**
* Tests for format_topics::get_default_section_name.
*
* @return void
*/
public function test_get_default_section_name(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(['numsections' => $numsections, 'format' => 'topics'],
['createsections' => true]);
// Get section names for course.
$coursesections = $DB->get_records('course_sections', ['course' => $course->id]);
// Test get_default_section_name with default section names.
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
if ($section->section == 0) {
$sectionname = get_string('section0name', 'format_topics');
$this->assertEquals($sectionname, $courseformat->get_default_section_name($section));
} else {
$sectionname = get_string('newsection', 'format_topics');
$this->assertEquals($sectionname, $courseformat->get_default_section_name($section));
}
}
}
/**
* Test web service updating section name.
*
* @return void
*/
public function test_update_inplace_editable(): void {
global $CFG, $DB, $PAGE;
require_once($CFG->dirroot . '/lib/external/externallib.php');
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course(['numsections' => 5, 'format' => 'topics'],
['createsections' => true]);
$section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]);
// Call webservice without necessary permissions.
try {
\core_external::update_inplace_editable('format_topics', 'sectionname', $section->id, 'New section name');
$this->fail('Exception expected');
} catch (\moodle_exception $e) {
$this->assertEquals('Course or activity not accessible. (Not enrolled)',
$e->getMessage());
}
// Change to teacher and make sure that section name can be updated using web service update_inplace_editable().
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
$res = \core_external::update_inplace_editable('format_topics', 'sectionname', $section->id, 'New section name');
$res = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $res);
$this->assertEquals('New section name', $res['value']);
$this->assertEquals('New section name', $DB->get_field('course_sections', 'name', ['id' => $section->id]));
}
/**
* Test callback updating section name.
*
* @return void
*/
public function test_inplace_editable(): void {
global $DB, $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['numsections' => 5, 'format' => 'topics'],
['createsections' => true]);
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
$this->setUser($user);
$section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]);
// Call callback format_topics_inplace_editable() directly.
$tmpl = component_callback('format_topics', 'inplace_editable', ['sectionname', $section->id, 'Rename me again']);
$this->assertInstanceOf('core\output\inplace_editable', $tmpl);
$res = $tmpl->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('Rename me again', $res['value']);
$this->assertEquals('Rename me again', $DB->get_field('course_sections', 'name', ['id' => $section->id]));
// Try updating using callback from mismatching course format.
try {
component_callback('format_weeks', 'inplace_editable', ['sectionname', $section->id, 'New name']);
$this->fail('Exception expected');
} catch (\moodle_exception $e) {
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
}
}
/**
* Test get_default_course_enddate.
*
* @return void
*/
public function test_default_course_enddate(): void {
global $CFG, $DB;
$this->resetAfterTest(true);
require_once($CFG->dirroot . '/course/tests/fixtures/testable_course_edit_form.php');
$this->setTimezone('UTC');
$params = ['format' => 'topics', 'numsections' => 5, 'startdate' => 1445644800];
$course = $this->getDataGenerator()->create_course($params);
$category = $DB->get_record('course_categories', ['id' => $course->category]);
$args = [
'course' => $course,
'category' => $category,
'editoroptions' => [
'context' => \context_course::instance($course->id),
'subdirs' => 0
],
'returnto' => new \moodle_url('/'),
'returnurl' => new \moodle_url('/'),
];
$courseform = new \testable_course_edit_form(null, $args);
$courseform->definition_after_data();
$enddate = $params['startdate'] + get_config('moodlecourse', 'courseduration');
$weeksformat = course_get_format($course->id);
$this->assertEquals($enddate, $weeksformat->get_default_course_enddate($courseform->get_quick_form()));
}
/**
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(['format' => 'topics']);
course_create_sections_if_missing($course1, [0, 1]);
$data = (object)['id' => $course1->id];
$format = course_get_format($course1);
$format->update_course_format_options($data);
// In page.
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['navigation' => 1]));
// When sr parameter is defined, the section.php page should be returned.
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'topics']);
$format = course_get_format($course);
$this->assertEmpty($format->get_required_jsfiles());
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Version details.
*
* @package format_topics
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'format_topics'; // Full name of the plugin (used for diagnostics).