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
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
<?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 core_courseformat\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/dnduploadlib.php');
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use dndupload_handler;
/**
* Class for exporting a course file handlers.
*
* @package core_courseformat
* @copyright 2022 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.2
*/
class file_handlers extends external_api {
/**
* Webservice parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'courseid' => new external_value(PARAM_INT, 'course id', VALUE_REQUIRED),
]
);
}
/**
* Return the list of available file handlers.
*
* @param int $courseid the course id
* @return array of file hanlders.
*/
public static function execute(int $courseid): array {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
$params = external_api::validate_parameters(self::execute_parameters(), [
'courseid' => $courseid,
]);
$courseid = $params['courseid'];
self::validate_context(\context_course::instance($courseid));
$format = course_get_format($courseid);
$course = $format->get_course();
$handler = new dndupload_handler($course, null);
$data = $handler->get_js_data();
return $data->filehandlers ?? [];
}
/**
* Webservice returns.
*
* @return external_multiple_structure
*/
public static function execute_returns(): external_multiple_structure {
return new external_multiple_structure(
new external_single_structure([
'extension' => new external_value(PARAM_TEXT, 'File extension'),
'module' => new external_value(PARAM_TEXT, 'Target module'),
'message' => new external_value(PARAM_TEXT, 'Output message'),
])
);
}
}
+127
View File
@@ -0,0 +1,127 @@
<?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 core_courseformat\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
/**
* Class for exporting a course state.
*
* @package core_course
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.0
*/
class get_state extends external_api {
/**
* Webservice parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'courseid' => new external_value(PARAM_INT, 'course id', VALUE_REQUIRED),
]);
}
/**
* This method will load all course, sections and cm states needed to initialize the frontend
* course editor module. The state data of every individual course, section and cm is
* build using the specifics "state" output components.
*
* By default, the states are generated by:
* - core_courseformat\output\state\course
* - core_courseformat\output\state\section
* - core_courseformat\output\state\cm
*
* As the other main course outputs, format plugins can override those output components
* to send more information to the frontend course editor. These extended classes should
* be located in format_XXX\output\courseformat\state\course, format_XXX\output\courseformat\state\section
* or format_XXX\output\courseformat\state\cm.
*
* @param int $courseid the course id
* @return string Course state in JSON
*/
public static function execute(int $courseid): string {
global $PAGE, $CFG, $USER;
require_once($CFG->dirroot.'/course/lib.php');
$params = external_api::validate_parameters(self::execute_parameters(), [
'courseid' => $courseid,
]);
$courseid = $params['courseid'];
self::validate_context(\context_course::instance($courseid));
$courseformat = course_get_format($courseid);
$modinfo = $courseformat->get_modinfo();
$completioninfo = new \completion_info(get_course($courseid));
$istrackeduser = $completioninfo->is_tracked_user($USER->id);
// Get the proper renderer.
$renderer = $courseformat->get_renderer($PAGE);
$result = (object)[
'course' => (object)[],
'section' => [],
'cm' => [],
];
// Load the output class names.
$courseclass = $courseformat->get_output_classname('state\\course');
$sectionclass = $courseformat->get_output_classname('state\\section');
$cmclass = $courseformat->get_output_classname('state\\cm');
// General state.
$coursestate = new $courseclass($courseformat);
$result->course = $coursestate->export_for_template($renderer);
// Sections and course modules state.
$sections = $modinfo->get_section_info_all();
foreach ($sections as $section) {
if ($courseformat->is_section_visible($section)) {
// Only return this section data if it's visible by current user on the course page.
$sectionstate = new $sectionclass($courseformat, $section);
$result->section[] = $sectionstate->export_for_template($renderer);
}
}
foreach ($modinfo->cms as $cm) {
if ($cm->is_visible_on_course_page()) {
// Only return this course module data if it's visible by current user on the course page.
$section = $sections[$cm->sectionnum];
$cmstate = new $cmclass($courseformat, $section, $cm, istrackeduser: $istrackeduser);
$result->cm[] = $cmstate->export_for_template($renderer);
}
}
return json_encode($result);
}
/**
* Webservice returns.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_RAW, 'Encoded course state JSON');
}
}
+153
View File
@@ -0,0 +1,153 @@
<?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 core_courseformat\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_value;
use moodle_exception;
use coding_exception;
use context_course;
use core_courseformat\base as course_format;
/**
* External secrvie to update the course from the course editor components.
*
* @package core_course
* @copyright 2021 Ferran Recio <moodle@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.0
*/
class update_course extends external_api {
/**
* Webservice parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'action' => new external_value(
PARAM_ALPHANUMEXT,
'action: cm_hide, cm_show, section_hide, section_show, cm_moveleft...',
VALUE_REQUIRED
),
'courseid' => new external_value(PARAM_INT, 'course id', VALUE_REQUIRED),
'ids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Target id'),
'Affected ids',
VALUE_DEFAULT,
[]
),
'targetsectionid' => new external_value(
PARAM_INT, 'Optional target section id', VALUE_DEFAULT, null
),
'targetcmid' => new external_value(
PARAM_INT, 'Optional target cm id', VALUE_DEFAULT, null
),
]
);
}
/**
* This webservice will execute any action from the course editor. The default actions
* are located in {@see \core_courseformat\stateactions} but the format plugin can extend that class
* in format_XXX\course.
*
* The specific action methods will register in a {@see \core_courseformat\stateupdates} all the affected
* sections, cms and course attribute. This object (in JSON) will be sent back to the
* frontend editor to refresh the updated state elements.
*
* By default, {@see \core_courseformat\stateupdates} will register only create, delete and update events
* on cms, sections and the general course data. However, if some plugin needs adhoc messages for
* its own mutation module, extend this class in format_XXX\course.
*
* @param string $action the action name to execute
* @param int $courseid the course id
* @param int[] $ids the affected ids (section or cm depending on the action)
* @param int|null $targetsectionid optional target section id (for move action)
* @param int|null $targetcmid optional target cm id (for move action)
* @return string Course state in JSON
*/
public static function execute(string $action, int $courseid, array $ids = [],
?int $targetsectionid = null, ?int $targetcmid = null): string {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
$params = external_api::validate_parameters(self::execute_parameters(), [
'action' => $action,
'courseid' => $courseid,
'ids' => $ids,
'targetsectionid' => $targetsectionid,
'targetcmid' => $targetcmid,
]);
$action = $params['action'];
$courseid = $params['courseid'];
$ids = $params['ids'];
$targetsectionid = $params['targetsectionid'];
$targetcmid = $params['targetcmid'];
self::validate_context(context_course::instance($courseid));
$courseformat = course_get_format($courseid);
// Create a course changes tracker object.
$defaultupdatesclass = 'core_courseformat\\stateupdates';
$updatesclass = 'format_' . $courseformat->get_format() . '\\courseformat\\stateupdates';
if (!class_exists($updatesclass)) {
$updatesclass = $defaultupdatesclass;
}
$updates = new $updatesclass($courseformat);
if (!is_a($updates, $defaultupdatesclass)) {
throw new coding_exception("The \"$updatesclass\" class must extend \"$defaultupdatesclass\"");
}
// Get the actions class from the course format.
$actionsclass = 'format_'. $courseformat->get_format().'\\courseformat\\stateactions';
if (!class_exists($actionsclass)) {
$actionsclass = 'core_courseformat\\stateactions';
}
$actions = new $actionsclass();
if (!is_callable([$actions, $action])) {
throw new moodle_exception("Invalid course state action $action in ".get_class($actions));
}
$course = $courseformat->get_course();
// Execute the action.
$actions->$action($updates, $course, $ids, $targetsectionid, $targetcmid);
// Any state action mark the state cache as dirty.
course_format::session_cache_reset($course);
return json_encode($updates);
}
/**
* Webservice returns.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_RAW, 'Encoded course update JSON');
}
}
+150
View File
@@ -0,0 +1,150 @@
<?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 core_courseformat;
use core_courseformat\local\courseactions;
use core_courseformat\local\sectionactions;
use core_courseformat\local\cmactions;
use coding_exception;
use stdClass;
/**
* Class to instantiate course format actions.
*
* This class is used to access course content actions.
*
* All course actions are divided into three main clases:
* - course: actions related to the course.
* - section: actions related to the sections.
* - cm: actions related to the course modules.
*
* Format plugin can provide their own actions classes by extending the actions classes
* with the following namespaces:
* - course: format_{PLUGINNAME}\courseformat\courseactions
* - section: format_{PLUGINNAME}\courseformat\sectionactions
* - cm: format_{PLUGINNAME}\courseformat\cmactions
*
* There a static method to get the general formatactions instance:
* - formatactions::instance($courseorid): returns an instance to access all available actions.
*
* The class also provides some convenience methods to get specific actions level on a specific course:
* - formatactions::course($courseorid): returns an instance of the course actions class.
* - formatactions::section($courseorid): returns an instance of the section actions class.
* - formatactions::cm($courseorid): returns an instance of the cm actions class.
*
* There are two ways of executing actions. For example, to execute a section action
* called "move_after" the options are:
*
* Option A: ideal for executing only one action.
*
* formatactions::section($courseid)->move_after($sectioninfo, $aftersectioninfo);
*
* Option B: when actions in the same course are going to be executed at different levels.
*
* $actions = formatactions::instance($courseid);
* $actions->section->move_after($sectioninfo, $aftersectioninfo);
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class formatactions {
/**
* @var courseactions|null courseactions instance.
*/
public courseactions $course;
/**
* @var sectionactions sectionactions instance.
*/
public sectionactions $section;
/**
* @var cmactions cmactions instance.
*/
public cmactions $cm;
/**
* Returns an instance of the actions class for the given course format.
*
* @param base $format the course format.
*/
protected function __construct(base $format) {
$actionclasses = [
'course' => courseactions::class,
'section' => sectionactions::class,
'cm' => cmactions::class,
];
foreach ($actionclasses as $action => $classname) {
$formatalternative = 'format_' . $format->get_format() . '\\courseformat\\' . $action . 'actions';
if (class_exists($formatalternative)) {
if (!is_subclass_of($formatalternative, $classname)) {
throw new coding_exception("The \"$formatalternative\" must extend \"$classname\"");
}
$actionclasses[$action] = $formatalternative;
}
$this->$action = new $actionclasses[$action]($format->get_course());
}
}
/**
* Returns an instance of the actions class for the given course format.
* @param int|stdClass $courseorid course id or record.
* @return courseactions
*/
public static function course($courseorid): courseactions {
return self::instance($courseorid)->course;
}
/**
* Returns an instance of the actions class for the given course format.
*
* @param int|stdClass $courseorid course id or record.
* @return sectionactions
*/
public static function section($courseorid): sectionactions {
return self::instance($courseorid)->section;
}
/**
* Returns an instance of the actions class for the given course format.
* @param int|stdClass $courseorid course id or record.
* @return cmactions
*/
public static function cm($courseorid): cmactions {
return self::instance($courseorid)->cm;
}
/**
* Get a course action loader instance.
* @param int|stdClass $courseorid course id or course.
* @return self
*/
public static function instance(int|stdClass $courseorid): self {
$coursesectionscache = \cache::make('core', 'courseactionsinstances');
$format = base::instance($courseorid);
$courseid = $format->get_courseid();
$cachekey = "{$courseid}_{$format->get_format()}";
$cachedinstance = $coursesectionscache->get($cachekey);
if ($cachedinstance) {
return $cachedinstance;
}
$result = new self($format);
$coursesectionscache->set($cachekey, $result);
return $result;
}
}
@@ -0,0 +1,79 @@
<?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 core_courseformat\hook;
use core\hook\described_hook;
use cm_info;
/**
* Hook for course-module name edited.
*
* @package core_courseformat
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class after_cm_name_edited implements described_hook {
/**
* Constructor.
*
* @param cm_info $cm the course module
* @param string $newname the new name
*/
public function __construct(
/** @var cm_info the course module */
protected cm_info $cm,
/** @var string the new name */
protected string $newname,
) {
}
/**
* Describes the hook purpose.
*
* @return string
*/
public static function get_hook_description(): string {
return 'This hook is triggered when a course module name is edited.';
}
/**
* List of tags that describe this hook.
*
* @return string[]
*/
public static function get_hook_tags(): array {
return ['cm_name_edited'];
}
/**
* Get course module instance.
*
* @return cm_info
*/
public function get_cm(): cm_info {
return $this->cm;
}
/**
* Get new name.
* @return string
*/
public function get_newname(): string {
return $this->newname;
}
}
@@ -0,0 +1,87 @@
<?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 core_courseformat\local;
use core_courseformat\base as course_format;
use section_info;
use cm_info;
use stdClass;
/**
* Format base actions.
*
* This class defined the format actions base class extended by the course, section and cm actions.
*
* It also provides helpers to get the most recent modinfo and format information. Those
* convenience methods are meant to improve the actions readability and prevent excessive
* message chains.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class baseactions {
/**
* @var stdClass the course object.
*/
protected stdClass $course;
/**
* Constructor.
* @param stdClass $course the course object.
*/
public function __construct(stdClass $course) {
$this->course = $course;
}
/**
* Get the course.
* @return stdClass the course object.
*/
protected function get_course(): stdClass {
return $this->course;
}
/**
* Get the course format.
* @return course_format the course format.
*/
protected function get_format(): course_format {
return course_format::instance($this->course);
}
/**
* Get the section info.
* @param int $sectionid the section id.
* @param int $strictness Use MUST_EXIST to throw exception if it doesn't
* @return section_info|null Information for numbered section or null if not found
*/
protected function get_section_info($sectionid, int $strictness = IGNORE_MISSING): ?section_info {
// Course actions must always get the most recent version of the section info.
return get_fast_modinfo($this->course->id)->get_section_info_by_id($sectionid, $strictness);
}
/**
* Get the cm info.
* @param int $cmid the cm id.
* @return cm_info|null Information for numbered cm or null if not found
*/
protected function get_cm_info($cmid): ?cm_info {
// Course actions must always get the most recent version of the cm info.
return get_fast_modinfo($this->course->id)->get_cm($cmid);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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 core_courseformat\local;
use course_modinfo;
/**
* Course module course format actions.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmactions extends baseactions {
/**
* Rename a course module.
* @param int $cmid the course module id.
* @param string $name the new name.
* @return bool true if the course module was renamed, false otherwise.
*/
public function rename(int $cmid, string $name): bool {
global $CFG, $DB;
require_once($CFG->libdir . '/gradelib.php');
$paramcleaning = empty($CFG->formatstringstriptags) ? PARAM_CLEANHTML : PARAM_TEXT;
$name = clean_param($name, $paramcleaning);
if (empty($name)) {
return false;
}
if (\core_text::strlen($name) > 255) {
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
}
// The name is stored in the activity instance record.
// However, events, gradebook and calendar API uses a legacy
// course module data extraction from the DB instead of a section_info.
$cm = get_coursemodule_from_id('', $cmid, 0, false, MUST_EXIST);
if ($name === $cm->name) {
return false;
}
$DB->update_record(
$cm->modname,
(object)[
'id' => $cm->instance,
'name' => $name,
'timemodified' => time(),
]
);
$cm->name = $name;
\core\event\course_module_updated::create_from_cm($cm)->trigger();
course_modinfo::purge_course_module_cache($cm->course, $cm->id);
rebuild_course_cache($cm->course, false, true);
// Modules may add some logic to renaming.
$modinfo = get_fast_modinfo($cm->course);
\core\di::get(\core\hook\manager::class)->dispatch(
new \core_courseformat\hook\after_cm_name_edited($modinfo->get_cm($cm->id), $name),
);
// Attempt to update the grade item if relevant.
$grademodule = $DB->get_record($cm->modname, ['id' => $cm->instance]);
$grademodule->cmidnumber = $cm->idnumber;
$grademodule->modname = $cm->modname;
grade_update_mod_grades($grademodule);
// Update calendar events with the new name.
course_module_update_calendar_events($cm->modname, $grademodule, $cm);
return true;
}
}
@@ -0,0 +1,28 @@
<?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 core_courseformat\local;
/**
* Course actions.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class courseactions extends baseactions {
// All general course actions will go here.
}
@@ -0,0 +1,452 @@
<?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 core_courseformat\local;
use section_info;
use stdClass;
use core\event\course_module_updated;
use core\event\course_section_deleted;
/**
* Section course format actions.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sectionactions extends baseactions {
/**
* Create a course section using a record object.
*
* If $fields->section is not set, the section is added to the end of the course.
*
* @param stdClass $fields the fields to set on the section
* @param bool $skipcheck the position check has already been made and we know it can be used
* @return stdClass the created section record
*/
protected function create_from_object(stdClass $fields, bool $skipcheck = false): stdClass {
global $DB;
[
'position' => $position,
'lastsection' => $lastsection,
] = $this->calculate_positions($fields, $skipcheck);
// First add section to the end.
$sectionrecord = (object) [
'course' => $this->course->id,
'section' => $lastsection + 1,
'summary' => $fields->summary ?? '',
'summaryformat' => $fields->summaryformat ?? FORMAT_HTML,
'sequence' => '',
'name' => $fields->name ?? null,
'visible' => $fields->visible ?? 1,
'availability' => null,
'component' => $fields->component ?? null,
'itemid' => $fields->itemid ?? null,
'timemodified' => time(),
];
$sectionrecord->id = $DB->insert_record("course_sections", $sectionrecord);
// Now move it to the specified position.
if ($position > 0 && $position <= $lastsection) {
move_section_to($this->course, $sectionrecord->section, $position, true);
$sectionrecord->section = $position;
}
\core\event\course_section_created::create_from_section($sectionrecord)->trigger();
rebuild_course_cache($this->course->id, true);
return $sectionrecord;
}
/**
* Calculate the position and lastsection values.
*
* Each section number must be unique inside a course. However, the section creation is not always
* explicit about the final position. By default, regular sections are created at the last position.
* However, delegated section can alter that order, because all delegated sections should have higher
* numbers. Apart, restore operations can also create sections with a forced specific number.
*
* This method returns what is the best position for a new section data and, also, what is the current
* last section number. The last section is needed to decide if the new section must be moved or not after
* insertion.
*
* @param stdClass $fields the fields to set on the section
* @param bool $skipcheck the position check has already been made and we know it can be used
* @return array with the new section position (position key) and the course last section value (lastsection key)
*/
private function calculate_positions($fields, $skipcheck): array {
if (!isset($fields->section)) {
$skipcheck = false;
}
if ($skipcheck) {
return [
'position' => $fields->section,
'lastsection' => $fields->section - 1,
];
}
$lastsection = $this->get_last_section_number();
if (!empty($fields->component)) {
return [
'position' => $fields->section ?? $lastsection + 1,
'lastsection' => $lastsection,
];
}
return [
'position' => $fields->section ?? $this->get_last_section_number(false) + 1,
'lastsection' => $lastsection,
];
}
/**
* Get the last section number in the course.
* @param bool $includedelegated whether to include delegated sections
* @return int
*/
protected function get_last_section_number(bool $includedelegated = true): int {
global $DB;
$delegtadefilter = $includedelegated ? '' : ' AND component IS NULL';
return (int) $DB->get_field_sql(
'SELECT max(section) from {course_sections} WHERE course = ?' . $delegtadefilter,
[$this->course->id]
);
}
/**
* Create a delegated section.
*
* @param string $component the name of the plugin
* @param int|null $itemid the id of the delegated section
* @param stdClass|null $fields the fields to set on the section
* @return section_info the created section
*/
public function create_delegated(
string $component,
?int $itemid = null,
?stdClass $fields = null
): section_info {
$record = ($fields) ? clone $fields : new stdClass();
$record->component = $component;
$record->itemid = $itemid;
$record = $this->create_from_object($record);
return $this->get_section_info($record->id);
}
/**
* Creates a course section and adds it to the specified position
*
* This method returns a section record, not a section_info object. This prevents the regeneration
* of the modinfo object each time we create a section.
*
* If position is greater than number of existing sections, the section is added to the end.
* This will become sectionnum of the new section. All existing sections at this or bigger
* position will be shifted down.
*
* @param int $position The position to add to, 0 means to the end.
* @param bool $skipcheck the check has already been made and we know that the section with this position does not exist
* @return stdClass created section object
*/
public function create(int $position = 0, bool $skipcheck = false): stdClass {
$record = (object) [
'section' => ($position == 0 && !$skipcheck) ? null : $position,
];
return $this->create_from_object($record, $skipcheck);
}
/**
* Create course sections if they are not created yet.
*
* The calculations will ignore sections delegated to components.
* If the section is created, all delegated sections will be pushed down.
*
* @param int[] $sectionnums the section numbers to create
* @return bool whether any section was created
*/
public function create_if_missing(array $sectionnums): bool {
$result = false;
$modinfo = get_fast_modinfo($this->course);
// Ensure we add the sections in order.
sort($sectionnums);
// Delegated sections must be displaced when creating a regular section.
$skipcheck = !$modinfo->has_delegated_sections();
$sections = $modinfo->get_section_info_all();
foreach ($sectionnums as $sectionnum) {
if (isset($sections[$sectionnum]) && empty($sections[$sectionnum]->component)) {
continue;
}
$this->create($sectionnum, $skipcheck);
$result = true;
}
return $result;
}
/**
* Delete a course section.
* @param section_info $sectioninfo the section to delete.
* @param bool $forcedeleteifnotempty whether to force section deletion if it contains modules.
* @param bool $async whether or not to try to delete the section using an adhoc task. Async also depends on a plugin hook.
* @return bool whether section was deleted
*/
public function delete(section_info $sectioninfo, bool $forcedeleteifnotempty = true, bool $async = false): bool {
// Check the 'course_module_background_deletion_recommended' hook first.
// Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested.
// Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it.
// It's up to plugins to handle things like whether or not they are enabled.
if ($async && $pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
if ($pluginfunction()) {
return $this->delete_async($sectioninfo, $forcedeleteifnotempty);
}
}
}
}
return $this->delete_format_data(
$sectioninfo,
$forcedeleteifnotempty,
$this->get_delete_event($sectioninfo)
);
}
/**
* Get the event to trigger when deleting a section.
* @param section_info $sectioninfo the section to delete.
* @return course_section_deleted the event to trigger
*/
protected function get_delete_event(section_info $sectioninfo): course_section_deleted {
global $DB;
// Section record is needed for the event snapshot.
$sectionrecord = $DB->get_record('course_sections', ['id' => $sectioninfo->id]);
$format = course_get_format($this->course);
$sectionname = $format->get_section_name($sectioninfo);
$context = \context_course::instance($this->course->id);
$event = course_section_deleted::create(
[
'objectid' => $sectioninfo->id,
'courseid' => $this->course->id,
'context' => $context,
'other' => [
'sectionnum' => $sectioninfo->section,
'sectionname' => $sectionname,
],
]
);
$event->add_record_snapshot('course_sections', $sectionrecord);
return $event;
}
/**
* Delete a course section.
* @param section_info $sectioninfo the section to delete.
* @param bool $forcedeleteifnotempty whether to force section deletion if it contains modules.
* @param course_section_deleted $event the event to trigger
* @return bool whether section was deleted
*/
protected function delete_format_data(
section_info $sectioninfo,
bool $forcedeleteifnotempty,
course_section_deleted $event
): bool {
$format = course_get_format($this->course);
$result = $format->delete_section($sectioninfo, $forcedeleteifnotempty);
if ($result) {
$event->trigger();
}
rebuild_course_cache($this->course->id, true);
return $result;
}
/**
* Course section deletion, using an adhoc task for deletion of the modules it contains.
* 1. Schedule all modules within the section for adhoc removal.
* 2. Move all modules to course section 0.
* 3. Delete the resulting empty section.
*
* @param section_info $sectioninfo the section to schedule for deletion.
* @param bool $forcedeleteifnotempty whether to force section deletion if it contains modules.
* @return bool true if the section was scheduled for deletion, false otherwise.
*/
protected function delete_async(section_info $sectioninfo, bool $forcedeleteifnotempty = true): bool {
global $DB, $USER;
if (!$forcedeleteifnotempty && (!empty($sectioninfo->sequence) || !empty($sectioninfo->summary))) {
return false;
}
// Event needs to be created before the section activities are moved to section 0.
$event = $this->get_delete_event($sectioninfo);
$affectedmods = $DB->get_records_select(
'course_modules',
'course = ? AND section = ? AND deletioninprogress <> ?',
[$this->course->id, $sectioninfo->id, 1],
'',
'id'
);
// Flag those modules having no existing deletion flag. Some modules may have been
// scheduled for deletion manually, and we don't want to create additional adhoc deletion
// tasks for these. Moving them to section 0 will suffice.
$DB->set_field(
'course_modules',
'deletioninprogress',
'1',
['course' => $this->course->id, 'section' => $sectioninfo->id]
);
// Move all modules to section 0.
$sectionzero = $DB->get_record('course_sections', ['course' => $this->course->id, 'section' => '0']);
$modules = $DB->get_records('course_modules', ['section' => $sectioninfo->id], '');
foreach ($modules as $mod) {
moveto_module($mod, $sectionzero);
}
$removaltask = new \core_course\task\course_delete_modules();
$data = [
'cms' => $affectedmods,
'userid' => $USER->id,
'realuserid' => \core\session\manager::get_realuser()->id,
];
$removaltask->set_custom_data($data);
\core\task\manager::queue_adhoc_task($removaltask);
// Ensure we have the latest section info.
$sectioninfo = $this->get_section_info($sectioninfo->id);
return $this->delete_format_data($sectioninfo, $forcedeleteifnotempty, $event);
}
/**
* Update a course section.
*
* @param section_info $sectioninfo the section info or database record to update.
* @param array|stdClass $fields the fields to update.
* @return bool whether section was updated
*/
public function update(section_info $sectioninfo, array|stdClass $fields): bool {
global $DB;
$courseid = $this->course->id;
// Some fields can not be updated using this method.
$fields = array_diff_key((array) $fields, array_flip(['id', 'course', 'section', 'sequence']));
if (array_key_exists('name', $fields) && \core_text::strlen($fields['name']) > 255) {
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
}
// If the section is delegated to a component, it may control some section values.
$fields = $this->preprocess_delegated_section_fields($sectioninfo, $fields);
if (empty($fields)) {
return false;
}
$fields['id'] = $sectioninfo->id;
$fields['timemodified'] = time();
$DB->update_record('course_sections', $fields);
// We need to update the section cache before the format options are updated.
\course_modinfo::purge_course_section_cache_by_id($courseid, $sectioninfo->id);
rebuild_course_cache($courseid, false, true);
course_get_format($courseid)->update_section_format_options($fields);
$event = \core\event\course_section_updated::create(
[
'objectid' => $sectioninfo->id,
'courseid' => $courseid,
'context' => \context_course::instance($courseid),
'other' => ['sectionnum' => $sectioninfo->section],
]
);
$event->trigger();
if (isset($fields['visible'])) {
$this->transfer_visibility_to_cms($sectioninfo, (bool) $fields['visible']);
}
return true;
}
/**
* Transfer the visibility of the section to the course modules.
*
* @param section_info $sectioninfo the section info or database record to update.
* @param bool $visibility the new visibility of the section.
*/
protected function transfer_visibility_to_cms(section_info $sectioninfo, bool $visibility): void {
global $DB;
if (empty($sectioninfo->sequence) || $visibility == (bool) $sectioninfo->visible) {
return;
}
$modules = explode(',', $sectioninfo->sequence);
$cmids = [];
foreach ($modules as $moduleid) {
$cm = get_coursemodule_from_id(null, $moduleid, $this->course->id);
if (!$cm) {
continue;
}
$modupdated = false;
if ($visibility) {
// As we unhide the section, we use the previously saved visibility stored in visibleold.
$modupdated = set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage, false);
} else {
// We hide the section, so we hide the module but we store the original state in visibleold.
$modupdated = set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage, false);
if ($modupdated) {
$DB->set_field('course_modules', 'visibleold', $cm->visible, ['id' => $moduleid]);
}
}
if ($modupdated) {
$cmids[] = $cm->id;
course_module_updated::create_from_cm($cm)->trigger();
}
}
\course_modinfo::purge_course_modules_cache($this->course->id, $cmids);
rebuild_course_cache($this->course->id, false, true);
}
/**
* Preprocess the section fields before updating a delegated section.
*
* @param section_info $sectioninfo the section info or database record to update.
* @param array $fields the fields to update.
* @return array the updated fields
*/
protected function preprocess_delegated_section_fields(section_info $sectioninfo, array $fields): array {
$delegated = $sectioninfo->get_component_instance();
if (!$delegated) {
return $fields;
}
if (array_key_exists('name', $fields)) {
$fields['name'] = $delegated->preprocess_section_name($sectioninfo, $fields['name']);
}
return $fields;
}
}
@@ -0,0 +1,129 @@
<?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 core_courseformat\output;
use cm_info;
use core_courseformat\output\local\courseformat_named_templatable;
use core\output\named_templatable;
use renderer_base;
use stdClass;
/**
* Base class to render an activity badge.
*
* Plugins can extend this class and override some methods to customize the content to be displayed in the activity badge.
*
* @package core_courseformat
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activitybadge implements named_templatable, \renderable {
use courseformat_named_templatable;
/** @var array Badge defined styles. */
public const STYLES = [
'none' => 'badge-none',
'dark' => 'bg-dark text-white',
'danger' => 'bg-danger text-white',
'warning' => 'bg-warning text-dark',
'info' => 'bg-info text-white',
];
/** @var cm_info The course module information. */
protected $cminfo = null;
/** @var string The content to be displayed in the activity badge. */
protected $content = null;
/** @var string The style for the activity badge. */
protected $style = self::STYLES['none'];
/** @var \moodle_url An optional URL to redirect the user when the activity badge is clicked. */
protected $url = null;
/** @var string An optional element id in case the module wants to add some code for the activity badge (events, CSS...). */
protected $elementid = null;
/**
* @var array An optional array of extra HTML attributes to add to the badge element (for example, data attributes).
* The format for this array is [['name' => 'attr1', 'value' => 'attrval1'], ['name' => 'attr2', 'value' => 'attrval2']].
*/
protected $extraattributes = [];
/**
* Constructor.
*
* @param cm_info $cminfo The course module information.
*/
public function __construct(cm_info $cminfo) {
$this->cminfo = $cminfo;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
*/
final public function export_for_template(renderer_base $output): stdClass {
$this->update_content();
if (empty($this->content)) {
return new stdClass();
}
$data = (object)[
'badgecontent' => $this->content,
'badgestyle' => $this->style,
];
if (!empty($this->url)) {
$data->badgeurl = $this->url->out();
}
if (!empty($this->elementid)) {
$data->badgeelementid = $this->elementid;
}
if (!empty($this->extraattributes)) {
$data->badgeextraattributes = $this->extraattributes;
}
return $data;
}
/**
* Creates an instance of activityclass for the given course module, in case it implements it.
*
* @param cm_info $cminfo
* @return self|null An instance of activityclass for the given module or null if the module doesn't implement it.
*/
final public static function create_instance(cm_info $cminfo): ?self {
$classname = '\mod_' . $cminfo->modname . '\output\courseformat\activitybadge';
if (!class_exists($classname)) {
return null;
}
return new $classname($cminfo);
}
/**
* This method will be called before exporting the template.
*
* It should be implemented by any module extending this class and will be in charge of updating any of the class attributes
* with the proper information that will be displayed in the activity badge (like the content or the badge style).
*/
abstract protected function update_content(): void;
}
@@ -0,0 +1,34 @@
<?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 core_courseformat\output;
use core_courseformat\output\section_renderer;
/**
* Legacy course format renderer.
*
* Since Moodle 4.0, renderer.php file was optional (although highly recommended) for course formats. From Moodle 4.0 onwards,
* renderer is required to support the new course editor implementation.
* This legacy class has been created for backward compatibility, to avoid some errors with course formats (such as social)
* without this renderer.php file.
*
* @package core_courseformat
* @copyright 2021 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class legacy_renderer extends section_renderer {
}
@@ -0,0 +1,206 @@
<?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 core_courseformat\output\local;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use course_modinfo;
use renderable;
/**
* Base class to render a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var \core_courseformat\base the course format class */
protected $format;
/** @var string the section format class */
protected $sectionclass;
/** @var string the add section output class name */
protected $addsectionclass;
/** @var string section navigation class name */
protected $sectionnavigationclass;
/** @var string section selector class name */
protected $sectionselectorclass;
/** @var string the section control menu class */
protected $sectioncontrolmenuclass;
/** @var string bulk editor bar toolbox */
protected $bulkedittoolsclass;
/** @var bool if uses add section */
protected $hasaddsection = true;
/**
* Constructor.
*
* @param course_format $format the coruse format
*/
public function __construct(course_format $format) {
$this->format = $format;
// Load output classes names from format.
$this->sectionclass = $format->get_output_classname('content\\section');
$this->addsectionclass = $format->get_output_classname('content\\addsection');
$this->sectionnavigationclass = $format->get_output_classname('content\\sectionnavigation');
$this->sectionselectorclass = $format->get_output_classname('content\\sectionselector');
$this->bulkedittoolsclass = $format->get_output_classname('content\\bulkedittools');
$this->sectioncontrolmenuclass = $format->get_output_classname('content\\section\\controlmenu');
}
/**
* 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;
$format = $this->format;
$sections = $this->export_sections($output);
$initialsection = '';
$data = (object)[
'title' => $format->page_title(), // This method should be in the course_format class.
'initialsection' => $initialsection,
'sections' => $sections,
'format' => $format->get_format(),
'sectionreturn' => null,
];
// The single section format has extra navigation.
if ($this->format->get_sectionid()) {
$singlesectionnum = $this->format->get_sectionnum();
if (!$PAGE->theme->usescourseindex) {
$sectionnavigation = new $this->sectionnavigationclass($format, $singlesectionnum);
$data->sectionnavigation = $sectionnavigation->export_for_template($output);
$sectionselector = new $this->sectionselectorclass($format, $sectionnavigation);
$data->sectionselector = $sectionselector->export_for_template($output);
}
$data->hasnavigation = true;
$data->singlesection = array_shift($data->sections);
$data->sectionreturn = $singlesectionnum;
}
if ($this->hasaddsection) {
$addsection = new $this->addsectionclass($format);
$data->numsections = $addsection->export_for_template($output);
}
if ($format->show_editor()) {
$bulkedittools = new $this->bulkedittoolsclass($format);
$data->bulkedittools = $bulkedittools->export_for_template($output);
}
return $data;
}
/**
* Retrieves the action menu for the page header of the local content section.
*
* @param \renderer_base $output The renderer object used for rendering the action menu.
* @return string|null The rendered action menu HTML, null if page no action menu is available.
*/
public function get_page_header_action(\renderer_base $output): ?string {
$sectionid = $this->format->get_sectionid();
if ($sectionid !== null) {
$modinfo = $this->format->get_modinfo();
$sectioninfo = $modinfo->get_section_info_by_id($sectionid);
/** @var \core_courseformat\output\local\content\section\controlmenu */
$controlmenu = new $this->sectioncontrolmenuclass($this->format, $sectioninfo);
return $output->render($controlmenu->get_action_menu($output));
}
return null;
}
/**
* Export sections array data.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
protected function export_sections(\renderer_base $output): array {
$format = $this->format;
$course = $format->get_course();
$modinfo = $this->format->get_modinfo();
// Generate section list.
$sections = [];
$stealthsections = [];
$numsections = $format->get_last_section_number();
foreach ($this->get_sections_to_display($modinfo) as $sectionnum => $thissection) {
// The course/view.php check the section existence but the output can be called
// from other parts so we need to check it.
if (!$thissection) {
throw new \moodle_exception('unknowncoursesection', 'error', course_get_url($course),
format_string($course->fullname));
}
$section = new $this->sectionclass($format, $thissection);
if ($sectionnum > $numsections) {
// Activities inside this section are 'orphaned', this section will be printed as 'stealth' below.
if (!empty($modinfo->sections[$sectionnum])) {
$stealthsections[] = $section->export_for_template($output);
}
continue;
}
if (!$format->is_section_visible($thissection)) {
continue;
}
$sections[] = $section->export_for_template($output);
}
if (!empty($stealthsections)) {
$sections = array_merge($sections, $stealthsections);
}
return $sections;
}
/**
* Return an array of sections to display.
*
* This method is used to differentiate between display a specific section
* or a list of them.
*
* @param course_modinfo $modinfo the current course modinfo object
* @return section_info[] an array of section_info to display
*/
private function get_sections_to_display(course_modinfo $modinfo): array {
$singlesectionid = $this->format->get_sectionid();
if ($singlesectionid) {
return [
$modinfo->get_section_info_by_id($singlesectionid),
];
}
return $modinfo->get_listed_section_info_all();
}
}
@@ -0,0 +1,164 @@
<?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 course format output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use moodle_url;
use renderable;
use stdClass;
/**
* Base class to render a course add section buttons.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class addsection implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/**
* Constructor.
*
* @param course_format $format the course format
*/
public function __construct(course_format $format) {
$this->format = $format;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
// If no editor must be displayed, just return an empty structure.
if (!$this->format->show_editor(['moodle/course:update'])) {
return new stdClass();
}
$format = $this->format;
$course = $format->get_course();
$options = $format->get_format_options();
$lastsection = $format->get_last_section_number();
$maxsections = $format->get_max_sections();
// Component based formats handle add section button in the frontend.
$show = ($lastsection < $maxsections) || $format->supports_components();
$supportsnumsections = array_key_exists('numsections', $options);
if ($supportsnumsections) {
$data = $this->get_num_sections_data($output, $lastsection, $maxsections);
} else if (course_get_format($course)->uses_sections() && $show) {
$data = $this->get_add_section_data($output, $lastsection, $maxsections);
}
if (count((array)$data)) {
$data->showaddsection = true;
}
return $data;
}
/**
* Get the legacy num section add/remove section buttons data.
*
* Current course format has 'numsections' option, which is very confusing and we suggest course format
* developers to get rid of it (see MDL-57769 on how to do it).
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param int $lastsection the last section number
* @param int $maxsections the maximum number of sections
* @return stdClass data context for a mustache template
*/
protected function get_num_sections_data(\renderer_base $output, int $lastsection, int $maxsections): stdClass {
$format = $this->format;
$course = $format->get_course();
$data = new stdClass();
if ($lastsection < $maxsections) {
$data->increase = (object) [
'url' => new moodle_url(
'/course/changenumsections.php',
['courseid' => $course->id, 'increase' => true, 'sesskey' => sesskey()]
),
];
}
if ($course->numsections > 0) {
$data->decrease = (object) [
'url' => new moodle_url(
'/course/changenumsections.php',
['courseid' => $course->id, 'increase' => false, 'sesskey' => sesskey()]
),
];
}
return $data;
}
/**
* Get the add section button data.
*
* Current course format does not have 'numsections' option but it has multiple sections suppport.
* Display the "Add section" link that will insert a section in the end.
* Note to course format developers: inserting sections in the other positions should check both
* capabilities 'moodle/course:update' and 'moodle/course:movesections'.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param int $lastsection the last section number
* @param int $maxsections the maximum number of sections
* @return stdClass data context for a mustache template
*/
protected function get_add_section_data(\renderer_base $output, int $lastsection, int $maxsections): stdClass {
$format = $this->format;
$course = $format->get_course();
$data = new stdClass();
$addstring = $format->get_format_string('addsection');
$params = ['courseid' => $course->id, 'insertsection' => 0, 'sesskey' => sesskey()];
$singlesection = $this->format->get_sectionnum();
if ($singlesection) {
$params['sectionreturn'] = $singlesection;
}
$data->addsections = (object) [
'url' => new moodle_url('/course/changenumsections.php', $params),
'title' => $addstring,
'newsection' => $maxsections - $lastsection,
];
return $data;
}
}
@@ -0,0 +1,69 @@
<?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 core_courseformat\output\local\content;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
/**
* Course bulk edit mode toggler button.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class bulkedittoggler implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var core_courseformat\base the course format class */
protected $format;
/**
* Constructor.
*
* @param course_format $format the course format
*/
public function __construct(course_format $format) {
$this->format = $format;
}
/**
* 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) {
$section = optional_param('section', 0, PARAM_INT);
$format = $this->format;
$course = $format->get_course();
$data = (object)[
'id' => $course->id,
'coursename' => format_string($course->fullname),
];
if ($section) {
$data->sectionname = get_string('sectionname', "format_$course->format");
$data->sectiontitle = get_section_name($course, $section);
}
return $data;
}
}
@@ -0,0 +1,200 @@
<?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 core_courseformat\output\local\content;
use core\moodlenet\utilities;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use stdClass;
/**
* Contains the bulk editor tools bar.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class bulkedittools implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var core_courseformat\base the course format class */
protected $format;
/**
* Constructor.
*
* @param course_format $format the course format
*/
public function __construct(course_format $format) {
$this->format = $format;
}
/**
* 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): stdClass {
$format = $this->format;
$course = $format->get_course();
$data = (object)[
'id' => $course->id,
'actions' => $this->get_toolbar_actions(),
];
$data->hasactions = !empty($data->actions);
return $data;
}
/**
* Get the toolbar actions.
* @return array the array of buttons
*/
protected function get_toolbar_actions(): array {
return array_merge(
array_values($this->section_control_items()),
array_values($this->cm_control_items()),
);
}
/**
* Generate the bulk edit control items of a course module.
*
* Format plugins can override the method to add or remove elements
* from the toolbar.
*
* @return array of edit control items
*/
protected function cm_control_items(): array {
global $CFG, $USER;
$format = $this->format;
$context = $format->get_context();
$user = $USER;
$controls = [];
if (has_capability('moodle/course:activityvisibility', $context, $user)) {
$controls['availability'] = [
'icon' => 't/show',
'action' => 'cmAvailability',
'name' => get_string('availability'),
'title' => get_string('cmavailability', 'core_courseformat'),
'bulk' => 'cm',
];
}
$duplicatecapabilities = ['moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport'];
if (has_all_capabilities($duplicatecapabilities, $context, $user)) {
$controls['duplicate'] = [
'icon' => 't/copy',
'action' => 'cmDuplicate',
'name' => get_string('duplicate'),
'title' => get_string('cmsduplicate', 'core_courseformat'),
'bulk' => 'cm',
];
}
$hasmanageactivities = has_capability('moodle/course:manageactivities', $context, $user);
if ($hasmanageactivities) {
$controls['move'] = [
'icon' => 'i/dragdrop',
'action' => 'moveCm',
'name' => get_string('move'),
'title' => get_string('cmsmove', 'core_courseformat'),
'bulk' => 'cm',
];
$controls['delete'] = [
'icon' => 'i/delete',
'action' => 'cmDelete',
'name' => get_string('delete'),
'title' => get_string('cmsdelete', 'core_courseformat'),
'bulk' => 'cm',
];
}
$usercanshare = utilities::can_user_share($context, $user->id, 'course');
if ($CFG->enablesharingtomoodlenet && $usercanshare) {
$controls['sharetomoodlenet'] = [
'id' => 'cmShareToMoodleNet',
'icon' => 'i/share',
'action' => 'cmShareToMoodleNet',
'name' => get_string('moodlenet:sharetomoodlenet'),
'title' => get_string('moodlenet:sharetomoodlenet'),
'bulk' => 'cm',
];
}
return $controls;
}
/**
* Generate the bulk edit control items of a section.
*
* Format plugins can override the method to add or remove elements
* from the toolbar.
*
* @return array of edit control items
*/
protected function section_control_items(): array {
global $USER;
$format = $this->format;
$context = $format->get_context();
$sectionreturn = $format->get_sectionnum();
$user = $USER;
$controls = [];
if (has_capability('moodle/course:sectionvisibility', $context, $user)) {
$controls['availability'] = [
'icon' => 't/show',
'action' => 'sectionAvailability',
'name' => get_string('availability'),
'title' => $this->format->get_format_string('sectionsavailability'),
'bulk' => 'section',
];
}
if (!$sectionreturn && has_capability('moodle/course:movesections', $context, $user)) {
$controls['move'] = [
'icon' => 'i/dragdrop',
'action' => 'moveSection',
'name' => get_string('move', 'moodle'),
'title' => $this->format->get_format_string('sectionsmove'),
'bulk' => 'section',
];
}
$deletecapabilities = ['moodle/course:movesections', 'moodle/course:update'];
if (!$sectionreturn && has_all_capabilities($deletecapabilities, $context, $user)) {
$controls['delete'] = [
'icon' => 'i/delete',
'action' => 'deleteSection',
'name' => get_string('delete'),
'title' => $this->format->get_format_string('sectionsdelete'),
'bulk' => 'section',
];
}
return $controls;
}
}
@@ -0,0 +1,410 @@
<?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 activity list from a section.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use cm_info;
use context_course;
use core\output\named_templatable;
use core_availability\info_module;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use renderer_base;
use section_info;
use stdClass;
/**
* Base class to render a course module inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cm implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $section;
/** @var cm_info the course module instance */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/** @var string the activity name output class name */
protected $cmnameclass;
/** @var string the activity control menu class name */
protected $controlmenuclass;
/** @var string the activity availability class name */
protected $availabilityclass;
/** @var string the activity completion class name */
protected $completionclass;
/** @var string the activity visibility class name */
protected $visibilityclass;
/** @var string the activity groupmode badge class name */
protected $groupmodeclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param array $displayoptions optional extra display options
*/
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
// Add extra display options.
$this->displayoptions = $displayoptions;
$this->load_classes();
// Get the necessary classes.
$this->cmnameclass = $format->get_output_classname('content\\cm\\cmname');
$this->controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu');
$this->availabilityclass = $format->get_output_classname('content\\cm\\availability');
$this->completionclass = $format->get_output_classname('content\\cm\\completion');
$this->visibilityclass = $format->get_output_classname('content\\cm\\visibility');
$this->groupmodeclass = $format->get_output_classname('content\\cm\\groupmode');
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
global $PAGE;
$mod = $this->mod;
$displayoptions = $this->displayoptions;
$data = (object)[
'grouping' => $mod->get_grouping_label($displayoptions['textclasses']),
'modname' => get_string('pluginname', 'mod_' . $mod->modname),
'url' => $mod->url,
'activityname' => $mod->get_formatted_name(),
'textclasses' => $displayoptions['textclasses'],
'classlist' => [],
'cmid' => $mod->id,
'editing' => $PAGE->user_is_editing(),
'sectionnum' => $this->section->section,
];
// Add partial data segments.
$haspartials = [];
$haspartials['cmname'] = $this->add_cm_name_data($data, $output);
$haspartials['availability'] = $this->add_availability_data($data, $output);
$haspartials['alternative'] = $this->add_alternative_content_data($data, $output);
$haspartials['completion'] = $this->add_completion_data($data, $output);
$haspartials['dates'] = $this->add_dates_data($data, $output);
$haspartials['editor'] = $this->add_editor_data($data, $output);
$haspartials['groupmode'] = $this->add_groupmode_data($data, $output);
$haspartials['visibility'] = $this->add_visibility_data($data, $output);
$this->add_format_data($data, $haspartials, $output);
// Calculated fields.
if (!empty($data->url)) {
$data->hasurl = true;
}
return $data;
}
/**
* Add course module name attributes to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_cm_name_data(stdClass &$data, renderer_base $output): bool {
// Mod inplace name editable.
$cmname = new $this->cmnameclass(
$this->format,
$this->section,
$this->mod,
null,
$this->displayoptions
);
$data->cmname = $cmname->export_for_template($output);
$data->hasname = $cmname->has_name();
return $data->hasname;
}
/**
* Add the module availability to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has mod availability
*/
protected function add_availability_data(stdClass &$data, renderer_base $output): bool {
if (!$this->mod->visible) {
$data->modavailability = null;
return false;
}
// Mod availability output class.
$availability = new $this->availabilityclass(
$this->format,
$this->section,
$this->mod,
$this->displayoptions
);
$modavailability = $availability->export_for_template($output);
$data->modavailability = $modavailability;
return $availability->has_availability($output);
}
/**
* Add the alternative content to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has alternative content
*/
protected function add_alternative_content_data(stdClass &$data, renderer_base $output): bool {
$altcontent = $this->mod->get_formatted_content(
['overflowdiv' => true, 'noclean' => true]
);
$data->altcontent = (empty($altcontent)) ? false : $altcontent;
$data->afterlink = $this->mod->afterlink;
$activitybadgedata = $this->mod->get_activitybadge($output);
if (!empty($activitybadgedata)) {
$data->activitybadge = $activitybadgedata;
}
return !empty($data->altcontent);
}
/**
* Add activity dates information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool the module has completion information
*/
protected function add_dates_data(stdClass &$data, renderer_base $output): bool {
global $USER;
$course = $this->mod->get_course();
if (!$course->showactivitydates) {
return false;
}
$activitydates = \core\activity_dates::get_dates_for_module($this->mod, $USER->id);
$templatedata = new \core_course\output\activity_dates($activitydates);
$data->dates = $templatedata->export_for_template($output);
return $data->dates->hasdates;
}
/**
* Add activity completion information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool the module has completion information
*/
protected function add_completion_data(stdClass &$data, renderer_base $output): bool {
$completion = new $this->completionclass($this->format, $this->section, $this->mod);
$templatedata = $completion->export_for_template($output);
if ($templatedata) {
$data->completion = $templatedata;
return true;
}
return false;
}
/**
* Add activity information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param bool[] $haspartials the result of loading partial data elements
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has format data
*/
protected function add_format_data(stdClass &$data, array $haspartials, renderer_base $output): bool {
$result = false;
// Legacy indentation.
if (!empty($this->mod->indent) && $this->format->uses_indentation()) {
$data->indent = $this->mod->indent;
if ($this->mod->indent > 15) {
$data->hugeindent = true;
$result = true;
}
}
// Stealth and hidden from student.
if (!$this->mod->visible) {
// This module is hidden but current user has capability to see it.
$data->modhiddenfromstudents = true;
$result = true;
} else if ($this->mod->is_stealth()) {
// This module is available but is normally not displayed on the course page
// (this user can see it because they can manage it).
$data->modstealth = true;
$result = true;
}
// Special inline activity format.
if (
$this->mod->has_custom_cmlist_item() &&
!$haspartials['availability'] &&
!$haspartials['completion'] &&
!$haspartials['dates'] &&
!$haspartials['groupmode'] &&
!isset($data->modhiddenfromstudents) &&
!isset($data->modstealth) &&
!$this->format->show_editor()
) {
$data->modinline = true;
$result = true;
}
return $result;
}
/**
* Add course editor attributes to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has editor data
*/
protected function add_editor_data(stdClass &$data, renderer_base $output): bool {
$course = $this->format->get_course();
$coursecontext = context_course::instance($course->id);
$editcaps = [];
if (has_capability('moodle/course:activityvisibility', $coursecontext)) {
$editcaps = ['moodle/course:activityvisibility'];
}
if (!$this->format->show_editor($editcaps)) {
return false;
}
$returnsection = $this->format->get_sectionnum();
// Edit actions.
$controlmenu = new $this->controlmenuclass(
$this->format,
$this->section,
$this->mod,
$this->displayoptions
);
$data->controlmenu = $controlmenu->export_for_template($output);
if (!$this->format->supports_components()) {
// Add the legacy YUI move link.
$data->moveicon = course_get_cm_move($this->mod, $returnsection);
}
return true;
}
/**
* Add group mode information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool the module has group mode information
*/
protected function add_groupmode_data(stdClass &$data, renderer_base $output): bool {
$groupmode = new $this->groupmodeclass($this->format, $this->section, $this->mod);
$data->groupmodeinfo = $groupmode->export_for_template($output);
return !empty($data->groupmodeinfo);
}
/**
* Add visibility information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has visibility data
*/
protected function add_visibility_data(stdClass &$data, renderer_base $output): bool {
$visibility = new $this->visibilityclass($this->format, $this->section, $this->mod);
$templatedata = $visibility->export_for_template($output);
if ($templatedata) {
$data->visibility = $templatedata;
return true;
}
return false;
}
/**
* Returns the CSS classes for the activity name/content
*
*/
protected function load_classes() {
$mod = $this->mod;
$linkclasses = '';
$textclasses = '';
if ($mod->uservisible) {
$info = new info_module($mod);
$conditionalhidden = !$info->is_available_for_all();
$accessiblebutdim = (!$mod->visible || $conditionalhidden) &&
has_capability('moodle/course:viewhiddenactivities', $mod->context);
if ($accessiblebutdim && $conditionalhidden) {
$linkclasses .= ' conditionalhidden';
$textclasses .= ' conditionalhidden';
}
}
$this->displayoptions['linkclasses'] = $linkclasses;
$this->displayoptions['textclasses'] = $textclasses;
$this->displayoptions['onclick'] = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);;
}
/**
* Get the activity link classes.
*
* @return string the activity link classes.
*/
public function get_link_classes(): string {
return $this->displayoptions['linkclasses'] ?? '';
}
/**
* Get the activity text/description classes.
*
* @return string the activity text classes.
*/
public function get_text_classes(): string {
return $this->displayoptions['textclasses'] ?? '';
}
/**
* Get the activity onclick code.
*
* @return string the activity onclick.
*/
public function get_onclick_code(): string {
return $this->displayoptions['onclick'];
}
}
@@ -0,0 +1,159 @@
<?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 activity availability information.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use core_courseformat\output\local\content\section\availability as section_avalability;
use cm_info;
use core_courseformat\base as course_format;
use section_info;
use stdClass;
use core_availability\info_module;
use core_availability\info;
/**
* Base class to render a course module availability inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class availability extends section_avalability {
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
protected $section;
/** @var cm_info the course module instance */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/** @var bool the has availability attribute name */
protected $hasavailabilityname;
/** @var stdClass|null the instance export data */
protected $data = null;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param array $displayoptions optional extra display options
*/
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
$this->displayoptions = $displayoptions;
$this->hasavailabilityname = 'hasmodavailability';
}
/**
* Get the availability data to be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array the availability data.
*/
protected function get_info(\renderer_base $output): array {
if (!$this->mod->is_visible_on_course_page()) {
// Nothing to be displayed to the user.
return [];
}
if (!$this->mod->uservisible) {
return ['info' => $this->user_availability_info($output)];
}
$editurl = new \moodle_url(
'/course/modedit.php',
['update' => $this->mod->id, 'showonly' => 'availabilityconditionsheader']
);
return ['editurl' => $editurl->out(false), 'info' => $this->conditional_availability_info($output)];
}
/**
* Get the current user availability data.
*
* This is a student who is not allowed to see the module but might be allowed
* to see availability info (i.e. "Available from ...").
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array the availability data.
*/
protected function user_availability_info(\renderer_base $output): array {
if (empty($this->mod->availableinfo)) {
return [];
}
$info = [];
$info[] = $this->get_availability_data($output, $this->mod->availableinfo, 'isrestricted');
return $info;
}
/**
* Get the activity availability data to display.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array the availability data.
*/
protected function conditional_availability_info(\renderer_base $output): array {
global $CFG;
// This is a teacher who is allowed to see module but still should see the
// information that module is not available to all/some students.
$mod = $this->mod;
$modcontext = $mod->context;
$canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
if (!$canviewhidden || empty($CFG->enableavailability)) {
return [];
}
// Display information about conditional availability.
// Don't add availability information if user is not editing and activity is hidden.
if (!$mod->visible && !$this->format->show_editor()) {
return [];
}
$ci = new info_module($mod);
$fullinfo = $ci->get_full_information();
if (!$fullinfo) {
return [];
}
$info = [];
$hidinfoclass = 'isrestricted isfullinfo';
if (!$mod->visible) {
$hidinfoclass .= ' hide';
}
$info[] = $this->get_availability_data($output, $fullinfo, $hidinfoclass);
return $info;
}
}
@@ -0,0 +1,106 @@
<?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 activity icon.
*
* @package core_courseformat
* @copyright 2023 Mikel Martin <mikel@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use cm_info;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use stdClass;
/**
* Base class to render a course module icon.
*
* @package core_courseformat
* @copyright 2023 Mikel Martin <mikel@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmicon implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var cm_info the course module instance */
protected $mod;
/**
* Constructor.
*
* @param course_format $format the course format
* @param cm_info $mod the course module ionfo
*/
public function __construct(
course_format $format,
cm_info $mod,
) {
$this->format = $format;
$this->mod = $mod;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): array {
$mod = $this->mod;
if (!$this->is_icon_visible()) {
// Nothing to be displayed to the user.
return [];
}
$iconurl = $mod->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$isbranded = component_callback('mod_' . $mod->modname, 'is_branded', [], false);
$data = [
'uservisible' => $mod->uservisible,
'url' => $mod->url,
'icon' => $iconurl,
'iconclass' => $iconclass,
'modname' => $mod->modname,
'pluginname' => get_string('pluginname', 'mod_' . $mod->modname),
'showtooltip' => $this->format->show_editor(),
'purpose' => plugin_supports('mod', $mod->modname, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER),
'branded' => $isbranded,
];
return $data;
}
/**
* Return if the activity has a visible icon.
*
* @return bool if the icon should be shown.
*/
public function is_icon_visible(): bool {
return $this->mod->is_visible_on_course_page() && $this->mod->url;
}
}
@@ -0,0 +1,158 @@
<?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 activity name inplace editable.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use cm_info;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course module inplace editable header.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmname implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $section;
/** @var cm_info the course module instance */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/** @var string the activity title output class name */
protected $titleclass;
/** @var string the activity icon output class name */
protected $iconclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param null $unused This parameter has been deprecated since 4.1 and should not be used anymore.
* @param array $displayoptions optional extra display options
*/
public function __construct(
course_format $format,
section_info $section,
cm_info $mod,
?bool $unused = null,
array $displayoptions = []
) {
if ($unused !== null) {
debugging('Deprecated argument passed to ' . __FUNCTION__, DEBUG_DEVELOPER);
}
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
$this->displayoptions = $displayoptions;
// Get the necessary classes.
$this->titleclass = $format->get_output_classname('content\\cm\\title');
$this->iconclass = $format->get_output_classname('content\\cm\\cmicon');
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): array {
$mod = $this->mod;
$displayoptions = $this->displayoptions;
if (!$this->has_name()) {
// Nothing to be displayed to the user.
return [];
}
$data = [
'url' => $mod->url,
'modname' => $mod->modname,
'textclasses' => $displayoptions['textclasses'] ?? '',
'activityicon' => $this->get_icon_data($output),
'activityname' => $this->get_title_data($output),
];
return $data;
}
/**
* Get the title data.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
protected function get_title_data(\renderer_base $output): array {
$title = new $this->titleclass(
$this->format,
$this->section,
$this->mod,
$this->displayoptions
);
return (array) $title->export_for_template($output);
}
/**
* Get the icon data.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
protected function get_icon_data(\renderer_base $output): array {
$icon = new $this->iconclass(
$this->format,
$this->mod,
);
return (array) $icon->export_for_template($output);
}
/**
* Return if the activity has a visible name.
*
* @return bool if the title is visible.
*/
public function has_name(): bool {
return $this->mod->is_visible_on_course_page() && $this->mod->url;
}
}
@@ -0,0 +1,122 @@
<?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 core_courseformat\output\local\content\cm;
use cm_info;
use core_course\output\activity_completion;
use section_info;
use renderable;
use stdClass;
use core\output\named_templatable;
use core\output\local\dropdown\dialog as dropdown_dialog;
use core_completion\cm_completion_details;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
/**
* Base class to render course module completion.
*
* @package core_courseformat
* @copyright 2023 Mikel Martin <mikel@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion implements named_templatable, renderable {
use courseformat_named_templatable;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
*/
public function __construct(
protected course_format $format,
protected section_info $section,
protected cm_info $mod,
) {
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): ?stdClass {
global $USER;
$course = $this->mod->get_course();
$showcompletionconditions = $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS;
$completiondetails = cm_completion_details::get_instance($this->mod, $USER->id, $showcompletionconditions);
$showcompletioninfo = $completiondetails->has_completion() &&
($showcompletionconditions || $completiondetails->show_manual_completion());
if (!$showcompletioninfo) {
return null;
}
$completion = new activity_completion($this->mod, $completiondetails);
$completiondata = $completion->export_for_template($output);
if ($completiondata->isautomatic || ($completiondata->ismanual && !$completiondata->istrackeduser)) {
$completiondata->completiondialog = $this->get_completion_dialog($output, $completiondata);
}
return $completiondata;
}
/**
* Get the completion dialog.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param stdClass $completioninfo the completion info
* @return array the completion dialog exported for template
*/
private function get_completion_dialog(\renderer_base $output, stdClass $completioninfo): array {
global $PAGE;
$editurl = new \moodle_url(
'/course/modedit.php',
['update' => $this->mod->id, 'showonly' => 'activitycompletionheader']
);
$completioninfo->editurl = $editurl->out(false);
$completioninfo->editing = $PAGE->user_is_editing();
$completioninfo->hasconditions = $completioninfo->ismanual || count($completioninfo->completiondetails) > 0;
$dialogcontent = $output->render_from_template('core_courseformat/local/content/cm/completion_dialog', $completioninfo);
$buttoncontent = get_string('completionmenuitem', 'completion');
$buttonclass = '';
if ($completioninfo->istrackeduser) {
$buttoncontent = get_string('todo', 'completion');
if ($completioninfo->overallcomplete) {
$buttoncontent = $output->pix_icon('i/checked', '') . " " . get_string('completion_manual:done', 'core_course');
$buttonclass = 'btn-success';
}
}
$completiondialog = new dropdown_dialog($buttoncontent, $dialogcontent, [
'classes' => 'completion-dropdown',
'buttonclasses' => 'btn btn-sm dropdown-toggle icon-no-margin ' . $buttonclass,
'dropdownposition' => dropdown_dialog::POSITION['end'],
]);
return $completiondialog->export_for_template($output);
}
}
@@ -0,0 +1,172 @@
<?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 activity control menu.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use action_menu;
use action_menu_link;
use cm_info;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course module menu inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class controlmenu implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $section;
/** @var action_menu the activity aciton menu */
protected $menu;
/** @var cm_info the course module instance */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module info
* @param array $displayoptions optional extra display options
*/
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
$this->displayoptions = $displayoptions;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
$mod = $this->mod;
$menu = $this->get_action_menu($output);
if (empty($menu)) {
return new stdClass();
}
$data = (object)[
'menu' => $menu->export_for_template($output),
'hasmenu' => true,
'id' => $mod->id,
];
// After icons.
if (!empty($mod->afterediticons)) {
$data->afterediticons = $mod->afterediticons;
}
return $data;
}
/**
* Generate the action menu element.
*
* This method is public in case some block needs to modify the menu before output it.
* @param \renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the activity action menu
*/
public function get_action_menu(\renderer_base $output): ?action_menu {
if (!empty($this->menu)) {
return $this->menu;
}
$mod = $this->mod;
$controls = $this->cm_control_items();
if (empty($controls)) {
return null;
}
// Convert control array into an action_menu.
$menu = new action_menu();
$menu->set_kebab_trigger(get_string('edit'));
$menu->attributes['class'] .= ' section-cm-edit-actions commands';
// Prioritise the menu ahead of all other actions.
$menu->prioritise = true;
$ownerselector = $this->displayoptions['ownerselector'] ?? '#module-' . $mod->id;
$menu->set_owner_selector($ownerselector);
foreach ($controls as $control) {
if ($control instanceof action_menu_link) {
$control->add_class('cm-edit-action');
}
$menu->add($control);
}
$this->menu = $menu;
return $menu;
}
/**
* Generate the edit control items of a course module.
*
* This method uses course_get_cm_edit_actions function to get the cm actions.
* However, format plugins can override the method to add or remove elements
* from the menu.
*
* @return array of edit control items
*/
protected function cm_control_items() {
$format = $this->format;
$mod = $this->mod;
$sectionreturn = $format->get_sectionnum();
if (!empty($this->displayoptions['disableindentation']) || !$format->uses_indentation()) {
$indent = -1;
} else {
$indent = $mod->indent;
}
return course_get_cm_edit_actions($mod, $indent, $sectionreturn);
}
}
@@ -0,0 +1,210 @@
<?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 core_courseformat\output\local\content\cm;
use cm_info;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use core\output\named_templatable;
use core\output\choicelist;
use core\output\local\dropdown\status;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render an activity group mode badge.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupmode implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $section;
/** @var cm_info the course module instance */
protected $mod;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
*/
public function __construct(
course_format $format,
section_info $section,
cm_info $mod,
) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
if (!$this->format->show_groupmode($this->mod)) {
return null;
}
$usecomponents = $this->format->supports_components();
if ($this->format->show_editor() && $usecomponents && !$this->mod->coursegroupmodeforce) {
return $this->build_editor_data($output);
}
// If the group mode is not editable, the no groups badge is not displayed.
if ($this->mod->effectivegroupmode === NOGROUPS) {
return null;
}
return $this->build_static_data($output);
}
/**
* Build the data for the static badge.
* @param \renderer_base $output
* @return stdClass
*/
protected function build_static_data(\renderer_base $output): stdClass {
switch ($this->mod->effectivegroupmode) {
case SEPARATEGROUPS:
$groupalt = get_string('groupsseparate', 'group');
$groupicon = $this->get_action_icon('cmSeparateGroups', $groupalt);
break;
case VISIBLEGROUPS:
$groupalt = get_string('groupsvisible', 'group');
$groupicon = $this->get_action_icon('cmVisibleGroups', $groupalt);
break;
case NOGROUPS:
default:
$groupalt = get_string('groupsnone', 'group');
$groupicon = $this->get_action_icon('cmNoGroups', $groupalt);
break;
}
$data = (object) [
'groupicon' => $output->render($groupicon),
'groupalt' => $groupalt,
'isInteractive' => false,
];
return $data;
}
/**
* Build the data for the interactive dropdown.
* @param \renderer_base $output
* @return stdClass
*/
protected function build_editor_data(\renderer_base $output): stdClass {
$choice = $this->get_choice_list();
$result = $this->get_dropdown_data($output, $choice);
$result->autohide = ($this->mod->effectivegroupmode === NOGROUPS);
return $result;
}
/**
* Build the data for the interactive dropdown.
* @param \renderer_base $output
* @param choicelist $choice the choice list
* @return stdClass
*/
protected function get_dropdown_data(\renderer_base $output, choicelist $choice): stdClass {
$buttondata = $this->build_static_data($output);
$dropdown = new status(
$buttondata->groupicon,
$choice,
['dialogwidth' => status::WIDTH['big']],
);
$dropdown->set_dialog_width(status::WIDTH['small']);
$dropdown->set_position(status::POSITION['end']);
return (object) [
'isInteractive' => true,
'groupicon' => $buttondata->groupicon,
'groupalt' => $buttondata->groupalt,
'dropwdown' => $dropdown->export_for_template($output),
];
}
/**
* Create a choice list for the dropdown.
* @return choicelist the choice list
*/
public function get_choice_list(): choicelist {
$choice = new choicelist();
$choice->add_option(
NOGROUPS,
get_string('groupsnone', 'group'),
$this->get_option_data(null, 'cmNoGroups', $this->mod->id)
);
$choice->add_option(
SEPARATEGROUPS,
get_string('groupsseparate', 'group'),
$this->get_option_data('groupsseparate', 'cmSeparateGroups', $this->mod->id)
);
$choice->add_option(
VISIBLEGROUPS,
get_string('groupsvisible', 'group'),
$this->get_option_data('groupsvisible', 'cmVisibleGroups', $this->mod->id)
);
$choice->set_selected_value($this->mod->effectivegroupmode);
return $choice;
}
/**
* Get the data for the option.
* @param string|null $name the name of the option
* @param string $action the state action of the option
* @param int $id the id of the module
* @return array
*/
private function get_option_data(?string $name, string $action, int $id): array {
return [
'description' => ($name) ? get_string("groupmode_{$name}_help", 'group') : null,
// The dropdown icons are decorative, so we don't need to provide alt text.
'icon' => $this->get_action_icon($action),
'extras' => [
'data-id' => $id,
'data-action' => $action,
]
];
}
/**
* Get the group mode icon.
* @param string $groupmode the group mode
* @param string $groupalt the alt text
* @return pix_icon
*/
protected function get_action_icon(string $groupmode, string $groupalt = ''): pix_icon {
$icons = [
'cmNoGroups' => 'i/groupn',
'cmSeparateGroups' => 'i/groups',
'cmVisibleGroups' => 'i/groupv',
];
return new pix_icon($icons[$groupmode], $groupalt);
}
}
@@ -0,0 +1,236 @@
<?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 activity title.
*
* This class is usually rendered inside the cmname inplace editable.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use cm_info;
use core\output\inplace_editable;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_text;
use lang_string;
use renderable;
use section_info;
use stdClass;
use core_external\external_api;
use context_module;
/**
* Base class to render a course module title inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class title extends inplace_editable implements named_templatable, renderable {
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $section;
/** @var cm_info the course module instance */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/** @var editable if the title is editable */
protected $editable;
/** @var displaytemplate the default display template */
protected $displaytemplate = 'core_courseformat/local/content/cm/title';
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param array $displayoptions optional extra display options
* @param bool|null $editable force editable value
*/
public function __construct(
course_format $format,
section_info $section,
cm_info $mod,
array $displayoptions = [],
?bool $editable = null
) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
// Usually displayoptions are loaded in the main cm output. However when the user uses the inplace editor
// the cmname output does not calculate the css classes.
$this->displayoptions = $this->load_display_options($displayoptions);
if ($editable === null) {
$editable = $format->show_editor();
}
$this->editable = $editable;
// Setup inplace editable.
parent::__construct(
'core_course',
'activityname',
$mod->id,
$this->editable,
$mod->name,
$mod->name,
new lang_string('edittitle'),
new lang_string('newactivityname', '', $mod->get_formatted_name())
);
}
/**
* Get the name of the template to use for this templatable.
*
* @param \renderer_base $renderer The renderer requesting the template name
* @return string
*/
public function get_template_name(\renderer_base $renderer): string {
return 'core/inplace_editable';
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): array {
// Inplace editable uses pre-rendered elements and does not allow line beaks in the UI value.
$this->displayvalue = str_replace("\n", "", $this->get_title_displayvalue());
if (trim($this->displayvalue) == '') {
$this->editable = false;
}
return parent::export_for_template($output);
}
/**
* Return the title template data to be used inside the inplace editable.
*
*/
protected function get_title_displayvalue(): string {
global $PAGE;
// Inplace editable uses core renderer by default. However, course elements require
// the format specific renderer.
$courseoutput = $this->format->get_renderer($PAGE);
$mod = $this->mod;
$data = (object)[
'url' => $mod->url,
'instancename' => $mod->get_formatted_name(),
'uservisible' => $mod->uservisible,
'linkclasses' => $this->displayoptions['linkclasses'],
];
// File type after name, for alphabetic lists (screen reader).
if (strpos(
core_text::strtolower($data->instancename),
core_text::strtolower($mod->modfullname)
) === false) {
$data->altname = get_accesshide(' ' . $mod->modfullname);
}
// Get on-click attribute value if specified and decode the onclick - it
// has already been encoded for display (puke).
$data->onclick = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);
return $courseoutput->render_from_template(
$this->displaytemplate,
$data
);
}
/**
* Load the required display options if not present already.
*
* In most cases, display options are provided as a param when creating the
* object. However, inplace_editable and some blocks does not know all of them as it is
* called in a webservice and we need to ensure it is calculated.
*
* @param array $displayoptions the provided dispaly options
* @return array the full display options list
*/
protected function load_display_options(array $displayoptions): array {
$format = $this->format;
$mod = $this->mod;
if (
isset($displayoptions['linkclasses']) &&
isset($displayoptions['textclasses']) &&
isset($displayoptions['onclick'])
) {
return $displayoptions;
}
$cmclass = $format->get_output_classname('content\\cm');
$cmoutput = new $cmclass(
$format,
$this->section,
$mod,
$displayoptions
);
$displayoptions['linkclasses'] = $cmoutput->get_link_classes();
$displayoptions['textclasses'] = $cmoutput->get_text_classes();
$displayoptions['onclick'] = $cmoutput->get_onclick_code();
return $displayoptions;
}
/**
* Updates course module name.
*
* This method is used mainly by inplace_editable webservice.
*
* @param int $itemid course module id
* @param string $newvalue new name
* @return static
*/
public static function update($itemid, $newvalue) {
$context = context_module::instance($itemid);
// Check access.
external_api::validate_context($context);
require_capability('moodle/course:manageactivities', $context);
// Trim module name and Update value.
set_coursemodule_name($itemid, trim($newvalue));
$coursemodulerecord = get_coursemodule_from_id('', $itemid, 0, false, MUST_EXIST);
// Return instance.
$modinfo = get_fast_modinfo($coursemodulerecord->course);
$cm = $modinfo->get_cm($itemid);
$section = $modinfo->get_section_info($cm->sectionnum);
$format = course_get_format($cm->course);
return new static($format, $section, $cm, [], true);
}
}
@@ -0,0 +1,292 @@
<?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 activity availability information.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use action_menu_link_secondary;
use core\output\local\action_menu\subpanel as action_menu_subpanel;
use cm_info;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use core\output\choicelist;
use core\output\local\dropdown\status;
use core\output\named_templatable;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course module availability inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class visibility implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
protected $section;
/** @var cm_info the course module instance */
protected $mod;
/**
* Constructor.
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
*/
public function __construct(course_format $format, section_info $section, cm_info $mod) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
if (!$this->show_visibility()) {
return null;
}
$format = $this->format;
// In rare legacy cases, the section could be stealth (orphaned) but they are not editable.
if (!$format->show_editor()
|| !has_capability('moodle/course:activityvisibility', $this->mod->context)) {
return $this->build_static_data($output);
} else {
return $this->build_editor_data($output);
}
}
/**
* Check if the visibility is displayed.
* @return bool
*/
protected function show_visibility(): bool {
return !$this->mod->visible || $this->mod->is_stealth();
}
/**
* Get the icon for the section visibility.
* @param string $selected the visibility selected value
* @return pix_icon
*/
protected function get_icon(string $selected): pix_icon {
if ($selected === 'hide') {
return new pix_icon('t/show', '');
} else if ($selected === 'stealth') {
return new pix_icon('t/stealth', '');
} else {
return new pix_icon('t/hide', '');
}
}
/**
* Build the data for the editor.
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function build_editor_data(\renderer_base $output): ?stdClass {
$choice = $this->get_choice_list();
return $this->get_dropdown_data($output, $choice);
}
/**
* Build the data for the interactive dropdown.
* @param \renderer_base $output
* @param choicelist $choice the choice list
* @return stdClass
*/
protected function get_dropdown_data(
\renderer_base $output,
choicelist $choice,
): stdClass {
$badgetext = $output->sr_text(get_string('availability'));
if (!$this->mod->visible) {
$badgetext .= get_string('hiddenfromstudents');
$icon = $this->get_icon('hide');
} else if ($this->mod->is_stealth()) {
$badgetext .= get_string('hiddenoncoursepage');
$icon = $this->get_icon('stealth');
} else {
$badgetext .= get_string("availability_show", 'core_courseformat');
$icon = $this->get_icon('show');
}
$dropdown = new status(
$output->render($icon) . ' ' . $badgetext,
$choice,
['dialogwidth' => status::WIDTH['big']],
);
return (object) [
'isInteractive' => true,
'dropwdown' => $dropdown->export_for_template($output),
];
}
/**
* Get the availability choice list.
* @return choicelist
*/
public function get_choice_list(): choicelist {
$choice = $this->create_choice_list();
$choice->set_selected_value($this->get_selected_choice_value());
return $choice;
}
/**
* Return the cm availability menu item.
*
* By default, the cm availability is displayed as a menu item subpanel.
* However, it can be simplified when there is only one option and
* it is not stealth (stealth require a subpanel to inform the user).
*
* @return action_menu_link_secondary|action_menu_subpanel|null
*/
public function get_menu_item(): action_menu_link_secondary|action_menu_subpanel|null {
$choice = $this->get_choice_list();
$selectableoptions = $choice->get_selectable_options();
if (count($selectableoptions) === 0) {
return null;
}
// Visible activities in hidden sections are always considered stealth.
if ($this->section->visible && count($selectableoptions) === 1) {
$option = reset($selectableoptions);
$actionlabel = $option->value === 'show' ? 'modshow' : 'modhide';
return new action_menu_link_secondary(
$option->url,
$option->icon,
get_string($actionlabel, 'moodle'),
$choice->get_option_extras($option->value)
);
}
return new action_menu_subpanel(
get_string('availability', 'moodle'),
$choice,
['class' => 'editing_availability'],
new pix_icon('t/hide', '', 'moodle', ['class' => 'iconsmall'])
);
}
/**
* Get the selected choice value depending on the course, section and stealth settings.
* @return string
*/
protected function get_selected_choice_value(): string {
if (!$this->mod->visible) {
return 'hide';
}
if (!$this->mod->is_stealth()) {
return 'show';
}
if (!$this->section->visible) {
// All visible activities in a hidden sections are considered stealth
// but they don't use the stealth attribute for it. It is just implicit.
return 'show';
}
return 'stealth';
}
/**
* Create a choice list for the dropdown.
* @return choicelist the choice list
*/
protected function create_choice_list(): choicelist {
global $CFG;
$choice = new choicelist();
if ($this->section->visible || $this->mod->has_view()) {
$label = $this->section->visible ? 'show' : 'stealth';
$choice->add_option(
'show',
get_string("availability_{$label}", 'core_courseformat'),
$this->get_option_data($label, 'cmShow')
);
}
$choice->add_option(
'hide',
get_string('availability_hide', 'core_courseformat'),
$this->get_option_data('hide', 'cmHide')
);
if ($CFG->allowstealth && $this->format->allow_stealth_module_visibility($this->mod, $this->section)) {
$choice->add_option(
'stealth',
get_string('availability_stealth', 'core_courseformat'),
$this->get_option_data('stealth', 'cmStealth')
);
}
return $choice;
}
/**
* Get the data for the option.
* @param string $name the name of the option
* @param string $action the state action of the option
* @return array
*/
private function get_option_data(string $name, string $action): array {
return [
'description' => get_string("availability_{$name}_help", 'core_courseformat'),
'icon' => $this->get_icon($name),
// Non-ajax behat is not smart enough to discrimante hidden links
// so we need to keep providing the non-ajax links.
'url' => $this->format->get_non_ajax_cm_action_url($action, $this->mod),
'extras' => [
'data-id' => $this->mod->id,
'data-action' => $action,
]
];
}
/**
* Build the static badges data.
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function build_static_data(\renderer_base $output): ?stdClass {
$data = (object) [
'isInteractive' => false,
];
if (!$this->mod->visible) {
$data->modhiddenfromstudents = true;
} else if ($this->mod->is_stealth()) {
$data->modstealth = true;
}
return $data;
}
}
@@ -0,0 +1,102 @@
<?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 frontpage section displayer.
*
* The frontpage has a different wat of rendering the main topic.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use context_course;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use moodle_url;
use renderable;
use section_info;
use stdClass;
/**
* Represents the frontpage section 1.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class frontpagesection implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/** @var string the section output class name */
protected $sectionclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
// Get the necessary classes.
$this->sectionclass = $format->get_output_classname('content\\section');
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
global $USER;
$format = $this->format;
$section = $this->section;
$sectionoutput = new $this->sectionclass($format, $section);
$sectionoutput->hide_controls();
if (trim($section->name ?? '') == '') {
$sectionoutput->hide_title();
}
$data = (object)[
'sections' => [$sectionoutput->export_for_template($output)],
];
if ($format->show_editor(['moodle/course:update'])) {
$data->showsettings = true;
$data->settingsurl = new moodle_url('/course/editsection.php', ['id' => $section->id]);
}
return $data;
}
}
@@ -0,0 +1,371 @@
<?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 course format output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use context_course;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use renderer_base;
use section_info;
use stdClass;
/**
* Base class to render a course section.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class section implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section info */
protected $section;
/** @var section header output class */
protected $headerclass;
/** @var cm list output class */
protected $cmlistclass;
/** @var section summary output class */
protected $summaryclass;
/** @var activities summary output class */
protected $cmsummaryclass;
/** @var section control menu output class */
protected $controlclass;
/** @var section availability output class */
protected $availabilityclass;
/** @var optional move here output class */
protected $movehereclass;
/** @var optional visibility output class */
protected $visibilityclass;
/** @var bool if the title is hidden for some reason */
protected $hidetitle = false;
/** @var bool if the title is hidden for some reason */
protected $hidecontrols = false;
/** @var bool if the section is considered stealth */
protected $isstealth = false;
/** @var string control menu class. */
protected $controlmenuclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
if ($section->section > $format->get_last_section_number()) {
$this->isstealth = true;
}
// Load output classes names from format.
$this->headerclass = $format->get_output_classname('content\\section\\header');
$this->cmlistclass = $format->get_output_classname('content\\section\\cmlist');
$this->summaryclass = $format->get_output_classname('content\\section\\summary');
$this->cmsummaryclass = $format->get_output_classname('content\\section\\cmsummary');
$this->controlmenuclass = $format->get_output_classname('content\\section\\controlmenu');
$this->availabilityclass = $format->get_output_classname('content\\section\\availability');
$this->movehereclass = $format->get_output_classname('content\\section\\movehere');
$this->visibilityclass = $format->get_output_classname('content\\section\\visibility');
}
/**
* Hide the section title.
*
* This is used on blocks or in the home page where an isolated section is displayed.
*/
public function hide_title(): void {
$this->hidetitle = true;
}
/**
* Hide the section controls.
*
* This is used on blocks or in the home page where an isolated section is displayed.
*/
public function hide_controls(): void {
$this->hidecontrols = true;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
global $USER, $PAGE;
$format = $this->format;
$course = $format->get_course();
$section = $this->section;
$summary = new $this->summaryclass($format, $section);
$data = (object)[
'num' => $section->section ?? '0',
'id' => $section->id,
'sectionreturnid' => $format->get_sectionnum(),
'insertafter' => false,
'summary' => $summary->export_for_template($output),
'highlightedlabel' => $format->get_section_highlighted_name(),
'sitehome' => $course->id == SITEID,
'editing' => $PAGE->user_is_editing(),
'displayonesection' => ($course->id != SITEID && !is_null($format->get_sectionid())),
];
$haspartials = [];
$haspartials['availability'] = $this->add_availability_data($data, $output);
$haspartials['visibility'] = $this->add_visibility_data($data, $output);
$haspartials['editor'] = $this->add_editor_data($data, $output);
$haspartials['header'] = $this->add_header_data($data, $output);
$haspartials['cm'] = $this->add_cm_data($data, $output);
$this->add_format_data($data, $haspartials, $output);
return $data;
}
/**
* Add the section header to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_header_data(stdClass &$data, renderer_base $output): bool {
if (!empty($this->hidetitle)) {
return false;
}
$section = $this->section;
$format = $this->format;
$header = new $this->headerclass($format, $section);
$headerdata = $header->export_for_template($output);
// When a section is displayed alone the title goes over the section, not inside it.
if ($section->section != 0 && $section->section == $format->get_sectionnum()) {
$data->singleheader = $headerdata;
} else {
$data->header = $headerdata;
}
return true;
}
/**
* Add the section cm list to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_cm_data(stdClass &$data, renderer_base $output): bool {
$result = false;
$section = $this->section;
$format = $this->format;
$showsummary = ($section->section != 0 &&
$section->section != $format->get_sectionnum() &&
$format->get_course_display() == COURSE_DISPLAY_MULTIPAGE &&
!$format->show_editor()
);
$showcmlist = $section->uservisible;
// Add activities summary if necessary.
if ($showsummary) {
$cmsummary = new $this->cmsummaryclass($format, $section);
$data->cmsummary = $cmsummary->export_for_template($output);
$data->onlysummary = true;
$result = true;
if (!$format->is_section_current($section)) {
// In multipage, only the current section (and the section zero) has elements.
$showcmlist = false;
}
}
// Add the cm list.
if ($showcmlist) {
$cmlist = new $this->cmlistclass($format, $section);
$data->cmlist = $cmlist->export_for_template($output);
$result = true;
}
return $result;
}
/**
* Add the section availability to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_availability_data(stdClass &$data, renderer_base $output): bool {
$availability = new $this->availabilityclass($this->format, $this->section);
$data->availability = $availability->export_for_template($output);
$data->restrictionlock = !empty($this->section->availableinfo);
$data->hasavailability = $availability->has_availability($output);
return true;
}
/**
* Add the section vibility information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_visibility_data(stdClass &$data, renderer_base $output): bool {
global $USER;
$result = false;
// Check if it is a stealth sections (orphaned).
if ($this->isstealth) {
$data->isstealth = true;
$data->ishidden = true;
$result = true;
}
if (!$this->section->visible) {
$data->ishidden = true;
$course = $this->format->get_course();
$context = context_course::instance($course->id);
if (has_capability('moodle/course:viewhiddensections', $context, $USER)) {
$result = true;
}
}
/* @var \core_courseformat\output\local\content\section\visibility $visibility By default the visibility class used
* here but can be overriden by any course format */
$visibility = new $this->visibilityclass($this->format, $this->section);
$data->visibility = $visibility->export_for_template($output);
return $result;
}
/**
* Add the section editor attributes to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_editor_data(stdClass &$data, renderer_base $output): bool {
$course = $this->format->get_course();
$coursecontext = context_course::instance($course->id);
$editcaps = [];
if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
$editcaps = ['moodle/course:sectionvisibility'];
}
if (!$this->format->show_editor($editcaps)) {
return false;
}
// In a single section page the control menu is located in the page header.
if (empty($this->hidecontrols) && $this->format->get_sectionid() != $this->section->id) {
$controlmenu = new $this->controlmenuclass($this->format, $this->section);
$data->controlmenu = $controlmenu->export_for_template($output);
}
if (!$this->isstealth) {
$data->cmcontrols = $output->course_section_add_cm_control(
$course,
$this->section->section,
$this->format->get_sectionnum()
);
}
return true;
}
/**
* Add the section format attributes to the data structure.
*
* @param stdClass $data the current cm data reference
* @param bool[] $haspartials the result of loading partial data elements
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has name data
*/
protected function add_format_data(stdClass &$data, array $haspartials, renderer_base $output): bool {
$section = $this->section;
$format = $this->format;
$data->iscoursedisplaymultipage = ($format->get_course_display() == COURSE_DISPLAY_MULTIPAGE);
if ($data->num === 0 && !$data->iscoursedisplaymultipage) {
$data->collapsemenu = true;
}
$data->contentcollapsed = $this->is_section_collapsed();
if ($format->is_section_current($section)) {
$data->iscurrent = true;
$data->currentlink = get_accesshide(
get_string('currentsection', 'format_' . $format->get_format())
);
}
return true;
}
/**
* Returns true if the current section should be shown collapsed.
*
* @return bool
*/
protected function is_section_collapsed(): bool {
global $PAGE;
$contentcollapsed = false;
$preferences = $this->format->get_sections_preferences();
if (isset($preferences[$this->section->id])) {
$sectionpreferences = $preferences[$this->section->id];
if (!empty($sectionpreferences->contentcollapsed)) {
$contentcollapsed = true;
}
}
// No matter if the user's preference was to collapse the section or not: If the
// 'expandsection' parameter has been specified, it will be shown uncollapsed.
$expandsection = $PAGE->url->get_param('expandsection');
if ($expandsection !== null && $this->section->section == $expandsection) {
$contentcollapsed = false;
}
return $contentcollapsed;
}
}
@@ -0,0 +1,281 @@
<?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 availability output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use context_course;
use core_availability_multiple_messages;
use core\output\named_templatable;
use core_availability\info;
use core_availability\info_section;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render section availability.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class availability implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the section object */
protected $section;
/** @var string the has availability attribute name */
protected $hasavailabilityname;
/** @var stdClass|null the instance export data */
protected $data = null;
/** @var int Availability excerpt text max size treshold */
protected const AVAILABILITY_EXCERPT_MAXSIZE = 100;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
$this->hasavailabilityname = 'hasavailability';
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
$this->build_export_data($output);
return $this->data;
}
/**
* Returns if the output has availability info to display.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return bool if the element has availability data to display
*/
public function has_availability(\renderer_base $output): bool {
$this->build_export_data($output);
$attributename = $this->hasavailabilityname;
return $this->data->$attributename;
}
/**
* Protected method to build the export data.
*
* @param \renderer_base $output typically, the renderer that's calling this function
*/
protected function build_export_data(\renderer_base $output) {
if (!empty($this->data)) {
return;
}
$data = (object) $this->get_info($output);
$attributename = $this->hasavailabilityname;
$data->$attributename = !empty($data->info);
$this->data = $data;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* If section is not visible, display the message about that ('Not available
* until...', that sort of thing). Otherwise, returns blank.
*
* For users with the ability to view hidden sections, it shows the
* information even though you can view the section and also may include
* slightly fuller information (so that teachers can tell when sections
* are going to be unavailable etc). This logic is the same as for
* activities.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdclass data context for a mustache template
*/
protected function get_info(\renderer_base $output): array {
global $CFG, $USER;
$section = $this->section;
$context = context_course::instance($section->course);
$canviewhidden = has_capability('moodle/course:viewhiddensections', $context, $USER);
$editurl = new \moodle_url(
'/course/editsection.php',
['id' => $this->section->id, 'showonly' => 'availabilityconditions']
);
$info = ['editurl' => $editurl->out(false)];
if (!$section->visible) {
return [];
} else if (!$section->uservisible) {
if ($section->availableinfo) {
// Note: We only get to this function if availableinfo is non-empty,
// so there is definitely something to print.
$info['info'] = $this->get_availability_data($output, $section->availableinfo, 'isrestricted');
}
} else if ($canviewhidden && !empty($CFG->enableavailability)) {
// Check if there is an availability restriction.
$ci = new info_section($section);
$fullinfo = $ci->get_full_information();
if ($fullinfo) {
$info['info'] = $this->get_availability_data($output, $fullinfo, 'isrestricted isfullinfo');
}
}
return $info;
}
/**
* Get the basic availability information data.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param string|core_availability_multiple_messages $availabilityinfo the avalability info
* @param string $additionalclasses additional css classes
* @return stdClass the availability information data
*/
protected function get_availability_data($output, $availabilityinfo, $additionalclasses = ''): stdClass {
// At this point, availabilityinfo is either a string or a renderable. We need to handle both cases in a different way.
if (is_string($availabilityinfo)) {
$data = $this->availability_info_from_string($output, $availabilityinfo);
} else {
$data = $this->availability_info_from_output($output, $availabilityinfo);
}
$data->classes = $additionalclasses;
$additionalclasses = array_filter(explode(' ', $additionalclasses));
if (in_array('ishidden', $additionalclasses)) {
$data->ishidden = 1;
} else if (in_array('isstealth', $additionalclasses)) {
$data->isstealth = 1;
} else if (in_array('isrestricted', $additionalclasses)) {
$data->isrestricted = 1;
if (in_array('isfullinfo', $additionalclasses)) {
$data->isfullinfo = 1;
}
}
return $data;
}
/**
* Generate the basic availability information data from a string.
* Just shorten availability text to generate the excerpt text.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param string $availabilityinfo the avalability info
* @return stdClass the availability information data
*/
protected function availability_info_from_string(\renderer_base $output, string $availabilityinfo): stdClass {
$course = $this->format->get_course();
$text = info::format_info($availabilityinfo, $course);
$data = ['text' => $text];
if (strlen(html_to_text($text, 0, false)) > self::AVAILABILITY_EXCERPT_MAXSIZE) {
$data['excerpt'] = shorten_text($text, self::AVAILABILITY_EXCERPT_MAXSIZE);
}
return (object) $data;
}
/**
* Generate the basic availability information data from a renderable.
* Use the header and the first item to generate the excerpt text.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @param core_availability_multiple_messages $availabilityinfo the avalability info
* @return stdClass the availability information data
*/
protected function availability_info_from_output(
\renderer_base $output,
core_availability_multiple_messages $availabilityinfo
): stdClass {
$course = $this->format->get_course();
$renderable = new \core_availability\output\availability_info($availabilityinfo);
// We need to export_for_template() instead of directly render, to reuse the info for both 'text' and 'excerpt'.
$info = $renderable->export_for_template($output);
$text = $output->render_from_template('core_availability/availability_info', $info);
$data = ['text' => info::format_info($text, $course)];
if (!empty($info->items)) {
$excerpttext = $info->header . ' ' . $info->items[0]->header;
$data['excerpt'] = info::format_info($excerpttext, $course);
}
return (object) $data;
}
/**
* Generate the basic availability information data.
*
* @deprecated since Moodle 4.3 MDL-78204. Please use {@see self::get_availability_data} instead.
* @todo MDL-78489 This will be deleted in Moodle 4.7.
* @param string $text the formatted avalability text
* @param string $additionalclasses additional css classes
* @return stdClass the availability information data
*/
protected function availability_info($text, $additionalclasses = ''): stdClass {
debugging('Use of ' . __FUNCTION__ . '() have been deprecated, ' .
'please use core_courseformat\output\local\content\section\availability::get_availability_data()', DEBUG_DEVELOPER);
$data = (object)[
'text' => $text,
'classes' => $additionalclasses
];
$additionalclasses = array_filter(explode(' ', $additionalclasses));
if (in_array('ishidden', $additionalclasses)) {
$data->ishidden = 1;
} else if (in_array('isstealth', $additionalclasses)) {
$data->isstealth = 1;
} else if (in_array('isrestricted', $additionalclasses)) {
$data->isrestricted = 1;
if (in_array('isfullinfo', $additionalclasses)) {
$data->isfullinfo = 1;
}
}
return $data;
}
}
@@ -0,0 +1,113 @@
<?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 activity item from a section.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use cm_info;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use renderer_base;
use section_info;
use stdClass;
/**
* Base class to render a section activity in the activities list.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmitem implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/** @var cm_info the course module to display */
protected $mod;
/** @var array optional display options */
protected $displayoptions;
/** @var string the cm output class name */
protected $cmclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
* @param array $displayoptions optional extra display options
*/
public function __construct(course_format $format, section_info $section, cm_info $mod, array $displayoptions = []) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
$this->displayoptions = $displayoptions;
// Get the necessary classes.
$this->cmclass = $format->get_output_classname('content\\cm');
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
$format = $this->format;
$course = $format->get_course();
$mod = $this->mod;
$data = new stdClass();
$data->cms = [];
$completionenabled = $course->enablecompletion == COMPLETION_ENABLED;
$showactivityconditions = $completionenabled && $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS;
$showactivitydates = !empty($course->showactivitydates);
// This will apply styles to the course homepage when the activity information output component is displayed.
$hasinfo = $showactivityconditions || $showactivitydates;
$item = new $this->cmclass($format, $this->section, $mod, $this->displayoptions);
return (object)[
'id' => $mod->id,
'anchor' => "module-{$mod->id}",
'module' => $mod->modname,
'extraclasses' => $mod->extraclasses,
'cmformat' => $item->export_for_template($output),
'hasinfo' => $hasinfo,
'indent' => ($format->uses_indentation()) ? $mod->indent : 0,
'groupmode' => $mod->groupmode,
];
}
}
@@ -0,0 +1,134 @@
<?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 activity list from a section.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use moodle_url;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a section activity list.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmlist implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/** @var array optional display options */
protected $displayoptions;
/** @var string the item output class name */
protected $itemclass;
/** @var optional move here output class */
protected $movehereclass;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param array $displayoptions optional extra display options
*/
public function __construct(course_format $format, section_info $section, array $displayoptions = []) {
$this->format = $format;
$this->section = $section;
$this->displayoptions = $displayoptions;
// Get the necessary classes.
$this->itemclass = $format->get_output_classname('content\\section\\cmitem');
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
global $USER;
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$modinfo = $format->get_modinfo();
$user = $USER;
$data = new stdClass();
$data->cms = [];
// By default, non-ajax controls are disabled but in some places like the frontpage
// it is necessary to display them. This is a temporal solution while JS is still
// optional for course editing.
$showmovehere = ismoving($course->id);
if ($showmovehere) {
$data->hascms = true;
$data->showmovehere = true;
$data->strmovefull = strip_tags(get_string("movefull", "", "'$user->activitycopyname'"));
$data->movetosectionurl = new moodle_url('/course/mod.php', ['movetosection' => $section->id, 'sesskey' => sesskey()]);
$data->movingstr = strip_tags(get_string('activityclipboard', '', $user->activitycopyname));
$data->cancelcopyurl = new moodle_url('/course/mod.php', ['cancelcopy' => 'true', 'sesskey' => sesskey()]);
}
if (empty($modinfo->sections[$section->section])) {
return $data;
}
foreach ($modinfo->sections[$section->section] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
// If the old non-ajax move is necessary, we do not print the selected cm.
if ($showmovehere && $USER->activitycopy == $mod->id) {
continue;
}
if ($mod->is_visible_on_course_page()) {
$item = new $this->itemclass($format, $section, $mod, $this->displayoptions);
$data->cms[] = (object)[
'cmitem' => $item->export_for_template($output),
'moveurl' => new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey())),
];
}
}
if (!empty($data->cms)) {
$data->hascms = true;
}
return $data;
}
}
@@ -0,0 +1,132 @@
<?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 activities summary (used for singlesection format).
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use completion_info;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course section summary.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmsummary implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
list($mods, $complete, $total, $showcompletion) = $this->calculate_section_stats();
$totalactivities = array_reduce($mods, fn($carry, $item) => $carry + ($item["count"] ?? 0), 0);
$data = (object)[
'showcompletion' => $showcompletion,
'total' => $total,
'complete' => $complete,
'mods' => array_values($mods),
'totalactivities' => $totalactivities,
];
$data->modprogress = get_string('progresstotal', 'completion', $data);
return $data;
}
/**
* Calculate the activities count of the current section.
*
* @return array with [[count by activity type], completed activities, total of activitites]
*/
private function calculate_section_stats(): array {
$format = $this->format;
$course = $format->get_course();
$section = $this->section;
$modinfo = $format->get_modinfo();
$completioninfo = new completion_info($course);
$mods = [];
$total = 0;
$complete = 0;
$cmids = $modinfo->sections[$section->section] ?? [];
$cancomplete = isloggedin() && !isguestuser();
$showcompletion = false;
foreach ($cmids as $cmid) {
$thismod = $modinfo->cms[$cmid];
if ($thismod->uservisible) {
if (isset($mods[$thismod->modname])) {
$mods[$thismod->modname]['name'] = $thismod->modplural;
$mods[$thismod->modname]['count']++;
} else {
$mods[$thismod->modname]['name'] = $thismod->modfullname;
$mods[$thismod->modname]['count'] = 1;
}
if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) {
$showcompletion = true;
$total++;
$completiondata = $completioninfo->get_data($thismod, true);
if ($completiondata->completionstate == COMPLETION_COMPLETE ||
$completiondata->completionstate == COMPLETION_COMPLETE_PASS) {
$complete++;
}
}
}
}
return [$mods, $complete, $total, $showcompletion];
}
}
@@ -0,0 +1,365 @@
<?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 core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use action_menu;
use action_menu_link_secondary;
use context_course;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use moodle_url;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render section controls.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class controlmenu implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
$menu = $this->get_action_menu($output);
if (empty($menu)) {
return new stdClass();
}
$data = (object)[
'menu' => $output->render($menu),
'hasmenu' => true,
'id' => $this->section->id,
];
return $data;
}
/**
* Generate the action menu element depending on the section.
*
* Sections controlled by a plugin will delegate the control menu to the plugin.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the activity action menu or null if no action menu is available
*/
public function get_action_menu(\renderer_base $output): ?action_menu {
$sectiondelegate = $this->section->get_component_instance();
if ($sectiondelegate) {
return $sectiondelegate->get_section_action_menu($this->format, $this, $output);
}
return $this->get_default_action_menu($output);
}
/**
* Generate the default section action menu.
*
* This method is public in case some block needs to modify the menu before output it.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return action_menu|null the activity action menu
*/
public function get_default_action_menu(\renderer_base $output): ?action_menu {
$controls = $this->section_control_items();
if (empty($controls)) {
return null;
}
// Convert control array into an action_menu.
$menu = new action_menu();
$menu->set_kebab_trigger(get_string('edit'));
$menu->attributes['class'] .= ' section-actions';
$menu->attributes['data-sectionid'] = $this->section->id;
foreach ($controls as $value) {
$url = empty($value['url']) ? '' : $value['url'];
$icon = empty($value['icon']) ? '' : $value['icon'];
$name = empty($value['name']) ? '' : $value['name'];
$attr = empty($value['attr']) ? [] : $value['attr'];
$class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class'];
$al = new action_menu_link_secondary(
new moodle_url($url),
new pix_icon($icon, '', null, ['class' => "smallicon " . $class]),
$name,
$attr
);
$menu->add($al);
}
return $menu;
}
/**
* Generate the edit control items of a section.
*
* It is not clear this kind of controls are still available in 4.0 so, for now, this
* method is almost a clone of the previous section_control_items from the course/renderer.php.
*
* 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() {
global $USER, $PAGE;
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$sectionreturn = !is_null($format->get_sectionid()) ? $format->get_sectionnum() : null;
$user = $USER;
$usecomponents = $format->supports_components();
$coursecontext = context_course::instance($course->id);
$numsections = $format->get_last_section_number();
$isstealth = $section->section > $numsections;
$baseurl = course_get_url($course, $sectionreturn);
$baseurl->param('sesskey', sesskey());
$controls = [];
// Only show the view link if we are not already in the section view page.
if ($PAGE->pagetype !== 'course-view-section-' . $course->format) {
$controls['view'] = [
'url' => new moodle_url('/course/section.php', ['id' => $section->id]),
'icon' => 'i/viewsection',
'name' => get_string('view'),
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon view'],
];
}
if (!$isstealth && has_capability('moodle/course:update', $coursecontext, $user)) {
$params = ['id' => $section->id];
$params['sr'] = $section->section;
if (get_string_manager()->string_exists('editsection', 'format_'.$format->get_format())) {
$streditsection = get_string('editsection', 'format_'.$format->get_format());
} else {
$streditsection = get_string('editsection');
}
$controls['edit'] = [
'url' => new moodle_url('/course/editsection.php', $params),
'icon' => 'i/settings',
'name' => $streditsection,
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon edit'],
];
if ($section->section) {
$duplicatesectionurl = clone($baseurl);
$duplicatesectionurl->param('sectionid', $section->id);
$duplicatesectionurl->param('duplicatesection', 1);
if (!is_null($sectionreturn)) {
$duplicatesectionurl->param('sr', $sectionreturn);
}
$controls['duplicate'] = [
'url' => $duplicatesectionurl,
'icon' => 't/copy',
'name' => get_string('duplicate'),
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon duplicate'],
];
}
}
if ($section->section) {
$url = clone($baseurl);
if (!is_null($sectionreturn)) {
$url->param('sectionid', $format->get_sectionid());
}
if (!$isstealth) {
if (has_capability('moodle/course:sectionvisibility', $coursecontext, $user)) {
$strhidefromothers = get_string('hidefromothers', 'format_' . $course->format);
$strshowfromothers = get_string('showfromothers', 'format_' . $course->format);
if ($section->visible) { // Show the hide/show eye.
$url->param('hide', $section->section);
$controls['visibility'] = [
'url' => $url,
'icon' => 'i/show',
'name' => $strhidefromothers,
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => ($usecomponents) ? 'sectionHide' : 'hide',
'data-id' => $section->id,
'data-icon' => 'i/show',
'data-swapname' => $strshowfromothers,
'data-swapicon' => 'i/hide',
],
];
} else {
$url->param('show', $section->section);
$controls['visibility'] = [
'url' => $url,
'icon' => 'i/hide',
'name' => $strshowfromothers,
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'icon editing_showhide',
'data-sectionreturn' => $sectionreturn,
'data-action' => ($usecomponents) ? 'sectionShow' : 'show',
'data-id' => $section->id,
'data-icon' => 'i/hide',
'data-swapname' => $strhidefromothers,
'data-swapicon' => 'i/show',
],
];
}
}
if (!$sectionreturn && has_capability('moodle/course:movesections', $coursecontext, $user)) {
if ($usecomponents) {
// This tool will appear only when the state is ready.
$url = clone ($baseurl);
$url->param('movesection', $section->section);
$url->param('section', $section->section);
$controls['movesection'] = [
'url' => $url,
'icon' => 'i/dragdrop',
'name' => get_string('move', 'moodle'),
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'icon move waitstate',
'data-action' => 'moveSection',
'data-id' => $section->id,
],
];
}
// Legacy move up and down links for non component-based formats.
$url = clone($baseurl);
if ($section->section > 1) { // Add a arrow to move section up.
$url->param('section', $section->section);
$url->param('move', -1);
$strmoveup = get_string('moveup');
$controls['moveup'] = [
'url' => $url,
'icon' => 'i/up',
'name' => $strmoveup,
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon moveup whilenostate'],
];
}
$url = clone($baseurl);
if ($section->section < $numsections) { // Add a arrow to move section down.
$url->param('section', $section->section);
$url->param('move', 1);
$strmovedown = get_string('movedown');
$controls['movedown'] = [
'url' => $url,
'icon' => 'i/down',
'name' => $strmovedown,
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon movedown whilenostate'],
];
}
}
}
if (course_can_delete_section($course, $section)) {
if (get_string_manager()->string_exists('deletesection', 'format_'.$course->format)) {
$strdelete = get_string('deletesection', 'format_'.$course->format);
} else {
$strdelete = get_string('deletesection');
}
$params = [
'id' => $section->id,
'delete' => 1,
'sesskey' => sesskey(),
];
if (!is_null($sectionreturn)) {
$params['sr'] = $sectionreturn;
}
$url = new moodle_url(
'/course/editsection.php',
$params,
);
$controls['delete'] = [
'url' => $url,
'icon' => 'i/delete',
'name' => $strdelete,
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'icon editing_delete text-danger',
'data-action' => 'deleteSection',
'data-id' => $section->id,
],
];
}
}
if (
has_any_capability([
'moodle/course:movesections',
'moodle/course:update',
'moodle/course:sectionvisibility',
], $coursecontext)
) {
$sectionlink = new moodle_url(
'/course/section.php',
['id' => $section->id]
);
$controls['permalink'] = [
'url' => $sectionlink,
'icon' => 'i/link',
'name' => get_string('sectionlink', 'course'),
'pixattr' => ['class' => ''],
'attr' => [
'class' => 'icon',
'data-action' => 'permalink',
],
];
}
return $controls;
}
}
@@ -0,0 +1,123 @@
<?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 header format output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a section header.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class header implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
$format = $this->format;
$section = $this->section;
$course = $format->get_course();
$data = (object)[
'num' => $section->section,
'id' => $section->id,
];
$data->editing = $format->show_editor();
if ($course->id == SITEID) {
$data->title = $output->section_title_without_link($section, $course);
$data->sitehome = true;
} else {
if (is_null($format->get_sectionid())) {
// All sections are displayed.
if (!$data->editing) {
$data->title = $output->section_title($section, $course);
} else {
$data->title = $output->section_title_without_link($section, $course);
}
} else {
// Only one section is displayed.
$data->displayonesection = true;
$data->title = $output->section_title_without_link($section, $course);
}
}
$coursedisplay = $format->get_course_display();
$data->headerdisplaymultipage = ($coursedisplay == COURSE_DISPLAY_MULTIPAGE);
if ($section->section > $format->get_last_section_number()) {
// Stealth sections (orphaned) has special title.
$data->title = get_string('orphanedactivitiesinsectionno', '', $section->section);
}
if (!$section->visible) {
$data->ishidden = true;
}
if (!$data->editing && $section->uservisible) {
$data->url = course_get_url($course, $section->section, ['navigation' => true]);
}
$data->name = get_section_name($course, $section);
$data->selecttext = $format->get_format_string('selectsection', $data->name);
if (!$format->get_sectionnum()) {
$data->sectionbulk = true;
}
return $data;
}
}
@@ -0,0 +1,97 @@
<?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 summary (used for multipage format).
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\section;
use context_course;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course section summary.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class summary implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
private $section;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
$section = $this->section;
$data = new stdClass();
if ($section->uservisible || $section->visible) {
$data->summarytext = $this->format_summary_text();
}
return $data;
}
/**
* Generate html for a section summary text
*
* @return string HTML to output.
*/
public function format_summary_text(): string {
$section = $this->section;
$context = context_course::instance($section->course);
$summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php',
$context->id, 'course', 'section', $section->id);
$options = new stdClass();
$options->noclean = true;
$options->overflowdiv = true;
return format_text($summarytext, $section->summaryformat, $options);
}
}
@@ -0,0 +1,141 @@
<?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 core_courseformat\output\local\content\section;
use context_course;
use core\output\choicelist;
use core\output\local\dropdown\status;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a section visibility inside a course format.
*
* @package core_courseformat
* @copyright 2024 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class visibility implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
protected $section;
/**
* Constructor.
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
global $USER;
$context = context_course::instance($this->section->course);
$data = new stdClass();
$data->editing = $this->format->show_editor();
if (!$this->section->visible) {
$data->notavailable = true;
if (has_capability('moodle/course:sectionvisibility', $context, $USER)) {
$data->hiddenfromstudents = true;
$data->notavailable = false;
$badgetext = $output->sr_text(get_string('availability'));
$badgetext .= get_string("hiddenfromstudents");
$icon = $this->get_icon('hide');
$choice = new choicelist();
$choice->add_option(
'show',
get_string("availability_show", 'core_courseformat'),
$this->get_option_data('show', 'sectionShow')
);
$choice->add_option(
'hide',
get_string('availability_hide', 'core_courseformat'),
$this->get_option_data('hide', 'sectionHide')
);
$choice->set_selected_value('hide');
$dropdown = new status(
$output->render($icon) . ' ' . $badgetext,
$choice,
['dialogwidth' => status::WIDTH['big']],
);
$data->dropwdown = $dropdown->export_for_template($output);
}
}
return $data;
}
/**
* Get the data for the option.
*
* @param string $name the name of the option
* @param string $action the state action of the option
* @return array
*/
private function get_option_data(string $name, string $action): array {
$baseurl = course_get_url($this->section->course, $this->section);
$baseurl->param('sesskey', sesskey());
$baseurl->param($name, $this->section->section);
// The section page is not yet fully reactive and it needs to use the old non-ajax links.
$pagesectionid = $this->format->get_sectionid();
if ($this->section->id == $pagesectionid) {
$baseurl->param('sectionid', $pagesectionid);
$action = '';
}
return [
'description' => get_string("availability_{$name}_help", 'core_courseformat'),
'icon' => $this->get_icon($name),
// Non-ajax behat is not smart enough to discrimante hidden links
// so we need to keep providing the non-ajax links.
'url' => $baseurl,
'extras' => [
'data-id' => $this->section->id,
'data-action' => $action,
],
];
}
/**
* Get the icon for the section visibility.
* @param string $selected the visibility selected value
* @return pix_icon
*/
protected function get_icon(string $selected): pix_icon {
if ($selected === 'hide') {
return new pix_icon('t/show', '');
} else {
return new pix_icon('t/hide', '');
}
}
}
@@ -0,0 +1,126 @@
<?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 navigation output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use context_course;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use stdClass;
/**
* Base class to render a course add section navigation.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sectionnavigation implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var int the course displayed section */
protected $sectionno;
/** @var stdClass the calculated data to prevent calculations when rendered several times */
private $data = null;
/**
* Constructor.
*
* @param course_format $format the course format
* @param int $sectionno the section number
*/
public function __construct(course_format $format, int $sectionno) {
$this->format = $format;
$this->sectionno = $sectionno;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
global $USER;
if ($this->data !== null) {
return $this->data;
}
$format = $this->format;
$course = $format->get_course();
$context = context_course::instance($course->id);
$modinfo = $this->format->get_modinfo();
$sections = $modinfo->get_section_info_all();
// FIXME: This is really evil and should by using the navigation API.
$canviewhidden = has_capability('moodle/course:viewhiddensections', $context, $USER);
$data = (object)[
'previousurl' => '',
'nexturl' => '',
'larrow' => $output->larrow(),
'rarrow' => $output->rarrow(),
'currentsection' => $this->sectionno,
];
$back = $this->sectionno - 1;
while ($back >= 0 && empty($data->previousurl)) {
if ($canviewhidden || $sections[$back]->uservisible) {
if (!$sections[$back]->visible) {
$data->previoushidden = true;
}
$data->previousname = get_section_name($course, $sections[$back]);
$data->previousurl = course_get_url($course, $back, ['navigation' => true]);
$data->hasprevious = true;
}
$back--;
}
$forward = $this->sectionno + 1;
$numsections = course_get_format($course)->get_last_section_number();
while ($forward <= $numsections and empty($data->nexturl)) {
if ($canviewhidden || $sections[$forward]->uservisible) {
if (!$sections[$forward]->visible) {
$data->nexthidden = true;
}
$data->nextname = get_section_name($course, $sections[$forward]);
$data->nexturl = course_get_url($course, $forward, ['navigation' => true]);
$data->hasnext = true;
}
$forward++;
}
$this->data = $data;
return $data;
}
}
@@ -0,0 +1,102 @@
<?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 selector.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
use core\output\named_templatable;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use renderable;
use stdClass;
use url_select;
/**
* Represents the section selector.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sectionselector implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format class */
protected $format;
/** @var sectionnavigation the main section navigation class */
protected $navigation;
/**
* Constructor.
*
* In the current imeplementaiton the seciton selector is almost a variation of the section navigator
* but in the 4.0 this selector will be a kind of dropdown menu. When this happens the construct params
* will change.
*
* @param course_format $format the course format
* @param sectionnavigation $navigation the current section navigation
*/
public function __construct(course_format $format, sectionnavigation $navigation) {
$this->format = $format;
$this->navigation = $navigation;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @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): stdClass {
$format = $this->format;
$course = $format->get_course();
$modinfo = $this->format->get_modinfo();
$data = $this->navigation->export_for_template($output);
// Add the section selector.
$sectionmenu = [];
$sectionmenu[course_get_url($course)->out(false)] = get_string('maincoursepage');
$section = 1;
$numsections = $format->get_last_section_number();
while ($section <= $numsections) {
$thissection = $modinfo->get_section_info($section);
$url = course_get_url($course, $section, ['navigation' => true]);
if ($thissection->uservisible && $url && $section != $data->currentsection) {
$sectionmenu[$url->out(false)] = get_section_name($course, $section);
}
$section++;
}
$select = new url_select($sectionmenu, '', ['' => get_string('jumpto')]);
$select->class = 'jumpmenu';
$select->formid = 'sectionmenu';
$data->selector = $output->render($select);
return $data;
}
}
@@ -0,0 +1,46 @@
<?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 core_courseformat\output\local;
/**
* Base templatable class for coursformat templateables which are typically overridden by course formats.
*
* @package core_courseformat
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait courseformat_named_templatable {
/**
* Get the name of the template to use for this templatable.
*
* @param \renderer_base $renderer The renderer requesting the template name
* @return string
*/
public function get_template_name(\renderer_base $renderer): string {
$fullpath = str_replace('\\', '/', get_class($this));
$specialrenderers = '@^.*/output/(local|courseformat)/(?<template>.+)$@';
$matches = null;
if (preg_match($specialrenderers, $fullpath, $matches)) {
return "core_courseformat/local/{$matches['template']}";
}
throw new \coding_exception("Unable to determine template name for class " . get_class($this));
}
}
@@ -0,0 +1,147 @@
<?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 core_courseformat\output\local\state;
use core_courseformat\base as course_format;
use completion_info;
use core_courseformat\sectiondelegate;
use renderer_base;
use section_info;
use cm_info;
use renderable;
use stdClass;
use core_availability\info_module;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/completionlib.php');
/**
* Contains the ajax update course module structure.
*
* @package core_courseformat
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cm implements renderable {
/**
* Constructor.
*/
public function __construct(
/** @var course_format $format The course format. */
protected course_format $format,
/** @var section_info $section The section data. */
protected section_info $section,
/** @var cm_info $cm The course module data. */
protected cm_info $cm,
/** @var bool $exportcontent False if pre-rendered cmitem HTML content must be exported. */
protected bool $exportcontent = false,
/** @var ?bool $istrackeduser If is_tracked_user is pre-computed for this CM's course, it can be provided here. */
protected ?bool $istrackeduser = null,
) {
}
/**
* Export this data so it can be used as state object in the course editor.
*
* @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): stdClass {
global $CFG, $USER;
$format = $this->format;
$section = $this->section;
$cm = $this->cm;
$course = $format->get_course();
$data = (object)[
'id' => $cm->id,
'anchor' => "module-{$cm->id}",
'name' => \core_external\util::format_string($cm->name, $cm->context, true),
'visible' => !empty($cm->visible),
'stealth' => $cm->is_stealth(),
'sectionid' => $section->id,
'sectionnumber' => $section->section,
'uservisible' => $cm->uservisible,
'hascmrestrictions' => $this->get_has_restrictions(),
'modname' => get_string('pluginname', 'mod_' . $cm->modname),
'indent' => ($format->uses_indentation()) ? $cm->indent : 0,
'groupmode' => $cm->groupmode,
'module' => $cm->modname,
'plugin' => 'mod_' . $cm->modname,
// Activities with delegate section has some restriction to prevent structure loops.
'delegatesection' => sectiondelegate::has_delegate_class('mod_'.$cm->modname),
];
// Check the user access type to this cm.
$info = new info_module($cm);
$data->accessvisible = ($data->visible && $info->is_available_for_all());
// Add url if the activity is compatible.
$url = $cm->url;
if ($url) {
$data->url = $url->out();
}
if ($this->exportcontent) {
$data->content = $output->course_section_updated_cm_item($format, $section, $cm);
}
// Completion status.
$completioninfo = new completion_info($course);
$data->istrackeduser = $this->istrackeduser ?? $completioninfo->is_tracked_user($USER->id);
if ($data->istrackeduser && $completioninfo->is_enabled($cm)) {
$completiondata = new \core_completion\cm_completion_details($completioninfo, $cm, $USER->id, false);
$data->completionstate = $completiondata->get_overall_completion();
$data->isoverallcomplete = $completiondata->is_overall_complete();
}
$data->allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section);
return $data;
}
/**
* Return if the activity has a restrictions icon displayed or not.
*
* @return bool if the activity has visible restrictions for the user.
*/
protected function get_has_restrictions(): bool {
global $CFG;
$cm = $this->cm;
if (empty($cm->visible) || empty($CFG->enableavailability)) {
return false;
}
// Nothing to be displayed to the user.
if (!$cm->is_visible_on_course_page()) {
return false;
}
// Not allowed to see the module but might be allowed to see some availability.
if (!$cm->uservisible) {
return !empty($cm->availableinfo);
}
// Content editors can see all restrictions if the activity is visible.
if (has_capability('moodle/course:viewhiddenactivities', $cm->context)) {
$ci = new info_module($cm);
return !empty($ci->get_full_information());
}
// Regular users can only see restrictions if apply to them.
return false;
}
}
@@ -0,0 +1,87 @@
<?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 core_courseformat\output\local\state;
use core_courseformat\base as course_format;
use course_modinfo;
use moodle_url;
use renderable;
use stdClass;
/**
* Contains the ajax update course structure.
*
* @package core_course
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course implements renderable {
/** @var course_format the course format class */
protected $format;
/**
* Constructor.
*
* @param course_format $format the course format
*/
public function __construct(course_format $format) {
$this->format = $format;
}
/**
* Export this data so it can be used as state object in the course editor.
*
* @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): stdClass {
global $CFG;
$format = $this->format;
$course = $format->get_course();
$context = $format->get_context();
// State must represent always the most updated version of the course.
$modinfo = course_modinfo::instance($course);
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
$maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $course->maxbytes);
$data = (object)[
'id' => $course->id,
'numsections' => $format->get_last_section_number(),
'sectionlist' => [],
'editmode' => $format->show_editor(),
'highlighted' => $format->get_section_highlighted_name(),
'maxsections' => $format->get_max_sections(),
'baseurl' => $url->out(),
'statekey' => course_format::session_cache($course),
'maxbytes' => $maxbytes,
'maxbytestext' => display_size($maxbytes),
];
$sections = $modinfo->get_section_info_all();
foreach ($sections as $section) {
if ($format->is_section_visible($section)) {
$data->sectionlist[] = $section->id;
}
}
return $data;
}
}
@@ -0,0 +1,147 @@
<?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 core_courseformat\output\local\state;
use core_availability\info_section;
use core_courseformat\base as course_format;
use section_info;
use renderable;
use stdClass;
use context_course;
/**
* Contains the ajax update section structure.
*
* @package core_course
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class section implements renderable {
/** @var course_format the course format class */
protected $format;
/** @var section_info the course section class */
protected $section;
/**
* Constructor.
*
* @param course_format $format the course format
* @param section_info $section the section info
*/
public function __construct(course_format $format, section_info $section) {
$this->format = $format;
$this->section = $section;
}
/**
* Export this data so it can be used as state object in the course editor.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
$format = $this->format;
$course = $format->get_course();
$section = $this->section;
$modinfo = $format->get_modinfo();
$indexcollapsed = false;
$contentcollapsed = false;
$preferences = $format->get_sections_preferences();
if (isset($preferences[$section->id])) {
$sectionpreferences = $preferences[$section->id];
if (!empty($sectionpreferences->contentcollapsed)) {
$contentcollapsed = true;
}
if (!empty($sectionpreferences->indexcollapsed)) {
$indexcollapsed = true;
}
}
$data = (object)[
'id' => $section->id,
'section' => $section->section,
'number' => $section->section,
'title' => $format->get_section_name($section),
'hassummary' => !empty($section->summary),
'rawtitle' => $section->name,
'cmlist' => [],
'visible' => !empty($section->visible),
'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])->out(),
'current' => $format->is_section_current($section),
'indexcollapsed' => $indexcollapsed,
'contentcollapsed' => $contentcollapsed,
'hasrestrictions' => $this->get_has_restrictions(),
'bulkeditable' => $this->is_bulk_editable(),
'component' => $section->component,
'itemid' => $section->itemid,
];
if (empty($modinfo->sections[$section->section])) {
return $data;
}
foreach ($modinfo->sections[$section->section] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if ($section->uservisible && $mod->is_visible_on_course_page()) {
$data->cmlist[] = $mod->id;
}
}
return $data;
}
/**
* Return if the section can be selected for bulk editing.
* @return bool if the section can be edited in bulk
*/
protected function is_bulk_editable(): bool {
$section = $this->section;
return ($section->section != 0);
}
/**
* Return if the section has a restrictions icon displayed or not.
*
* @return bool if the section has visible restrictions for the user.
*/
protected function get_has_restrictions(): bool {
global $CFG;
$section = $this->section;
$course = $this->format->get_course();
$context = context_course::instance($course->id);
// Hidden sections have no restriction indicator displayed.
if (empty($section->visible) || empty($CFG->enableavailability)) {
return false;
}
// The activity is not visible to the user but it may have some availability information.
if (!$section->uservisible) {
return !empty($section->availableinfo);
}
// Course editors can see all restrictions if the section is visible.
if (has_capability('moodle/course:viewhiddensections', $context)) {
$ci = new info_section($section);
return !empty($ci->get_full_information());
}
// Regular users can only see restrictions if apply to them.
return false;
}
}
@@ -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/>.
namespace core_courseformat\output;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/renderer.php');
use cm_info;
use coding_exception;
use core_course_renderer;
use core_courseformat\base as course_format;
use html_writer;
use moodle_page;
use renderable;
use section_info;
use stdClass;
/**
* Contains the default section course format output class.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class section_renderer extends core_course_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);
// Ensure capabilities for section editing controls match those defined in course/view.php to ensure that they work
// when called via an AJAX request.
if (course_get_format($page->course)->uses_sections()) {
$page->set_other_editing_capability('moodle/course:sectionvisibility');
$page->set_other_editing_capability('moodle/course:movesections');
}
}
/**
* Renders the provided widget and returns the HTML to display it.
*
* Course format templates uses a similar subfolder structure to the renderable classes.
* This method find out the specific template for a course widget. That's the reason why
* this render method is different from the normal plugin renderer one.
*
* course format templatables can be rendered using the core_course/local/* templates.
* Format plugins are free to override the default template location using render_xxx methods as usual.
*
* @param renderable $widget instance with renderable interface
* @return string the widget HTML
*/
public function render(renderable $widget) {
global $CFG;
$fullpath = str_replace('\\', '/', get_class($widget));
$classparts = explode('/', $fullpath);
// Strip namespaces.
$classname = array_pop($classparts);
// Remove _renderable suffixes.
$classname = preg_replace('/_renderable$/', '', $classname);
$rendermethod = 'render_' . $classname;
if (method_exists($this, $rendermethod)) {
return $this->$rendermethod($widget);
}
// If nothing works, let the parent class decide.
return parent::render($widget);
}
/**
* Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
*
* @param 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) {
$title = get_section_name($course, $section);
$url = course_get_url($course, $section->section, array('navigation' => true));
if ($url) {
$title = html_writer::link($url, $title);
}
return $title;
}
/**
* Generate the section title to be displayed on the section page, without a link
*
* @param 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_without_link($section, $course) {
return get_section_name($course, $section);
}
/**
* Get the updated rendered version of a cm list item.
*
* This method is used when an activity is duplicated or copied in on the client side without refreshing the page.
* Note that the previous method is used every time an activity is rendered, independent of it is the initial page
* loading or an Ajax update. In this case, course_section_updated_cm_item will only be used when the course editor
* requires to get an updated cm item HTML to perform partial page refresh. It will be used for suporting the course
* editor webservices.
*
* By default, the template used for update a cm_item is the same as when it renders initially, but format plugins are
* free to override this methos to provide extra affects or so.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $cm the course module ionfo
* @param array $displayoptions optional extra display options
* @return string the rendered element
*/
public function course_section_updated_cm_item(
course_format $format,
section_info $section,
cm_info $cm,
array $displayoptions = []
) {
$cmitemclass = $format->get_output_classname('content\\section\\cmitem');
$cmitem = new $cmitemclass($format, $section, $cm, $displayoptions);
return $this->render($cmitem);
}
/**
* Get the updated rendered version of a section.
*
* This method will only be used when the course editor requires to get an updated cm item HTML
* to perform partial page refresh. It will be used for supporting the course editor webservices.
*
* By default, the template used for update a section is the same as when it renders initially,
* but format plugins are free to override this method to provide extra effects or so.
*
* @param course_format $format the course format
* @param section_info $section the section info
* @return string the rendered element
*/
public function course_section_updated(
course_format $format,
section_info $section
): string {
$sectionclass = $format->get_output_classname('content\\section');
$output = new $sectionclass($format, $section);
return $this->render($output);
}
/**
* Get the course index drawer with placeholder.
*
* The default course index is loaded after the page is ready. Format plugins can override
* this method to provide an alternative course index.
*
* If the format is not compatible with the course index, this method will return an empty string.
*
* @param course_format $format the course format
* @return String the course index HTML.
*/
public function course_index_drawer(course_format $format): ?String {
if ($format->uses_course_index()) {
include_course_editor($format);
return $this->render_from_template('core_courseformat/local/courseindex/drawer', []);
}
return '';
}
/**
* Render the enable bulk editing button.
* @param course_format $format the course format
* @return string|null the enable bulk button HTML (or null if no bulk available).
*/
public function bulk_editing_button(course_format $format): ?string {
if (!$format->show_editor() || !$format->supports_components()) {
return null;
}
$widgetclass = $format->get_output_classname('content\\bulkedittoggler');
$widget = new $widgetclass($format);
return $this->render($widget);
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_edit_control_menu($controls, $course, $section) {
throw new coding_exception('section_edit_control_menu() can not be used anymore. Please use ' .
'core_courseformat\\output\\local\\content\\section to render a section. In case you need to modify those controls ' .
'override core_courseformat\\output\\local\\content\\section\\controlmenu in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_right_content($section, $course, $onsectionpage) {
throw new coding_exception('section_right_content() can not be used anymore. Please use ' .
'core_courseformat\\output\\local\\content\\section to render a section.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_left_content($section, $course, $onsectionpage) {
throw new coding_exception('section_left_content() can not be used anymore. Please use ' .
'core_courseformat\\output\\local\\content\\section to render a section.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_header($section, $course, $onsectionpage, $sectionreturn = null) {
throw new coding_exception('section_header() can not be used any more. Please use ' .
'core_courseformat\\output\\local\\content\\section to render a section ' .
'or core_courseformat\output\\local\\content\\section\\header ' .
'to print only the header.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_footer() {
throw new coding_exception('section_footer() can not be used any more. Please use ' .
'core_courseformat\\output\\local\\content\\section to render individual sections or .' .
'core_courseformat\\output\\local\\content to render the full course');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function start_section_list() {
throw new coding_exception('start_section_list() can not be used any more. Please use ' .
'core_courseformat\\output\\local\\content\\section to render individual sections or .' .
'core_courseformat\\output\\local\\content to render the full course');
}
/**
* @deprecated since 4.0 - use core_course output components instead.y
*/
protected function end_section_list() {
throw new coding_exception('end_section_list() can not be used any more. Please use ' .
'core_courseformat\\output\\local\\content\\section to render individual sections or .' .
'core_courseformat\\output\\local\\content to render the full course');
}
/**
* Old method to print section edit controls. Do not use it!
*
* @deprecated since Moodle 3.0 MDL-48947 - Use core_courseformat\output\section_renderer::section_edit_control_items() instead
*/
protected function section_edit_controls() {
throw new coding_exception('section_edit_controls() can not be used anymore. Please use ' .
'section_edit_control_items() instead.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_edit_control_items($course, $section, $onsectionpage = false) {
throw new coding_exception('section_edit_control_items() can not be used any more. Please use or extend' .
'core_courseformat\output\\local\\content\\section\\controlmenu instead (like topics format does).');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_summary($section, $course, $mods) {
throw new coding_exception('section_summary() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections. If you need to modify those summary, extend ' .
'core_courseformat\output\\local\\content\\section\\summary in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_activity_summary($section, $course, $mods) {
throw new coding_exception('section_activity_summary() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections. ' .
'If you need to modify those information, extend ' .
'core_courseformat\output\\local\\content\\section\\cmsummary in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_availability_message($section, $canviewhidden) {
throw new coding_exception('section_availability_message() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections. If you need to modify this element, extend ' .
'core_courseformat\output\\local\\content\\section\\availability in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
public function section_availability($section) {
throw new coding_exception('section_availability() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections. If you need to modify this element, extend ' .
'core_courseformat\output\\local\\content\\section\\availability in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function course_activity_clipboard($course, $sectionno = null) {
throw new coding_exception('Non ajax course edition using course_activity_clipboard is not supported anymore.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function get_nav_links($course, $sections, $sectionno) {
throw new coding_exception('get_nav_links() can not be used any more. Please use ' .
'core_courseformat\\output\\local\\content to render a course. If you need to modify this element, extend ' .
'core_courseformat\\output\\local\\content\\sectionnavigation in your format plugin.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function stealth_section_header($sectionno) {
throw new coding_exception('stealth_section_header() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections.');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function stealth_section_footer() {
throw new coding_exception('stealth_section_footer() can not be used any more. Please use ' .
'core_courseformat\output\\local\\content\\section to render sections.');
}
/**
* Generate the html for a hidden section
*
* @param int $sectionno The section number in the course which is being displayed
* @param int|stdClass $courseorid The course to get the section name for (object or just course id)
* @return string HTML to output.
*/
protected function section_hidden($sectionno, $courseorid = null) {
if ($courseorid) {
$sectionname = get_section_name($courseorid, $sectionno);
$strnotavailable = get_string('notavailablecourse', '', $sectionname);
} else {
$strnotavailable = get_string('notavailable');
}
$o = '';
$o .= html_writer::start_tag('li', [
'id' => 'section-' . $sectionno,
'class' => 'section main clearfix hidden',
'data-sectionid' => $sectionno
]);
$o .= html_writer::tag('div', '', array('class' => 'left side'));
$o .= html_writer::tag('div', '', array('class' => 'right side'));
$o .= html_writer::start_tag('div', array('class' => 'content'));
$o .= html_writer::tag('div', $strnotavailable);
$o .= html_writer::end_tag('div');
$o .= html_writer::end_tag('li');
return $o;
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function section_nav_selection($course, $sections, $displaysection) {
throw new coding_exception('section_nav_selection() can not be used anymore. Please use ' .
'core_courseformat\\output\\local\\content to render a course. If you need to modify this element, extend ' .
'core_courseformat\\output\\local\\content\\sectionnavigation or ' .
'core_courseformat\\output\\local\\content\\sectionselector in your format plugin.');
}
/**
* @deprecated since 4.0
*/
public function print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection) {
throw new coding_exception('Method print_single_section_page can not be used anymore. Please use' .
'core_courseformat\\output\\local\\content instead ' .
'or override render_content method to use a different template');
}
/**
* @deprecated since 4.0
*/
public function print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused) {
throw new coding_exception('Method print_multiple_section_page can not be used anymore. Please use' .
'core_courseformat\\output\\local\\content instead ' .
'or override render_content method to use a diferent template');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function change_number_sections($course, $sectionreturn = null) {
throw new coding_exception('Method change_number_sections can not be used anymore. Please use' .
'core_courseformat\\output\\local\\content\\addsection instead');
}
/**
* @deprecated since 4.0 - use core_course output components instead.
*/
protected function format_summary_text($section) {
throw new coding_exception('Method format_summary_text can not be used anymore. Please use' .
'core_courseformat\output\\local\\content\\section\\summary::format_summary_text instead');
}
}
@@ -0,0 +1,30 @@
<?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/>.
/**
* Site topics renderer.
*
* Site course is not a real course format, but it requires a format renderer to use the output
* components.
*
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output;
class site_renderer extends section_renderer {
}
@@ -0,0 +1,83 @@
<?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 core_courseformat\privacy;
use core_privacy\local\metadata\collection;
/**
* Privacy provider implementation for courseformat core subsystem.
*
* @package core_courseformat
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This system has data.
\core_privacy\local\metadata\provider,
// This system has some sitewide user preferences to export.
\core_privacy\local\request\user_preference_provider {
/** The user preference for the navigation drawer. */
public const SECTION_PREFERENCES_PREFIX = 'coursesectionspreferences';
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference(
self::SECTION_PREFERENCES_PREFIX,
'privacy:metadata:preference:' . self::SECTION_PREFERENCES_PREFIX
);
return $collection;
}
/**
* Store all user preferences for this system.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
// Get user courses.
$courses = enrol_get_all_users_courses($userid);
if (empty($courses)) {
return;
}
foreach ($courses as $course) {
$preferencename = self::SECTION_PREFERENCES_PREFIX . '_' . $course->id;
$preference = get_user_preferences($preferencename, null, $userid);
if (isset($preference)) {
$preferencestring = get_string('preference:' . self::SECTION_PREFERENCES_PREFIX, 'courseformat', $course->fullname);
\core_privacy\local\request\writer::export_user_preference(
'core_courseformat',
$preferencename,
$preference,
$preferencestring
);
}
}
}
}
+127
View File
@@ -0,0 +1,127 @@
<?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 core_courseformat;
use action_menu;
use renderer_base;
use section_info;
use core_courseformat\stateupdates;
use core_courseformat\output\local\content\section\controlmenu;
use core_courseformat\base as course_format;
/**
* Section delegate base class.
*
* Plugins using delegate sections must extend this class into
* their PLUGINNAME\courseformat\sectiondelegate class.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class sectiondelegate {
/**
* Constructor.
* @param section_info $sectioninfo
*/
public function __construct(
protected section_info $sectioninfo
) {
}
/**
* Get the section info instance if available.
*
* @param section_info $sectioninfo
* @return section_info|null
*/
public static function instance(section_info $sectioninfo): ?self {
if (empty($sectioninfo->component)) {
return null;
}
$classname = self::get_delegate_class_name($sectioninfo->component);
if ($classname === null) {
return null;
}
return new $classname($sectioninfo);
}
/**
* Return the delgate class name of a plugin, if any.
* @param string $pluginname
* @return string|null the delegate class name or null if not found.
*/
protected static function get_delegate_class_name(string $pluginname): ?string {
$classname = $pluginname . '\courseformat\sectiondelegate';
if (!class_exists($classname)) {
return null;
}
return $classname;
}
/**
* Check if a plugin has a delegate class.
* @param string $pluginname
* @return bool
*/
public static function has_delegate_class(string $pluginname): bool {
return self::get_delegate_class_name($pluginname) !== null;
}
/**
* Define the section final name.
*
* This method can process the section name and return the validated new name.
*
* @param section_info $section
* @param string|null $newname the new name value to store in the database
* @return string|null the name value to store in the database
*/
public function preprocess_section_name(section_info $section, ?string $newname): ?string {
return $newname;
}
/**
* Add extra state updates when put or create a section.
*
* This method is called every time the backend sends a delegated section
* state update to the UI.
*
* @param section_info $section the affected section.
* @param stateupdates $updates the state updates object to notify the UI.
*/
public function put_section_state_extra_updates(section_info $section, stateupdates $updates): void {
// By default, do nothing.
}
/**
* Allow delegate plugin to modify the available section menu.
*
* @param course_format $format The course format instance.
* @param controlmenu $controlmenu The control menu instance.
* @param renderer_base $output The renderer instance.
* @return action_menu|null The new action menu with the list of edit control items or null if no action menu is available.
*/
public function get_section_action_menu(
course_format $format,
controlmenu $controlmenu,
renderer_base $output,
): ?action_menu {
return $controlmenu->get_default_action_menu($output);
}
}
File diff suppressed because it is too large Load Diff
+232
View File
@@ -0,0 +1,232 @@
<?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 core_courseformat;
use coding_exception;
use core_courseformat\base as course_format;
use renderer_base;
use stdClass;
use course_modinfo;
use JsonSerializable;
/**
* Class to track state actions.
*
* The methods from this class should be executed via "stateactions" methods.
*
* Each format plugin could extend this class to provide new updates to the frontend
* mutation module.
* Extended classes should be located in "format_XXX\course" namespace and
* extends {@see \core_courseformat\stateupdates}.
*
* @package core_course
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stateupdates implements JsonSerializable {
/** @var course_format format the course format */
protected $format;
/** @var renderer_base renderer format renderer */
protected $output;
/** @var array the tracked updates */
protected $updates;
/**
* State update class constructor.
*
* @param course_format $format Course format.
*/
public function __construct(course_format $format) {
global $PAGE;
$this->format = $format;
$this->output = $this->format->get_renderer($PAGE);
$this->updates = [];
}
/**
* Return the data to serialize the current track in JSON.
*
* @return stdClass the statement data structure
*/
public function jsonSerialize(): array {
return $this->updates;
}
/**
* Add track about a general course state change.
*/
public function add_course_put(): void {
$courseclass = $this->format->get_output_classname('state\\course');
$currentstate = new $courseclass($this->format);
$this->add_update('course', 'put', $currentstate->export_for_template($this->output));
}
/**
* Add track about a section state put.
*
* @param int $sectionid The affected section id.
*/
public function add_section_put(int $sectionid): void {
$this->create_or_put_section($sectionid, 'put');
}
/**
* Add track about a new section created.
*
* @param int $sectionid The affected section id.
*/
public function add_section_create(int $sectionid): void {
$this->create_or_put_section($sectionid, 'create');
}
/**
* Add track about section created or put.
*
* @param int $sectionid The affected section id.
* @param string $action The action to track for the section ('create' or 'put').
*/
protected function create_or_put_section(int $sectionid, string $action): void {
if ($action != 'create' && $action != 'put') {
throw new coding_exception(
"Invalid action passed ($action) to create_or_put_section. Only 'create' and 'put' are valid."
);
}
$course = $this->format->get_course();
$modinfo = course_modinfo::instance($course);
$format = $this->format;
$section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST);
if (!$format->is_section_visible($section)) {
return;
}
$sectionclass = $format->get_output_classname('state\\section');
$currentstate = new $sectionclass($this->format, $section);
$this->add_update('section', $action, $currentstate->export_for_template($this->output));
// If the section is delegated to a component, give the component oportunity to add updates.
$delegated = $section->get_component_instance();
if ($delegated) {
$delegated->put_section_state_extra_updates($section, $this);
}
}
/**
* Add track about a section deleted.
*
* @deprecated since Moodle 4.1 MDL-74925 - please call add_section_remove() instead.
* @param int $sectionid The affected section id.
*/
public function add_section_delete(int $sectionid): void {
debugging('add_section_delete() is deprecated. Please use add_section_remove() instead.', DEBUG_DEVELOPER);
$this->add_update('section', 'remove', (object)['id' => $sectionid]);
}
/**
* Add track about a section removed.
*
* @param int $sectionid The affected section id.
*/
public function add_section_remove(int $sectionid): void {
$this->add_update('section', 'remove', (object)['id' => $sectionid]);
}
/**
* Add track about a course module state update.
*
* @param int $cmid the affected course module id
*/
public function add_cm_put(int $cmid): void {
$this->create_or_put_cm($cmid, 'put');
}
/**
* Add track about a course module created.
*
* @param int $cmid the affected course module id
*/
public function add_cm_create(int $cmid): void {
$this->create_or_put_cm($cmid, 'create', true);
}
/**
* Add track about section created or put.
*
* @param int $cmid The affected course module id.
* @param string $action The action to track for the section ('create' or 'put').
*/
protected function create_or_put_cm(int $cmid, string $action): void {
$modinfo = course_modinfo::instance($this->format->get_course());
$cm = $modinfo->get_cm($cmid);
$section = $modinfo->get_section_info_by_id($cm->section);
$format = $this->format;
if (!$section->uservisible || !$cm->is_visible_on_course_page()) {
return;
}
$cmclass = $format->get_output_classname('state\\cm');
$currentstate = new $cmclass($this->format, $section, $cm);
$this->add_update('cm', $action, $currentstate->export_for_template($this->output));
}
/**
* Add track about a course module deleted.
*
* @deprecated since Moodle 4.1 MDL-74925 - please call add_cm_remove() instead.
* @param int $cmid the affected course module id
*/
public function add_cm_delete(int $cmid): void {
debugging('add_cm_delete() is deprecated. Please use add_cm_remove() instead.', DEBUG_DEVELOPER);
$this->add_update('cm', 'remove', (object)['id' => $cmid]);
}
/**
* Add track about a course module removed.
*
* @param int $cmid the affected course module id
*/
public function add_cm_remove(int $cmid): void {
$this->add_update('cm', 'remove', (object)['id' => $cmid]);
}
/**
* Add a valid update message to the update list.
*
* @param string $name the update name
* @param string $action the update action (usually update, create, remove)
* @param stdClass $fields the object fields
*/
protected function add_update(string $name, string $action, stdClass $fields): void {
$this->updates[] = (object)[
'name' => $name,
'action' => $action,
'fields' => $fields,
];
}
}