MDL-67264 core_course: Activity chooser new feature
[moodle.git] / course / lib.php
CommitLineData
d9cb06dc 1<?php
d9cb06dc 2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Library of useful functions
19 *
20 * @copyright 1999 Martin Dougiamas http://dougiamas.com
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5dc361e1 22 * @package core_course
d9cb06dc 23 */
f9903ed0 24
df997f84
PS
25defined('MOODLE_INTERNAL') || die;
26
4e781c7b 27require_once($CFG->libdir.'/completionlib.php');
8bdc9cac 28require_once($CFG->libdir.'/filelib.php');
160e77f9 29require_once($CFG->libdir.'/datalib.php');
ee7084e9 30require_once($CFG->dirroot.'/course/format/lib.php');
f9903ed0 31
5dc361e1
SH
32define('COURSE_MAX_LOGS_PER_PAGE', 1000); // Records.
33define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds.
3b7bfbb5
FM
34
35/**
36 * Number of courses to display when summaries are included.
37 * @var int
38 * @deprecated since 2.4, use $CFG->courseswithsummarieslimit instead.
39 */
40define('COURSE_MAX_SUMMARIES_PER_PAGE', 10);
41
5dc361e1
SH
42// Max courses in log dropdown before switching to optional.
43define('COURSE_MAX_COURSES_PER_DROPDOWN', 1000);
44// Max users in log dropdown before switching to optional.
45define('COURSE_MAX_USERS_PER_DROPDOWN', 1000);
46define('FRONTPAGENEWS', '0');
5dc361e1 47define('FRONTPAGECATEGORYNAMES', '2');
5dc361e1 48define('FRONTPAGECATEGORYCOMBO', '4');
0fd26350 49define('FRONTPAGEENROLLEDCOURSELIST', '5');
5dc361e1
SH
50define('FRONTPAGEALLCOURSELIST', '6');
51define('FRONTPAGECOURSESEARCH', '7');
52// Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage.
6f24e48e 53define('EXCELROWS', 65535);
54define('FIRSTUSEDEXCELROW', 3);
60fdc714 55
89bfeee0 56define('MOD_CLASS_ACTIVITY', 0);
57define('MOD_CLASS_RESOURCE', 1);
58
cd4abbc7 59define('COURSE_TIMELINE_ALLINCLUDINGHIDDEN', 'allincludinghidden');
6481a21f 60define('COURSE_TIMELINE_ALL', 'all');
d07373f2
DW
61define('COURSE_TIMELINE_PAST', 'past');
62define('COURSE_TIMELINE_INPROGRESS', 'inprogress');
63define('COURSE_TIMELINE_FUTURE', 'future');
3cfff885 64define('COURSE_FAVOURITES', 'favourites');
e6f03948 65define('COURSE_TIMELINE_HIDDEN', 'hidden');
8d166d77 66define('COURSE_CUSTOMFIELD', 'customfield');
2c1d19fd 67define('COURSE_DB_QUERY_LIMIT', 1000);
8d166d77
DS
68/** Searching for all courses that have no value for the specified custom field. */
69define('COURSE_CUSTOMFIELD_EMPTY', -1);
d07373f2 70
600149be 71function make_log_url($module, $url) {
72 switch ($module) {
bd7be234 73 case 'course':
d48f6068 74 if (strpos($url, 'report/') === 0) {
3ce3bb88 75 // there is only one report type, course reports are deprecated
d48f6068
PS
76 $url = "/$url";
77 break;
78 }
bd7be234 79 case 'file':
80 case 'login':
81 case 'lib':
82 case 'admin':
95d1d037 83 case 'category':
bd5d0ce5 84 case 'mnet course':
11003188 85 if (strpos($url, '../') === 0) {
86 $url = ltrim($url, '.');
87 } else {
88 $url = "/course/$url";
89 }
bd5d0ce5 90 break;
e662454a
AA
91 case 'calendar':
92 $url = "/calendar/$url";
93 break;
01d5d399 94 case 'user':
bd7be234 95 case 'blog':
11003188 96 $url = "/$module/$url";
600149be 97 break;
bd7be234 98 case 'upload':
11003188 99 $url = $url;
c80b7585 100 break;
38fb8190 101 case 'coursetags':
11003188 102 $url = '/'.$url;
38fb8190 103 break;
bd7be234 104 case 'library':
105 case '':
11003188 106 $url = '/';
de2dfe68 107 break;
4597d533 108 case 'message':
11003188 109 $url = "/message/$url";
110 break;
111 case 'notes':
112 $url = "/notes/$url";
4597d533 113 break;
b89e4ad8 114 case 'tag':
115 $url = "/tag/$url";
116 break;
dbcf271b 117 case 'role':
118 $url = '/'.$url;
119 break;
afd2efc0
AD
120 case 'grade':
121 $url = "/grade/$url";
122 break;
600149be 123 default:
11003188 124 $url = "/mod/$module/$url";
600149be 125 break;
126 }
11003188 127
128 //now let's sanitise urls - there might be some ugly nasties:-(
129 $parts = explode('?', $url);
130 $script = array_shift($parts);
131 if (strpos($script, 'http') === 0) {
132 $script = clean_param($script, PARAM_URL);
133 } else {
134 $script = clean_param($script, PARAM_PATH);
135 }
136
137 $query = '';
138 if ($parts) {
139 $query = implode('', $parts);
140 $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
141 $parts = explode('&', $query);
142 $eq = urlencode('=');
143 foreach ($parts as $key=>$part) {
144 $part = urlencode(urldecode($part));
145 $part = str_replace($eq, '=', $part);
146 $parts[$key] = $part;
147 }
148 $query = '?'.implode('&amp;', $parts);
149 }
150
151 return $script.$query;
600149be 152}
153
92890025 154
c215b32b 155function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
156 $modname="", $modid=0, $modaction="", $groupid=0) {
cb6fec1f 157 global $CFG, $DB;
c215b32b 158
159 // It is assumed that $date is the GMT time of midnight for that day,
160 // and so the next 86400 seconds worth of logs are printed.
161
162 /// Setup for group handling.
238c0dd9 163
164 // TODO: I don't understand group/context/etc. enough to be able to do
c215b32b 165 // something interesting with it here
166 // What is the context of a remote course?
238c0dd9 167
c215b32b 168 /// If the group mode is separate, and this user does not have editing privileges,
169 /// then only the user's group can be viewed.
9a5e297b 170 //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
c215b32b 171 // $groupid = get_current_group($course->id);
172 //}
173 /// If this course doesn't have groups, no groupid can be specified.
174 //else if (!$course->groupmode) {
175 // $groupid = 0;
176 //}
cb6fec1f 177
c215b32b 178 $groupid = 0;
179
180 $joins = array();
92701024 181 $where = '';
c215b32b 182
cb6fec1f 183 $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
184 FROM {mnet_log} l
185 LEFT JOIN {user} u ON l.userid = u.id
186 WHERE ";
187 $params = array();
188
189 $where .= "l.hostid = :hostid";
190 $params['hostid'] = $hostid;
c215b32b 191
192 // TODO: Is 1 really a magic number referring to the sitename?
cb6fec1f 193 if ($course != SITEID || $modid != 0) {
194 $where .= " AND l.course=:courseid";
195 $params['courseid'] = $course;
c215b32b 196 }
197
198 if ($modname) {
cb6fec1f 199 $where .= " AND l.module = :modname";
200 $params['modname'] = $modname;
c215b32b 201 }
202
203 if ('site_errors' === $modid) {
cb6fec1f 204 $where .= " AND ( l.action='error' OR l.action='infected' )";
c215b32b 205 } else if ($modid) {
238c0dd9 206 //TODO: This assumes that modids are the same across sites... probably
c215b32b 207 //not true
cb6fec1f 208 $where .= " AND l.cmid = :modid";
209 $params['modid'] = $modid;
c215b32b 210 }
211
212 if ($modaction) {
213 $firstletter = substr($modaction, 0, 1);
8086b083 214 if ($firstletter == '-') {
47586394 215 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
8086b083
PS
216 $params['modaction'] = '%'.substr($modaction, 1).'%';
217 } else {
218 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
219 $params['modaction'] = '%'.$modaction.'%';
c215b32b 220 }
221 }
222
223 if ($user) {
cb6fec1f 224 $where .= " AND l.userid = :user";
225 $params['user'] = $user;
c215b32b 226 }
227
228 if ($date) {
229 $enddate = $date + 86400;
cb6fec1f 230 $where .= " AND l.time > :date AND l.time < :enddate";
231 $params['date'] = $date;
232 $params['enddate'] = $enddate;
c215b32b 233 }
234
235 $result = array();
cb6fec1f 236 $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
c215b32b 237 if(!empty($result['totalcount'])) {
cb6fec1f 238 $where .= " ORDER BY $order";
239 $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
c215b32b 240 } else {
241 $result['logs'] = array();
242 }
243 return $result;
244}
245
749ce98e
DW
246/**
247 * Checks the integrity of the course data.
248 *
249 * In summary - compares course_sections.sequence and course_modules.section.
250 *
251 * More detailed, checks that:
252 * - course_sections.sequence contains each module id not more than once in the course
253 * - for each moduleid from course_sections.sequence the field course_modules.section
254 * refers to the same section id (this means course_sections.sequence is more
255 * important if they are different)
256 * - ($fullcheck only) each module in the course is present in one of
257 * course_sections.sequence
258 * - ($fullcheck only) removes non-existing course modules from section sequences
259 *
260 * If there are any mismatches, the changes are made and records are updated in DB.
261 *
262 * Course cache is NOT rebuilt if there are any errors!
263 *
264 * This function is used each time when course cache is being rebuilt with $fullcheck = false
265 * and in CLI script admin/cli/fix_course_sequence.php with $fullcheck = true
266 *
267 * @param int $courseid id of the course
268 * @param array $rawmods result of funciton {@link get_course_mods()} - containst
269 * the list of enabled course modules in the course. Retrieved from DB if not specified.
270 * Argument ignored in cashe of $fullcheck, the list is retrieved form DB anyway.
271 * @param array $sections records from course_sections table for this course.
272 * Retrieved from DB if not specified
273 * @param bool $fullcheck Will add orphaned modules to their sections and remove non-existing
274 * course modules from sequences. Only to be used in site maintenance mode when we are
275 * sure that another user is not in the middle of the process of moving/removing a module.
276 * @param bool $checkonly Only performs the check without updating DB, outputs all errors as debug messages.
277 * @return array array of messages with found problems. Empty output means everything is ok
278 */
279function course_integrity_check($courseid, $rawmods = null, $sections = null, $fullcheck = false, $checkonly = false) {
280 global $DB;
281 $messages = array();
282 if ($sections === null) {
283 $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section', 'id,section,sequence');
284 }
285 if ($fullcheck) {
286 // Retrieve all records from course_modules regardless of module type visibility.
287 $rawmods = $DB->get_records('course_modules', array('course' => $courseid), 'id', 'id,section');
288 }
289 if ($rawmods === null) {
290 $rawmods = get_course_mods($courseid);
291 }
292 if (!$fullcheck && (empty($sections) || empty($rawmods))) {
293 // If either of the arrays is empty, no modules are displayed anyway.
294 return true;
295 }
296 $debuggingprefix = 'Failed integrity check for course ['.$courseid.']. ';
297
298 // First make sure that each module id appears in section sequences only once.
299 // If it appears in several section sequences the last section wins.
300 // If it appears twice in one section sequence, the first occurence wins.
301 $modsection = array();
302 foreach ($sections as $sectionid => $section) {
303 $sections[$sectionid]->newsequence = $section->sequence;
304 if (!empty($section->sequence)) {
305 $sequence = explode(",", $section->sequence);
306 $sequenceunique = array_unique($sequence);
307 if (count($sequenceunique) != count($sequence)) {
308 // Some course module id appears in this section sequence more than once.
309 ksort($sequenceunique); // Preserve initial order of modules.
310 $sequence = array_values($sequenceunique);
311 $sections[$sectionid]->newsequence = join(',', $sequence);
312 $messages[] = $debuggingprefix.'Sequence for course section ['.
313 $sectionid.'] is "'.$sections[$sectionid]->sequence.'", must be "'.$sections[$sectionid]->newsequence.'"';
314 }
315 foreach ($sequence as $cmid) {
316 if (array_key_exists($cmid, $modsection) && isset($rawmods[$cmid])) {
317 // Some course module id appears to be in more than one section's sequences.
318 $wrongsectionid = $modsection[$cmid];
319 $sections[$wrongsectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$wrongsectionid]->newsequence. ','), ',');
320 $messages[] = $debuggingprefix.'Course module ['.$cmid.'] must be removed from sequence of section ['.
321 $wrongsectionid.'] because it is also present in sequence of section ['.$sectionid.']';
322 }
323 $modsection[$cmid] = $sectionid;
324 }
325 }
326 }
327
328 // Add orphaned modules to their sections if they exist or to section 0 otherwise.
329 if ($fullcheck) {
330 foreach ($rawmods as $cmid => $mod) {
331 if (!isset($modsection[$cmid])) {
332 // This is a module that is not mentioned in course_section.sequence at all.
333 // Add it to the section $mod->section or to the last available section.
334 if ($mod->section && isset($sections[$mod->section])) {
335 $modsection[$cmid] = $mod->section;
336 } else {
337 $firstsection = reset($sections);
338 $modsection[$cmid] = $firstsection->id;
339 }
340 $sections[$modsection[$cmid]]->newsequence = trim($sections[$modsection[$cmid]]->newsequence.','.$cmid, ',');
341 $messages[] = $debuggingprefix.'Course module ['.$cmid.'] is missing from sequence of section ['.
e2e9cb6a 342 $modsection[$cmid].']';
749ce98e
DW
343 }
344 }
345 foreach ($modsection as $cmid => $sectionid) {
346 if (!isset($rawmods[$cmid])) {
347 // Section $sectionid refers to module id that does not exist.
348 $sections[$sectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$sectionid]->newsequence.','), ',');
349 $messages[] = $debuggingprefix.'Course module ['.$cmid.
350 '] does not exist but is present in the sequence of section ['.$sectionid.']';
351 }
352 }
353 }
354
355 // Update changed sections.
356 if (!$checkonly && !empty($messages)) {
357 foreach ($sections as $sectionid => $section) {
358 if ($section->newsequence !== $section->sequence) {
359 $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $section->newsequence));
360 }
361 }
362 }
363
364 // Now make sure that all modules point to the correct sections.
365 foreach ($rawmods as $cmid => $mod) {
366 if (isset($modsection[$cmid]) && $modsection[$cmid] != $mod->section) {
367 if (!$checkonly) {
368 $DB->update_record('course_modules', array('id' => $cmid, 'section' => $modsection[$cmid]));
369 }
370 $messages[] = $debuggingprefix.'Course module ['.$cmid.
371 '] points to section ['.$mod->section.'] instead of ['.$modsection[$cmid].']';
372 }
373 }
374
375 return $messages;
376}
377
cb6fec1f 378/**
379 * For a given course, returns an array of course activity objects
380 * Each item in the array contains he following properties:
381 */
d897cae4 382function get_array_of_activities($courseid) {
d897cae4 383// cm - course module id
384// mod - name of the module (eg forum)
385// section - the number of the section (eg week or topic)
386// name - the name of the instance
5867bfb5 387// visible - is the instance visible or not
13534ef7 388// groupingid - grouping id
86aa7ccf 389// extra - contains extra string to include in any link
cb6fec1f 390 global $CFG, $DB;
8dddba42 391
a0c30e1b 392 $course = $DB->get_record('course', array('id'=>$courseid));
393
394 if (empty($course)) {
395 throw new moodle_exception('courseidnotfound');
396 }
397
d897cae4 398 $mod = array();
399
a0c30e1b 400 $rawmods = get_course_mods($courseid);
401 if (empty($rawmods)) {
dd97c328 402 return $mod; // always return array
d897cae4 403 }
8341055e 404 $courseformat = course_get_format($course);
d897cae4 405
8341055e
MG
406 if ($sections = $DB->get_records('course_sections', array('course' => $courseid),
407 'section ASC', 'id,section,sequence,visible')) {
749ce98e
DW
408 // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
409 if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
410 debugging(join('<br>', $errormessages));
411 $rawmods = get_course_mods($courseid);
8341055e
MG
412 $sections = $DB->get_records('course_sections', array('course' => $courseid),
413 'section ASC', 'id,section,sequence,visible');
749ce98e
DW
414 }
415 // Build array of activities.
d897cae4 416 foreach ($sections as $section) {
74666583 417 if (!empty($section->sequence)) {
d897cae4 418 $sequence = explode(",", $section->sequence);
419 foreach ($sequence as $seq) {
7af6281f 420 if (empty($rawmods[$seq])) {
421 continue;
422 }
8341055e
MG
423 // Adjust visibleoncoursepage, value in DB may not respect format availability.
424 $rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible
425 || $rawmods[$seq]->visibleoncoursepage
426 || empty($CFG->allowstealth)
427 || !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0;
428
429 // Create an object that will be cached.
b85b25eb 430 $mod[$seq] = new stdClass();
dd97c328 431 $mod[$seq]->id = $rawmods[$seq]->instance;
432 $mod[$seq]->cm = $rawmods[$seq]->id;
433 $mod[$seq]->mod = $rawmods[$seq]->modname;
adaeccb6 434
435 // Oh dear. Inconsistent names left here for backward compatibility.
dd97c328 436 $mod[$seq]->section = $section->section;
adaeccb6 437 $mod[$seq]->sectionid = $rawmods[$seq]->section;
438
439 $mod[$seq]->module = $rawmods[$seq]->module;
440 $mod[$seq]->added = $rawmods[$seq]->added;
441 $mod[$seq]->score = $rawmods[$seq]->score;
66b250fd 442 $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
dd97c328 443 $mod[$seq]->visible = $rawmods[$seq]->visible;
8341055e 444 $mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage;
adaeccb6 445 $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
dd97c328 446 $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
447 $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
82bd6a5e 448 $mod[$seq]->indent = $rawmods[$seq]->indent;
449 $mod[$seq]->completion = $rawmods[$seq]->completion;
dd97c328 450 $mod[$seq]->extra = "";
adaeccb6 451 $mod[$seq]->completiongradeitemnumber =
452 $rawmods[$seq]->completiongradeitemnumber;
453 $mod[$seq]->completionview = $rawmods[$seq]->completionview;
454 $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
8c40662e 455 $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
8d1f33e1 456 $mod[$seq]->availability = $rawmods[$seq]->availability;
3869d774 457 $mod[$seq]->deletioninprogress = $rawmods[$seq]->deletioninprogress;
8dddba42 458
459 $modname = $mod[$seq]->mod;
460 $functionname = $modname."_get_coursemodule_info";
461
3a37b3f8 462 if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
463 continue;
464 }
465
8dddba42 466 include_once("$CFG->dirroot/mod/$modname/lib.php");
467
b3a89232 468 if ($hasfunction = function_exists($functionname)) {
9d361034 469 if ($info = $functionname($rawmods[$seq])) {
9d361034 470 if (!empty($info->icon)) {
471 $mod[$seq]->icon = $info->icon;
472 }
9a9012dc
PS
473 if (!empty($info->iconcomponent)) {
474 $mod[$seq]->iconcomponent = $info->iconcomponent;
475 }
1ea543df 476 if (!empty($info->name)) {
9a9012dc 477 $mod[$seq]->name = $info->name;
1ea543df 478 }
0d8b6a69 479 if ($info instanceof cached_cm_info) {
480 // When using cached_cm_info you can include three new fields
481 // that aren't available for legacy code
482 if (!empty($info->content)) {
483 $mod[$seq]->content = $info->content;
484 }
485 if (!empty($info->extraclasses)) {
486 $mod[$seq]->extraclasses = $info->extraclasses;
487 }
c443a1cd 488 if (!empty($info->iconurl)) {
4fcdb012
MG
489 // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
490 $url = new moodle_url($info->iconurl);
491 $mod[$seq]->iconurl = $url->out(false);
c443a1cd 492 }
0d8b6a69 493 if (!empty($info->onclick)) {
494 $mod[$seq]->onclick = $info->onclick;
495 }
496 if (!empty($info->customdata)) {
497 $mod[$seq]->customdata = $info->customdata;
498 }
499 } else {
500 // When using a stdclass, the (horrible) deprecated ->extra field
501 // is available for BC
502 if (!empty($info->extra)) {
503 $mod[$seq]->extra = $info->extra;
504 }
505 }
c9f6251e 506 }
507 }
b3a89232 508 // When there is no modname_get_coursemodule_info function,
509 // but showdescriptions is enabled, then we use the 'intro'
510 // and 'introformat' fields in the module table
511 if (!$hasfunction && $rawmods[$seq]->showdescription) {
512 if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
513 array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
514 // Set content from intro and introformat. Filters are disabled
515 // because we filter it with format_text at display time
516 $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
517 $modvalues, $rawmods[$seq]->id, false);
518
519 // To save making another query just below, put name in here
520 $mod[$seq]->name = $modvalues->name;
521 }
522 }
1ea543df 523 if (!isset($mod[$seq]->name)) {
9a9012dc 524 $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1ea543df 525 }
0d8b6a69 526
061e6b28 527 // Minimise the database size by unsetting default options when they are
528 // 'empty'. This list corresponds to code in the cm_info constructor.
529 foreach (array('idnumber', 'groupmode', 'groupingid',
530 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
531 'icon', 'iconcomponent', 'customdata', 'availability', 'completionview',
3869d774 532 'completionexpected', 'score', 'showdescription', 'deletioninprogress') as $property) {
0d8b6a69 533 if (property_exists($mod[$seq], $property) &&
534 empty($mod[$seq]->{$property})) {
535 unset($mod[$seq]->{$property});
536 }
537 }
adaeccb6 538 // Special case: this value is usually set to null, but may be 0
539 if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
540 is_null($mod[$seq]->completiongradeitemnumber)) {
541 unset($mod[$seq]->completiongradeitemnumber);
542 }
d897cae4 543 }
544 }
545 }
546 }
547 return $mod;
548}
549
cb6fec1f 550/**
d57aa283
MG
551 * Returns the localised human-readable names of all used modules
552 *
553 * @param bool $plural if true returns the plural forms of the names
554 * @return array where key is the module name (component name without 'mod_') and
555 * the value is the human-readable string. Array sorted alphabetically by value
cb6fec1f 556 */
d57aa283
MG
557function get_module_types_names($plural = false) {
558 static $modnames = null;
559 global $DB, $CFG;
560 if ($modnames === null) {
561 $modnames = array(0 => array(), 1 => array());
562 if ($allmods = $DB->get_records("modules")) {
563 foreach ($allmods as $mod) {
564 if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) {
d91aa1e7
MJ
565 $modnames[0][$mod->name] = get_string("modulename", "$mod->name", null, true);
566 $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name", null, true);
d57aa283 567 }
13534ef7 568 }
c7da6f7a 569 }
7468bf01 570 }
d57aa283 571 return $modnames[(int)$plural];
7468bf01 572}
573
93d46f48
RK
574/**
575 * Set highlighted section. Only one section can be highlighted at the time.
576 *
577 * @param int $courseid course id
578 * @param int $marker highlight section with this number, 0 means remove higlightin
579 * @return void
580 */
581function course_set_marker($courseid, $marker) {
8341055e 582 global $DB, $COURSE;
93d46f48 583 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
8341055e
MG
584 if ($COURSE && $COURSE->id == $courseid) {
585 $COURSE->marker = $marker;
586 }
587 if (class_exists('format_base')) {
588 format_base::reset_course_cache($courseid);
589 }
590 course_modinfo::clear_instance_cache($courseid);
93d46f48
RK
591}
592
cb6fec1f 593/**
7e85563d 594 * For a given course section, marks it visible or hidden,
cb6fec1f 595 * and does the same for every activity in that section
ebaa29d1
ARN
596 *
597 * @param int $courseid course id
598 * @param int $sectionnumber The section number to adjust
599 * @param int $visibility The new visibility
600 * @return array A list of resources which were hidden in the section
cb6fec1f 601 */
7d99d695 602function set_section_visible($courseid, $sectionnumber, $visibility) {
cb6fec1f 603 global $DB;
7d99d695 604
ebaa29d1 605 $resourcestotoggle = array();
cb6fec1f 606 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
f26481c2 607 course_update_section($courseid, $section, array('visible' => $visibility));
ebaa29d1
ARN
608
609 // Determine which modules are visible for AJAX update
f26481c2 610 $modules = !empty($section->sequence) ? explode(',', $section->sequence) : array();
ebaa29d1
ARN
611 if (!empty($modules)) {
612 list($insql, $params) = $DB->get_in_or_equal($modules);
613 $select = 'id ' . $insql . ' AND visible = ?';
614 array_push($params, $visibility);
615 if (!$visibility) {
616 $select .= ' AND visibleold = 1';
617 }
618 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
619 }
7d99d695 620 }
ebaa29d1 621 return $resourcestotoggle;
7d99d695 622}
ba2e5d73 623
59155a3f
ARN
624/**
625 * Retrieve all metadata for the requested modules
626 *
627 * @param object $course The Course
628 * @param array $modnames An array containing the list of modules and their
629 * names
a41b1d96 630 * @param int $sectionreturn The section to return to
59155a3f
ARN
631 * @return array A list of stdClass objects containing metadata about each
632 * module
633 */
923451c5 634function get_module_metadata($course, $modnames, $sectionreturn = null) {
9ca0420e 635 global $OUTPUT;
59155a3f
ARN
636
637 // get_module_metadata will be called once per section on the page and courses may show
638 // different modules to one another
639 static $modlist = array();
640 if (!isset($modlist[$course->id])) {
641 $modlist[$course->id] = array();
642 }
643
644 $return = array();
17079335
MG
645 $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
646 if ($sectionreturn !== null) {
647 $urlbase->param('sr', $sectionreturn);
648 }
59155a3f
ARN
649 foreach($modnames as $modname => $modnamestr) {
650 if (!course_allowed_module($course, $modname)) {
651 continue;
652 }
ad47d025 653 if (isset($modlist[$course->id][$modname])) {
59155a3f 654 // This module is already cached
9ca0420e 655 $return += $modlist[$course->id][$modname];
59155a3f
ARN
656 continue;
657 }
9ca0420e
MG
658 $modlist[$course->id][$modname] = array();
659
660 // Create an object for a default representation of this module type in the activity chooser. It will be used
661 // if module does not implement callback get_shortcuts() and it will also be passed to the callback if it exists.
662 $defaultmodule = new stdClass();
663 $defaultmodule->title = $modnamestr;
664 $defaultmodule->name = $modname;
665 $defaultmodule->link = new moodle_url($urlbase, array('add' => $modname));
666 $defaultmodule->icon = $OUTPUT->pix_icon('icon', '', $defaultmodule->name, array('class' => 'icon'));
667 $sm = get_string_manager();
668 if ($sm->string_exists('modulename_help', $modname)) {
669 $defaultmodule->help = get_string('modulename_help', $modname);
670 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs.
671 $link = get_string('modulename_link', $modname);
672 $linktext = get_string('morehelp');
673 $defaultmodule->help .= html_writer::tag('div',
674 $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
675 }
59155a3f 676 }
9ca0420e
MG
677 $defaultmodule->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
678
9ca0420e
MG
679 // Each module can implement callback modulename_get_shortcuts() in its lib.php and return the list
680 // of elements to be added to activity chooser.
681 $items = component_callback($modname, 'get_shortcuts', array($defaultmodule), null);
682 if ($items !== null) {
683 foreach ($items as $item) {
684 // Add all items to the return array. All items must have different links, use them as a key in the return array.
685 if (!isset($item->archetype)) {
686 $item->archetype = $defaultmodule->archetype;
687 }
688 if (!isset($item->icon)) {
689 $item->icon = $defaultmodule->icon;
690 }
691 // If plugin returned the only one item with the same link as default item - cache it as $modname,
692 // otherwise append the link url to the module name.
693 $item->name = (count($items) == 1 &&
694 $item->link->out() === $defaultmodule->link->out()) ? $modname : $modname . ':' . $item->link;
f418d899
JD
695
696 // If the module provides the helptext property, append it to the help text to match the look and feel
697 // of the default course modules.
698 if (isset($item->help) && isset($item->helplink)) {
699 $linktext = get_string('morehelp');
700 $item->help .= html_writer::tag('div',
701 $OUTPUT->doc_link($item->helplink, $linktext, true), array('class' => 'helpdoclink'));
702 }
9ca0420e
MG
703 $modlist[$course->id][$modname][$item->name] = $item;
704 }
705 $return += $modlist[$course->id][$modname];
9ca0420e
MG
706 // If get_shortcuts() callback is defined, the default module action is not added.
707 // It is a responsibility of the callback to add it to the return value unless it is not needed.
708 continue;
01e8bfd7
JO
709 }
710
4bebed40
AG
711 // The callback get_shortcuts() was not found, use the default item for the activity chooser.
712 $modlist[$course->id][$modname][$modname] = $defaultmodule;
713 $return[$modname] = $defaultmodule;
59155a3f
ARN
714 }
715
9ca0420e 716 core_collator::asort_objects_by_property($return, 'title');
59155a3f
ARN
717 return $return;
718}
719
8ed5dd63 720/**
721 * Return the course category context for the category with id $categoryid, except
722 * that if $categoryid is 0, return the system context.
723 *
724 * @param integer $categoryid a category id or 0.
5dc361e1 725 * @return context the corresponding context
8ed5dd63 726 */
727function get_category_or_system_context($categoryid) {
728 if ($categoryid) {
4658aec5 729 return context_coursecat::instance($categoryid, IGNORE_MISSING);
8ed5dd63 730 } else {
9a5e297b 731 return context_system::instance();
8ed5dd63 732 }
733}
734
cb6fec1f 735/**
4e0b6025
MG
736 * Returns full course categories trees to be used in html_writer::select()
737 *
442f12f8 738 * Calls {@link core_course_category::make_categories_list()} to build the tree and
4e0b6025
MG
739 * adds whitespace to denote nesting
740 *
442f12f8 741 * @return array array mapping course category id to the display name
cb6fec1f 742 */
0705ff84 743function make_categories_options() {
442f12f8 744 $cats = core_course_category::make_categories_list('', 0, ' / ');
0705ff84 745 foreach ($cats as $key => $value) {
a7a04f6d
MG
746 // Prefix the value with the number of spaces equal to category depth (number of separators in the value).
747 $cats[$key] = str_repeat('&nbsp;', substr_count($value, ' / ')). $value;
0705ff84 748 }
749 return $cats;
750}
c2cb4545 751
77eddcd5 752/**
753 * Print the buttons relating to course requests.
754 *
3e15abe5 755 * @param context $context current page context.
77eddcd5 756 */
8e57a6df 757function print_course_request_buttons($context) {
b4531207 758 global $CFG, $DB, $OUTPUT;
77eddcd5 759 if (empty($CFG->enablecourserequests)) {
760 return;
761 }
3e15abe5
MG
762 if (course_request::can_request($context)) {
763 // Print a button to request a new course.
764 $params = [];
765 if ($context instanceof context_coursecat) {
766 $params['category'] = $context->instanceid;
767 }
768 echo $OUTPUT->single_button(new moodle_url('/course/request.php', $params),
769 get_string('requestcourse'), 'get');
77eddcd5 770 }
771 /// Print a button to manage pending requests
beff3806 772 if (has_capability('moodle/site:approvecourse', $context)) {
5c2ed7e2 773 $disabled = !$DB->record_exists('course_request', array());
8e57a6df 774 echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled));
77eddcd5 775 }
776}
777
5048e034 778/**
779 * Does the user have permission to edit things in this category?
780 *
781 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
782 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
783 */
784function can_edit_in_category($categoryid = 0) {
785 $context = get_category_or_system_context($categoryid);
786 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
787}
788
11b0c469 789/// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
790
791function add_course_module($mod) {
cb6fec1f 792 global $DB;
11b0c469 793
e5dfd0f3 794 $mod->added = time();
53f4ad2c 795 unset($mod->id);
11b0c469 796
38b19bbc
MG
797 $cmid = $DB->insert_record("course_modules", $mod);
798 rebuild_course_cache($mod->course, true);
799 return $cmid;
11b0c469 800}
801
89b909f6
MG
802/**
803 * Creates a course section and adds it to the specified position
804 *
805 * @param int|stdClass $courseorid course id or course object
806 * @param int $position position to add to, 0 means to the end. If position is greater than
807 * number of existing secitons, the section is added to the end. This will become sectionnum of the
808 * new section. All existing sections at this or bigger position will be shifted down.
809 * @param bool $skipcheck the check has already been made and we know that the section with this position does not exist
810 * @return stdClass created section object
811 */
812function course_create_section($courseorid, $position = 0, $skipcheck = false) {
813 global $DB;
814 $courseid = is_object($courseorid) ? $courseorid->id : $courseorid;
815
816 // Find the last sectionnum among existing sections.
817 if ($skipcheck) {
818 $lastsection = $position - 1;
819 } else {
820 $lastsection = (int)$DB->get_field_sql('SELECT max(section) from {course_sections} WHERE course = ?', [$courseid]);
821 }
822
823 // First add section to the end.
824 $cw = new stdClass();
825 $cw->course = $courseid;
826 $cw->section = $lastsection + 1;
827 $cw->summary = '';
828 $cw->summaryformat = FORMAT_HTML;
829 $cw->sequence = '';
830 $cw->name = null;
831 $cw->visible = 1;
832 $cw->availability = null;
4ddf7c60 833 $cw->timemodified = time();
89b909f6
MG
834 $cw->id = $DB->insert_record("course_sections", $cw);
835
836 // Now move it to the specified position.
837 if ($position > 0 && $position <= $lastsection) {
838 $course = is_object($courseorid) ? $courseorid : get_course($courseorid);
839 move_section_to($course, $cw->section, $position, true);
840 $cw->section = $position;
841 }
842
843 core\event\course_section_created::create_from_section($cw)->trigger();
844
845 rebuild_course_cache($courseid, true);
846 return $cw;
847}
848
97928ddf 849/**
4ede27b2 850 * Creates missing course section(s) and rebuilds course cache
99e9f9a6 851 *
b46be6ad 852 * @param int|stdClass $courseorid course id or course object
4ede27b2
MG
853 * @param int|array $sections list of relative section numbers to create
854 * @return bool if there were any sections created
97928ddf 855 */
b46be6ad 856function course_create_sections_if_missing($courseorid, $sections) {
4ede27b2
MG
857 if (!is_array($sections)) {
858 $sections = array($sections);
859 }
b46be6ad 860 $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all());
89b909f6
MG
861 if ($newsections = array_diff($sections, $existing)) {
862 foreach ($newsections as $sectionnum) {
863 course_create_section($courseorid, $sectionnum, true);
4ede27b2 864 }
89b909f6 865 return true;
4ede27b2 866 }
89b909f6 867 return false;
97928ddf 868}
52f14061 869
ece966f0 870/**
722e6ba9 871 * Adds an existing module to the section
ece966f0 872 *
722e6ba9
MG
873 * Updates both tables {course_sections} and {course_modules}
874 *
5523c344 875 * Note: This function does not use modinfo PROVIDED that the section you are
876 * adding the module to already exists. If the section does not exist, it will
877 * build modinfo if necessary and create the section.
878 *
722e6ba9 879 * @param int|stdClass $courseorid course id or course object
46453565 880 * @param int $cmid id of the module already existing in course_modules table
722e6ba9
MG
881 * @param int $sectionnum relative number of the section (field course_sections.section)
882 * If section does not exist it will be created
883 * @param int|stdClass $beforemod id or object with field id corresponding to the module
884 * before which the module needs to be included. Null for inserting in the
885 * end of the section
886 * @return int The course_sections ID where the module is inserted
ece966f0 887 */
46453565 888function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) {
722e6ba9
MG
889 global $DB, $COURSE;
890 if (is_object($beforemod)) {
891 $beforemod = $beforemod->id;
892 }
46453565
PS
893 if (is_object($courseorid)) {
894 $courseid = $courseorid->id;
895 } else {
896 $courseid = $courseorid;
897 }
46453565 898 // Do not try to use modinfo here, there is no guarantee it is valid!
5523c344 899 $section = $DB->get_record('course_sections',
900 array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING);
901 if (!$section) {
902 // This function call requires modinfo.
903 course_create_sections_if_missing($courseorid, $sectionnum);
904 $section = $DB->get_record('course_sections',
905 array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST);
906 }
907
722e6ba9 908 $modarray = explode(",", trim($section->sequence));
281d730e 909 if (empty($section->sequence)) {
46453565 910 $newsequence = "$cmid";
722e6ba9 911 } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) {
46453565 912 $insertarray = array($cmid, $beforemod);
722e6ba9
MG
913 array_splice($modarray, $key[0], 1, $insertarray);
914 $newsequence = implode(",", $modarray);
915 } else {
46453565 916 $newsequence = "$section->sequence,$cmid";
722e6ba9
MG
917 }
918 $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id));
46453565 919 $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid));
b46be6ad
MG
920 if (is_object($courseorid)) {
921 rebuild_course_cache($courseorid->id, true);
922 } else {
923 rebuild_course_cache($courseorid, true);
924 }
722e6ba9 925 return $section->id; // Return course_sections ID that was used.
11b0c469 926}
927
9e533215
SL
928/**
929 * Change the group mode of a course module.
930 *
931 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
932 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
933 *
934 * @param int $id course module ID.
935 * @param int $groupmode the new groupmode value.
936 * @return bool True if the $groupmode was updated.
937 */
48e535bc 938function set_coursemodule_groupmode($id, $groupmode) {
cb6fec1f 939 global $DB;
38b19bbc
MG
940 $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST);
941 if ($cm->groupmode != $groupmode) {
942 $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id));
943 rebuild_course_cache($cm->course, true);
944 }
945 return ($cm->groupmode != $groupmode);
3d575e6f 946}
947
177d4abf 948function set_coursemodule_idnumber($id, $idnumber) {
cb6fec1f 949 global $DB;
38b19bbc
MG
950 $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST);
951 if ($cm->idnumber != $idnumber) {
952 $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
953 rebuild_course_cache($cm->course, true);
954 }
955 return ($cm->idnumber != $idnumber);
177d4abf 956}
4e781c7b 957
02f66c42 958/**
00a34234
FM
959 * Set the visibility of a module and inherent properties.
960 *
9e533215
SL
961 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
962 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
963 *
00a34234
FM
964 * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
965 * has been moved to {@link set_section_visible()} which was the only place from which
966 * the parameter was used.
967 *
968 * @param int $id of the module
969 * @param int $visible state of the module
8341055e 970 * @param int $visibleoncoursepage state of the module on the course page
00a34234
FM
971 * @return bool false when the module was not found, true otherwise
972 */
8341055e 973function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
f5e2602a 974 global $DB, $CFG;
0f078024 975 require_once($CFG->libdir.'/gradelib.php');
c5ceaeaf 976 require_once($CFG->dirroot.'/calendar/lib.php');
0f078024 977
cb6fec1f 978 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
978abb42 979 return false;
980 }
40fcc261
AD
981
982 // Create events and propagate visibility to associated grade items if the value has changed.
983 // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
8341055e 984 if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
40fcc261
AD
985 return true;
986 }
987
cb6fec1f 988 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
978abb42 989 return false;
990 }
8341055e
MG
991 if (($cm->visible != $visible) &&
992 ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename)))) {
dcd338ff 993 foreach($events as $event) {
48e535bc 994 if ($visible) {
379cd2d4
EL
995 $event = new calendar_event($event);
996 $event->toggle_visibility(true);
48e535bc 997 } else {
379cd2d4
EL
998 $event = new calendar_event($event);
999 $event->toggle_visibility(false);
48e535bc 1000 }
dcd338ff 1001 }
1002 }
f5e2602a 1003
00a34234
FM
1004 // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
1005 // affect visibleold to allow for an original visibility restore. See set_section_visible().
1006 $cminfo = new stdClass();
1007 $cminfo->id = $id;
1008 $cminfo->visible = $visible;
8341055e 1009 $cminfo->visibleoncoursepage = $visibleoncoursepage;
00a34234
FM
1010 $cminfo->visibleold = $visible;
1011 $DB->update_record('course_modules', $cminfo);
1012
1c73df9e
TH
1013 // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
1014 // Note that this must be done after updating the row in course_modules, in case
1015 // the modules grade_item_update function needs to access $cm->visible.
8341055e
MG
1016 if ($cm->visible != $visible &&
1017 plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
1c73df9e
TH
1018 component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
1019 $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
1020 component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
8341055e 1021 } else if ($cm->visible != $visible) {
1c73df9e
TH
1022 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
1023 if ($grade_items) {
1024 foreach ($grade_items as $grade_item) {
1025 $grade_item->set_hidden(!$visible);
1026 }
1027 }
1028 }
1029
38b19bbc
MG
1030 rebuild_course_cache($cm->course, true);
1031 return true;
1acfbce5 1032}
1033
f59f89b4
MG
1034/**
1035 * Changes the course module name
1036 *
1037 * @param int $id course module id
1038 * @param string $name new value for a name
1039 * @return bool whether a change was made
1040 */
1041function set_coursemodule_name($id, $name) {
1042 global $CFG, $DB;
1043 require_once($CFG->libdir . '/gradelib.php');
1044
1045 $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
1046
1047 $module = new \stdClass();
1048 $module->id = $cm->instance;
1049
1050 // Escape strings as they would be by mform.
1051 if (!empty($CFG->formatstringstriptags)) {
1052 $module->name = clean_param($name, PARAM_TEXT);
1053 } else {
1054 $module->name = clean_param($name, PARAM_CLEANHTML);
1055 }
1056 if ($module->name === $cm->name || strval($module->name) === '') {
1057 return false;
1058 }
1059 if (\core_text::strlen($module->name) > 255) {
1060 throw new \moodle_exception('maximumchars', 'moodle', '', 255);
1061 }
1062
1063 $module->timemodified = time();
1064 $DB->update_record($cm->modname, $module);
1065 $cm->name = $module->name;
1066 \core\event\course_module_updated::create_from_cm($cm)->trigger();
1067 rebuild_course_cache($cm->course, true);
1068
1069 // Attempt to update the grade item if relevant.
1070 $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
1071 $grademodule->cmidnumber = $cm->idnumber;
1072 $grademodule->modname = $cm->modname;
1073 grade_update_mod_grades($grademodule);
1074
9851111f 1075 // Update calendar events with the new name.
a85e191c 1076 course_module_update_calendar_events($cm->modname, $grademodule, $cm);
9851111f 1077
f59f89b4
MG
1078 return true;
1079}
1080
cb6fec1f 1081/**
7f7144fd 1082 * This function will handle the whole deletion process of a module. This includes calling
a347aee3
MN
1083 * the modules delete_instance function, deleting files, events, grades, conditional data,
1084 * the data in the course_module and course_sections table and adding a module deletion
1085 * event to the DB.
290130b3 1086 *
a347aee3 1087 * @param int $cmid the course module id
3869d774
JD
1088 * @param bool $async whether or not to try to delete the module using an adhoc task. Async also depends on a plugin hook.
1089 * @throws moodle_exception
5bcfd504 1090 * @since Moodle 2.5
290130b3 1091 */
3869d774
JD
1092function course_delete_module($cmid, $async = false) {
1093 // Check the 'course_module_background_deletion_recommended' hook first.
1094 // Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested.
1095 // Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it.
1096 // It's up to plugins to handle things like whether or not they are enabled.
1097 if ($async && $pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
1098 foreach ($pluginsfunction as $plugintype => $plugins) {
1099 foreach ($plugins as $pluginfunction) {
1100 if ($pluginfunction()) {
1101 return course_module_flag_for_async_deletion($cmid);
1102 }
1103 }
1104 }
1105 }
1106
63deb5c3 1107 global $CFG, $DB;
a347aee3 1108
f615fbab 1109 require_once($CFG->libdir.'/gradelib.php');
7f7144fd 1110 require_once($CFG->libdir.'/questionlib.php');
cae83708 1111 require_once($CFG->dirroot.'/blog/lib.php');
c5ceaeaf 1112 require_once($CFG->dirroot.'/calendar/lib.php');
f615fbab 1113
a347aee3
MN
1114 // Get the course module.
1115 if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
290130b3 1116 return true;
1117 }
a347aee3
MN
1118
1119 // Get the module context.
1120 $modcontext = context_module::instance($cm->id);
1121
1122 // Get the course module name.
1123 $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
1124
1125 // Get the file location of the delete_instance function for this module.
1126 $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
1127
1128 // Include the file required to call the delete_instance function for this module.
1129 if (file_exists($modlib)) {
1130 require_once($modlib);
1131 } else {
9a9cb741
MN
1132 throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
1133 "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
a347aee3
MN
1134 }
1135
9a9cb741 1136 $deleteinstancefunction = $modulename . '_delete_instance';
a347aee3 1137
9a9cb741
MN
1138 // Ensure the delete_instance function exists for this module.
1139 if (!function_exists($deleteinstancefunction)) {
1140 throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
1141 "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
1142 }
1143
ea88fd4e
MN
1144 // Allow plugins to use this course module before we completely delete it.
1145 if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) {
1146 foreach ($pluginsfunction as $plugintype => $plugins) {
1147 foreach ($plugins as $pluginfunction) {
1148 $pluginfunction($cm);
1149 }
1150 }
1151 }
1152
7f7144fd 1153 // Delete activity context questions and question categories.
1d674587
TH
1154 $showinfo = !defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0';
1155
1156 question_delete_activity($cm, $showinfo);
7f7144fd 1157
9a9cb741 1158 // Call the delete_instance function, if it returns false throw an exception.
a347aee3 1159 if (!$deleteinstancefunction($cm->instance)) {
9a9cb741
MN
1160 throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
1161 "Cannot delete the module $modulename (instance).");
a347aee3
MN
1162 }
1163
1164 // Remove all module files in case modules forget to do that.
1165 $fs = get_file_storage();
1166 $fs->delete_area_files($modcontext->id);
1167
1168 // Delete events from calendar.
1169 if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
70b62308 1170 $coursecontext = context_course::instance($cm->course);
dcd338ff 1171 foreach($events as $event) {
70b62308
MG
1172 $event->context = $coursecontext;
1173 $calendarevent = calendar_event::load($event);
c5ceaeaf 1174 $calendarevent->delete();
dcd338ff 1175 }
1176 }
a347aee3
MN
1177
1178 // Delete grade items, outcome items and grades attached to modules.
1179 if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
1180 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
f615fbab 1181 foreach ($grade_items as $grade_item) {
1182 $grade_item->delete('moddelete');
1183 }
f615fbab 1184 }
a347aee3 1185
c69a43f2
MG
1186 // Delete associated blogs and blog tag instances.
1187 blog_remove_associations_for_module($modcontext->id);
1188
46e12372
SM
1189 // Delete completion and availability data; it is better to do this even if the
1190 // features are not turned on, in case they were turned on previously (these will be
a347aee3 1191 // very quick on an empty table).
46e12372 1192 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
ede323e2 1193 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
e9f7c4a4 1194 'course' => $cm->course,
ede323e2 1195 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
06b3a6b2 1196
d9994071 1197 // Delete all tag instances associated with the instance of this module.
74fa9f76 1198 core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
dffcf46f 1199 core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
cc033d48 1200
56537316
FM
1201 // Notify the competency subsystem.
1202 \core_competency\api::hook_course_module_deleted($cm);
1203
a347aee3 1204 // Delete the context.
c592eea2 1205 context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
a347aee3
MN
1206
1207 // Delete the module from the course_modules table.
1208 $DB->delete_records('course_modules', array('id' => $cm->id));
1209
1210 // Delete module from that section.
1211 if (!delete_mod_from_section($cm->id, $cm->section)) {
9a9cb741
MN
1212 throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
1213 "Cannot delete the module $modulename (instance) from section.");
a347aee3
MN
1214 }
1215
63deb5c3
AA
1216 // Trigger event for course module delete action.
1217 $event = \core\event\course_module_deleted::create(array(
1218 'courseid' => $cm->course,
1219 'context' => $modcontext,
1220 'objectid' => $cm->id,
1221 'other' => array(
1222 'modulename' => $modulename,
1223 'instanceid' => $cm->instance,
1224 )
1225 ));
1226 $event->add_record_snapshot('course_modules', $cm);
1227 $event->trigger();
38b19bbc 1228 rebuild_course_cache($cm->course, true);
11b0c469 1229}
1230
3869d774
JD
1231/**
1232 * Schedule a course module for deletion in the background using an adhoc task.
1233 *
1234 * This method should not be called directly. Instead, please use course_delete_module($cmid, true), to denote async deletion.
1235 * The real deletion of the module is handled by the task, which calls 'course_delete_module($cmid)'.
1236 *
1237 * @param int $cmid the course module id.
1238 * @return bool whether the module was successfully scheduled for deletion.
1239 * @throws \moodle_exception
1240 */
1241function course_module_flag_for_async_deletion($cmid) {
44eb1490 1242 global $CFG, $DB, $USER;
3869d774
JD
1243 require_once($CFG->libdir.'/gradelib.php');
1244 require_once($CFG->libdir.'/questionlib.php');
1245 require_once($CFG->dirroot.'/blog/lib.php');
1246 require_once($CFG->dirroot.'/calendar/lib.php');
1247
1248 // Get the course module.
1249 if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
1250 return true;
1251 }
1252
1253 // We need to be reasonably certain the deletion is going to succeed before we background the process.
1254 // Make the necessary delete_instance checks, etc. before proceeding further. Throw exceptions if required.
1255
1256 // Get the course module name.
1257 $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
1258
1259 // Get the file location of the delete_instance function for this module.
1260 $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
1261
1262 // Include the file required to call the delete_instance function for this module.
1263 if (file_exists($modlib)) {
1264 require_once($modlib);
1265 } else {
1266 throw new \moodle_exception('cannotdeletemodulemissinglib', '', '', null,
1267 "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
1268 }
1269
1270 $deleteinstancefunction = $modulename . '_delete_instance';
1271
1272 // Ensure the delete_instance function exists for this module.
1273 if (!function_exists($deleteinstancefunction)) {
1274 throw new \moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
1275 "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
1276 }
1277
1278 // We are going to defer the deletion as we can't be sure how long the module's pre_delete code will run for.
1279 $cm->deletioninprogress = '1';
1280 $DB->update_record('course_modules', $cm);
1281
1282 // Create an adhoc task for the deletion of the course module. The task takes an array of course modules for removal.
1283 $removaltask = new \core_course\task\course_delete_modules();
44eb1490
DM
1284 $removaltask->set_custom_data(array(
1285 'cms' => array($cm),
1286 'userid' => $USER->id,
1287 'realuserid' => \core\session\manager::get_realuser()->id
1288 ));
3869d774
JD
1289
1290 // Queue the task for the next run.
1291 \core\task\manager::queue_adhoc_task($removaltask);
1292
1293 // Reset the course cache to hide the module.
1294 rebuild_course_cache($cm->course, true);
1295}
1296
1297/**
1298 * Checks whether the given course has any course modules scheduled for adhoc deletion.
1299 *
1300 * @param int $courseid the id of the course.
36272110 1301 * @param bool $onlygradable whether to check only gradable modules or all modules.
3869d774
JD
1302 * @return bool true if the course contains any modules pending deletion, false otherwise.
1303 */
36272110 1304function course_modules_pending_deletion(int $courseid, bool $onlygradable = false) : bool {
3869d774
JD
1305 if (empty($courseid)) {
1306 return false;
1307 }
36272110
SR
1308
1309 if ($onlygradable) {
1310 // Fetch modules with grade items.
1311 if (!$coursegradeitems = grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $courseid])) {
1312 // Return early when there is none.
1313 return false;
1314 }
1315 }
1316
3869d774
JD
1317 $modinfo = get_fast_modinfo($courseid);
1318 foreach ($modinfo->get_cms() as $module) {
1319 if ($module->deletioninprogress == '1') {
36272110
SR
1320 if ($onlygradable) {
1321 // Check if the module being deleted is in the list of course modules with grade items.
1322 foreach ($coursegradeitems as $coursegradeitem) {
1323 if ($coursegradeitem->itemmodule == $module->modname && $coursegradeitem->iteminstance == $module->instance) {
1324 // The module being deleted is within the gradable modules.
1325 return true;
1326 }
1327 }
1328 } else {
1329 return true;
1330 }
3869d774
JD
1331 }
1332 }
1333 return false;
1334}
1335
1336/**
1337 * Checks whether the course module, as defined by modulename and instanceid, is scheduled for deletion within the given course.
1338 *
1339 * @param int $courseid the course id.
1340 * @param string $modulename the module name. E.g. 'assign', 'book', etc.
1341 * @param int $instanceid the module instance id.
1342 * @return bool true if the course module is pending deletion, false otherwise.
1343 */
1344function course_module_instance_pending_deletion($courseid, $modulename, $instanceid) {
1345 if (empty($courseid) || empty($modulename) || empty($instanceid)) {
1346 return false;
1347 }
1348 $modinfo = get_fast_modinfo($courseid);
1349 $instances = $modinfo->get_instances_of($modulename);
1350 return isset($instances[$instanceid]) && $instances[$instanceid]->deletioninprogress;
1351}
1352
722e6ba9 1353function delete_mod_from_section($modid, $sectionid) {
cb6fec1f 1354 global $DB;
11b0c469 1355
722e6ba9 1356 if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) {
11b0c469 1357
e5dfd0f3 1358 $modarray = explode(",", $section->sequence);
11b0c469 1359
722e6ba9 1360 if ($key = array_keys ($modarray, $modid)) {
11b0c469 1361 array_splice($modarray, $key[0], 1);
1362 $newsequence = implode(",", $modarray);
38b19bbc
MG
1363 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
1364 rebuild_course_cache($section->course, true);
1365 return true;
11b0c469 1366 } else {
1367 return false;
1368 }
89adb174 1369
11b0c469 1370 }
7977cffd 1371 return false;
11b0c469 1372}
1373
e3658a6a
AG
1374/**
1375 * This function updates the calendar events from the information stored in the module table and the course
1376 * module table.
1377 *
1378 * @param string $modulename Module name
1379 * @param stdClass $instance Module object. Either the $instance or the $cm must be supplied.
1380 * @param stdClass $cm Course module object. Either the $instance or the $cm must be supplied.
1381 * @return bool Returns true if calendar events are updated.
1382 * @since Moodle 3.3.4
1383 */
1384function course_module_update_calendar_events($modulename, $instance = null, $cm = null) {
1385 global $DB;
1386
1387 if (isset($instance) || isset($cm)) {
1388
1389 if (!isset($instance)) {
1390 $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
1391 }
1392 if (!isset($cm)) {
1393 $cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course);
1394 }
a66de3ca
DW
1395 if (!empty($cm)) {
1396 course_module_calendar_event_update_process($instance, $cm);
1397 }
e3658a6a
AG
1398 return true;
1399 }
1400 return false;
1401}
1402
1403/**
1404 * Update all instances through out the site or in a course.
1405 *
1406 * @param string $modulename Module type to update.
1407 * @param integer $courseid Course id to update events. 0 for the whole site.
1408 * @return bool Returns True if the update was successful.
1409 * @since Moodle 3.3.4
1410 */
1411function course_module_bulk_update_calendar_events($modulename, $courseid = 0) {
1412 global $DB;
1413
1414 $instances = null;
1415 if ($courseid) {
1416 if (!$instances = $DB->get_records($modulename, array('course' => $courseid))) {
1417 return false;
1418 }
1419 } else {
1420 if (!$instances = $DB->get_records($modulename)) {
1421 return false;
1422 }
1423 }
1424
1425 foreach ($instances as $instance) {
a66de3ca
DW
1426 if ($cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course)) {
1427 course_module_calendar_event_update_process($instance, $cm);
1428 }
e3658a6a
AG
1429 }
1430 return true;
1431}
1432
1433/**
1434 * Calendar events for a module instance are updated.
1435 *
1436 * @param stdClass $instance Module instance object.
1437 * @param stdClass $cm Course Module object.
1438 * @since Moodle 3.3.4
1439 */
1440function course_module_calendar_event_update_process($instance, $cm) {
1441 // We need to call *_refresh_events() first because some modules delete 'old' events at the end of the code which
1442 // will remove the completion events.
1443 $refresheventsfunction = $cm->modname . '_refresh_events';
1444 if (function_exists($refresheventsfunction)) {
1445 call_user_func($refresheventsfunction, $cm->course, $instance, $cm);
1446 }
1447 $completionexpected = (!empty($cm->completionexpected)) ? $cm->completionexpected : null;
1448 \core_completion\api::update_completion_date_event($cm->id, $cm->modname, $instance, $completionexpected);
1449}
1450
3440ec12 1451/**
1452 * Moves a section within a course, from a position to another.
1453 * Be very careful: $section and $destination refer to section number,
1454 * not id!.
1455 *
1456 * @param object $course
1457 * @param int $section Section number (not id!!!)
1458 * @param int $destination
ca9cae84 1459 * @param bool $ignorenumsections
3440ec12 1460 * @return boolean Result
1461 */
ca9cae84 1462function move_section_to($course, $section, $destination, $ignorenumsections = false) {
3440ec12 1463/// Moves a whole course section up and down within the course
1464 global $USER, $DB;
1465
ca255392 1466 if (!$destination && $destination != 0) {
3440ec12 1467 return true;
1468 }
1469
850acb35
MG
1470 // compartibility with course formats using field 'numsections'
1471 $courseformatoptions = course_get_format($course)->get_format_options();
ca9cae84 1472 if ((!$ignorenumsections && array_key_exists('numsections', $courseformatoptions) &&
850acb35 1473 ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
3440ec12 1474 return false;
1475 }
1476
1477 // Get all sections for this course and re-order them (2 of them should now share the same section number)
1478 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
1479 'section ASC, id ASC', 'id, section')) {
1480 return false;
1481 }
1482
cf76b335 1483 $movedsections = reorder_sections($sections, $section, $destination);
3440ec12 1484
cf76b335 1485 // Update all sections. Do this in 2 steps to avoid breaking database
1486 // uniqueness constraint
1487 $transaction = $DB->start_delegated_transaction();
1488 foreach ($movedsections as $id => $position) {
1489 if ($sections[$id] !== $position) {
1490 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
1491 }
1492 }
e7e0f8d2
DP
1493 foreach ($movedsections as $id => $position) {
1494 if ($sections[$id] !== $position) {
1495 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
1496 }
3440ec12 1497 }
1498
15e2552f
RK
1499 // If we move the highlighted section itself, then just highlight the destination.
1500 // Adjust the higlighted section location if we move something over it either direction.
1501 if ($section == $course->marker) {
e7b6e6b9 1502 course_set_marker($course->id, $destination);
2365213f 1503 } elseif ($section > $course->marker && $course->marker >= $destination) {
e7b6e6b9 1504 course_set_marker($course->id, $course->marker+1);
2365213f 1505 } elseif ($section < $course->marker && $course->marker <= $destination) {
e7b6e6b9 1506 course_set_marker($course->id, $course->marker-1);
15e2552f
RK
1507 }
1508
cf76b335 1509 $transaction->allow_commit();
38b19bbc 1510 rebuild_course_cache($course->id, true);
3440ec12 1511 return true;
1512}
1513
ca9cae84
MG
1514/**
1515 * This method will delete a course section and may delete all modules inside it.
1516 *
1517 * No permissions are checked here, use {@link course_can_delete_section()} to
1518 * check if section can actually be deleted.
1519 *
1520 * @param int|stdClass $course
1521 * @param int|stdClass|section_info $section
1522 * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.
3869d774 1523 * @param bool $async whether or not to try to delete the section using an adhoc task. Async also depends on a plugin hook.
ca9cae84
MG
1524 * @return bool whether section was deleted
1525 */
3869d774 1526function course_delete_section($course, $section, $forcedeleteifnotempty = true, $async = false) {
fa29c0c3
RK
1527 global $DB;
1528
1529 // Prepare variables.
1530 $courseid = (is_object($course)) ? $course->id : (int)$course;
1531 $sectionnum = (is_object($section)) ? $section->section : (int)$section;
1532 $section = $DB->get_record('course_sections', array('course' => $courseid, 'section' => $sectionnum));
1533 if (!$section) {
1534 // No section exists, can't proceed.
1535 return false;
1536 }
3869d774
JD
1537
1538 // Check the 'course_module_background_deletion_recommended' hook first.
1539 // Only use asynchronous deletion if at least one plugin returns true and if async deletion has been requested.
1540 // Both are checked because plugins should not be allowed to dictate the deletion behaviour, only support/decline it.
1541 // It's up to plugins to handle things like whether or not they are enabled.
1542 if ($async && $pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
1543 foreach ($pluginsfunction as $plugintype => $plugins) {
1544 foreach ($plugins as $pluginfunction) {
1545 if ($pluginfunction()) {
1546 return course_delete_section_async($section, $forcedeleteifnotempty);
1547 }
1548 }
1549 }
1550 }
1551
fa29c0c3
RK
1552 $format = course_get_format($course);
1553 $sectionname = $format->get_section_name($section);
1554
1555 // Delete section.
1556 $result = $format->delete_section($section, $forcedeleteifnotempty);
1557
1558 // Trigger an event for course section deletion.
1559 if ($result) {
1560 $context = context_course::instance($courseid);
1561 $event = \core\event\course_section_deleted::create(
3869d774
JD
1562 array(
1563 'objectid' => $section->id,
1564 'courseid' => $courseid,
1565 'context' => $context,
1566 'other' => array(
1567 'sectionnum' => $section->section,
1568 'sectionname' => $sectionname,
fa29c0c3 1569 )
3869d774
JD
1570 )
1571 );
fa29c0c3
RK
1572 $event->add_record_snapshot('course_sections', $section);
1573 $event->trigger();
1574 }
1575 return $result;
ca9cae84
MG
1576}
1577
3869d774
JD
1578/**
1579 * Course section deletion, using an adhoc task for deletion of the modules it contains.
1580 * 1. Schedule all modules within the section for adhoc removal.
1581 * 2. Move all modules to course section 0.
1582 * 3. Delete the resulting empty section.
1583 *
1584 * @param \stdClass $section the section to schedule for deletion.
1585 * @param bool $forcedeleteifnotempty whether to force section deletion if it contains modules.
1586 * @return bool true if the section was scheduled for deletion, false otherwise.
1587 */
1588function course_delete_section_async($section, $forcedeleteifnotempty = true) {
44eb1490 1589 global $DB, $USER;
3869d774
JD
1590
1591 // Objects only, and only valid ones.
1592 if (!is_object($section) || empty($section->id)) {
1593 return false;
1594 }
1595
1596 // Does the object currently exist in the DB for removal (check for stale objects).
1597 $section = $DB->get_record('course_sections', array('id' => $section->id));
1598 if (!$section || !$section->section) {
1599 // No section exists, or the section is 0. Can't proceed.
1600 return false;
1601 }
1602
1603 // Check whether the section can be removed.
1604 if (!$forcedeleteifnotempty && (!empty($section->sequence) || !empty($section->summary))) {
1605 return false;
1606 }
1607
1608 $format = course_get_format($section->course);
1609 $sectionname = $format->get_section_name($section);
1610
1611 // Flag those modules having no existing deletion flag. Some modules may have been scheduled for deletion manually, and we don't
1612 // want to create additional adhoc deletion tasks for these. Moving them to section 0 will suffice.
1613 $affectedmods = $DB->get_records_select('course_modules', 'course = ? AND section = ? AND deletioninprogress <> ?',
1614 [$section->course, $section->id, 1], '', 'id');
1615 $DB->set_field('course_modules', 'deletioninprogress', '1', ['course' => $section->course, 'section' => $section->id]);
1616
1617 // Move all modules to section 0.
1618 $modules = $DB->get_records('course_modules', ['section' => $section->id], '');
1619 $sectionzero = $DB->get_record('course_sections', ['course' => $section->course, 'section' => '0']);
1620 foreach ($modules as $mod) {
1621 moveto_module($mod, $sectionzero);
1622 }
1623
1624 // Create and queue an adhoc task for the deletion of the modules.
1625 $removaltask = new \core_course\task\course_delete_modules();
1626 $data = array(
44eb1490
DM
1627 'cms' => $affectedmods,
1628 'userid' => $USER->id,
1629 'realuserid' => \core\session\manager::get_realuser()->id
3869d774
JD
1630 );
1631 $removaltask->set_custom_data($data);
1632 \core\task\manager::queue_adhoc_task($removaltask);
1633
1634 // Delete the now empty section, passing in only the section number, which forces the function to fetch a new object.
1635 // The refresh is needed because the section->sequence is now stale.
1636 $result = $format->delete_section($section->section, $forcedeleteifnotempty);
1637
1638 // Trigger an event for course section deletion.
1639 if ($result) {
1640 $context = \context_course::instance($section->course);
1641 $event = \core\event\course_section_deleted::create(
1642 array(
1643 'objectid' => $section->id,
1644 'courseid' => $section->course,
1645 'context' => $context,
1646 'other' => array(
1647 'sectionnum' => $section->section,
1648 'sectionname' => $sectionname,
1649 )
1650 )
1651 );
1652 $event->add_record_snapshot('course_sections', $section);
1653 $event->trigger();
1654 }
1655 rebuild_course_cache($section->course, true);
1656
1657 return $result;
1658}
1659
f26481c2
MG
1660/**
1661 * Updates the course section
1662 *
1663 * This function does not check permissions or clean values - this has to be done prior to calling it.
1664 *
1665 * @param int|stdClass $course
1666 * @param stdClass $section record from course_sections table - it will be updated with the new values
1667 * @param array|stdClass $data
1668 */
1669function course_update_section($course, $section, $data) {
1670 global $DB;
1671
1672 $courseid = (is_object($course)) ? $course->id : (int)$course;
1673
1674 // Some fields can not be updated using this method.
1675 $data = array_diff_key((array)$data, array('id', 'course', 'section', 'sequence'));
1676 $changevisibility = (array_key_exists('visible', $data) && (bool)$data['visible'] != (bool)$section->visible);
1677 if (array_key_exists('name', $data) && \core_text::strlen($data['name']) > 255) {
1678 throw new moodle_exception('maximumchars', 'moodle', '', 255);
1679 }
1680
1681 // Update record in the DB and course format options.
1682 $data['id'] = $section->id;
4ddf7c60 1683 $data['timemodified'] = time();
f26481c2
MG
1684 $DB->update_record('course_sections', $data);
1685 rebuild_course_cache($courseid, true);
1686 course_get_format($courseid)->update_section_format_options($data);
1687
1688 // Update fields of the $section object.
1689 foreach ($data as $key => $value) {
1690 if (property_exists($section, $key)) {
1691 $section->$key = $value;
1692 }
1693 }
1694
1695 // Trigger an event for course section update.
1696 $event = \core\event\course_section_updated::create(
1697 array(
1698 'objectid' => $section->id,
1699 'courseid' => $courseid,
1700 'context' => context_course::instance($courseid),
1701 'other' => array('sectionnum' => $section->section)
1702 )
1703 );
1704 $event->trigger();
1705
1706 // If section visibility was changed, hide the modules in this section too.
1707 if ($changevisibility && !empty($section->sequence)) {
1708 $modules = explode(',', $section->sequence);
1709 foreach ($modules as $moduleid) {
1710 if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
1711 if ($data['visible']) {
1712 // As we unhide the section, we use the previously saved visibility stored in visibleold.
8341055e 1713 set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage);
f26481c2
MG
1714 } else {
1715 // We hide the section, so we hide the module but we store the original state in visibleold.
8341055e 1716 set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage);
f26481c2
MG
1717 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
1718 }
1719 \core\event\course_module_updated::create_from_cm($cm)->trigger();
1720 }
1721 }
1722 }
1723}
1724
ca9cae84
MG
1725/**
1726 * Checks if the current user can delete a section (if course format allows it and user has proper permissions).
1727 *
1728 * @param int|stdClass $course
1729 * @param int|stdClass|section_info $section
1730 * @return bool
1731 */
1732function course_can_delete_section($course, $section) {
1733 if (is_object($section)) {
1734 $section = $section->section;
1735 }
1736 if (!$section) {
1737 // Not possible to delete 0-section.
1738 return false;
1739 }
1740 // Course format should allow to delete sections.
1741 if (!course_get_format($course)->can_delete_section($section)) {
1742 return false;
1743 }
1744 // Make sure user has capability to update course and move sections.
1745 $context = context_course::instance(is_object($course) ? $course->id : $course);
1746 if (!has_all_capabilities(array('moodle/course:movesections', 'moodle/course:update'), $context)) {
1747 return false;
1748 }
1749 // Make sure user has capability to delete each activity in this section.
1750 $modinfo = get_fast_modinfo($course);
1751 if (!empty($modinfo->sections[$section])) {
1752 foreach ($modinfo->sections[$section] as $cmid) {
1753 if (!has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
1754 return false;
1755 }
1756 }
1757 }
1758 return true;
1759}
1760
3440ec12 1761/**
1762 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
1763 * an original position number and a target position number, rebuilds the array so that the
1764 * move is made without any duplication of section positions.
1765 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
1766 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
1767 *
1768 * @param array $sections
1769 * @param int $origin_position
1770 * @param int $target_position
1771 * @return array
1772 */
1773function reorder_sections($sections, $origin_position, $target_position) {
1774 if (!is_array($sections)) {
1775 return false;
1776 }
1777
1778 // We can't move section position 0
1779 if ($origin_position < 1) {
1780 echo "We can't move section position 0";
1781 return false;
1782 }
1783
1784 // Locate origin section in sections array
1785 if (!$origin_key = array_search($origin_position, $sections)) {
1786 echo "searched position not in sections array";
1787 return false; // searched position not in sections array
1788 }
1789
1790 // Extract origin section
1791 $origin_section = $sections[$origin_key];
1792 unset($sections[$origin_key]);
1793
1794 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
1795 $found = false;
1796 $append_array = array();
1797 foreach ($sections as $id => $position) {
1798 if ($found) {
1799 $append_array[$id] = $position;
1800 unset($sections[$id]);
1801 }
1802 if ($position == $target_position) {
eb01aa2c
RT
1803 if ($target_position < $origin_position) {
1804 $append_array[$id] = $position;
1805 unset($sections[$id]);
1806 }
3440ec12 1807 $found = true;
1808 }
1809 }
1810
1811 // Append moved section
1812 $sections[$origin_key] = $origin_section;
1813
1814 // Append rest of array (if applicable)
1815 if (!empty($append_array)) {
1816 foreach ($append_array as $id => $position) {
1817 $sections[$id] = $position;
1818 }
1819 }
1820
1821 // Renumber positions
1822 $position = 0;
1823 foreach ($sections as $id => $p) {
1824 $sections[$id] = $position;
1825 $position++;
1826 }
1827
1828 return $sections;
1829
1830}
1831
cb6fec1f 1832/**
1833 * Move the module object $mod to the specified $section
1834 * If $beforemod exists then that is the module
1835 * before which $modid should be inserted
d55f05ef
MG
1836 *
1837 * @param stdClass|cm_info $mod
1838 * @param stdClass|section_info $section
1839 * @param int|stdClass $beforemod id or object with field id corresponding to the module
1840 * before which the module needs to be included. Null for inserting in the
1841 * end of the section
1842 * @return int new value for module visibility (0 or 1)
cb6fec1f 1843 */
7977cffd 1844function moveto_module($mod, $section, $beforemod=NULL) {
a83dd077 1845 global $OUTPUT, $DB;
7977cffd 1846
d55f05ef
MG
1847 // Current module visibility state - return value of this function.
1848 $modvisible = $mod->visible;
1849
1850 // Remove original module from original section.
7977cffd 1851 if (! delete_mod_from_section($mod->id, $mod->section)) {
e6db3026 1852 echo $OUTPUT->notification("Could not delete module from existing section");
7977cffd 1853 }
1854
d55f05ef 1855 // If moving to a hidden section then hide module.
bb1592c8
CF
1856 if ($mod->section != $section->id) {
1857 if (!$section->visible && $mod->visible) {
d55f05ef
MG
1858 // Module was visible but must become hidden after moving to hidden section.
1859 $modvisible = 0;
bb1592c8
CF
1860 set_coursemodule_visible($mod->id, 0);
1861 // Set visibleold to 1 so module will be visible when section is made visible.
1862 $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
1863 }
1864 if ($section->visible && !$mod->visible) {
d55f05ef 1865 // Hidden module was moved to the visible section, restore the module visibility from visibleold.
bb1592c8 1866 set_coursemodule_visible($mod->id, $mod->visibleold);
d55f05ef 1867 $modvisible = $mod->visibleold;
bb1592c8 1868 }
7977cffd 1869 }
1870
d55f05ef 1871 // Add the module into the new section.
722e6ba9 1872 course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod);
d55f05ef 1873 return $modvisible;
7977cffd 1874}
1875
f558b291
MG
1876/**
1877 * Returns the list of all editing actions that current user can perform on the module
1878 *
1879 * @param cm_info $mod The module to produce editing buttons for
1880 * @param int $indent The current indenting (default -1 means no move left-right actions)
1881 * @param int $sr The section to link back to (used for creating the links)
1882 * @return array array of action_link or pix_icon objects
1883 */
1884function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
8341055e 1885 global $COURSE, $SITE, $CFG;
94361e02 1886
3d575e6f 1887 static $str;
1888
9a5e297b
AA
1889 $coursecontext = context_course::instance($mod->course);
1890 $modcontext = context_module::instance($mod->id);
8341055e 1891 $courseformat = course_get_format($mod->get_course());
7749e187 1892
af189935
PS
1893 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
1894 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
1895
cf69a00a 1896 // No permission to edit anything.
af189935 1897 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
f558b291 1898 return array();
217a8ee9 1899 }
1900
af189935
PS
1901 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
1902
3d575e6f 1903 if (!isset($str)) {
f558b291 1904 $str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
125c4c4c 1905 'editsettings', 'duplicate', 'modhide', 'makeavailable', 'makeunavailable', 'modshow'), 'moodle');
f558b291 1906 $str->assign = get_string('assignroles', 'role');
6dc5908e
ARN
1907 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
1908 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
1909 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
1acfbce5 1910 }
94361e02 1911
af189935 1912 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
24e1eae4 1913
f558b291
MG
1914 if ($sr !== null) {
1915 $baseurl->param('sr', $sr);
7749e187
SH
1916 }
1917 $actions = array();
1918
a83d83e4
AN
1919 // Update.
1920 if ($hasmanageactivities) {
1921 $actions['update'] = new action_menu_link_secondary(
7a9a07d2 1922 new moodle_url($baseurl, array('update' => $mod->id)),
cce54c47 1923 new pix_icon('t/edit', '', 'moodle', array('class' => 'iconsmall')),
a83d83e4
AN
1924 $str->editsettings,
1925 array('class' => 'editing_update', 'data-action' => 'update')
7a9a07d2
ARN
1926 );
1927 }
1928
cf69a00a 1929 // Indent.
639d9904 1930 if ($hasmanageactivities && $indent >= 0) {
a83d83e4
AN
1931 $indentlimits = new stdClass();
1932 $indentlimits->min = 0;
1933 $indentlimits->max = 16;
7749e187
SH
1934 if (right_to_left()) { // Exchange arrows on RTL
1935 $rightarrow = 't/left';
1936 $leftarrow = 't/right';
1937 } else {
1938 $rightarrow = 't/right';
1939 $leftarrow = 't/left';
1940 }
1941
a83d83e4
AN
1942 if ($indent >= $indentlimits->max) {
1943 $enabledclass = 'hidden';
1944 } else {
1945 $enabledclass = '';
1946 }
1947 $actions['moveright'] = new action_menu_link_secondary(
1948 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
cce54c47 1949 new pix_icon($rightarrow, '', 'moodle', array('class' => 'iconsmall')),
a83d83e4 1950 $str->moveright,
8341055e
MG
1951 array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright',
1952 'data-keepopen' => true, 'data-sectionreturn' => $sr)
a83d83e4
AN
1953 );
1954
1955 if ($indent <= $indentlimits->min) {
1956 $enabledclass = 'hidden';
1957 } else {
1958 $enabledclass = '';
7749e187 1959 }
3665af78 1960 $actions['moveleft'] = new action_menu_link_secondary(
e282c679 1961 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
cce54c47 1962 new pix_icon($leftarrow, '', 'moodle', array('class' => 'iconsmall')),
e282c679 1963 $str->moveleft,
8341055e
MG
1964 array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft',
1965 'data-keepopen' => true, 'data-sectionreturn' => $sr)
e282c679 1966 );
a83d83e4
AN
1967
1968 }
1969
8341055e 1970 // Hide/Show/Available/Unavailable.
a83d83e4 1971 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
8341055e
MG
1972 $allowstealth = !empty($CFG->allowstealth) && $courseformat->allow_stealth_module_visibility($mod, $mod->get_section_info());
1973
1974 $sectionvisible = $mod->get_section_info()->visible;
1975 // The module on the course page may be in one of the following states:
1976 // - Available and displayed on the course page ($displayedoncoursepage);
1977 // - Not available and not displayed on the course page ($unavailable);
1978 // - Available but not displayed on the course page ($stealth) - this can also be a visible activity in a hidden section.
1979 $displayedoncoursepage = $mod->visible && $mod->visibleoncoursepage && $sectionvisible;
1980 $unavailable = !$mod->visible;
1981 $stealth = $mod->visible && (!$mod->visibleoncoursepage || !$sectionvisible);
1982 if ($displayedoncoursepage) {
a83d83e4
AN
1983 $actions['hide'] = new action_menu_link_secondary(
1984 new moodle_url($baseurl, array('hide' => $mod->id)),
cce54c47 1985 new pix_icon('t/hide', '', 'moodle', array('class' => 'iconsmall')),
125c4c4c 1986 $str->modhide,
a83d83e4
AN
1987 array('class' => 'editing_hide', 'data-action' => 'hide')
1988 );
8341055e
MG
1989 } else if (!$displayedoncoursepage && $sectionvisible) {
1990 // Offer to "show" only if the section is visible.
a83d83e4
AN
1991 $actions['show'] = new action_menu_link_secondary(
1992 new moodle_url($baseurl, array('show' => $mod->id)),
cce54c47 1993 new pix_icon('t/show', '', 'moodle', array('class' => 'iconsmall')),
125c4c4c 1994 $str->modshow,
a83d83e4
AN
1995 array('class' => 'editing_show', 'data-action' => 'show')
1996 );
7749e187 1997 }
8341055e
MG
1998
1999 if ($stealth) {
2000 // When making the "stealth" module unavailable we perform the same action as hiding the visible module.
2001 $actions['hide'] = new action_menu_link_secondary(
2002 new moodle_url($baseurl, array('hide' => $mod->id)),
cce54c47 2003 new pix_icon('t/unblock', '', 'moodle', array('class' => 'iconsmall')),
8341055e
MG
2004 $str->makeunavailable,
2005 array('class' => 'editing_makeunavailable', 'data-action' => 'hide', 'data-sectionreturn' => $sr)
2006 );
2007 } else if ($unavailable && (!$sectionvisible || $allowstealth) && $mod->has_view()) {
2008 // Allow to make visually hidden module available in gradebook and other reports by making it a "stealth" module.
2009 // When the section is hidden it is an equivalent of "showing" the module.
2010 // Activities without the link (i.e. labels) can not be made available but hidden on course page.
2011 $action = $sectionvisible ? 'stealth' : 'show';
2012 $actions[$action] = new action_menu_link_secondary(
2013 new moodle_url($baseurl, array($action => $mod->id)),
cce54c47 2014 new pix_icon('t/block', '', 'moodle', array('class' => 'iconsmall')),
8341055e
MG
2015 $str->makeavailable,
2016 array('class' => 'editing_makeavailable', 'data-action' => $action, 'data-sectionreturn' => $sr)
2017 );
2018 }
dc0dc7d5 2019 }
7749e187 2020
8a1a951f 2021 // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
03c39daa 2022 if (has_all_capabilities($dupecaps, $coursecontext) &&
8341055e
MG
2023 plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2) &&
2024 course_allowed_module($mod->get_course(), $mod->modname)) {
3665af78 2025 $actions['duplicate'] = new action_menu_link_secondary(
8645a28f 2026 new moodle_url($baseurl, array('duplicate' => $mod->id)),
cce54c47 2027 new pix_icon('t/copy', '', 'moodle', array('class' => 'iconsmall')),
e282c679 2028 $str->duplicate,
8341055e 2029 array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sectionreturn' => $sr)
8645a28f
EL
2030 );
2031 }
7749e187 2032
cf69a00a 2033 // Groupmode.
9ac099a1 2034 if ($hasmanageactivities && !$mod->coursegroupmodeforce) {
ea191cf1 2035 if (plugin_supports('mod', $mod->modname, FEATURE_GROUPS, false)) {
9ac099a1
AN
2036 if ($mod->effectivegroupmode == SEPARATEGROUPS) {
2037 $nextgroupmode = VISIBLEGROUPS;
2038 $grouptitle = $str->groupsseparate;
2039 $actionname = 'groupsseparate';
8341055e 2040 $nextactionname = 'groupsvisible';
6272fce6 2041 $groupimage = 'i/groups';
9ac099a1
AN
2042 } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
2043 $nextgroupmode = NOGROUPS;
2044 $grouptitle = $str->groupsvisible;
2045 $actionname = 'groupsvisible';
8341055e 2046 $nextactionname = 'groupsnone';
6272fce6 2047 $groupimage = 'i/groupv';
9ac099a1
AN
2048 } else {
2049 $nextgroupmode = SEPARATEGROUPS;
2050 $grouptitle = $str->groupsnone;
2051 $actionname = 'groupsnone';
8341055e 2052 $nextactionname = 'groupsseparate';
6272fce6 2053 $groupimage = 'i/groupn';
9ac099a1
AN
2054 }
2055
2056 $actions[$actionname] = new action_menu_link_primary(
2057 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
cce54c47 2058 new pix_icon($groupimage, '', 'moodle', array('class' => 'iconsmall')),
9ac099a1 2059 $grouptitle,
8341055e
MG
2060 array('class' => 'editing_'. $actionname, 'data-action' => $nextactionname,
2061 'aria-live' => 'assertive', 'data-sectionreturn' => $sr)
9ac099a1 2062 );
32d03b7b 2063 } else {
9ac099a1 2064 $actions['nogroupsupport'] = new action_menu_filler();
32d03b7b 2065 }
7977cffd 2066 }
2067
cf69a00a 2068 // Assign.
af189935 2069 if (has_capability('moodle/role:assign', $modcontext)){
3665af78 2070 $actions['assign'] = new action_menu_link_secondary(
f558b291 2071 new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
cce54c47 2072 new pix_icon('t/assignroles', '', 'moodle', array('class' => 'iconsmall')),
e282c679 2073 $str->assign,
8341055e 2074 array('class' => 'editing_assign', 'data-action' => 'assignroles', 'data-sectionreturn' => $sr)
7749e187
SH
2075 );
2076 }
2077
a83d83e4
AN
2078 // Delete.
2079 if ($hasmanageactivities) {
2080 $actions['delete'] = new action_menu_link_secondary(
2081 new moodle_url($baseurl, array('delete' => $mod->id)),
cce54c47 2082 new pix_icon('t/delete', '', 'moodle', array('class' => 'iconsmall')),
a83d83e4 2083 $str->delete,
8341055e 2084 array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr)
a83d83e4
AN
2085 );
2086 }
2087
f558b291 2088 return $actions;
90845098 2089}
2090
4657ba81
AN
2091/**
2092 * Returns the move action.
2093 *
2094 * @param cm_info $mod The module to produce a move button for
2095 * @param int $sr The section to link back to (used for creating the links)
2096 * @return The markup for the move action, or an empty string if not available.
2097 */
2098function course_get_cm_move(cm_info $mod, $sr = null) {
2099 global $OUTPUT;
2100
2101 static $str;
2102 static $baseurl;
2103
2104 $modcontext = context_module::instance($mod->id);
2105 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2106
2107 if (!isset($str)) {
2108 $str = get_strings(array('move'));
2109 }
2110
2111 if (!isset($baseurl)) {
2112 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2113
2114 if ($sr !== null) {
2115 $baseurl->param('sr', $sr);
2116 }
2117 }
2118
2119 if ($hasmanageactivities) {
2120 $pixicon = 'i/dragdrop';
2121
69353dce 2122 if (!course_ajax_enabled($mod->get_course())) {
4657ba81
AN
2123 // Override for course frontpage until we get drag/drop working there.
2124 $pixicon = 't/move';
2125 }
2126
2127 return html_writer::link(
2128 new moodle_url($baseurl, array('copy' => $mod->id)),
2129 $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
8341055e 2130 array('class' => 'editing_move', 'data-action' => 'move', 'data-sectionreturn' => $sr)
4657ba81
AN
2131 );
2132 }
2133 return '';
2134}
2135
b61efafb 2136/**
264867fd 2137 * given a course object with shortname & fullname, this function will
b61efafb 2138 * truncate the the number of chars allowed and add ... if it was too long
2139 */
2140function course_format_name ($course,$max=100) {
264867fd 2141
9a5e297b 2142 $context = context_course::instance($course->id);
8ebbb06a 2143 $shortname = format_string($course->shortname, true, array('context' => $context));
9a5e297b 2144 $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
d1e36240 2145 $str = $shortname.': '. $fullname;
2f1e464a 2146 if (core_text::strlen($str) <= $max) {
b61efafb 2147 return $str;
2148 }
2149 else {
2f1e464a 2150 return core_text::substr($str,0,$max-3).'...';
b61efafb 2151 }
2152}
2153
0705ff84 2154/**
9665ecd2
TH
2155 * Is the user allowed to add this type of module to this course?
2156 * @param object $course the course settings. Only $course->id is used.
2157 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
2158 * @return bool whether the current user is allowed to add this type of module to this course.
0705ff84 2159 */
9665ecd2 2160function course_allowed_module($course, $modname) {
9665ecd2
TH
2161 if (is_numeric($modname)) {
2162 throw new coding_exception('Function course_allowed_module no longer
2163 supports numeric module ids. Please update your code to pass the module name.');
0705ff84 2164 }
264867fd 2165
9665ecd2
TH
2166 $capability = 'mod/' . $modname . ':addinstance';
2167 if (!get_capability_info($capability)) {
2168 // Debug warning that the capability does not exist, but no more than once per page.
2169 static $warned = array();
2170 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2171 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
2172 debugging('The module ' . $modname . ' does not define the standard capability ' .
2173 $capability , DEBUG_DEVELOPER);
2174 $warned[$modname] = 1;
2175 }
ddb0a19f 2176
9665ecd2
TH
2177 // If the capability does not exist, the module can always be added.
2178 return true;
0705ff84 2179 }
238c0dd9 2180
9665ecd2
TH
2181 $coursecontext = context_course::instance($course->id);
2182 return has_capability($capability, $coursecontext);
0705ff84 2183}
2184
cb6fec1f 2185/**
2186 * Efficiently moves many courses around while maintaining
2187 * sortorder in order.
2188 *
e92c39ca
PS
2189 * @param array $courseids is an array of course ids
2190 * @param int $categoryid
df997f84 2191 * @return bool success
cb6fec1f 2192 */
2193function move_courses($courseids, $categoryid) {
ed54cb41 2194 global $DB;
861efb19 2195
df997f84 2196 if (empty($courseids)) {
ed54cb41 2197 // Nothing to do.
5dc361e1 2198 return false;
df997f84 2199 }
264867fd 2200
ed54cb41 2201 if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
df997f84
PS
2202 return false;
2203 }
19f601d1 2204
df997f84 2205 $courseids = array_reverse($courseids);
9a5e297b 2206 $newparent = context_coursecat::instance($category->id);
df997f84 2207 $i = 1;
0cbe8111 2208
aec8fe2f 2209 list($where, $params) = $DB->get_in_or_equal($courseids);
3a11e2d2 2210 $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname');
30fe1181
MG
2211 foreach ($dbcourses as $dbcourse) {
2212 $course = new stdClass();
2213 $course->id = $dbcourse->id;
bc6efcb4 2214 $course->timemodified = time();
30fe1181
MG
2215 $course->category = $category->id;
2216 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
2217 if ($category->visible == 0) {
2218 // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
2219 // to previous state if somebody unhides the category.
2220 $course->visible = 0;
2221 }
ed54cb41 2222
30fe1181 2223 $DB->update_record('course', $course);
ed54cb41 2224
3a11e2d2 2225 // Update context, so it can be passed to event.
30fe1181 2226 $context = context_course::instance($course->id);
3a11e2d2 2227 $context->update_moved($newparent);
30fe1181
MG
2228
2229 // Trigger a course updated event.
2230 $event = \core\event\course_updated::create(array(
2231 'objectid' => $course->id,
3a11e2d2 2232 'context' => context_course::instance($course->id),
30fe1181 2233 'other' => array('shortname' => $dbcourse->shortname,
288d6ebb
MJ
2234 'fullname' => $dbcourse->fullname,
2235 'updatedfields' => array('category' => $category->id))
30fe1181 2236 ));
30fe1181
MG
2237 $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
2238 $event->trigger();
0cbe8111 2239 }
df997f84 2240 fix_course_sortorder();
eabbfa82 2241 cache_helper::purge_by_event('changesincourse');
df997f84 2242
861efb19 2243 return true;
2244}
2245
ae628043 2246/**
ee7084e9 2247 * Returns the display name of the given section that the course prefers
7487c856 2248 *
ee7084e9
MG
2249 * Implementation of this function is provided by course format
2250 * @see format_base::get_section_name()
7487c856 2251 *
ee7084e9
MG
2252 * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
2253 * @param int|stdClass $section Section object from database or just field course_sections.section
2254 * @return string Display name that the course format prefers, e.g. "Week 2"
ae628043 2255 */
ee7084e9
MG
2256function get_section_name($courseorid, $section) {
2257 return course_get_format($courseorid)->get_section_name($section);
7487c856
SH
2258}
2259
2260/**
ee7084e9 2261 * Tells if current course format uses sections
1b048629 2262 *
7487c856 2263 * @param string $format Course format ID e.g. 'weeks' $course->format
ee7084e9 2264 * @return bool
7487c856 2265 */
7487c856 2266function course_format_uses_sections($format) {
ee7084e9
MG
2267 $course = new stdClass();
2268 $course->format = $format;
2269 return course_get_format($course)->uses_sections();
ae628043 2270}
2271
c0b5d925
DM
2272/**
2273 * Returns the information about the ajax support in the given source format
2274 *
2275 * The returned object's property (boolean)capable indicates that
2276 * the course format supports Moodle course ajax features.
c0b5d925
DM
2277 *
2278 * @param string $format
2279 * @return stdClass
2280 */
2281function course_format_ajax_support($format) {
ee7084e9
MG
2282 $course = new stdClass();
2283 $course->format = $format;
2284 return course_get_format($course)->supports_ajax();
c0b5d925
DM
2285}
2286
2585a68d 2287/**
2288 * Can the current user delete this course?
8b449a39 2289 * Course creators have exception,
2290 * 1 day after the creation they can sill delete the course.
2585a68d 2291 * @param int $courseid
2292 * @return boolean
2585a68d 2293 */
2294function can_delete_course($courseid) {
536c0865 2295 global $USER;
2585a68d 2296
9a5e297b 2297 $context = context_course::instance($courseid);
2585a68d 2298
8b449a39 2299 if (has_capability('moodle/course:delete', $context)) {
2300 return true;
2301 }
2302
2303 // hack: now try to find out if creator created this course recently (1 day)
2304 if (!has_capability('moodle/course:create', $context)) {
2305 return false;
2306 }
2307
2308 $since = time() - 60*60*24;
536c0865
AA
2309 $course = get_course($courseid);
2310
2311 if ($course->timecreated < $since) {
2312 return false; // Return if the course was not created in last 24 hours.
2313 }
2314
2315 $logmanger = get_log_manager();
59aebbed 2316 $readers = $logmanger->get_readers('\core\log\sql_reader');
536c0865
AA
2317 $reader = reset($readers);
2318
2319 if (empty($reader)) {
2320 return false; // No log reader found.
2321 }
8b449a39 2322
536c0865
AA
2323 // A proper reader.
2324 $select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since";
2325 $params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created');
8b449a39 2326
536c0865 2327 return (bool)$reader->get_events_select_count($select, $params);
2585a68d 2328}
2329
bef12c99 2330/**
2331 * Save the Your name for 'Some role' strings.
2332 *
2333 * @param integer $courseid the id of this course.
2334 * @param array $data the data that came from the course settings form.
2335 */
2336function save_local_role_names($courseid, $data) {
2337 global $DB;
9a5e297b 2338 $context = context_course::instance($courseid);
bef12c99 2339
2340 foreach ($data as $fieldname => $value) {
df997f84 2341 if (strpos($fieldname, 'role_') !== 0) {
bef12c99 2342 continue;
2343 }
2344 list($ignored, $roleid) = explode('_', $fieldname);
2345
2346 // make up our mind whether we want to delete, update or insert
2347 if (!$value) {
2348 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
2349
2350 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
2351 $rolename->name = $value;
2352 $DB->update_record('role_names', $rolename);
2353
2354 } else {
2355 $rolename = new stdClass;
2356 $rolename->contextid = $context->id;
2357 $rolename->roleid = $roleid;
2358 $rolename->name = $value;
2359 $DB->insert_record('role_names', $rolename);
2360 }
29d4cc65 2361 // This will ensure the course contacts cache is purged..
442f12f8 2362 core_course_category::role_assignment_changed($roleid, $context);
bef12c99 2363 }
2364}
2585a68d 2365
d1f8c1bd
MG
2366/**
2367 * Returns options to use in course overviewfiles filemanager
2368 *
442f12f8 2369 * @param null|stdClass|core_course_list_element|int $course either object that has 'id' property or just the course id;
d1f8c1bd
MG
2370 * may be empty if course does not exist yet (course create form)
2371 * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc.
2372 * or null if overviewfiles are disabled
2373 */
2374function course_overviewfiles_options($course) {
2375 global $CFG;
2376 if (empty($CFG->courseoverviewfileslimit)) {
2377 return null;
2378 }
2379 $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY);
2380 if (in_array('*', $accepted_types) || empty($accepted_types)) {
2381 $accepted_types = '*';
2382 } else {
2383 // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered.
2384 // Make sure extensions are prefixed with dot unless they are valid typegroups
2385 foreach ($accepted_types as $i => $type) {
2386 if (substr($type, 0, 1) !== '.') {
2387 require_once($CFG->libdir. '/filelib.php');
2388 if (!count(file_get_typegroup('extension', $type))) {
2389 // It does not start with dot and is not a valid typegroup, this is most likely extension.
2390 $accepted_types[$i] = '.'. $type;
2391 $corrected = true;
2392 }
2393 }
2394 }
2395 if (!empty($corrected)) {
2396 set_config('courseoverviewfilesext', join(',', $accepted_types));
2397 }
2398 }
2399 $options = array(
2400 'maxfiles' => $CFG->courseoverviewfileslimit,
2401 'maxbytes' => $CFG->maxbytes,
2402 'subdirs' => 0,
2403 'accepted_types' => $accepted_types
2404 );
2405 if (!empty($course->id)) {
2406 $options['context'] = context_course::instance($course->id);
2407 } else if (is_int($course) && $course > 0) {
2408 $options['context'] = context_course::instance($course);
2409 }
2410 return $options;
2411}
2412
c3df0901 2413/**
df997f84
PS
2414 * Create a course and either return a $course object
2415 *
2416 * Please note this functions does not verify any access control,
2417 * the calling code is responsible for all validation (usually it is the form definition).
bfefa87e 2418 *
df997f84 2419 * @param array $editoroptions course description editor options
bfefa87e 2420 * @param object $data - all the data needed for an entry in the 'course' table
df997f84 2421 * @return object new course instance
bfefa87e 2422 */
df997f84 2423function create_course($data, $editoroptions = NULL) {
0d1e5456 2424 global $DB, $CFG;
bfefa87e 2425
df997f84
PS
2426 //check the categoryid - must be given for all new courses
2427 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
71b77297 2428
9930e426 2429 // Check if the shortname already exists.
df997f84
PS
2430 if (!empty($data->shortname)) {
2431 if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
9930e426 2432 throw new moodle_exception('shortnametaken', '', '', $data->shortname);
71b77297 2433 }
2434 }
2435
9930e426 2436 // Check if the idnumber already exists.
df997f84
PS
2437 if (!empty($data->idnumber)) {
2438 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
9930e426 2439 throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
71b77297 2440 }
2441 }
2f48819b 2442
f96623cb
JP
2443 if (empty($CFG->enablecourserelativedates)) {
2444 // Make sure we're not setting the relative dates mode when the setting is disabled.
2445 unset($data->relativedatesmode);
2446 }
2447
8643c576
DM
2448 if ($errorcode = course_validate_dates((array)$data)) {
2449 throw new moodle_exception($errorcode);
2450 }
2451
9a4231e9
S
2452 // Check if timecreated is given.
2453 $data->timecreated = !empty($data->timecreated) ? $data->timecreated : time();
df997f84 2454 $data->timemodified = $data->timecreated;
71b77297 2455
df997f84
PS
2456 // place at beginning of any category
2457 $data->sortorder = 0;
00f270bc 2458
df997f84
PS
2459 if ($editoroptions) {
2460 // summary text is updated later, we need context to store the files first
2461 $data->summary = '';
2462 $data->summary_format = FORMAT_HTML;
2463 }
2464
db1218a9
PS
2465 if (!isset($data->visible)) {
2466 // data not from form, add missing visibility info
54a01598 2467 $data->visible = $category->visible;
bfefa87e 2468 }
db1218a9 2469 $data->visibleold = $data->visible;
2585a68d 2470
df997f84 2471 $newcourseid = $DB->insert_record('course', $data);
9a5e297b 2472 $context = context_course::instance($newcourseid, MUST_EXIST);
bfefa87e 2473
df997f84
PS
2474 if ($editoroptions) {
2475 // Save the files used in the summary editor and store
64f93798 2476 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
df997f84
PS
2477 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
2478 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
2479 }
d1f8c1bd
MG
2480 if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) {
2481 // Save the course overviewfiles
2482 $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2483 }
bfefa87e 2484
fc79ede5
MG
2485 // update course format options
2486 course_get_format($newcourseid)->update_course_format_options($data);
2487
2488 $course = course_get_format($newcourseid)->get_course();
bfefa87e 2489
df997f84 2490 fix_course_sortorder();
eabbfa82
MG
2491 // purge appropriate caches in case fix_course_sortorder() did not change anything
2492 cache_helper::purge_by_event('changesincourse');
bfefa87e 2493
2542cb54
MG
2494 // Trigger a course created event.
2495 $event = \core\event\course_created::create(array(
2496 'objectid' => $course->id,
2497 'context' => context_course::instance($course->id),
2498 'other' => array('shortname' => $course->shortname,
2499 'fullname' => $course->fullname)
2500 ));
16076f1e 2501
2542cb54
MG
2502 $event->trigger();
2503
2504 // Setup the blocks
2505 blocks_add_default_course_blocks($course);
2506
89b909f6
MG
2507 // Create default section and initial sections if specified (unless they've already been created earlier).
2508 // We do not want to call course_create_sections_if_missing() because to avoid creating course cache.
2509 $numsections = isset($data->numsections) ? $data->numsections : 0;
2510 $existingsections = $DB->get_fieldset_sql('SELECT section from {course_sections} WHERE course = ?', [$newcourseid]);
2511 $newsections = array_diff(range(0, $numsections), $existingsections);
2512 foreach ($newsections as $sectionnum) {
2513 course_create_section($newcourseid, $sectionnum, true);
2514 }
2542cb54 2515
df997f84 2516 // Save any custom role names.
e92c39ca 2517 save_local_role_names($course->id, (array)$data);
bfefa87e 2518
df997f84
PS
2519 // set up enrolments
2520 enrol_course_updated(true, $course, $data);
bef12c99 2521
0d1e5456 2522 // Update course tags.
74fa9f76
MG
2523 if (isset($data->tags)) {
2524 core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
0d1e5456
MG
2525 }
2526
7a0162f1
DM
2527 // Save custom fields if there are any of them in the form.
2528 $handler = core_course\customfield\course_handler::create();
2f61fe97
JP
2529 // Make sure to set the handler's parent context first.
2530 $coursecatcontext = context_coursecat::instance($category->id);
2531 $handler->set_parent_context($coursecatcontext);
2532 // Save the custom field data.
7a0162f1
DM
2533 $data->id = $course->id;
2534 $handler->instance_form_save($data, true);
2535
df997f84 2536 return $course;
bfefa87e 2537}
2538
c3df0901 2539/**
df997f84
PS
2540 * Update a course.
2541 *
2542 * Please note this functions does not verify any access control,
2543 * the calling code is responsible for all validation (usually it is the form definition).
bfefa87e 2544 *
2545 * @param object $data - all the data needed for an entry in the 'course' table
df997f84
PS
2546 * @param array $editoroptions course description editor options
2547 * @return void
bfefa87e 2548 */
df997f84 2549function update_course($data, $editoroptions = NULL) {
0d1e5456 2550 global $DB, $CFG;
bfefa87e 2551
12b94016
SL
2552 // Prevent changes on front page course.
2553 if ($data->id == SITEID) {
2554 throw new moodle_exception('invalidcourse', 'error');
2555 }
2556
fc79ede5 2557 $oldcourse = course_get_format($data->id)->get_course();
9a5e297b 2558 $context = context_course::instance($oldcourse->id);
bfefa87e 2559
f96623cb
JP
2560 // Make sure we're not changing whatever the course's relativedatesmode setting is.
2561 unset($data->relativedatesmode);
2562
288d6ebb
MJ
2563 // Capture the updated fields for the log data.
2564 $updatedfields = [];
2565 foreach (get_object_vars($oldcourse) as $field => $value) {
2566 if ($field == 'summary_editor') {
2567 if (($data->$field)['text'] !== $value['text']) {
2568 // The summary might be very long, we don't wan't to fill up the log record with the full text.
2569 $updatedfields[$field] = '(updated)';
2570 }
50987d04 2571 } else if ($field == 'tags' && !empty($CFG->usetags)) {
288d6ebb
MJ
2572 // Tags might not have the same array keys, just check the values.
2573 if (array_values($data->$field) !== array_values($value)) {
2574 $updatedfields[$field] = $data->$field;
2575 }
2576 } else {
2577 if (isset($data->$field) && $data->$field != $value) {
2578 $updatedfields[$field] = $data->$field;
2579 }
2580 }
2581 }
2582
2583 $data->timemodified = time();
2584
df997f84 2585 if ($editoroptions) {
64f93798 2586 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
71b77297 2587 }
d1f8c1bd
MG
2588 if ($overviewfilesoptions = course_overviewfiles_options($data->id)) {
2589 $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2590 }
71b77297 2591
5536a561
FD
2592 // Check we don't have a duplicate shortname.
2593 if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
e1301685 2594 if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) {
5536a561
FD
2595 throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2596 }
2597 }
2598
2599 // Check we don't have a duplicate idnumber.
2600 if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
e1301685 2601 if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) {
5536a561
FD
2602 throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2603 }
2604 }
2605
8643c576
DM
2606 if ($errorcode = course_validate_dates((array)$data)) {
2607 throw new moodle_exception($errorcode);
2608 }
2609
df997f84
PS
2610 if (!isset($data->category) or empty($data->category)) {
2611 // prevent nulls and 0 in category field
bfefa87e 2612 unset($data->category);
2613 }
b80a50f7 2614 $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category);
bfefa87e 2615
db1218a9
PS
2616 if (!isset($data->visible)) {
2617 // data not from form, add missing visibility info
df997f84
PS
2618 $data->visible = $oldcourse->visible;
2619 }
bfefa87e 2620
df997f84
PS
2621 if ($data->visible != $oldcourse->visible) {
2622 // reset the visibleold flag when manually hiding/unhiding course
2623 $data->visibleold = $data->visible;
b55248d5 2624 $changesincoursecat = true;
df997f84 2625 } else {
a372aab5 2626 if ($movecat) {
df997f84
PS
2627 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
2628 if (empty($newcategory->visible)) {
2629 // make sure when moving into hidden category the course is hidden automatically
2630 $data->visible = 0;
2631 }
a372aab5 2632 }
df997f84 2633 }
a372aab5 2634
019186b7
AT
2635 // Set newsitems to 0 if format does not support announcements.
2636 if (isset($data->format)) {
2637 $newcourseformat = course_get_format((object)['format' => $data->format]);
745f79da 2638 if (!$newcourseformat->supports_news()) {
019186b7
AT
2639 $data->newsitems = 0;
2640 }
2641 }
2642
7a0162f1
DM
2643 // Update custom fields if there are any of them in the form.
2644 $handler = core_course\customfield\course_handler::create();
2645 $handler->instance_form_save($data);
2646
df997f84 2647 // Update with the new data
487caf6b 2648 $DB->update_record('course', $data);
38b19bbc
MG
2649 // make sure the modinfo cache is reset
2650 rebuild_course_cache($data->id);
bfefa87e 2651
fc79ede5
MG
2652 // update course format options with full course data
2653 course_get_format($data->id)->update_course_format_options($data, $oldcourse);
2654
df997f84 2655 $course = $DB->get_record('course', array('id'=>$data->id));
bfefa87e 2656
df997f84 2657 if ($movecat) {
9a5e297b 2658 $newparent = context_coursecat::instance($course->category);
2c5b0eb7 2659 $context->update_moved($newparent);
df997f84 2660 }
3a11e2d2
RT
2661 $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder));
2662 if ($fixcoursesortorder) {
24f824b9
MG
2663 fix_course_sortorder();
2664 }
2665
eabbfa82
MG
2666 // purge appropriate caches in case fix_course_sortorder() did not change anything
2667 cache_helper::purge_by_event('changesincourse');
b55248d5 2668 if ($changesincoursecat) {
b80a50f7
MG
2669 cache_helper::purge_by_event('changesincoursecat');
2670 }
bfefa87e 2671
df997f84
PS
2672 // Test for and remove blocks which aren't appropriate anymore
2673 blocks_remove_inappropriate($course);
bfefa87e 2674
df997f84
PS
2675 // Save any custom role names.
2676 save_local_role_names($course->id, $data);
2677
2678 // update enrol settings
2679 enrol_course_updated(false, $course, $data);
2680
0d1e5456 2681 // Update course tags.
74fa9f76
MG
2682 if (isset($data->tags)) {
2683 core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
0d1e5456
MG
2684 }
2685
53a8e678
MN
2686 // Trigger a course updated event.
2687 $event = \core\event\course_updated::create(array(
2688 'objectid' => $course->id,
3a11e2d2 2689 'context' => context_course::instance($course->id),
53a8e678 2690 'other' => array('shortname' => $course->shortname,
288d6ebb
MJ
2691 'fullname' => $course->fullname,
2692 'updatedfields' => $updatedfields)
53a8e678 2693 ));
3a11e2d2 2694
2bf2f359 2695 $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
53a8e678 2696 $event->trigger();
fc79ede5
MG
2697
2698 if ($oldcourse->format !== $course->format) {
2699 // Remove all options stored for the previous format
2700 // We assume that new course format migrated everything it needed watching trigger
2701 // 'course_updated' and in method format_XXX::update_course_format_options()
2702 $DB->delete_records('course_format_options',
2703 array('courseid' => $course->id, 'format' => $oldcourse->format));
2704 }
bfefa87e 2705}
2585a68d 2706
07ab0c80 2707/**
fcb4decd 2708 * Average number of participants
07ab0c80 2709 * @return integer
2710 */
2711function average_number_of_participants() {
fcb4decd 2712 global $DB, $SITE;
2713
2714 //count total of enrolments for visible course (except front page)
2715 $sql = 'SELECT COUNT(*) FROM (
2716 SELECT DISTINCT ue.userid, e.courseid
2717 FROM {user_enrolments} ue, {enrol} e, {course} c
516c5eca 2718 WHERE ue.enrolid = e.id
fcb4decd 2719 AND e.courseid <> :siteid
2720 AND c.id = e.courseid
9f247093 2721 AND c.visible = 1) total';
fcb4decd 2722 $params = array('siteid' => $SITE->id);
2723 $enrolmenttotal = $DB->count_records_sql($sql, $params);
2724
2725
2726 //count total of visible courses (minus front page)
2727 $coursetotal = $DB->count_records('course', array('visible' => 1));
2728 $coursetotal = $coursetotal - 1 ;
2729
2730 //average of enrolment
2731 if (empty($coursetotal)) {
2732 $participantaverage = 0;
2733 } else {
2734 $participantaverage = $enrolmenttotal / $coursetotal;
2735 }
2736
2737 return $participantaverage;
07ab0c80 2738}
2739
2740/**
fcb4decd 2741 * Average number of course modules
07ab0c80 2742 * @return integer
2743 */
2744function average_number_of_courses_modules() {
fcb4decd 2745 global $DB, $SITE;
2746
2747 //count total of visible course module (except front page)
2748 $sql = 'SELECT COUNT(*) FROM (
2749 SELECT cm.course, cm.module
2750 FROM {course} c, {course_modules} cm
516c5eca 2751 WHERE c.id = cm.course
fcb4decd 2752 AND c.id <> :siteid
2753 AND cm.visible = 1
9f247093 2754 AND c.visible = 1) total';
fcb4decd 2755 $params = array('siteid' => $SITE->id);
2756 $moduletotal = $DB->count_records_sql($sql, $params);
2757
2758
2759 //count total of visible courses (minus front page)
2760 $coursetotal = $DB->count_records('course', array('visible' => 1));
2761 $coursetotal = $coursetotal - 1 ;
2762
2763 //average of course module
2764 if (empty($coursetotal)) {
2765 $coursemoduleaverage = 0;
2766 } else {
2767 $coursemoduleaverage = $moduletotal / $coursetotal;
2768 }
2769
2770 return $coursemoduleaverage;
07ab0c80 2771}
2772
8bdc9cac
SH
2773/**
2774 * This class pertains to course requests and contains methods associated with
2775 * create, approving, and removing course requests.
2776 *
64f93798
PS
2777 * Please note we do not allow embedded images here because there is no context
2778 * to store them with proper access control.
2779 *
8bdc9cac
SH
2780 * @copyright 2009 Sam Hemelryk
2781 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2782 * @since Moodle 2.0
2783 *
2784 * @property-read int $id
2785 * @property-read string $fullname
2786 * @property-read string $shortname
2787 * @property-read string $summary
2788 * @property-read int $summaryformat
2789 * @property-read int $summarytrust
2790 * @property-read string $reason
2791 * @property-read int $requester
8bdc9cac
SH
2792 */
2793class course_request {
2794
2795 /**
2796 * This is the stdClass that stores the properties for the course request
7e85563d 2797 * and is externally accessed through the __get magic method
8bdc9cac
SH
2798 * @var stdClass
2799 */
2800 protected $properties;
2801
2802 /**
2803 * An array of options for the summary editor used by course request forms.
2804 * This is initially set by {@link summary_editor_options()}
2805 * @var array
2806 * @static
2807 */
2808 protected static $summaryeditoroptions;
2809
8bdc9cac
SH
2810 /**
2811 * Static function to prepare the summary editor for working with a course
2812 * request.
2813 *
2814 * @static
2815 * @param null|stdClass $data Optional, an object containing the default values
2816 * for the form, these may be modified when preparing the
2817 * editor so this should be called before creating the form
2818 * @return stdClass An object that can be used to set the default values for
2819 * an mforms form
2820 */
2821 public static function prepare($data=null) {
2822 if ($data === null) {
2823 $data = new stdClass;
2824 }
64f93798 2825 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
8bdc9cac
SH
2826 return $data;
2827 }
2828
2829 /**
2830 * Static function to create a new course request when passed an array of properties
2831 * for it.
2832 *
2833 * This function also handles saving any files that may have been used in the editor
2834 *
2835 * @static
2836 * @param stdClass $data
2837 * @return course_request The newly created course request
2838 */
2839 public static function create($data) {
2840 global $USER, $DB, $CFG;
2841 $data->requester = $USER->id;
64f93798 2842
d347f304 2843 // Setting the default category if none set.
959e4f0e 2844 if (empty($data->category) || !empty($CFG->lockrequestcategory)) {
59b9a140
FM
2845 $data->category = $CFG->defaultrequestcategory;
2846 }
2847
64f93798
PS
2848 // Summary is a required field so copy the text over
2849 $data->summary = $data->summary_editor['text'];
2850 $data->summaryformat = $data->summary_editor['format'];
2851
8bdc9cac 2852 $data->id = $DB->insert_record('course_request', $data);
64f93798 2853
8bdc9cac
SH
2854 // Create a new course_request object and return it
2855 $request = new course_request($data);
2856
2857 // Notify the admin if required.
adf176d7 2858 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
aa6c1ced 2859
8bdc9cac
SH
2860 $a = new stdClass;
2861 $a->link = "$CFG->wwwroot/course/pending.php";
2862 $a->user = fullname($USER);
2863 $subject = get_string('courserequest');
2864 $message = get_string('courserequestnotifyemail', 'admin', $a);
2865 foreach ($users as $user) {
2a63b636 2866 $request->notify($user, $USER, 'courserequested', $subject, $message);
8bdc9cac
SH
2867 }
2868 }
2869
2870 return $request;
2871 }
2872
2873 /**
2874 * Returns an array of options to use with a summary editor
2875 *
2876 * @uses course_request::$summaryeditoroptions
2877 * @return array An array of options to use with the editor
2878 */
2879 public static function summary_editor_options() {
2880 global $CFG;
2881 if (self::$summaryeditoroptions === null) {
64f93798 2882 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
8bdc9cac
SH
2883 }
2884 return self::$summaryeditoroptions;
2885 }
2886
8bdc9cac
SH
2887 /**
2888 * Loads the properties for this course request object. Id is required and if
2889 * only id is provided then we load the rest of the properties from the database
2890 *
2891 * @param stdClass|int $properties Either an object containing properties
2892 * or the course_request id to load
2893 */
2894 public function __construct($properties) {
2895 global $DB;
2896 if (empty($properties->id)) {
2897 if (empty($properties)) {
2898 throw new coding_exception('You must provide a course request id when creating a course_request object');
2899 }
2900 $id = $properties;
2901 $properties = new stdClass;
2902 $properties->id = (int)$id;
2903 unset($id);
2904 }
2905 if (empty($properties->requester)) {
2906 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
2907 print_error('unknowncourserequest');
2908 }
2909 } else {
2910 $this->properties = $properties;
2911 }
2912 $this->properties->collision = null;
2913 }
2914
2915 /**
2916 * Returns the requested property
2917 *
2918 * @param string $key
2919 * @return mixed
2920 */
2921 public function __get($key) {
8bdc9cac
SH
2922 return $this->properties->$key;
2923 }
2924
2925 /**
2926 * Override this to ensure empty($request->blah) calls return a reliable answer...
2927 *
2928 * This is required because we define the __get method
2929 *
2930 * @param mixed $key
2931 * @return bool True is it not empty, false otherwise
2932 */
2933 public function __isset($key) {
2934 return (!empty($this->properties->$key));
2935 }
2936
2937 /**
2938 * Returns the user who requested this course
2939 *
2940 * Uses a static var to cache the results and cut down the number of db queries
2941 *
2942 * @staticvar array $requesters An array of cached users
2943 * @return stdClass The user who requested the course
2944 */
2945 public function get_requester() {
2946 global $DB;
2947 static $requesters= array();
2948 if (!array_key_exists($this->properties->requester, $requesters)) {
2949 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
2950 }
2951 return $requesters[$this->properties->requester];
2952 }
2953
2954 /**
2955 * Checks that the shortname used by the course does not conflict with any other
2956 * courses that exist
2957 *
2958 * @param string|null $shortnamemark The string to append to the requests shortname
2959 * should a conflict be found
2960 * @return bool true is there is a conflict, false otherwise
2961 */
2962 public function check_shortname_collision($shortnamemark = '[*]') {
2963 global $DB;
2964
2965 if ($this->properties->collision !== null) {
2966 return $this->properties->collision;
2967 }
2968
2969 if (empty($this->properties->shortname)) {
2970 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
2971 $this->properties->collision = false;
2972 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
2973 if (!empty($shortnamemark)) {
2974 $this->properties->shortname .= ' '.$shortnamemark;
2975 }
2976 $this->properties->collision = true;
2977 } else {
2978 $this->properties->collision = false;
2979 }
2980 return $this->properties->collision;
2981 }
2982
3e15abe5
MG
2983 /**
2984 * Checks user capability to approve a requested course
2985 *
2986 * If course was requested without category for some reason (might happen if $CFG->defaultrequestcategory is
2987 * misconfigured), we check capabilities 'moodle/site:approvecourse' and 'moodle/course:changecategory'.
2988 *
2989 * @return bool
2990 */
2991 public function can_approve() {
2992 global $CFG;
2993 $category = null;
2994 if ($this->properties->category) {
2995 $category = core_course_category::get($this->properties->category, IGNORE_MISSING);
2996 } else if ($CFG->defaultrequestcategory) {
2997 $category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING);
2998 }
2999 if ($category) {
3000 return has_capability('moodle/site:approvecourse', $category->get_context());
3001 }
3002
3003 // We can not determine the context where the course should be created. The approver should have
3004 // both capabilities to approve courses and change course category in the system context.
3005 return has_all_capabilities(['moodle/site:approvecourse', 'moodle/course:changecategory'], context_system::instance());
3006 }
3007
2d8a275b
MG
3008 /**
3009 * Returns the category where this course request should be created
3010 *
3011 * Note that we don't check here that user has a capability to view
3012 * hidden categories if he has capabilities 'moodle/site:approvecourse' and
3013 * 'moodle/course:changecategory'
3014 *
442f12f8 3015 * @return core_course_category
2d8a275b
MG
3016 */
3017 public function get_category() {
3018 global $CFG;
3e15abe5
MG
3019 if ($this->properties->category && ($category = core_course_category::get($this->properties->category, IGNORE_MISSING))) {
3020 return $category;
3021 } else if ($CFG->defaultrequestcategory &&
3022 ($category = core_course_category::get($CFG->defaultrequestcategory, IGNORE_MISSING))) {
3023 return $category;
3024 } else {
3025 return core_course_category::get_default();
2d8a275b 3026 }
2d8a275b
MG
3027 }
3028
8bdc9cac
SH
3029 /**
3030 * This function approves the request turning it into a course
3031 *
3032 * This function converts the course request into a course, at the same time
7e85563d 3033 * transferring any files used in the summary to the new course and then removing
8bdc9cac
SH
3034 * the course request and the files associated with it.
3035 *
3036 * @return int The id of the course that was created from this request
3037 */
3038 public function approve() {
3039 global $CFG, $DB, $USER;
70740405 3040
9f9d7bbe
AG
3041 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
3042
70740405
PS
3043 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
3044
8bdc9cac
SH
3045 $courseconfig = get_config('moodlecourse');
3046
3047 // Transfer appropriate settings
70740405
PS
3048 $data = clone($this->properties);
3049 unset($data->id);
3050 unset($data->reason);
3051 unset($data->requester);
8bdc9cac
SH
3052
3053 // Set category
2d8a275b 3054 $category = $this->get_category();
70740405 3055 $data->category = $category->id;
8bdc9cac 3056 // Set misc settings
70740405 3057 $data->requested = 1;
8bdc9cac
SH
3058
3059 // Apply course default settings
70740405 3060 $data->format = $courseconfig->format;
70740405
PS
3061 $data->newsitems = $courseconfig->newsitems;
3062 $data->showgrades = $courseconfig->showgrades;
3063 $data->showreports = $courseconfig->showreports;
3064 $data->maxbytes = $courseconfig->maxbytes;
3065 $data->groupmode = $courseconfig->groupmode;
3066 $data->groupmodeforce = $courseconfig->groupmodeforce;
3067 $data->visible = $courseconfig->visible;
3068 $data->visibleold = $data->visible;
3069 $data->lang = $courseconfig->lang;
f6b9b6b7 3070 $data->enablecompletion = $courseconfig->enablecompletion;
ca66f965 3071 $data->numsections = $courseconfig->numsections;
6363c9ad
FK
3072 $data->startdate = usergetmidnight(time());
3073 if ($courseconfig->courseenddateenabled) {
3074 $data->enddate = usergetmidnight(time()) + $courseconfig->courseduration;
3075 }
8bdc9cac 3076
9f9d7bbe
AG
3077 list($data->fullname, $data->shortname) = restore_dbops::calculate_course_names(0, $data->fullname, $data->shortname);
3078
70740405 3079 $course = create_course($data);
1f364c87 3080 $context = context_course::instance($course->id, MUST_EXIST);
8bdc9cac 3081
70740405
PS
3082 // add enrol instances
3083 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
3084 if ($manual = enrol_get_plugin('manual')) {
3085 $manual->add_default_instance($course);
3086 }
8bdc9cac 3087 }
70740405
PS
3088
3089 // enrol the requester as teacher if necessary
3090 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
3091 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
3092 }
3093
3094 $this->delete();
3095
fbaea88f 3096 $a = new stdClass();
9a5e297b 3097 $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
70740405 3098 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
880fc15b 3099 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a), $course->id);
70740405
PS
3100
3101 return $course->id;
8bdc9cac
SH
3102 }
3103
3104 /**
3105 * Reject a course request
3106 *
3107 * This function rejects a course request, emailing the requesting user the
3108 * provided notice and then removing the request from the database
3109 *
3110 * @param string $notice The message to display to the user
3111 */
3112 public function reject($notice) {
70740405
PS
3113 global $USER, $DB;
3114 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
8bdc9cac
SH
3115 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
3116 $this->delete();
3117 }
3118
3119 /**
3120 * Deletes the course request and any associated files
3121 */
3122 public function delete() {
3123 global $DB;
3124 $DB->delete_records('course_request', array('id' => $this->properties->id));
8bdc9cac
SH
3125 }
3126
3127 /**
3128 * Send a message from one user to another using events_trigger
3129 *
3130 * @param object $touser
3131 * @param object $fromuser
3132 * @param string $name
3133 * @param string $subject
3134 * @param string $message
880fc15b 3135 * @param int|null $courseid
8bdc9cac 3136 */
880fc15b 3137 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message, $courseid = null) {
cc350fd9 3138 $eventdata = new \core\message\message();
880fc15b 3139 $eventdata->courseid = empty($courseid) ? SITEID : $courseid;
a1708181 3140 $eventdata->component = 'moodle';
8bdc9cac
SH
3141 $eventdata->name = $name;
3142 $eventdata->userfrom = $fromuser;
3143 $eventdata->userto = $touser;
3144 $eventdata->subject = $subject;
3145 $eventdata->fullmessage = $message;
3146 $eventdata->fullmessageformat = FORMAT_PLAIN;
3147 $eventdata->fullmessagehtml = '';
3148 $eventdata->smallmessage = '';
a1708181 3149 $eventdata->notification = 1;
7c7d3afa 3150 message_send($eventdata);
8bdc9cac 3151 }
3e15abe5
MG
3152
3153 /**
3154 * Checks if current user can request a course in this context
3155 *
3156 * @param context $context
3157 * @return bool
3158 */
3159 public static function can_request(context $context) {
3160 global $CFG;
3161 if (empty($CFG->enablecourserequests)) {
3162 return false;
3163 }
3164 if (has_capability('moodle/course:create', $context)) {
3165 return false;
3166 }
3167
3168 if ($context instanceof context_system) {
3169 $defaultcontext = context_coursecat::instance($CFG->defaultrequestcategory, IGNORE_MISSING);
3170 return $defaultcontext &&
3171 has_capability('moodle/course:request', $defaultcontext);
3172 } else if ($context instanceof context_coursecat) {
959e4f0e 3173 if (!$CFG->lockrequestcategory || $CFG->defaultrequestcategory == $context->instanceid) {
3e15abe5
MG
3174 return has_capability('moodle/course:request', $context);
3175 }
3176 }
3177 return false;
3178 }
5c34c4ba 3179}
b1627a92
DC
3180
3181/**
3182 * Return a list of page types
3183 * @param string $pagetype current page type
9dd85edf
MG
3184 * @param context $parentcontext Block's parent context
3185 * @param context $currentcontext Current context of block
3186 * @return array array of page types
b1627a92 3187 */
b38e2e28 3188function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
9dd85edf
MG
3189 if ($pagetype === 'course-index' || $pagetype === 'course-index-category') {
3190 // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page
3191 $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3192 'course-index-*' => get_string('page-course-index-x', 'pagetype'),
3193 );
3194 } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) {
3195 // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.)
3196 $pagetypes = array('*' => get_string('page-x', 'pagetype'));
3197 } else {
3198 // Otherwise consider it a page inside a course even if $currentcontext is null
3199 $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3200 'course-*' => get_string('page-course-x', 'pagetype'),
3201 'course-view-*' => get_string('page-course-view-x', 'pagetype')
3202 );
b1627a92 3203 }
9dd85edf 3204 return $pagetypes;
af27c69e 3205}
ebaa29d1 3206
5720019d
ARN
3207/**
3208 * Determine whether course ajax should be enabled for the specified course
3209 *
924c34df 3210 * @param stdClass $course The course to test against
5720019d
ARN
3211 * @return boolean Whether course ajax is enabled or note
3212 */
3213function course_ajax_enabled($course) {
3214 global $CFG, $PAGE, $SITE;
3215
5720019d
ARN
3216 // The user must be editing for AJAX to be included
3217 if (!$PAGE->user_is_editing()) {
3218 return false;
3219 }
3220
3221 // Check that the theme suports
3222 if (!$PAGE->theme->enablecourseajax) {
3223 return false;
3224 }
3225
3226 // Check that the course format supports ajax functionality
3227 // The site 'format' doesn't have information on course format support
3228 if ($SITE->id !== $course->id) {
3229 $courseformatajaxsupport = course_format_ajax_support($course->format);
3230 if (!$courseformatajaxsupport->capable) {
3231 return false;
3232 }
3233 }
3234
3235 // All conditions have been met so course ajax should be enabled
3236 return true;
3237}
3238
ebaa29d1
ARN
3239/**
3240 * Include the relevant javascript and language strings for the resource
3241 * toolbox YUI module
3242 *
3243 * @param integer $id The ID of the course being applied to
7b061512
SH
3244 * @param array $usedmodules An array containing the names of the modules in use on the page
3245 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
924c34df 3246 * @param stdClass $config An object containing configuration parameters for ajax modules including:
ebaa29d1
ARN
3247 * * resourceurl The URL to post changes to for resource changes
3248 * * sectionurl The URL to post changes to for section changes
3249 * * pageparams Additional parameters to pass through in the post
7b061512 3250 * @return bool
ebaa29d1 3251 */
7b061512 3252function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
52973369 3253 global $CFG, $PAGE, $SITE;
ebaa29d1
ARN
3254
3255 // Ensure that ajax should be included
5720019d
ARN
3256 if (!course_ajax_enabled($course)) {
3257 return false;
ebaa29d1
ARN
3258 }
3259
3260 if (!$config) {
3261 $config = new stdClass();
3262 }
3263
3264 // The URL to use for resource changes
3265 if (!isset($config->resourceurl)) {
3266 $config->resourceurl = '/course/rest.php';
3267 }
3268
3269 // The URL to use for section changes
3270 if (!isset($config->sectionurl)) {
3271 $config->sectionurl = '/course/rest.php';
3272 }
3273
3274 // Any additional parameters which need to be included on page submission
3275 if (!isset($config->pageparams)) {
3276 $config->pageparams = array();
3277 }
3278
15e2552f 3279 // Include course dragdrop
47281f7e 3280 if (course_format_uses_sections($course->format)) {
c77582fe 3281 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
15e2552f
RK
3282 array(array(
3283 'courseid' => $course->id,
56838156 3284 'ajaxurl' => $config->sectionurl,
15e2552f
RK
3285 'config' => $config,
3286 )), null, true);
3287
c77582fe 3288 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
15e2552f
RK
3289 array(array(
3290 'courseid' => $course->id,
56838156 3291 'ajaxurl' => $config->resourceurl,
15e2552f
RK
3292 'config' => $config,
3293 )), null, true);
56838156
RK
3294 }
3295
ebaa29d1
ARN
3296 // Require various strings for the command toolbox
3297 $PAGE->requires->strings_for_js(array(
3298 'moveleft',
3299 'deletechecktype',
3300 'deletechecktypename',
7a9a07d2
ARN
3301 'edittitle',
3302 'edittitleinstructions',
ebaa29d1
ARN
3303 'show',
3304 'hide',
60cf0742
S
3305 'highlight',
3306 'highlightoff',
ebaa29d1
ARN
3307 'groupsnone',
3308 'groupsvisible',
3309 'groupsseparate',
3310 'clicktochangeinbrackets',
3311 'markthistopic',
3312 'markedthistopic',
15e2552f 3313 'movesection',
34bcc6a9
AN
3314 'movecoursemodule',
3315 'movecoursesection',
dd66b6ab 3316 'movecontent',
bbb483b2 3317 'tocontent',
34bcc6a9
AN
3318 'emptydragdropregion',
3319 'afterresource',
3320 'aftersection',
3321 'totopofsection',
ebaa29d1
ARN
3322 ), 'moodle');
3323
47281f7e
AN
3324 // Include section-specific strings for formats which support sections.
3325 if (course_format_uses_sections($course->format)) {
ebaa29d1
ARN
3326 $PAGE->requires->strings_for_js(array(
3327 'showfromothers',
3328 'hidefromothers',
3329 ), 'format_' . $course->format);
3330 }
3331
3332 // For confirming resource deletion we need the name of the module in question
7b061512 3333 foreach ($usedmodules as $module => $modname) {
ebaa29d1
ARN
3334 $PAGE->requires->string_for_js('pluginname', $module);
3335 }
3aaa1843 3336
32528f94 3337 // Load drag and drop upload AJAX.
52973369 3338 require_once($CFG->dirroot.'/course/dnduploadlib.php');
7b061512
SH
3339 dndupload_add_to_course($course, $enabledmodules);
3340
8341055e
MG
3341 $PAGE->requires->js_call_amd('core_course/actions', 'initCoursePage', array($course->format));
3342
5720019d 3343 return true;
ebaa29d1 3344}
5218b9bb 3345
3776335c
MG
3346/**
3347 * Returns the sorted list of available course formats, filtered by enabled if necessary
3348 *
3349 * @param bool $enabledonly return only formats that are enabled
3350 * @return array array of sorted format names
3351 */
3352function get_sorted_course_formats($enabledonly = false) {
3353 global $CFG;
bd3b3bba 3354 $formats = core_component::get_plugin_list('format');
3776335c
MG
3355
3356 if (!empty($CFG->format_plugins_sortorder)) {
3357 $order = explode(',', $CFG->format_plugins_sortorder);
3358 $order = array_merge(array_intersect($order, array_keys($formats)),
3359 array_diff(array_keys($formats), $order));
3360 } else {
3361 $order = array_keys($formats);
3362 }
3363 if (!$enabledonly) {
3364 return $order;
3365 }
3366 $sortedformats = array();
3367 foreach ($order as $formatname) {
3368 if (!get_config('format_'.$formatname, 'disabled')) {
3369 $sortedformats[] = $formatname;
3370 }
3371 }
3372 return $sortedformats;
3373}
3374
5218b9bb
DP
3375/**
3376 * The URL to use for the specified course (with section)
3377 *
ee7084e9
MG
3378 * @param int|stdClass $courseorid The course to get the section name for (either object or just course id)
3379 * @param int|stdClass $section Section object from database or just field course_sections.section
923451c5
MG