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