ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç db/upgrade.php000064400000003177152266413710007310 0ustar00. /** * 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.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. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } db/events.php000064400000002565152266413710007165 0ustar00. /** * Format weeks event handler definition. * * @package format_weeks * @copyright 2017 Mark Nelson * @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', ) ); lang/en/format_weeks.php000064400000004034152266413710011276 0ustar00. /** * 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'; backup/moodle2/restore_format_weeks_plugin.class.php000064400000015404152266413710017011 0ustar00. /** * 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); } } } lib.php000064400000057333152266413710006045 0ustar00. /** * 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); // Find the prior day for display purposes. $enddate = new DateTime(); $enddate->setTimezone(core_date::get_user_timezone_object()); $enddate->setTimestamp(intval($dates->end)); $enddate->modify('-1 day'); $dates->end = $enddate->getTimestamp(); $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 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); } } version.php000064400000002254152266413710006754 0ustar00. /** * 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 = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'format_weeks'; // Full name of the plugin (used for diagnostics). classes/privacy/provider.php000064400000002760152266413710012235 0ustar00. /** * Privacy Subsystem implementation for format_weeks. * * @package format_weeks * @copyright 2018 Carlos Escobedo * @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 * @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'; } }classes/observer.php000064400000005447152266413710010562 0ustar00. /** * Event observers used by the weeks course format. * * @package format_weeks * @copyright 2017 Mark Nelson * @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 * @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); } } } classes/output/renderer.php000064400000004065152266413710012074 0ustar00. /** * 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)); } } tests/observer_test.php000064400000016301152266413710011315 0ustar00. namespace format_weeks; /** * Unit tests for the event observers used by the weeks course format. * * @package format_weeks * @copyright 2017 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class observer_test extends \advanced_testcase { /** * Test setup. */ public function setUp(): void { parent::setUp(); $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); } } tests/format_weeks_test.php000064400000027274152266413710012167 0ustar00. 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 */ final 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()); } } tests/behat/edit_delete_sections.feature000064400000007772152266413710014550 0ustar00@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 "This will 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" tests/behat/current_week.feature000064400000001657152266413710013063 0ustar00@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" tests/behat/bulk_actions.feature000064400000030350152266413710013033 0ustar00@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" format.php000064400000003400152266413710006551 0ustar00. /** * 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); settings.php000064400000002512152266413710007124 0ustar00. /** * Settings for format_weeks * * @package format_weeks * @copyright 2020 Amaia Anabitarte * @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').'
'.$link, 1 )); }