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
@@ -0,0 +1,177 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Specialised restore for format_weeks
*
* @package format_weeks
* @category backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Specialised restore for format_weeks
*
* Processes 'numsections' from the old backup files and hides sections that used to be "orphaned"
*
* @package format_weeks
* @category backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_format_weeks_plugin extends restore_format_plugin {
/** @var int */
protected $originalnumsections = 0;
/**
* Checks if backup file was made on Moodle before 3.3 and we should respect the 'numsections'
* and potential "orphaned" sections in the end of the course.
*
* @return bool
*/
protected function is_pre_33_backup() {
$backupinfo = $this->step->get_task()->get_info();
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
return version_compare($backuprelease, '3.3', '<');
}
/**
* Handles setting the automatic end date for a restored course.
*
* @param int $enddate The end date in the backup file.
*/
protected function update_automatic_end_date($enddate) {
global $DB;
// At this stage the 'course_format_options' table will already have a value set for this option as it is
// part of the course format and the default will have been set.
// Get the current course format option.
$params = array(
'courseid' => $this->step->get_task()->get_courseid(),
'format' => 'weeks',
'sectionid' => 0,
'name' => 'automaticenddate'
);
$cfoid = $DB->get_field('course_format_options', 'id', $params);
$update = new stdClass();
$update->id = $cfoid;
if (empty($enddate)) {
$update->value = 1;
$DB->update_record('course_format_options', $update);
// Now, let's update the course end date.
format_weeks::update_end_date($this->step->get_task()->get_courseid());
} else {
$update->value = 0;
$DB->update_record('course_format_options', $update);
// The end date may have been changed by observers during restore, ensure we set it back to what was in the backup.
$DB->set_field('course', 'enddate', $enddate, array('id' => $this->step->get_task()->get_courseid()));
}
}
/**
* Handles updating the visibility of sections in the restored course.
*
* @param int $numsections The number of sections in the restored course.
*/
protected function update_course_sections_visibility($numsections) {
global $DB;
$backupinfo = $this->step->get_task()->get_info();
foreach ($backupinfo->sections as $key => $section) {
// For each section from the backup file check if it was restored and if was "orphaned" in the original
// course and mark it as hidden. This will leave all activities in it visible and available just as it was
// in the original course.
// Exception is when we restore with merging and the course already had a section with this section number,
// in this case we don't modify the visibility.
if ($this->step->get_task()->get_setting_value($key . '_included')) {
$sectionnum = (int)$section->title;
if ($sectionnum > $numsections && $sectionnum > $this->originalnumsections) {
$DB->execute("UPDATE {course_sections} SET visible = 0 WHERE course = ? AND section = ?",
[$this->step->get_task()->get_courseid(), $sectionnum]);
}
}
}
}
/**
* Creates a dummy path element in order to be able to execute code after restore
*
* @return restore_path_element[]
*/
public function define_course_plugin_structure() {
global $DB;
// Since this method is executed before the restore we can do some pre-checks here.
// In case of merging backup into existing course find the current number of sections.
$target = $this->step->get_task()->get_target();
if (($target == backup::TARGET_CURRENT_ADDING || $target == backup::TARGET_EXISTING_ADDING) &&
$this->is_pre_33_backup()) {
$maxsection = $DB->get_field_sql(
'SELECT max(section) FROM {course_sections} WHERE course = ?',
[$this->step->get_task()->get_courseid()]);
$this->originalnumsections = (int)$maxsection;
}
// Dummy path element is needed in order for after_restore_course() to be called.
return [new restore_path_element('dummy_course', $this->get_pathfor('/dummycourse'))];
}
/**
* Dummy process method
*/
public function process_dummy_course() {
}
/**
* Executed after course restore is complete
*
* This method is only executed if course configuration was overridden
*/
public function after_restore_course() {
if (!$this->is_pre_33_backup()) {
// Backup file was made in Moodle 3.3 or later, we don't need to process it.
return;
}
$backupinfo = $this->step->get_task()->get_info();
if ($backupinfo->original_course_format !== 'weeks') {
// Backup from another course format.
return;
}
$data = $this->connectionpoint->get_data();
// Backup may not include the end date, so set it to 0.
$enddate = isset($data['tags']['enddate']) ? $data['tags']['enddate'] : 0;
// Set the automatic end date setting and the course end date (if applicable).
$this->update_automatic_end_date($enddate);
if (isset($data['tags']['numsections'])) {
// Update course sections visibility.
$numsections = (int)$data['tags']['numsections'];
$this->update_course_sections_visibility($numsections);
}
}
}
+74
View File
@@ -0,0 +1,74 @@
<?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/>.
/**
* Event observers used by the weeks course format.
*
* @package format_weeks
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Event observer for format_weeks.
*
* @package format_weeks
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format_weeks_observer {
/**
* Triggered via \core\event\course_updated event.
*
* @param \core\event\course_updated $event
*/
public static function course_updated(\core\event\course_updated $event) {
if (class_exists('format_weeks', false)) {
// If class format_weeks was never loaded, this is definitely not a course in 'weeks' format.
// Course may still be in another format but format_weeks::update_end_date() will check it.
format_weeks::update_end_date($event->courseid);
}
}
/**
* Triggered via \core\event\course_section_created event.
*
* @param \core\event\course_section_created $event
*/
public static function course_section_created(\core\event\course_section_created $event) {
if (class_exists('format_weeks', false)) {
// If class format_weeks was never loaded, this is definitely not a course in 'weeks' format.
// Course may still be in another format but format_weeks::update_end_date() will check it.
format_weeks::update_end_date($event->courseid);
}
}
/**
* Triggered via \core\event\course_section_deleted event.
*
* @param \core\event\course_section_deleted $event
*/
public static function course_section_deleted(\core\event\course_section_deleted $event) {
if (class_exists('format_weeks', false)) {
// If class format_weeks was never loaded, this is definitely not a course in 'weeks' format.
// Course may still be in another format but format_weeks::update_end_date() will check it.
format_weeks::update_end_date($event->courseid);
}
}
}
@@ -0,0 +1,59 @@
<?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/>.
/**
* Renderer for outputting the weeks course format.
*
* @package format_weeks
* @copyright 2012 Dan Poltawski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.3
*/
namespace format_weeks\output;
use core_courseformat\output\section_renderer;
/**
* Basic renderer for weeks format.
*
* @copyright 2012 Dan Poltawski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends section_renderer {
/**
* 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) {
return $this->render(course_get_format($course)->inplace_editable_render_section_name($section));
}
/**
* Generate the section title to be displayed on the section page, without a link
*
* @param 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 $this->render(course_get_format($course)->inplace_editable_render_section_name($section, false));
}
}
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for format_weeks.
*
* @package format_weeks
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace format_weeks\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for format_weeks implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Format weeks event handler definition.
*
* @package format_weeks
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = array(
array(
'eventname' => '\core\event\course_updated',
'callback' => 'format_weeks_observer::course_updated',
),
array(
'eventname' => '\core\event\course_section_created',
'callback' => 'format_weeks_observer::course_section_created',
),
array(
'eventname' => '\core\event\course_section_deleted',
'callback' => 'format_weeks_observer::course_section_deleted',
)
);
+58
View File
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Upgrade scripts for course format "Weeks"
*
* @package format_weeks
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade script for format_weeks
*
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_format_weeks_upgrade($oldversion) {
global $CFG, $DB;
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2023030700) {
// For sites migrating from 4.0.x or 4.1.x where the indentation was removed,
// we are disabling 'indentation' value by default.
if ($oldversion >= 2022041900) {
set_config('indentation', 0, 'format_weeks');
} else {
set_config('indentation', 1, 'format_weeks');
}
upgrade_plugin_savepoint(true, 2023030700, 'format', 'weeks');
}
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+53
View File
@@ -0,0 +1,53 @@
<?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/>.
/**
* Weeks course format. Display the whole course as "weeks" made of modules.
*
* @package format_weeks
* @copyright 2006 The Open University
* @author N.D.Freear@open.ac.uk, and others.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/filelib.php');
require_once($CFG->libdir.'/completionlib.php');
// Horrible backwards compatible parameter aliasing..
if ($week = optional_param('week', 0, PARAM_INT)) {
$url = $PAGE->url;
$url->param('section', $week);
debugging('Outdated week param passed to course/view.php', DEBUG_DEVELOPER);
redirect($url);
}
// End backwards-compatible aliasing..
$format = course_get_format($course);
// Make sure section 0 is created.
course_create_sections_if_missing($format->get_course(), 0);
$renderer = $PAGE->get_renderer('format_weeks');
if (!is_null($displaysection)) {
$format->set_sectionnum($displaysection);
}
$outputclass = $format->get_output_classname('content');
$output = new $outputclass($format);
echo $renderer->render($output);
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'format_weeks', language 'en', branch 'MOODLE_20_STABLE'
*
* @package format_weeks
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addsection'] = 'Add week';
$string['automaticenddate'] = 'Calculate the end date from the number of sections';
$string['automaticenddate_help'] = 'If enabled, the end date for the course will be automatically calculated from the number of sections and the course start date.';
$string['currentsection'] = 'Current week';
$string['hidefromothers'] = 'Hide';
$string['page-course-view-weeks'] = 'Any course main page in weekly sections format';
$string['page-course-view-weeks-x'] = 'Any course page in weekly sections format';
$string['pluginname'] = 'Weekly sections';
$string['plugin_description'] = 'The course is divided into sections corresponding to each week, beginning from the course start date.';
$string['privacy:metadata'] = 'The Weekly sections format plugin does not store any personal data.';
$string['indentation'] = 'Allow indentation on course page';
$string['indentation_help'] = 'Allow teachers, and other users with the manage activities capability, to indent items on the course page.';
$string['section0name'] = 'General';
$string['sectionname'] = 'Week';
$string['showfromothers'] = 'Show';
+621
View File
@@ -0,0 +1,621 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains main class for the course format Weeks
*
* @since Moodle 2.0
* @package format_weeks
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot. '/course/format/lib.php');
require_once($CFG->dirroot. '/course/lib.php');
/**
* Main class for the Weeks course format
*
* @package format_weeks
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format_weeks extends core_courseformat\base {
/**
* Returns true if this course format uses sections
*
* @return bool
*/
public function uses_sections() {
return true;
}
public function uses_course_index() {
return true;
}
public function uses_indentation(): bool {
return (get_config('format_weeks', 'indentation')) ? true : false;
}
/**
* Generate the title for this section page
* @return string the page title
*/
public function page_title(): string {
return get_string('sectionoutline');
}
/**
* Returns the display name of the given section that the course prefers.
*
* @param int|stdClass $section Section object from database or just field section.section
* @return string Display name that the course format prefers, e.g. "Topic 2"
*/
public function get_section_name($section) {
$section = $this->get_section($section);
if ((string)$section->name !== '') {
// Return the name the user set.
return format_string($section->name, true, array('context' => context_course::instance($this->courseid)));
} else {
return $this->get_default_section_name($section);
}
}
/**
* Returns the default section name for the weekly course format.
*
* If the section number is 0, it will use the string with key = section0name from the course format's lang file.
* Otherwise, the default format of "[start date] - [end date]" will be returned.
*
* @param stdClass $section Section object from database or just field course_sections section
* @return string The default value for the section name.
*/
public function get_default_section_name($section) {
if ($section->section == 0) {
// Return the general section.
return get_string('section0name', 'format_weeks');
} else {
$dates = $this->get_section_dates($section);
// We subtract 24 hours for display purposes.
$dates->end = ($dates->end - 86400);
$dateformat = get_string('strftimedateshort');
$weekday = userdate($dates->start, $dateformat);
$endweekday = userdate($dates->end, $dateformat);
return $weekday.' - '.$endweekday;
}
}
/**
* Returns the name for the highlighted section.
*
* @return string The name for the highlighted section based on the given course format.
*/
public function get_section_highlighted_name(): string {
return get_string('currentsection', 'format_weeks');
}
/**
* The URL to use for the specified course (with section)
*
* @param int|stdClass $section Section object from database or just field course_sections.section
* if omitted the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
* 'sr' (int) used by course formats to specify to which section to return
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
$course = $this->get_course();
if (array_key_exists('sr', $options) && !is_null($options['sr'])) {
$sectionno = $options['sr'];
} else if (is_object($section)) {
$sectionno = $section->section;
} else {
$sectionno = $section;
}
if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {
// Display section on separate page.
$sectioninfo = $this->get_section($sectionno);
return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);
}
return new moodle_url('/course/view.php', ['id' => $course->id]);
}
/**
* Returns the information about the ajax support in the given source format
*
* The returned object's property (boolean)capable indicates that
* the course format supports Moodle course ajax features.
*
* @return stdClass
*/
public function supports_ajax() {
$ajaxsupport = new stdClass();
$ajaxsupport->capable = true;
return $ajaxsupport;
}
public function supports_components() {
return true;
}
/**
* Loads all of the course sections into the navigation
*
* @param global_navigation $navigation
* @param navigation_node $node The course node within the navigation
*/
public function extend_course_navigation($navigation, navigation_node $node) {
global $PAGE;
// if section is specified in course/view.php, make sure it is expanded in navigation
if ($navigation->includesectionnum === false) {
$selectedsection = optional_param('section', null, PARAM_INT);
if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
$PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
$navigation->includesectionnum = $selectedsection;
}
}
parent::extend_course_navigation($navigation, $node);
// We want to remove the general section if it is empty.
$modinfo = get_fast_modinfo($this->get_course());
$sections = $modinfo->get_sections();
if (!isset($sections[0])) {
// The general section is empty to find the navigation node for it we need to get its ID.
$section = $modinfo->get_section_info(0);
$generalsection = $node->get($section->id, navigation_node::TYPE_SECTION);
if ($generalsection) {
// We found the node - now remove it.
$generalsection->remove();
}
}
}
/**
* Custom action after section has been moved in AJAX mode
*
* Used in course/rest.php
*
* @return array This will be passed in ajax respose
*/
function ajax_section_move() {
global $PAGE;
$titles = array();
$current = -1;
$course = $this->get_course();
$modinfo = get_fast_modinfo($course);
$renderer = $this->get_renderer($PAGE);
if ($renderer && ($sections = $modinfo->get_section_info_all())) {
foreach ($sections as $number => $section) {
$titles[$number] = $renderer->section_title($section, $course);
if ($this->is_section_current($section)) {
$current = $number;
}
}
}
return array('sectiontitles' => $titles, 'current' => $current, 'action' => 'move');
}
/**
* Returns the list of blocks to be automatically added for the newly created course
*
* @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
* each of values is an array of block names (for left and right side columns)
*/
public function get_default_blocks() {
return array(
BLOCK_POS_LEFT => array(),
BLOCK_POS_RIGHT => array()
);
}
/**
* Definitions of the additional options that this course format uses for course
*
* Weeks format uses the following options:
* - coursedisplay
* - hiddensections
* - automaticenddate
*
* @param bool $foreditform
* @return array of options
*/
public function course_format_options($foreditform = false) {
static $courseformatoptions = false;
if ($courseformatoptions === false) {
$courseconfig = get_config('moodlecourse');
$courseformatoptions = array(
'hiddensections' => array(
'default' => $courseconfig->hiddensections,
'type' => PARAM_INT,
),
'coursedisplay' => array(
'default' => $courseconfig->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE,
'type' => PARAM_INT,
),
'automaticenddate' => array(
'default' => 1,
'type' => PARAM_BOOL,
),
);
}
if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) {
$courseformatoptionsedit = array(
'hiddensections' => array(
'label' => new lang_string('hiddensections'),
'help' => 'hiddensections',
'help_component' => 'moodle',
'element_type' => 'select',
'element_attributes' => array(
array(
0 => new lang_string('hiddensectionscollapsed'),
1 => new lang_string('hiddensectionsinvisible')
)
),
),
'coursedisplay' => array(
'label' => new lang_string('coursedisplay'),
'element_type' => 'select',
'element_attributes' => array(
array(
COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'),
COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi')
)
),
'help' => 'coursedisplay',
'help_component' => 'moodle',
),
'automaticenddate' => array(
'label' => new lang_string('automaticenddate', 'format_weeks'),
'help' => 'automaticenddate',
'help_component' => 'format_weeks',
'element_type' => 'advcheckbox',
)
);
$courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
}
return $courseformatoptions;
}
/**
* Adds format options elements to the course/section edit form.
*
* This function is called from {@link course_edit_form::definition_after_data()}.
*
* @param MoodleQuickForm $mform form the elements are added to.
* @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form.
* @return array array of references to the added form elements.
*/
public function create_edit_form_elements(&$mform, $forsection = false) {
global $COURSE;
$elements = parent::create_edit_form_elements($mform, $forsection);
if (!$forsection && (empty($COURSE->id) || $COURSE->id == SITEID)) {
// Add "numsections" element to the create course form - it will force new course to be prepopulated
// with empty sections.
// The "Number of sections" option is no longer available when editing course, instead teachers should
// delete and add sections when needed.
$courseconfig = get_config('moodlecourse');
$max = (int)$courseconfig->maxsections;
$element = $mform->addElement('select', 'numsections', get_string('numberweeks'), range(0, $max ?: 52));
$mform->setType('numsections', PARAM_INT);
if (is_null($mform->getElementValue('numsections'))) {
$mform->setDefault('numsections', $courseconfig->numsections);
}
array_unshift($elements, $element);
}
// Re-order things.
$mform->insertElementBefore($mform->removeElement('automaticenddate', false), 'idnumber');
$mform->disabledIf('enddate', 'automaticenddate', 'checked');
foreach ($elements as $key => $element) {
if ($element->getName() == 'automaticenddate') {
unset($elements[$key]);
}
}
return $elements;
}
/**
* Updates format options for a course
*
* In case if course format was changed to 'weeks', we try to copy options
* 'coursedisplay', 'numsections' and 'hiddensections' from the previous format.
* If previous course format did not have 'numsections' option, we populate it with the
* current number of sections
*
* @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
* @param stdClass $oldcourse if this function is called from {@link update_course()}
* this object contains information about the course before update
* @return bool whether there were any changes to the options values
*/
public function update_course_format_options($data, $oldcourse = null) {
global $DB;
$data = (array)$data;
if ($oldcourse !== null) {
$oldcourse = (array)$oldcourse;
$options = $this->course_format_options();
foreach ($options as $key => $unused) {
if (!array_key_exists($key, $data)) {
if (array_key_exists($key, $oldcourse)) {
$data[$key] = $oldcourse[$key];
}
}
}
}
return $this->update_format_options($data);
}
/**
* Return the start and end date of the passed section
*
* @param int|stdClass|section_info $section section to get the dates for
* @param int $startdate Force course start date, useful when the course is not yet created
* @return stdClass property start for startdate, property end for enddate
*/
public function get_section_dates($section, $startdate = false) {
global $USER;
if ($startdate === false) {
$course = $this->get_course();
$userdates = course_get_course_dates_for_user_id($course, $USER->id);
$startdate = $userdates['start'];
}
if (is_object($section)) {
$sectionnum = $section->section;
} else {
$sectionnum = $section;
}
// Create a DateTime object for the start date.
$startdateobj = new DateTime("@$startdate");
$startdateobj->setTimezone(core_date::get_user_timezone_object());
// Calculate the interval for one week.
$oneweekinterval = new DateInterval('P7D');
// Calculate the interval for the specified number of sections.
for ($i = 1; $i < $sectionnum; $i++) {
$startdateobj->add($oneweekinterval);
}
// Calculate the end date.
$enddateobj = clone $startdateobj;
$enddateobj->add($oneweekinterval);
$dates = new stdClass();
$dates->start = $startdateobj->getTimestamp();
$dates->end = $enddateobj->getTimestamp();
return $dates;
}
/**
* Returns true if the specified week is current
*
* @param int|stdClass|section_info $section
* @return bool
*/
public function is_section_current($section) {
if (is_object($section)) {
$sectionnum = $section->section;
} else {
$sectionnum = $section;
}
if ($sectionnum < 1) {
return false;
}
$timenow = time();
$dates = $this->get_section_dates($section);
return (($timenow >= $dates->start) && ($timenow < $dates->end));
}
/**
* Whether this format allows to delete sections
*
* Do not call this function directly, instead use {@link course_can_delete_section()}
*
* @param int|stdClass|section_info $section
* @return bool
*/
public function can_delete_section($section) {
return true;
}
/**
* Returns the default end date for weeks course format.
*
* @param moodleform $mform
* @param array $fieldnames The form - field names mapping.
* @return int
*/
public function get_default_course_enddate($mform, $fieldnames = array()) {
if (empty($fieldnames['startdate'])) {
$fieldnames['startdate'] = 'startdate';
}
if (empty($fieldnames['numsections'])) {
$fieldnames['numsections'] = 'numsections';
}
$startdate = $this->get_form_start_date($mform, $fieldnames);
if ($mform->elementExists($fieldnames['numsections'])) {
$numsections = $mform->getElementValue($fieldnames['numsections']);
$numsections = $mform->getElement($fieldnames['numsections'])->exportValue($numsections);
} else if ($this->get_courseid()) {
// For existing courses get the number of sections.
$numsections = $this->get_last_section_number();
} else {
// Fallback to the default value for new courses.
$numsections = get_config('moodlecourse', $fieldnames['numsections']);
}
// Final week's last day.
$dates = $this->get_section_dates(intval($numsections), $startdate);
return $dates->end;
}
/**
* Indicates whether the course format supports the creation of a news forum.
*
* @return bool
*/
public function supports_news() {
return true;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
// Allow the third visibility state inside visible sections or in section 0.
return !$section->section || $section->visible;
}
public function section_action($section, $action, $sr) {
global $PAGE;
// Call the parent method and return the new content for .section_availability element.
$rv = parent::section_action($section, $action, $sr);
$renderer = $PAGE->get_renderer('format_weeks');
if (!($section instanceof section_info)) {
$modinfo = course_modinfo::instance($this->courseid);
$section = $modinfo->get_section_info($section->section);
}
$elementclass = $this->get_output_classname('content\\section\\availability');
$availability = new $elementclass($this, $section);
$rv['section_availability'] = $renderer->render($availability);
return $rv;
}
/**
* Updates the end date for a course in weeks format if option automaticenddate is set.
*
* This method is called from event observers and it can not use any modinfo or format caches because
* events are triggered before the caches are reset.
*
* @param int $courseid
*/
public static function update_end_date($courseid) {
global $DB, $COURSE;
// Use one DB query to retrieve necessary fields in course, value for automaticenddate and number of the last
// section. This query will also validate that the course is indeed in 'weeks' format.
$insql = "SELECT c.id, c.format, c.startdate, c.enddate, MAX(s.section) AS lastsection
FROM {course} c
JOIN {course_sections} s
ON s.course = c.id
WHERE c.format = :format
AND c.id = :courseid
GROUP BY c.id, c.format, c.startdate, c.enddate";
$sql = "SELECT co.id, co.format, co.startdate, co.enddate, co.lastsection, fo.value AS automaticenddate
FROM ($insql) co
LEFT JOIN {course_format_options} fo
ON fo.courseid = co.id
AND fo.format = co.format
AND fo.name = :optionname
AND fo.sectionid = 0";
$course = $DB->get_record_sql($sql,
['optionname' => 'automaticenddate', 'format' => 'weeks', 'courseid' => $courseid]);
if (!$course) {
// Looks like it is a course in a different format, nothing to do here.
return;
}
// Create an instance of this class and mock the course object.
$format = new format_weeks('weeks', $courseid);
$format->course = $course;
// If automaticenddate is not specified take the default value.
if (!isset($course->automaticenddate)) {
$defaults = $format->course_format_options();
$course->automaticenddate = $defaults['automaticenddate']['default'];
}
// Check that the course format for setting an automatic date is set.
if (!empty($course->automaticenddate)) {
// Get the final week's last day.
$dates = $format->get_section_dates((int)$course->lastsection);
// Set the course end date.
if ($course->enddate != $dates->end) {
$DB->set_field('course', 'enddate', $dates->end, array('id' => $course->id));
if (isset($COURSE->id) && $COURSE->id == $courseid) {
$COURSE->enddate = $dates->end;
}
}
}
}
/**
* Return the plugin configs for external functions.
*
* @return array the list of configuration settings
* @since Moodle 3.5
*/
public function get_config_for_external() {
// Return everything (nothing to hide).
$formatoptions = $this->get_format_options();
$formatoptions['indentation'] = get_config('format_weeks', 'indentation');
return $formatoptions;
}
/**
* Get the required javascript files for the course format.
*
* @return array The list of javascript files required by the course format.
*/
public function get_required_jsfiles(): array {
return [];
}
}
/**
* Implements callback inplace_editable() allowing to edit values in-place
*
* @param string $itemtype
* @param int $itemid
* @param mixed $newvalue
* @return \core\output\inplace_editable
*/
function format_weeks_inplace_editable($itemtype, $itemid, $newvalue) {
global $DB, $CFG;
require_once($CFG->dirroot . '/course/lib.php');
if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
$section = $DB->get_record_sql(
'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?',
array($itemid, 'weeks'), MUST_EXIST);
return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Settings for format_weeks
*
* @package format_weeks
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
$url = new moodle_url('/admin/course/resetindentation.php', ['format' => 'weeks']);
$link = html_writer::link($url, get_string('resetindentation', 'admin'));
$settings->add(new admin_setting_configcheckbox(
'format_weeks/indentation',
new lang_string('indentation', 'format_weeks'),
new lang_string('indentation_help', 'format_weeks').'<br />'.$link,
1
));
}
@@ -0,0 +1,188 @@
@core @core_courseformat @core_course @format_weeks @show_editor @javascript
Feature: Weeks format bulk activity actions.
In order to edit the course weeks
As a teacher
I need to be able to edit weeks in bulk.
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 4 |
| format | weeks |
| startdate | 957139200 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 |
| assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 |
| assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I am on the "C1" "Course" page logged in as "teacher1"
And I turn editing mode on
And I click on "Bulk actions" "button"
And I should see "0 selected" in the "sticky-footer" "region"
Scenario: Bulk hide weeks
Given I should not see "Hidden from students" in the "Activity sample 1" "activity"
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
And I should not see "Hidden from students" in the "1 May - 7 May" "section"
And I should not see "Hidden from students" in the "8 May - 14 May" "section"
And I should not see "Hidden from students" in the "15 May - 21 May" "section"
And I should not see "Hidden from students" in the "22 May - 28 May" "section"
When I click on "Select section 1 May - 7 May" "checkbox"
And I click on "Select section 8 May - 14 May" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
And I click on "Sections availability" "button" in the "sticky-footer" "region"
And I click on "Hide on course page" "radio" in the "Sections availability" "dialogue"
And I click on "Apply" "button" in the "Sections availability" "dialogue"
Then I should see "Hidden from students" in the "Activity sample 1" "activity"
And I should see "Hidden from students" in the "Activity sample 2" "activity"
And I should see "Hidden from students" in the "Activity sample 3" "activity"
And I should see "Hidden from students" in the "Activity sample 4" "activity"
And I should see "Hidden from students" in the "1 May - 7 May" "section"
And I should see "Hidden from students" in the "8 May - 14 May" "section"
And I should not see "Hidden from students" in the "15 May - 21 May" "section"
And I should not see "Hidden from students" in the "22 May - 28 May" "section"
And I should see "0 selected" in the "sticky-footer" "region"
Scenario: Bulk show weeks
Given I click on "Select section 1 May - 7 May" "checkbox"
And I click on "Select section 15 May - 21 May" "checkbox"
And I click on "Sections availability" "button" in the "sticky-footer" "region"
And I click on "Hide on course page" "radio" in the "Sections availability" "dialogue"
And I click on "Apply" "button" in the "Sections availability" "dialogue"
And I should see "Hidden from students" in the "Activity sample 1" "activity"
And I should see "Hidden from students" in the "Activity sample 2" "activity"
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
And I should see "Hidden from students" in the "1 May - 7 May" "section"
And I should not see "Hidden from students" in the "8 May - 14 May" "section"
And I should see "Hidden from students" in the "15 May - 21 May" "section"
And I should not see "Hidden from students" in the "22 May - 28 May" "section"
When I click on "Select section 1 May - 7 May" "checkbox"
And I click on "Select section 8 May - 14 May" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
And I click on "Sections availability" "button" in the "sticky-footer" "region"
And I click on "Show on course page" "radio" in the "Sections availability" "dialogue"
And I click on "Apply" "button" in the "Sections availability" "dialogue"
Then I should not see "Hidden from students" in the "Activity sample 1" "activity"
And I should not see "Hidden from students" in the "Activity sample 2" "activity"
And I should not see "Hidden from students" in the "Activity sample 3" "activity"
And I should not see "Hidden from students" in the "Activity sample 4" "activity"
And I should not see "Hidden from students" in the "1 May - 7 May" "section"
And I should not see "Hidden from students" in the "8 May - 14 May" "section"
And I should see "Hidden from students" in the "15 May - 21 May" "section"
And I should not see "Hidden from students" in the "22 May - 28 May" "section"
Scenario: Delete a single week using bulk action
Given I should see "1 May - 7 May" in the "region-main" "region"
And I should see "8 May - 14 May" in the "region-main" "region"
And I should see "15 May - 21 May" in the "region-main" "region"
And I should see "22 May - 28 May" in the "region-main" "region"
And I should see "Activity sample 1" in the "1 May - 7 May" "section"
And I should see "Activity sample 2" in the "1 May - 7 May" "section"
And I should see "Activity sample 3" in the "8 May - 14 May" "section"
And I should see "Activity sample 4" in the "8 May - 14 May" "section"
And I click on "Select section 1 May - 7 May" "checkbox"
And I click on "Select section 8 May - 14 May" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
When I click on "Delete sections" "button" in the "sticky-footer" "region"
And I click on "Delete" "button" in the "Delete selected sections?" "dialogue"
Then I should see "1 May - 7 May" in the "region-main" "region"
And I should see "8 May - 14 May" in the "region-main" "region"
And I should not see "15 May - 21 May" in the "region-main" "region"
And I should not see "22 May - 28 May" in the "region-main" "region"
And I should not see "Activity sample 1" in the "1 May - 7 May" "section"
And I should not see "Activity sample 2" in the "1 May - 7 May" "section"
And I should not see "Activity sample 3" in the "8 May - 14 May" "section"
And I should not see "Activity sample 4" in the "8 May - 14 May" "section"
And I should see "0 selected" in the "sticky-footer" "region"
Scenario: Delete several weeks in bulk
Given I should see "1 May - 7 May" in the "region-main" "region"
And I should see "8 May - 14 May" in the "region-main" "region"
And I should see "15 May - 21 May" in the "region-main" "region"
And I should see "22 May - 28 May" in the "region-main" "region"
And I should see "Activity sample 1" in the "1 May - 7 May" "section"
And I should see "Activity sample 2" in the "1 May - 7 May" "section"
And I should see "Activity sample 3" in the "8 May - 14 May" "section"
And I should see "Activity sample 4" in the "8 May - 14 May" "section"
And I click on "Select section 8 May - 14 May" "checkbox"
And I click on "Select section 15 May - 21 May" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
When I click on "Delete sections" "button" in the "sticky-footer" "region"
And I click on "Delete" "button" in the "Delete selected sections?" "dialogue"
Then I should see "1 May - 7 May" in the "region-main" "region"
And I should see "8 May - 14 May" in the "region-main" "region"
And I should not see "15 May - 21 May" in the "region-main" "region"
And I should not see "22 May - 28 May" in the "region-main" "region"
And I should see "Activity sample 1" in the "1 May - 7 May" "section"
And I should see "Activity sample 1" in the "1 May - 7 May" "section"
And I should see "Activity sample 2" in the "1 May - 7 May" "section"
And I should not see "Activity sample 3" in the "8 May - 14 May" "section"
And I should not see "Activity sample 4" in the "8 May - 14 May" "section"
And I should see "0 selected" in the "sticky-footer" "region"
Scenario: Bulk move sections after general section
Given I set the field "Edit section name" in the "8 May - 14 May" "section" to "Move one"
And I set the field "Edit section name" in the "15 May - 21 May" "section" to "Move two"
And I click on "Select section Move one" "checkbox"
And I click on "Select section Move two" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
When I click on "Move sections" "button" in the "sticky-footer" "region"
And I click on "General" "link" in the "Move selected sections" "dialogue"
# Check activities are moved with the weeks.
Then I should see "Activity sample 1" in the "15 May - 21 May" "section"
And I should see "Activity sample 2" in the "15 May - 21 May" "section"
And I should see "Activity sample 3" in the "Move one" "section"
And I should see "Activity sample 4" in the "Move one" "section"
# Check new section order.
And "Move one" "section" should appear after "General" "section"
And "Move two" "section" should appear after "Move one" "section"
And "15 May - 21 May" "section" should appear after "Move two" "section"
And "22 May - 28 May" "section" should appear after "15 May - 21 May" "section"
Scenario: Bulk move sections at the end of the course
Given I set the field "Edit section name" in the "15 May - 21 May" "section" to "Move me"
And I click on "Select section 8 May - 14 May" "checkbox"
And I click on "Select section Move me" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
When I click on "Move sections" "button" in the "sticky-footer" "region"
And I click on "22 May - 28 May" "link" in the "Move selected sections" "dialogue"
# Check activities are moved with the weeks.
Then I should see "Activity sample 1" in the "1 May - 7 May" "section"
And I should see "Activity sample 2" in the "1 May - 7 May" "section"
And I should see "Activity sample 3" in the "15 May - 21 May" "section"
And I should see "Activity sample 4" in the "15 May - 21 May" "section"
# Check new section order.
And "1 May - 7 May" "section" should appear after "General" "section"
And "8 May - 14 May" "section" should appear after "1 May - 7 May" "section"
And "15 May - 21 May" "section" should appear after "8 May - 14 May" "section"
And "Move me" "section" should appear after "15 May - 21 May" "section"
Scenario: Bulk move sections in the middle of the course
Given I set the field "Edit section name" in the "22 May - 28 May" "section" to "Move me"
And I click on "Select section 1 May - 7 May" "checkbox"
And I click on "Select section Move me" "checkbox"
And I should see "2 selected" in the "sticky-footer" "region"
When I click on "Move sections" "button" in the "sticky-footer" "region"
And I click on "8 May - 14 May" "link" in the "Move selected sections" "dialogue"
# Check activities are moved with the weeks.
Then I should see "Activity sample 1" in the "8 May - 14 May" "section"
And I should see "Activity sample 2" in the "8 May - 14 May" "section"
And I should see "Activity sample 3" in the "1 May - 7 May" "section"
And I should see "Activity sample 4" in the "1 May - 7 May" "section"
# Check new section order.
And "1 May - 7 May" "section" should appear after "General" "section"
And "8 May - 14 May" "section" should appear after "1 May - 7 May" "section"
And "Move me" "section" should appear after "8 May - 14 May" "section"
And "22 May - 28 May" "section" should appear after "Move me" "section"
@@ -0,0 +1,19 @@
@format @format_weeks
Feature: The current week should be highlighted in the course.
In order to know which is the current week
As a user
I need to see the current week highlighted
Scenario: Highlight first week
Given the following "courses" exist:
| fullname | shortname | format | coursedisplay | numsections | startdate |
| Course 1 | C1 | weeks | 0 | 5 | ##yesterday## |
When I am on the "C1" "Course" page logged in as "admin"
Then I should see "Current week" in the "#section-1" "css_element"
Scenario: Highlight next week
Given the following "courses" exist:
| fullname | shortname | format | coursedisplay | numsections | startdate |
| Course 1 | C1 | weeks | 0 | 5 | ##monday last week## |
When I am on the "C1" "Course" page logged in as "admin"
Then I should see "Current week" in the "#section-2" "css_element"
@@ -0,0 +1,83 @@
@format @format_weeks
Feature: Sections can be edited and deleted in weekly sections format
In order to rearrange my course contents
As a teacher
I need to edit and delete sections
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format | coursedisplay | numsections | startdate |
| Course 1 | C1 | weeks | 0 | 5 | 957139200 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Test assignment name | Test assignment description | C1 | assign1 | 0 |
| book | Test book name | | C1 | book1 | 1 |
| lesson | Test lesson name | Test lesson description | C1 | lesson1 | 4 |
| choice | Test choice name | Test choice description | C1 | choice1 | 5 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
Scenario: View the default name of the general section in weeks format
When I edit the section "0"
Then the field "Section name" matches value ""
And I should see "General"
Scenario: Edit the default name of the general section in weeks format
When I edit the section "0" and I fill the form with:
| Section name | This is the general section |
Then I should see "This is the general section" in the "page" "region"
Scenario: View the default name of the second section in weeks format
When I edit the section "2"
Then the field "Section name" matches value ""
And I should see "8 May - 14 May"
Scenario: Edit section summary in weeks format
When I edit the section "2" and I fill the form with:
| Description | Welcome to section 2 |
Then I should see "Welcome to section 2" in the "page" "region"
Scenario: Edit section default name in weeks format
Given I should see "8 May - 14 May" in the "8 May - 14 May" "section"
When I edit the section "2" and I fill the form with:
| Section name | This is the second week |
Then I should see "This is the second week" in the "page" "region"
And I should not see "8 May - 14 May"
@javascript
Scenario: Inline edit section name in weeks format
When I set the field "Edit section name" in the "1 May - 7 May" "section" to "Midterm evaluation"
Then I should not see "1 May - 7 May" in the "region-main" "region"
And "New name for week" "field" should not exist
And I should see "Midterm evaluation" in the "Midterm evaluation" "section"
And I am on "Course 1" course homepage
And I should not see "1 May - 7 May" in the "region-main" "region"
And I should see "Midterm evaluation" in the "Midterm evaluation" "section"
Scenario: Deleting the last section in weeks format
Given I should see "29 May - 4 June" in the "29 May - 4 June" "section"
When I delete section "5"
Then I should see "Are you absolutely sure you want to completely delete \"29 May - 4 June\" and all the activities it contains?"
And I press "Delete"
And I should not see "29 May - 4 June"
And I should see "22 May - 28 May"
Scenario: Deleting the middle section in weeks format
Given I should see "29 May - 4 June" in the "29 May - 4 June" "section"
When I delete section "4"
And I press "Delete"
Then I should not see "29 May - 4 June"
And I should not see "Test lesson name"
And I should see "Test choice name" in the "22 May - 28 May" "section"
And I should see "22 May - 28 May"
@javascript
Scenario: Adding sections in weeks format
When I follow "Add week"
Then I should see "5 June - 11 June" in the "5 June - 11 June" "section"
@@ -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/>.
namespace format_weeks;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
/**
* format_weeks related unit tests
*
* @package format_weeks
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_weeks
*/
class format_weeks_test extends \advanced_testcase {
/**
* Tests for format_weeks::get_section_name method with default section names.
*/
public function test_get_section_name(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(array('numsections' => $numsections, 'format' => 'weeks'),
array('createsections' => true));
// Get section names for course.
$coursesections = $DB->get_records('course_sections', array('course' => $course->id));
// Test get_section_name with default section names.
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
// Assert that with unmodified section names, get_section_name returns the same result as get_default_section_name.
$this->assertEquals($courseformat->get_default_section_name($section), $courseformat->get_section_name($section));
}
}
/**
* Tests for format_weeks::get_section_name method with modified section names.
*/
public function test_get_section_name_customised(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(array('numsections' => $numsections, 'format' => 'weeks'),
array('createsections' => true));
// Get section names for course.
$coursesections = $DB->get_records('course_sections', array('course' => $course->id));
// Modify section names.
$customname = "Custom Section";
foreach ($coursesections as $section) {
$section->name = "$customname $section->section";
$DB->update_record('course_sections', $section);
}
// Requery updated section names then test get_section_name.
$coursesections = $DB->get_records('course_sections', array('course' => $course->id));
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
// Assert that with modified section names, get_section_name returns the modified section name.
$this->assertEquals($section->name, $courseformat->get_section_name($section));
}
}
/**
* Tests for format_weeks::get_default_section_name.
*/
public function test_get_default_section_name(): void {
global $DB;
$this->resetAfterTest(true);
// Generate a course with 5 sections.
$generator = $this->getDataGenerator();
$numsections = 5;
$course = $generator->create_course(array('numsections' => $numsections, 'format' => 'weeks'),
array('createsections' => true));
// Get section names for course.
$coursesections = $DB->get_records('course_sections', array('course' => $course->id));
// Test get_default_section_name with default section names.
$courseformat = course_get_format($course);
foreach ($coursesections as $section) {
if ($section->section == 0) {
$sectionname = get_string('section0name', 'format_weeks');
$this->assertEquals($sectionname, $courseformat->get_default_section_name($section));
} else {
$dates = $courseformat->get_section_dates($section);
$dates->end = ($dates->end - 86400);
$dateformat = get_string('strftimedateshort');
$weekday = userdate($dates->start, $dateformat);
$endweekday = userdate($dates->end, $dateformat);
$sectionname = $weekday.' - '.$endweekday;
$this->assertEquals($sectionname, $courseformat->get_default_section_name($section));
}
}
}
/**
* Test web service updating section name
*/
public function test_update_inplace_editable(): void {
global $CFG, $DB, $PAGE;
require_once($CFG->dirroot . '/lib/external/externallib.php');
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'weeks'),
array('createsections' => true));
$section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
// Call webservice without necessary permissions.
try {
\core_external::update_inplace_editable('format_weeks', 'sectionname', $section->id, 'New section name');
$this->fail('Exception expected');
} catch (\moodle_exception $e) {
$this->assertEquals('Course or activity not accessible. (Not enrolled)',
$e->getMessage());
}
// Change to teacher and make sure that section name can be updated using web service update_inplace_editable().
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
$res = \core_external::update_inplace_editable('format_weeks', 'sectionname', $section->id, 'New section name');
$res = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $res);
$this->assertEquals('New section name', $res['value']);
$this->assertEquals('New section name', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
}
/**
* Test callback updating section name
*/
public function test_inplace_editable(): void {
global $CFG, $DB, $PAGE;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'weeks'),
array('createsections' => true));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
$this->setUser($user);
$section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
// Call callback format_weeks_inplace_editable() directly.
$tmpl = component_callback('format_weeks', 'inplace_editable', array('sectionname', $section->id, 'Rename me again'));
$this->assertInstanceOf('core\output\inplace_editable', $tmpl);
$res = $tmpl->export_for_template($PAGE->get_renderer('core'));
$this->assertEquals('Rename me again', $res['value']);
$this->assertEquals('Rename me again', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
// Try updating using callback from mismatching course format.
try {
$tmpl = component_callback('format_topics', 'inplace_editable', array('sectionname', $section->id, 'New name'));
$this->fail('Exception expected');
} catch (\moodle_exception $e) {
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
}
}
/**
* Test get_default_course_enddate.
*
* @return void
*/
public function test_default_course_enddate(): void {
global $CFG, $DB, $PAGE;
$this->resetAfterTest(true);
require_once($CFG->dirroot . '/course/tests/fixtures/testable_course_edit_form.php');
$this->setTimezone('UTC');
$params = array('format' => 'weeks', 'numsections' => 5, 'startdate' => 1445644800);
$course = $this->getDataGenerator()->create_course($params);
$category = $DB->get_record('course_categories', array('id' => $course->category));
$args = [
'course' => $course,
'category' => $category,
'editoroptions' => [
'context' => \context_course::instance($course->id),
'subdirs' => 0
],
'returnto' => new \moodle_url('/'),
'returnurl' => new \moodle_url('/'),
];
$PAGE->set_course($course);
$courseform = new \testable_course_edit_form(null, $args);
$courseform->definition_after_data();
// Calculate the expected end date.
$enddate = $params['startdate'] + (WEEKSECS * $params['numsections']);
$weeksformat = course_get_format($course->id);
$this->assertEquals($enddate, $weeksformat->get_default_course_enddate($courseform->get_quick_form()));
}
/**
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(array('format' => 'weeks'));
course_create_sections_if_missing($course1, array(0, 1));
$data = (object)['id' => $course1->id];
$format = course_get_format($course1);
$format->update_course_format_options($data);
// In page.
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['navigation' => 1]));
// When sr parameter is defined, the section.php page should be returned.
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'weeks']);
$format = course_get_format($course);
$this->assertEmpty($format->get_required_jsfiles());
}
}
+201
View File
@@ -0,0 +1,201 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace format_weeks;
/**
* Unit tests for the event observers used by the weeks course format.
*
* @package format_weeks
* @copyright 2017 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class observer_test extends \advanced_testcase {
/**
* Test setup.
*/
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Tests when we update a course with automatic end date set.
*/
public function test_course_updated_with_automatic_end_date(): void {
global $DB;
// Generate a course with some sections.
$numsections = 6;
$startdate = time();
$course = $this->getDataGenerator()->create_course(array(
'numsections' => $numsections,
'format' => 'weeks',
'startdate' => $startdate,
'automaticenddate' => 1));
// Ok, let's update the course start date.
$newstartdate = $startdate + WEEKSECS;
update_course((object)['id' => $course->id, 'startdate' => $newstartdate]);
// Get the updated course end date.
$enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
$format = course_get_format($course->id);
$this->assertEquals($numsections, $format->get_last_section_number());
$this->assertEquals($newstartdate, $format->get_course()->startdate);
$dates = $format->get_section_dates($numsections);
$this->assertEquals($dates->end, $enddate);
}
/**
* Tests when we update a course with automatic end date set but no actual change is made.
*/
public function test_course_updated_with_automatic_end_date_no_change(): void {
global $DB;
// Generate a course with some sections.
$course = $this->getDataGenerator()->create_course(array(
'numsections' => 6,
'format' => 'weeks',
'startdate' => time(),
'automaticenddate' => 1));
// Get the end date from the DB as the results will have changed from $course above after observer processing.
$createenddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
// Ok, let's update the course - but actually not change anything.
update_course((object)['id' => $course->id]);
// Get the updated course end date.
$updateenddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
// Confirm nothing changed.
$this->assertEquals($createenddate, $updateenddate);
}
/**
* Tests when we update a course without automatic end date set.
*/
public function test_course_updated_without_automatic_end_date(): void {
global $DB;
// Generate a course with some sections.
$startdate = time();
$enddate = $startdate + WEEKSECS;
$course = $this->getDataGenerator()->create_course(array(
'numsections' => 6,
'format' => 'weeks',
'startdate' => $startdate,
'enddate' => $enddate,
'automaticenddate' => 0));
// Ok, let's update the course start date.
$newstartdate = $startdate + WEEKSECS;
update_course((object)['id' => $course->id, 'startdate' => $newstartdate]);
// Get the updated course end date.
$updateenddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
// Confirm nothing changed.
$this->assertEquals($enddate, $updateenddate);
}
/**
* Tests when we adding a course section with automatic end date set.
*/
public function test_course_section_created_with_automatic_end_date(): void {
global $DB;
$numsections = 6;
$course = $this->getDataGenerator()->create_course(array(
'numsections' => $numsections,
'format' => 'weeks',
'startdate' => time(),
'automaticenddate' => 1));
// Add a section to the course.
course_create_section($course->id);
// Get the updated course end date.
$enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
$format = course_get_format($course->id);
$dates = $format->get_section_dates($numsections + 1);
// Confirm end date was updated.
$this->assertEquals($enddate, $dates->end);
}
/**
* Tests when we update a course without automatic end date set.
*/
public function test_create_section_without_automatic_end_date(): void {
global $DB;
// Generate a course with some sections.
$startdate = time();
$enddate = $startdate + WEEKSECS;
$course = $this->getDataGenerator()->create_course(array(
'numsections' => 6,
'format' => 'weeks',
'startdate' => $startdate,
'enddate' => $enddate,
'automaticenddate' => 0));
// Delete automatic end date from the database.
$DB->delete_records('course_format_options', ['courseid' => $course->id, 'name' => 'automaticenddate']);
// Create a new section.
course_create_section($course->id, 0);
// Get the updated course end date.
$updateenddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
// Confirm enddate is automatic now - since automatic end date is not set it is assumed default (which is '1').
$format = course_get_format($course->id);
$this->assertEquals(7, $format->get_last_section_number());
$dates = $format->get_section_dates(7);
$this->assertEquals($dates->end, $updateenddate);
}
/**
* Tests when we deleting a course section with automatic end date set.
*/
public function test_course_section_deleted_with_automatic_end_date(): void {
global $DB;
// Generate a course with some sections.
$numsections = 6;
$course = $this->getDataGenerator()->create_course(array(
'numsections' => $numsections,
'format' => 'weeks',
'startdate' => time(),
'automaticenddate' => 1));
// Add a section to the course.
course_delete_section($course, $numsections);
// Get the updated course end date.
$enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
$format = course_get_format($course->id);
$dates = $format->get_section_dates($numsections - 1);
// Confirm end date was updated.
$this->assertEquals($enddate, $dates->end);
}
}
+30
View File
@@ -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/>.
/**
* Version details
*
* @package format
* @subpackage weeks
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'format_weeks'; // Full name of the plugin (used for diagnostics).