/course/lib.php
PHP | 5132 lines | 3085 code | 601 blank | 1446 comment | 634 complexity | 3a0059cea7d8c7feec8b0a9ea43804a9 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?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/>.
- /**
- * Library of useful functions
- *
- * @copyright 1999 Martin Dougiamas http://dougiamas.com
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @package core_course
- */
- defined('MOODLE_INTERNAL') || die;
- use core_courseformat\base as course_format;
- require_once($CFG->libdir.'/completionlib.php');
- require_once($CFG->libdir.'/filelib.php');
- require_once($CFG->libdir.'/datalib.php');
- require_once($CFG->dirroot.'/course/format/lib.php');
- define('COURSE_MAX_LOGS_PER_PAGE', 1000); // Records.
- define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds.
- /**
- * Number of courses to display when summaries are included.
- * @var int
- * @deprecated since 2.4, use $CFG->courseswithsummarieslimit instead.
- */
- define('COURSE_MAX_SUMMARIES_PER_PAGE', 10);
- // Max courses in log dropdown before switching to optional.
- define('COURSE_MAX_COURSES_PER_DROPDOWN', 1000);
- // Max users in log dropdown before switching to optional.
- define('COURSE_MAX_USERS_PER_DROPDOWN', 1000);
- define('FRONTPAGENEWS', '0');
- define('FRONTPAGECATEGORYNAMES', '2');
- define('FRONTPAGECATEGORYCOMBO', '4');
- define('FRONTPAGEENROLLEDCOURSELIST', '5');
- define('FRONTPAGEALLCOURSELIST', '6');
- define('FRONTPAGECOURSESEARCH', '7');
- // Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage.
- define('EXCELROWS', 65535);
- define('FIRSTUSEDEXCELROW', 3);
- define('MOD_CLASS_ACTIVITY', 0);
- define('MOD_CLASS_RESOURCE', 1);
- define('COURSE_TIMELINE_ALLINCLUDINGHIDDEN', 'allincludinghidden');
- define('COURSE_TIMELINE_ALL', 'all');
- define('COURSE_TIMELINE_PAST', 'past');
- define('COURSE_TIMELINE_INPROGRESS', 'inprogress');
- define('COURSE_TIMELINE_FUTURE', 'future');
- define('COURSE_TIMELINE_SEARCH', 'search');
- define('COURSE_FAVOURITES', 'favourites');
- define('COURSE_TIMELINE_HIDDEN', 'hidden');
- define('COURSE_CUSTOMFIELD', 'customfield');
- define('COURSE_DB_QUERY_LIMIT', 1000);
- /** Searching for all courses that have no value for the specified custom field. */
- define('COURSE_CUSTOMFIELD_EMPTY', -1);
- // Course activity chooser footer default display option.
- define('COURSE_CHOOSER_FOOTER_NONE', 'hidden');
- // Download course content options.
- define('DOWNLOAD_COURSE_CONTENT_DISABLED', 0);
- define('DOWNLOAD_COURSE_CONTENT_ENABLED', 1);
- define('DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT', 2);
- function make_log_url($module, $url) {
- switch ($module) {
- case 'course':
- if (strpos($url, 'report/') === 0) {
- // there is only one report type, course reports are deprecated
- $url = "/$url";
- break;
- }
- case 'file':
- case 'login':
- case 'lib':
- case 'admin':
- case 'category':
- case 'mnet course':
- if (strpos($url, '../') === 0) {
- $url = ltrim($url, '.');
- } else {
- $url = "/course/$url";
- }
- break;
- case 'calendar':
- $url = "/calendar/$url";
- break;
- case 'user':
- case 'blog':
- $url = "/$module/$url";
- break;
- case 'upload':
- $url = $url;
- break;
- case 'coursetags':
- $url = '/'.$url;
- break;
- case 'library':
- case '':
- $url = '/';
- break;
- case 'message':
- $url = "/message/$url";
- break;
- case 'notes':
- $url = "/notes/$url";
- break;
- case 'tag':
- $url = "/tag/$url";
- break;
- case 'role':
- $url = '/'.$url;
- break;
- case 'grade':
- $url = "/grade/$url";
- break;
- default:
- $url = "/mod/$module/$url";
- break;
- }
- //now let's sanitise urls - there might be some ugly nasties:-(
- $parts = explode('?', $url);
- $script = array_shift($parts);
- if (strpos($script, 'http') === 0) {
- $script = clean_param($script, PARAM_URL);
- } else {
- $script = clean_param($script, PARAM_PATH);
- }
- $query = '';
- if ($parts) {
- $query = implode('', $parts);
- $query = str_replace('&', '&', $query); // both & and & are stored in db :-|
- $parts = explode('&', $query);
- $eq = urlencode('=');
- foreach ($parts as $key=>$part) {
- $part = urlencode(urldecode($part));
- $part = str_replace($eq, '=', $part);
- $parts[$key] = $part;
- }
- $query = '?'.implode('&', $parts);
- }
- return $script.$query;
- }
- function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
- $modname="", $modid=0, $modaction="", $groupid=0) {
- global $CFG, $DB;
- // It is assumed that $date is the GMT time of midnight for that day,
- // and so the next 86400 seconds worth of logs are printed.
- /// Setup for group handling.
- // TODO: I don't understand group/context/etc. enough to be able to do
- // something interesting with it here
- // What is the context of a remote course?
- /// If the group mode is separate, and this user does not have editing privileges,
- /// then only the user's group can be viewed.
- //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
- // $groupid = get_current_group($course->id);
- //}
- /// If this course doesn't have groups, no groupid can be specified.
- //else if (!$course->groupmode) {
- // $groupid = 0;
- //}
- $groupid = 0;
- $joins = array();
- $where = '';
- $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
- FROM {mnet_log} l
- LEFT JOIN {user} u ON l.userid = u.id
- WHERE ";
- $params = array();
- $where .= "l.hostid = :hostid";
- $params['hostid'] = $hostid;
- // TODO: Is 1 really a magic number referring to the sitename?
- if ($course != SITEID || $modid != 0) {
- $where .= " AND l.course=:courseid";
- $params['courseid'] = $course;
- }
- if ($modname) {
- $where .= " AND l.module = :modname";
- $params['modname'] = $modname;
- }
- if ('site_errors' === $modid) {
- $where .= " AND ( l.action='error' OR l.action='infected' )";
- } else if ($modid) {
- //TODO: This assumes that modids are the same across sites... probably
- //not true
- $where .= " AND l.cmid = :modid";
- $params['modid'] = $modid;
- }
- if ($modaction) {
- $firstletter = substr($modaction, 0, 1);
- if ($firstletter == '-') {
- $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
- $params['modaction'] = '%'.substr($modaction, 1).'%';
- } else {
- $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
- $params['modaction'] = '%'.$modaction.'%';
- }
- }
- if ($user) {
- $where .= " AND l.userid = :user";
- $params['user'] = $user;
- }
- if ($date) {
- $enddate = $date + 86400;
- $where .= " AND l.time > :date AND l.time < :enddate";
- $params['date'] = $date;
- $params['enddate'] = $enddate;
- }
- $result = array();
- $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
- if(!empty($result['totalcount'])) {
- $where .= " ORDER BY $order";
- $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
- } else {
- $result['logs'] = array();
- }
- return $result;
- }
- /**
- * Checks the integrity of the course data.
- *
- * In summary - compares course_sections.sequence and course_modules.section.
- *
- * More detailed, checks that:
- * - course_sections.sequence contains each module id not more than once in the course
- * - for each moduleid from course_sections.sequence the field course_modules.section
- * refers to the same section id (this means course_sections.sequence is more
- * important if they are different)
- * - ($fullcheck only) each module in the course is present in one of
- * course_sections.sequence
- * - ($fullcheck only) removes non-existing course modules from section sequences
- *
- * If there are any mismatches, the changes are made and records are updated in DB.
- *
- * Course cache is NOT rebuilt if there are any errors!
- *
- * This function is used each time when course cache is being rebuilt with $fullcheck = false
- * and in CLI script admin/cli/fix_course_sequence.php with $fullcheck = true
- *
- * @param int $courseid id of the course
- * @param array $rawmods result of funciton {@link get_course_mods()} - containst
- * the list of enabled course modules in the course. Retrieved from DB if not specified.
- * Argument ignored in cashe of $fullcheck, the list is retrieved form DB anyway.
- * @param array $sections records from course_sections table for this course.
- * Retrieved from DB if not specified
- * @param bool $fullcheck Will add orphaned modules to their sections and remove non-existing
- * course modules from sequences. Only to be used in site maintenance mode when we are
- * sure that another user is not in the middle of the process of moving/removing a module.
- * @param bool $checkonly Only performs the check without updating DB, outputs all errors as debug messages.
- * @return array array of messages with found problems. Empty output means everything is ok
- */
- function course_integrity_check($courseid, $rawmods = null, $sections = null, $fullcheck = false, $checkonly = false) {
- global $DB;
- $messages = array();
- if ($sections === null) {
- $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section', 'id,section,sequence');
- }
- if ($fullcheck) {
- // Retrieve all records from course_modules regardless of module type visibility.
- $rawmods = $DB->get_records('course_modules', array('course' => $courseid), 'id', 'id,section');
- }
- if ($rawmods === null) {
- $rawmods = get_course_mods($courseid);
- }
- if (!$fullcheck && (empty($sections) || empty($rawmods))) {
- // If either of the arrays is empty, no modules are displayed anyway.
- return true;
- }
- $debuggingprefix = 'Failed integrity check for course ['.$courseid.']. ';
- // First make sure that each module id appears in section sequences only once.
- // If it appears in several section sequences the last section wins.
- // If it appears twice in one section sequence, the first occurence wins.
- $modsection = array();
- foreach ($sections as $sectionid => $section) {
- $sections[$sectionid]->newsequence = $section->sequence;
- if (!empty($section->sequence)) {
- $sequence = explode(",", $section->sequence);
- $sequenceunique = array_unique($sequence);
- if (count($sequenceunique) != count($sequence)) {
- // Some course module id appears in this section sequence more than once.
- ksort($sequenceunique); // Preserve initial order of modules.
- $sequence = array_values($sequenceunique);
- $sections[$sectionid]->newsequence = join(',', $sequence);
- $messages[] = $debuggingprefix.'Sequence for course section ['.
- $sectionid.'] is "'.$sections[$sectionid]->sequence.'", must be "'.$sections[$sectionid]->newsequence.'"';
- }
- foreach ($sequence as $cmid) {
- if (array_key_exists($cmid, $modsection) && isset($rawmods[$cmid])) {
- // Some course module id appears to be in more than one section's sequences.
- $wrongsectionid = $modsection[$cmid];
- $sections[$wrongsectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$wrongsectionid]->newsequence. ','), ',');
- $messages[] = $debuggingprefix.'Course module ['.$cmid.'] must be removed from sequence of section ['.
- $wrongsectionid.'] because it is also present in sequence of section ['.$sectionid.']';
- }
- $modsection[$cmid] = $sectionid;
- }
- }
- }
- // Add orphaned modules to their sections if they exist or to section 0 otherwise.
- if ($fullcheck) {
- foreach ($rawmods as $cmid => $mod) {
- if (!isset($modsection[$cmid])) {
- // This is a module that is not mentioned in course_section.sequence at all.
- // Add it to the section $mod->section or to the last available section.
- if ($mod->section && isset($sections[$mod->section])) {
- $modsection[$cmid] = $mod->section;
- } else {
- $firstsection = reset($sections);
- $modsection[$cmid] = $firstsection->id;
- }
- $sections[$modsection[$cmid]]->newsequence = trim($sections[$modsection[$cmid]]->newsequence.','.$cmid, ',');
- $messages[] = $debuggingprefix.'Course module ['.$cmid.'] is missing from sequence of section ['.
- $modsection[$cmid].']';
- }
- }
- foreach ($modsection as $cmid => $sectionid) {
- if (!isset($rawmods[$cmid])) {
- // Section $sectionid refers to module id that does not exist.
- $sections[$sectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$sectionid]->newsequence.','), ',');
- $messages[] = $debuggingprefix.'Course module ['.$cmid.
- '] does not exist but is present in the sequence of section ['.$sectionid.']';
- }
- }
- }
- // Update changed sections.
- if (!$checkonly && !empty($messages)) {
- foreach ($sections as $sectionid => $section) {
- if ($section->newsequence !== $section->sequence) {
- $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $section->newsequence));
- }
- }
- }
- // Now make sure that all modules point to the correct sections.
- foreach ($rawmods as $cmid => $mod) {
- if (isset($modsection[$cmid]) && $modsection[$cmid] != $mod->section) {
- if (!$checkonly) {
- $DB->update_record('course_modules', array('id' => $cmid, 'section' => $modsection[$cmid]));
- }
- $messages[] = $debuggingprefix.'Course module ['.$cmid.
- '] points to section ['.$mod->section.'] instead of ['.$modsection[$cmid].']';
- }
- }
- return $messages;
- }
- /**
- * For a given course, returns an array of course activity objects
- * Each item in the array contains he following properties:
- */
- function get_array_of_activities($courseid) {
- // cm - course module id
- // mod - name of the module (eg forum)
- // section - the number of the section (eg week or topic)
- // name - the name of the instance
- // visible - is the instance visible or not
- // groupingid - grouping id
- // extra - contains extra string to include in any link
- global $CFG, $DB;
- $course = $DB->get_record('course', array('id'=>$courseid));
- if (empty($course)) {
- throw new moodle_exception('courseidnotfound');
- }
- $mod = array();
- $rawmods = get_course_mods($courseid);
- if (empty($rawmods)) {
- return $mod; // always return array
- }
- $courseformat = course_get_format($course);
- if ($sections = $DB->get_records('course_sections', array('course' => $courseid),
- 'section ASC', 'id,section,sequence,visible')) {
- // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
- if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
- debugging(join('<br>', $errormessages));
- $rawmods = get_course_mods($courseid);
- $sections = $DB->get_records('course_sections', array('course' => $courseid),
- 'section ASC', 'id,section,sequence,visible');
- }
- // Build array of activities.
- foreach ($sections as $section) {
- if (!empty($section->sequence)) {
- $sequence = explode(",", $section->sequence);
- foreach ($sequence as $seq) {
- if (empty($rawmods[$seq])) {
- continue;
- }
- // Adjust visibleoncoursepage, value in DB may not respect format availability.
- $rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible
- || $rawmods[$seq]->visibleoncoursepage
- || empty($CFG->allowstealth)
- || !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0;
- // Create an object that will be cached.
- $mod[$seq] = new stdClass();
- $mod[$seq]->id = $rawmods[$seq]->instance;
- $mod[$seq]->cm = $rawmods[$seq]->id;
- $mod[$seq]->mod = $rawmods[$seq]->modname;
- // Oh dear. Inconsistent names left here for backward compatibility.
- $mod[$seq]->section = $section->section;
- $mod[$seq]->sectionid = $rawmods[$seq]->section;
- $mod[$seq]->module = $rawmods[$seq]->module;
- $mod[$seq]->added = $rawmods[$seq]->added;
- $mod[$seq]->score = $rawmods[$seq]->score;
- $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
- $mod[$seq]->visible = $rawmods[$seq]->visible;
- $mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage;
- $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
- $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
- $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
- $mod[$seq]->indent = $rawmods[$seq]->indent;
- $mod[$seq]->completion = $rawmods[$seq]->completion;
- $mod[$seq]->extra = "";
- $mod[$seq]->completiongradeitemnumber =
- $rawmods[$seq]->completiongradeitemnumber;
- $mod[$seq]->completionpassgrade = $rawmods[$seq]->completionpassgrade;
- $mod[$seq]->completionview = $rawmods[$seq]->completionview;
- $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
- $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
- $mod[$seq]->availability = $rawmods[$seq]->availability;
- $mod[$seq]->deletioninprogress = $rawmods[$seq]->deletioninprogress;
- $modname = $mod[$seq]->mod;
- $functionname = $modname."_get_coursemodule_info";
- if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
- continue;
- }
- include_once("$CFG->dirroot/mod/$modname/lib.php");
- if ($hasfunction = function_exists($functionname)) {
- if ($info = $functionname($rawmods[$seq])) {
- if (!empty($info->icon)) {
- $mod[$seq]->icon = $info->icon;
- }
- if (!empty($info->iconcomponent)) {
- $mod[$seq]->iconcomponent = $info->iconcomponent;
- }
- if (!empty($info->name)) {
- $mod[$seq]->name = $info->name;
- }
- if ($info instanceof cached_cm_info) {
- // When using cached_cm_info you can include three new fields
- // that aren't available for legacy code
- if (!empty($info->content)) {
- $mod[$seq]->content = $info->content;
- }
- if (!empty($info->extraclasses)) {
- $mod[$seq]->extraclasses = $info->extraclasses;
- }
- if (!empty($info->iconurl)) {
- // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
- $url = new moodle_url($info->iconurl);
- $mod[$seq]->iconurl = $url->out(false);
- }
- if (!empty($info->onclick)) {
- $mod[$seq]->onclick = $info->onclick;
- }
- if (!empty($info->customdata)) {
- $mod[$seq]->customdata = $info->customdata;
- }
- } else {
- // When using a stdclass, the (horrible) deprecated ->extra field
- // is available for BC
- if (!empty($info->extra)) {
- $mod[$seq]->extra = $info->extra;
- }
- }
- }
- }
- // When there is no modname_get_coursemodule_info function,
- // but showdescriptions is enabled, then we use the 'intro'
- // and 'introformat' fields in the module table
- if (!$hasfunction && $rawmods[$seq]->showdescription) {
- if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
- array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
- // Set content from intro and introformat. Filters are disabled
- // because we filter it with format_text at display time
- $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
- $modvalues, $rawmods[$seq]->id, false);
- // To save making another query just below, put name in here
- $mod[$seq]->name = $modvalues->name;
- }
- }
- if (!isset($mod[$seq]->name)) {
- $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
- }
- // Minimise the database size by unsetting default options when they are
- // 'empty'. This list corresponds to code in the cm_info constructor.
- foreach (array('idnumber', 'groupmode', 'groupingid',
- 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
- 'icon', 'iconcomponent', 'customdata', 'availability', 'completionview',
- 'completionexpected', 'score', 'showdescription', 'deletioninprogress') as $property) {
- if (property_exists($mod[$seq], $property) &&
- empty($mod[$seq]->{$property})) {
- unset($mod[$seq]->{$property});
- }
- }
- // Special case: this value is usually set to null, but may be 0
- if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
- is_null($mod[$seq]->completiongradeitemnumber)) {
- unset($mod[$seq]->completiongradeitemnumber);
- }
- }
- }
- }
- }
- return $mod;
- }
- /**
- * Returns the localised human-readable names of all used modules
- *
- * @param bool $plural if true returns the plural forms of the names
- * @return array where key is the module name (component name without 'mod_') and
- * the value is the human-readable string. Array sorted alphabetically by value
- */
- function get_module_types_names($plural = false) {
- static $modnames = null;
- global $DB, $CFG;
- if ($modnames === null) {
- $modnames = array(0 => array(), 1 => array());
- if ($allmods = $DB->get_records("modules")) {
- foreach ($allmods as $mod) {
- if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) {
- $modnames[0][$mod->name] = get_string("modulename", "$mod->name", null, true);
- $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name", null, true);
- }
- }
- }
- }
- return $modnames[(int)$plural];
- }
- /**
- * Set highlighted section. Only one section can be highlighted at the time.
- *
- * @param int $courseid course id
- * @param int $marker highlight section with this number, 0 means remove higlightin
- * @return void
- */
- function course_set_marker($courseid, $marker) {
- global $DB, $COURSE;
- $DB->set_field("course", "marker", $marker, array('id' => $courseid));
- if ($COURSE && $COURSE->id == $courseid) {
- $COURSE->marker = $marker;
- }
- core_courseformat\base::reset_course_cache($courseid);
- course_modinfo::clear_instance_cache($courseid);
- }
- /**
- * For a given course section, marks it visible or hidden,
- * and does the same for every activity in that section
- *
- * @param int $courseid course id
- * @param int $sectionnumber The section number to adjust
- * @param int $visibility The new visibility
- * @return array A list of resources which were hidden in the section
- */
- function set_section_visible($courseid, $sectionnumber, $visibility) {
- global $DB;
- $resourcestotoggle = array();
- if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
- course_update_section($courseid, $section, array('visible' => $visibility));
- // Determine which modules are visible for AJAX update
- $modules = !empty($section->sequence) ? explode(',', $section->sequence) : array();
- if (!empty($modules)) {
- list($insql, $params) = $DB->get_in_or_equal($modules);
- $select = 'id ' . $insql . ' AND visible = ?';
- array_push($params, $visibility);
- if (!$visibility) {
- $select .= ' AND visibleold = 1';
- }
- $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
- }
- }
- return $resourcestotoggle;
- }
- /**
- * Return the course category context for the category with id $categoryid, except
- * that if $categoryid is 0, return the system context.
- *
- * @param integer $categoryid a category id or 0.
- * @return context the corresponding context
- */
- function get_category_or_system_context($categoryid) {
- if ($categoryid) {
- return context_coursecat::instance($categoryid, IGNORE_MISSING);
- } else {
- return context_system::instance();
- }
- }
- /**
- * Print the buttons relating to course requests.
- *
- * @param context $context current page context.
- */
- function print_course_request_buttons($context) {
- global $CFG, $DB, $OUTPUT;
- if (empty($CFG->enablecourserequests)) {
- return;
- }
- if (course_request::can_request($context)) {
- // Print a button to request a new course.
- $params = [];
- if ($context instanceof context_coursecat) {
- $params['category'] = $context->instanceid;
- }
- echo $OUTPUT->single_button(new moodle_url('/course/request.php', $params),
- get_string('requestcourse'), 'get');
- }
- /// Print a button to manage pending requests
- if (has_capability('moodle/site:approvecourse', $context)) {
- $disabled = !$DB->record_exists('course_request', array());
- echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled));
- }
- }
- /**
- * Does the user have permission to edit things in this category?
- *
- * @param integer $categoryid The id of the category we are showing, or 0 for system context.
- * @return boolean has_any_capability(array(...), ...); in the appropriate context.
- */
- function can_edit_in_category($categoryid = 0) {
- $context = get_category_or_system_context($categoryid);
- return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
- }
- /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
- function add_course_module($mod) {
- global $DB;
- $mod->added = time();
- unset($mod->id);
- $cmid = $DB->insert_record("course_modules", $mod);
- rebuild_course_cache($mod->course, true);
- return $cmid;
- }
- /**
- * Creates a course section and adds it to the specified position
- *
- * @param int|stdClass $courseorid course id or course object
- * @param int $position position to add to, 0 means to the end. If position is greater than
- * number of existing secitons, the section is added to the end. This will become sectionnum of the
- * new section. All existing sections at this or bigger position will be shifted down.
- * @param bool $skipcheck the check has already been made and we know that the section with this position does not exist
- * @return stdClass created section object
- */
- function course_create_section($courseorid, $position = 0, $skipcheck = false) {
- global $DB;
- $courseid = is_object($courseorid) ? $courseorid->id : $courseorid;
- // Find the last sectionnum among existing sections.
- if ($skipcheck) {
- $lastsection = $position - 1;
- } else {
- $lastsection = (int)$DB->get_field_sql('SELECT max(section) from {course_sections} WHERE course = ?', [$courseid]);
- }
- // First add section to the end.
- $cw = new stdClass();
- $cw->course = $courseid;
- $cw->section = $lastsection + 1;
- $cw->summary = '';
- $cw->summaryformat = FORMAT_HTML;
- $cw->sequence = '';
- $cw->name = null;
- $cw->visible = 1;
- $cw->availability = null;
- $cw->timemodified = time();
- $cw->id = $DB->insert_record("course_sections", $cw);
- // Now move it to the specified position.
- if ($position > 0 && $position <= $lastsection) {
- $course = is_object($courseorid) ? $courseorid : get_course($courseorid);
- move_section_to($course, $cw->section, $position, true);
- $cw->section = $position;
- }
- core\event\course_section_created::create_from_section($cw)->trigger();
- rebuild_course_cache($courseid, true);
- return $cw;
- }
- /**
- * Creates missing course section(s) and rebuilds course cache
- *
- * @param int|stdClass $courseorid course id or course object
- * @param int|array $sections list of relative section numbers to create
- * @return bool if there were any sections created
- */
- function course_create_sections_if_missing($courseorid, $sections) {
- if (!is_array($sections)) {
- $sections = array($sections);
- }
- $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all());
- if ($newsections = array_diff($sections, $existing)) {
- foreach ($newsections as $sectionnum) {
- course_create_section($courseorid, $sectionnum, true);
- }
- return true;
- }
- return false;
- }
- /**
- * Adds an existing module to the section
- *
- * Updates both tables {course_sections} and {course_modules}
- *
- * Note: This function does not use modinfo PROVIDED that the section you are
- * adding the module to already exists. If the section does not exist, it will
- * build modinfo if necessary and create the section.
- *
- * @param int|stdClass $courseorid course id or course object
- * @param int $cmid id of the module already existing in course_modules table
- * @param int $sectionnum relative number of the section (field course_sections.section)
- * If section does not exist it will be created
- * @param int|stdClass $beforemod id or object with field id corresponding to the module
- * before which the module needs to be included. Null for inserting in the
- * end of the section
- * @return int The course_sections ID where the module is inserted
- */
- function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) {
- global $DB, $COURSE;
- if (is_object($beforemod)) {
- $beforemod = $beforemod->id;
- }
- if (is_object($courseorid)) {
- $courseid = $courseorid->id;
- } else {
- $courseid = $courseorid;
- }
- // Do not try to use modinfo here, there is no guarantee it is valid!
- $section = $DB->get_record('course_sections',
- array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING);
- if (!$section) {
- // This function call requires modinfo.
- course_create_sections_if_missing($courseorid, $sectionnum);
- $section = $DB->get_record('course_sections',
- array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST);
- }
- $modarray = explode(",", trim($section->sequence));
- if (empty($section->sequence)) {
- $newsequence = "$cmid";
- } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) {
- $insertarray = array($cmid, $beforemod);
- array_splice($modarray, $key[0], 1, $insertarray);
- $newsequence = implode(",", $modarray);
- } else {
- $newsequence = "$section->sequence,$cmid";
- }
- $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id));
- $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid));
- if (is_object($courseorid)) {
- rebuild_course_cache($courseorid->id, true);
- } else {
- rebuild_course_cache($courseorid, true);
- }
- return $section->id; // Return course_sections ID that was used.
- }
- /**
- * Change the group mode of a course module.
- *
- * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
- * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
- *
- * @param int $id course module ID.
- * @param int $groupmode the new groupmode value.
- * @return bool True if the $groupmode was updated.
- */
- function set_coursemodule_groupmode($id, $groupmode) {
- global $DB;
- $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST);
- if ($cm->groupmode != $groupmode) {
- $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id));
- rebuild_course_cache($cm->course, true);
- }
- return ($cm->groupmode != $groupmode);
- }
- function set_coursemodule_idnumber($id, $idnumber) {
- global $DB;
- $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST);
- if ($cm->idnumber != $idnumber) {
- $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
- rebuild_course_cache($cm->course, true);
- }
- return ($cm->idnumber != $idnumber);
- }
- /**
- * Set the visibility of a module and inherent properties.
- *
- * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
- * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
- *
- * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
- * has been moved to {@link set_section_visible()} which was the only place from which
- * the parameter was used.
- *
- * @param int $id of the module
- * @param int $visible state of the module
- * @param int $visibleoncoursepage state of the module on the course page
- * @return bool false when the module was not found, true otherwise
- */
- function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
- global $DB, $CFG;
- require_once($CFG->libdir.'/gradelib.php');
- require_once($CFG->dirroot.'/calendar/lib.php');
- if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
- return false;
- }
- // Create events and propagate visibility to associated grade items if the value has changed.
- // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
- if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
- return true;
- }
- if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
- return false;
- }
- if (($cm->visible != $visible) &&
- ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename)))) {
- foreach($events as $event) {
- if ($visible) {
- $event = new calendar_event($event);
- $event->toggle_visibility(true);
- } else {
- $event = new calendar_event($event);
- $event->toggle_visibility(false);
- }
- }
- }
- // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
- // affect visibleold to allow for an original visibility restore. See set_section_visible().
- $cminfo = new stdClass();
- $cminfo->id = $id;
- $cminfo->visible = $visible;
- $cminfo->visibleoncoursepage = $visibleoncoursepage;
- $cminfo->visibleold = $visible;
- $DB->update_record('course_modules', $cminfo);
- // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
- // Note that this must be done after updating the row in course_modules, in case
- // the modules grade_item_update function needs to access $cm->visible.
- if ($cm->visible != $visible &&
- plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
- component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
- $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
- component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
- } else if ($cm->visible != $visible) {
- $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
- if ($grade_items) {
- foreach ($grade_items as $grade_item) {
- $grade_item->set_hidden(!$visible);
- }
- }
- }
- rebuild_course_cache($cm->course, true);
- return true;
- }
- /**
- * Changes the course module name
- *
- * @param int $id course module id
- * @param string $name new value for a name
- * @return bool whether a change was made
- */
- function set_coursemodule_name($id, $name) {
- global $CFG, $DB;
- require_once($CFG->libdir . '/gradelib.php');
- $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
- $module = new \stdClass();
- $module->id = $cm->instance;
- // Escape strings as they would be by mform.
- if (!empty($CFG->formatstringstriptags)) {
- $module->name = clean_param($name, PARAM_TEXT);
- } else {
- $module->name = clean_param($name, PARAM_CLEANHTML);
- }
- if ($module->name === $cm->name || strval($module->name) === '') {
- return false;
- }
- if (\core_text::strlen($module->name) > 255) {
- throw new \moodle_exception('maximumchars', 'moodle', '', 255);
- }
- $module->timemodified = time();
- $DB->update_record($cm->modname, $module);
- $cm->name = $module->name;
- \core\event\course_module_updated::create_from_cm($cm)->trigger();
- rebuild_course_cache($cm->course, true);
- // Attempt to update the grade item if relevant.
- $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
- $grademodule->cmidnumber = $cm->idnumber;
- $grademodule->modname = $cm->modname;
- grade_update_mod_grades($grademodule);
- // Update calendar events with the new name.
- course_module_update_calendar_events($cm->modname, $grademodule, $cm);
- return true;
- }
- /**
- * This function will handle the whole deletion process of a module. This includes calling
- * the modules delete_instance function, deleting files, events, grades, conditional data,
- * the data in the course_module and course_sections table and adding a module deletion
- * event to the DB.
- *
- * @param int $cmid the course module id
- * @param bool $async whether or not to try to delete the module using an adhoc task. Async also depends on a plugin hook.
- * @throws moodle_exception
- * @since Moodle 2.5
- */
- function course_delete_module($cmid, $async = false) {
- // Check the 'course_module_background_deletion_recommended' hook first.
- // Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested.
- // Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it.
- // It's up to plugins to handle things like whether or not they are enabled.
- if ($async && $pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
- foreach ($pluginsfunction as $plugintype => $plugins) {
- foreach ($plugins as $pluginfunction) {
- if ($pluginfunction()) {
- return course_module_flag_for_async_deletion($cmid);
- }
- }
- }
- }
- global $CFG, $DB;
- require_once($CFG->libdir.'/gradelib.php');
- require_once($CFG->libdir.'/questionlib.php');
- require_once($CFG->dirroot.'/blog/lib.php');
- require_once($CFG->dirroot.'/calendar/lib.php');
- // Get the course module.
- if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
- return true;
- }
- // Get the module context.
- $modcontext = context_module::instance($cm->id);
- // Get the course module name.
- $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
- // Get the file location of the delete_instance function for this module.
- $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
- // Include the file required to call the delete_instance function for this module.
- if (file_exists($modlib)) {
- require_once($modlib);
- } else {
- throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
- "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
- }
- $deleteinstancefunction = $modulename . '_delete_instance';
- // Ensure the delete_instance function exists for this module.
- if (!function_exists($deleteinstancefunction)) {
- throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
- "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
- }
- // Allow plugins to use this course module before we completely delete it.
- if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) {
- foreach ($pluginsfunction as $plugintype => $plugins) {
- foreach ($plugins as $pluginfunction) {
- $pluginfunction($cm);
- }
- }
- }
- question_delete_activity($cm);
- // Call the delete_instance function, if it returns false throw an exception.
- if (!$deleteinstancefunction($cm->instance)) {
- throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
- "Cannot delete the module $modulename (instance).");
- }
- // Remove all module files in case modules forget to do that.
- $fs = get_file_storage();
- $fs->delete_area_files($modcontext->id);
- // Delete events from calendar.
- if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
- $coursecontext = context_course::instance($cm->course);
- foreach($events as $event) {
- $event->context = $coursecontext;
- $calendarevent = calendar_event::load($event);
- $calendarevent->delete();
- }
- }
- // Delete grade items, outcome items and grades attached to modules.
- if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
- 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
- foreach ($grade_items as $grade_item) {
- $grade_item->delete('moddelete');
- }
- }
- // Delete associated blogs and blog tag instances.
- blog_remove_associations_for_module($modcontext->id);
- // Delete completion and availability data; it is better to do this even if the
- // features are not turned on, in case they were turned on previously (these will be
- // very quick on an empty table).
- $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
- $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
- 'course' => $cm->course,
- 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
- // Delete all tag instances associated with the instance of this module.
- core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
- core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
- // Notify the competency subsystem.
- \core_competency\api::hook_course_module_deleted($cm);
- // Delete the context.
- context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
- // Delete the module from the course_modules table.
- $DB->delete_records('course_modules', array('id' => $cm->id));
- // Delete module from that section.
- if (!delete_mod_from_section($cm->id, $cm->section)) {
- throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
- "Cannot delete the module $modulename (instance) from section.");
- }
- // Trigger event for course module delete action.
- $event = \core\event\course_module_deleted::create(array(
- 'courseid' => $cm->course,
- 'context' => $modcontext,
- 'objectid' => $cm->id,
- 'other' => array(
- 'modulename' => $modulename,
- 'instanceid' => $cm->instance,
- )
- ));
- $event->add_record_snapshot('course_modules', $cm);
- $event->trigger();
- rebuild_course_cache($cm->course, true);
- }
- /**
- * Schedule a course module for deletion in the background using an adhoc task.
- *
- * This method should not be called directly. Instead, please use course_delete_module($cmid, true), to denote async deletion.
- * The real deletion of the module is handled by the task, which calls 'course_delete_module($cmid)'.
- *
- * @param int $cmid the course module id.
- * @return bool whether the module was successfully scheduled for deletion.
- * @throws \moodle_exception
- */
- function course_module_flag_for_async_deletion($cmid) {
- global $CFG, $DB, $USER;
- require_once($CFG->libdir.'/gradelib.php');
- require_once($CFG->libdir.'/questionlib.php');
- require_once($CFG->dirroot.'/blog/lib.php');
- require_once($CFG->dirroot.'/calendar/lib.php');
- // Get the course module.
- if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
- return true;
- }
- // We need to be reasonably certain the deletion is going to succeed before we background the process.
- // Make the necessary delete_instance checks, etc. before proceeding further. Throw exceptions if required.
- // Get the course module name.
- $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
- // Get the file location of the delete_instance function for this module.
- $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
- // Include the file required to call the delete_instance function for this module.
- if (file_exists($modlib)) {
- require_once($modlib);
- } else {
- throw new \moodle_exception('cannotdeletemodulemissinglib', '', '', null,
- "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
- }
- $deleteinstancefunction = $modulename . '_delete_instance';
- // Ensure the delete_instance function exists for this module.
- if (!function_exists($deleteinstancefunction)) {
- throw new \moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
- "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
- }
- // We are going to defer the deletion as we can't be sure how long the module's pre_delete code will run for.
- $cm->deletioninprogress = '1';
- $DB->update_record('course_modules', $cm);
- // Create an adhoc task for the deletion of the course module. The task takes an array of course modules for removal.
- $removaltask = new \core_course\task\co…
Large files files are truncated, but you can click here to view the full file