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