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