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