Commit | Line | Data |
---|---|---|
d9cb06dc | 1 | <?php |
2 | ||
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | /** | |
19 | * Library of useful functions | |
20 | * | |
21 | * @copyright 1999 Martin Dougiamas http://dougiamas.com | |
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
df997f84 PS |
23 | * @package core |
24 | * @subpackage course | |
d9cb06dc | 25 | */ |
f9903ed0 | 26 | |
df997f84 PS |
27 | defined('MOODLE_INTERNAL') || die; |
28 | ||
4e781c7b | 29 | require_once($CFG->libdir.'/completionlib.php'); |
8bdc9cac | 30 | require_once($CFG->libdir.'/filelib.php'); |
ee7084e9 | 31 | require_once($CFG->dirroot.'/course/format/lib.php'); |
f9903ed0 | 32 | |
92890025 | 33 | define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records |
92890025 | 34 | define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds |
3b7bfbb5 FM |
35 | |
36 | /** | |
37 | * Number of courses to display when summaries are included. | |
38 | * @var int | |
39 | * @deprecated since 2.4, use $CFG->courseswithsummarieslimit instead. | |
40 | */ | |
41 | define('COURSE_MAX_SUMMARIES_PER_PAGE', 10); | |
42 | ||
950c35a9 | 43 | define('COURSE_MAX_COURSES_PER_DROPDOWN',1000); // max courses in log dropdown before switching to optional |
92890025 | 44 | define('COURSE_MAX_USERS_PER_DROPDOWN',1000); // max users in log dropdown before switching to optional |
220a90c5 | 45 | define('FRONTPAGENEWS', '0'); |
0fd26350 | 46 | define('FRONTPAGECOURSELIST', '1'); // Not used. TODO MDL-38832 remove |
220a90c5 | 47 | define('FRONTPAGECATEGORYNAMES', '2'); |
0fd26350 | 48 | define('FRONTPAGETOPICONLY', '3'); // Not used. TODO MDL-38832 remove |
220a90c5 | 49 | define('FRONTPAGECATEGORYCOMBO', '4'); |
0fd26350 MG |
50 | define('FRONTPAGEENROLLEDCOURSELIST', '5'); |
51 | define('FRONTPAGEALLCOURSELIST', '6'); | |
52 | define('FRONTPAGECOURSESEARCH', '7'); | |
53 | define('FRONTPAGECOURSELIMIT', 200); // Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage. TODO MDL-38832 remove | |
6f24e48e | 54 | define('EXCELROWS', 65535); |
55 | define('FIRSTUSEDEXCELROW', 3); | |
60fdc714 | 56 | |
89bfeee0 | 57 | define('MOD_CLASS_ACTIVITY', 0); |
58 | define('MOD_CLASS_RESOURCE', 1); | |
59 | ||
600149be | 60 | function make_log_url($module, $url) { |
61 | switch ($module) { | |
bd7be234 | 62 | case 'course': |
d48f6068 | 63 | if (strpos($url, 'report/') === 0) { |
3ce3bb88 | 64 | // there is only one report type, course reports are deprecated |
d48f6068 PS |
65 | $url = "/$url"; |
66 | break; | |
67 | } | |
bd7be234 | 68 | case 'file': |
69 | case 'login': | |
70 | case 'lib': | |
71 | case 'admin': | |
bd7be234 | 72 | case 'calendar': |
95d1d037 | 73 | case 'category': |
bd5d0ce5 | 74 | case 'mnet course': |
11003188 | 75 | if (strpos($url, '../') === 0) { |
76 | $url = ltrim($url, '.'); | |
77 | } else { | |
78 | $url = "/course/$url"; | |
79 | } | |
bd5d0ce5 | 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('&', '&', $query); // both & and & 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('&', $parts); | |
136 | } | |
137 | ||
138 | return $script.$query; | |
600149be | 139 | } |
140 | ||
92890025 | 141 | |
c215b32b | 142 | function 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 | 233 | function 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 | 324 | function 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 | 441 | function 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 | 546 | function 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 | ||
628 | function 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 | ||
742 | function 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 | */ | |
889 | function 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 ['. | |
952 | $sectionid.']'; | |
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 | 992 | function 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; | |
1059 | $mod[$seq]->availablefrom = $rawmods[$seq]->availablefrom; | |
1060 | $mod[$seq]->availableuntil = $rawmods[$seq]->availableuntil; | |
1061 | $mod[$seq]->showavailability = $rawmods[$seq]->showavailability; | |
8c40662e | 1062 | $mod[$seq]->showdescription = $rawmods[$seq]->showdescription; |
adaeccb6 | 1063 | if (!empty($CFG->enableavailability)) { |
82bd6a5e | 1064 | condition_info::fill_availability_conditions($rawmods[$seq]); |
82bd6a5e | 1065 | $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion; |
1066 | $mod[$seq]->conditionsgrade = $rawmods[$seq]->conditionsgrade; | |
76af15bb | 1067 | $mod[$seq]->conditionsfield = $rawmods[$seq]->conditionsfield; |
82bd6a5e | 1068 | } |
8dddba42 | 1069 | |
1070 | $modname = $mod[$seq]->mod; | |
1071 | $functionname = $modname."_get_coursemodule_info"; | |
1072 | ||
3a37b3f8 | 1073 | if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) { |
1074 | continue; | |
1075 | } | |
1076 | ||
8dddba42 | 1077 | include_once("$CFG->dirroot/mod/$modname/lib.php"); |
1078 | ||
b3a89232 | 1079 | if ($hasfunction = function_exists($functionname)) { |
9d361034 | 1080 | if ($info = $functionname($rawmods[$seq])) { |
9d361034 | 1081 | if (!empty($info->icon)) { |
1082 | $mod[$seq]->icon = $info->icon; | |
1083 | } | |
9a9012dc PS |
1084 | if (!empty($info->iconcomponent)) { |
1085 | $mod[$seq]->iconcomponent = $info->iconcomponent; | |
1086 | } | |
1ea543df | 1087 | if (!empty($info->name)) { |
9a9012dc | 1088 | $mod[$seq]->name = $info->name; |
1ea543df | 1089 | } |
0d8b6a69 | 1090 | if ($info instanceof cached_cm_info) { |
1091 | // When using cached_cm_info you can include three new fields | |
1092 | // that aren't available for legacy code | |
1093 | if (!empty($info->content)) { | |
1094 | $mod[$seq]->content = $info->content; | |
1095 | } | |
1096 | if (!empty($info->extraclasses)) { | |
1097 | $mod[$seq]->extraclasses = $info->extraclasses; | |
1098 | } | |
c443a1cd | 1099 | if (!empty($info->iconurl)) { |
4fcdb012 MG |
1100 | // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB. |
1101 | $url = new moodle_url($info->iconurl); | |
1102 | $mod[$seq]->iconurl = $url->out(false); | |
c443a1cd | 1103 | } |
0d8b6a69 | 1104 | if (!empty($info->onclick)) { |
1105 | $mod[$seq]->onclick = $info->onclick; | |
1106 | } | |
1107 | if (!empty($info->customdata)) { | |
1108 | $mod[$seq]->customdata = $info->customdata; | |
1109 | } | |
1110 | } else { | |
1111 | // When using a stdclass, the (horrible) deprecated ->extra field | |
1112 | // is available for BC | |
1113 | if (!empty($info->extra)) { | |
1114 | $mod[$seq]->extra = $info->extra; | |
1115 | } | |
1116 | } | |
c9f6251e | 1117 | } |
1118 | } | |
b3a89232 | 1119 | // When there is no modname_get_coursemodule_info function, |
1120 | // but showdescriptions is enabled, then we use the 'intro' | |
1121 | // and 'introformat' fields in the module table | |
1122 | if (!$hasfunction && $rawmods[$seq]->showdescription) { | |
1123 | if ($modvalues = $DB->get_record($rawmods[$seq]->modname, | |
1124 | array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) { | |
1125 | // Set content from intro and introformat. Filters are disabled | |
1126 | // because we filter it with format_text at display time | |
1127 | $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname, | |
1128 | $modvalues, $rawmods[$seq]->id, false); | |
1129 | ||
1130 | // To save making another query just below, put name in here | |
1131 | $mod[$seq]->name = $modvalues->name; | |
1132 | } | |
1133 | } | |
1ea543df | 1134 | if (!isset($mod[$seq]->name)) { |
9a9012dc | 1135 | $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance)); |
1ea543df | 1136 | } |
0d8b6a69 | 1137 | |
1138 | // Minimise the database size by unsetting default options when they are | |
1139 | // 'empty'. This list corresponds to code in the cm_info constructor. | |
8c40662e | 1140 | foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly', |
c443a1cd | 1141 | 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content', |
adaeccb6 | 1142 | 'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom', |
1143 | 'availableuntil', 'conditionscompletion', 'conditionsgrade', | |
8c40662e | 1144 | 'completionview', 'completionexpected', 'score', 'showdescription') |
1145 | as $property) { | |
0d8b6a69 | 1146 | if (property_exists($mod[$seq], $property) && |
1147 | empty($mod[$seq]->{$property})) { | |
1148 | unset($mod[$seq]->{$property}); | |
1149 | } | |
1150 | } | |
adaeccb6 | 1151 | // Special case: this value is usually set to null, but may be 0 |
1152 | if (property_exists($mod[$seq], 'completiongradeitemnumber') && | |
1153 | is_null($mod[$seq]->completiongradeitemnumber)) { | |
1154 | unset($mod[$seq]->completiongradeitemnumber); | |
1155 | } | |
d897cae4 | 1156 | } |
1157 | } | |
1158 | } | |
1159 | } | |
1160 | return $mod; | |
1161 | } | |
1162 | ||
cb6fec1f | 1163 | /** |
d57aa283 MG |
1164 | * Returns the localised human-readable names of all used modules |
1165 | * | |
1166 | * @param bool $plural if true returns the plural forms of the names | |
1167 | * @return array where key is the module name (component name without 'mod_') and | |
1168 | * the value is the human-readable string. Array sorted alphabetically by value | |
cb6fec1f | 1169 | */ |
d57aa283 MG |
1170 | function get_module_types_names($plural = false) { |
1171 | static $modnames = null; | |
1172 | global $DB, $CFG; | |
1173 | if ($modnames === null) { | |
1174 | $modnames = array(0 => array(), 1 => array()); | |
1175 | if ($allmods = $DB->get_records("modules")) { | |
1176 | foreach ($allmods as $mod) { | |
1177 | if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) { | |
1178 | $modnames[0][$mod->name] = get_string("modulename", "$mod->name"); | |
1179 | $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name"); | |
1180 | } | |
13534ef7 | 1181 | } |
2f1e464a PS |
1182 | core_collator::asort($modnames[0]); |
1183 | core_collator::asort($modnames[1]); | |
c7da6f7a | 1184 | } |
7468bf01 | 1185 | } |
d57aa283 | 1186 | return $modnames[(int)$plural]; |
7468bf01 | 1187 | } |
1188 | ||
93d46f48 RK |
1189 | /** |
1190 | * Set highlighted section. Only one section can be highlighted at the time. | |
1191 | * | |
1192 | * @param int $courseid course id | |
1193 | * @param int $marker highlight section with this number, 0 means remove higlightin | |
1194 | * @return void | |
1195 | */ | |
1196 | function course_set_marker($courseid, $marker) { | |
1197 | global $DB; | |
1198 | $DB->set_field("course", "marker", $marker, array('id' => $courseid)); | |
a062c97c | 1199 | format_base::reset_course_cache($courseid); |
93d46f48 RK |
1200 | } |
1201 | ||
cb6fec1f | 1202 | /** |
7e85563d | 1203 | * For a given course section, marks it visible or hidden, |
cb6fec1f | 1204 | * and does the same for every activity in that section |
ebaa29d1 ARN |
1205 | * |
1206 | * @param int $courseid course id | |
1207 | * @param int $sectionnumber The section number to adjust | |
1208 | * @param int $visibility The new visibility | |
1209 | * @return array A list of resources which were hidden in the section | |
cb6fec1f | 1210 | */ |
7d99d695 | 1211 | function set_section_visible($courseid, $sectionnumber, $visibility) { |
cb6fec1f | 1212 | global $DB; |
7d99d695 | 1213 | |
ebaa29d1 | 1214 | $resourcestotoggle = array(); |
cb6fec1f | 1215 | if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) { |
1216 | $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id)); | |
7d99d695 | 1217 | if (!empty($section->sequence)) { |
1218 | $modules = explode(",", $section->sequence); | |
1219 | foreach ($modules as $moduleid) { | |
00a34234 FM |
1220 | if ($cm = $DB->get_record('course_modules', array('id' => $moduleid), 'visible, visibleold')) { |
1221 | if ($visibility) { | |
1222 | // As we unhide the section, we use the previously saved visibility stored in visibleold. | |
1223 | set_coursemodule_visible($moduleid, $cm->visibleold); | |
1224 | } else { | |
1225 | // We hide the section, so we hide the module but we store the original state in visibleold. | |
1226 | set_coursemodule_visible($moduleid, 0); | |
1227 | $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid)); | |
1228 | } | |
1229 | } | |
7d99d695 | 1230 | } |
1231 | } | |
38b19bbc | 1232 | rebuild_course_cache($courseid, true); |
ebaa29d1 ARN |
1233 | |
1234 | // Determine which modules are visible for AJAX update | |
1235 | if (!empty($modules)) { | |
1236 | list($insql, $params) = $DB->get_in_or_equal($modules); | |
1237 | $select = 'id ' . $insql . ' AND visible = ?'; | |
1238 | array_push($params, $visibility); | |
1239 | if (!$visibility) { | |
1240 | $select .= ' AND visibleold = 1'; | |
1241 | } | |
1242 | $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params); | |
1243 | } | |
7d99d695 | 1244 | } |
ebaa29d1 | 1245 | return $resourcestotoggle; |
7d99d695 | 1246 | } |
ba2e5d73 | 1247 | |
59155a3f ARN |
1248 | /** |
1249 | * Retrieve all metadata for the requested modules | |
1250 | * | |
1251 | * @param object $course The Course | |
1252 | * @param array $modnames An array containing the list of modules and their | |
1253 | * names | |
a41b1d96 | 1254 | * @param int $sectionreturn The section to return to |
59155a3f ARN |
1255 | * @return array A list of stdClass objects containing metadata about each |
1256 | * module | |
1257 | */ | |
923451c5 | 1258 | function get_module_metadata($course, $modnames, $sectionreturn = null) { |
59155a3f ARN |
1259 | global $CFG, $OUTPUT; |
1260 | ||
1261 | // get_module_metadata will be called once per section on the page and courses may show | |
1262 | // different modules to one another | |
1263 | static $modlist = array(); | |
1264 | if (!isset($modlist[$course->id])) { | |
1265 | $modlist[$course->id] = array(); | |
1266 | } | |
1267 | ||
1268 | $return = array(); | |
17079335 MG |
1269 | $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey())); |
1270 | if ($sectionreturn !== null) { | |
1271 | $urlbase->param('sr', $sectionreturn); | |
1272 | } | |
59155a3f ARN |
1273 | foreach($modnames as $modname => $modnamestr) { |
1274 | if (!course_allowed_module($course, $modname)) { | |
1275 | continue; | |
1276 | } | |
ad47d025 | 1277 | if (isset($modlist[$course->id][$modname])) { |
59155a3f ARN |
1278 | // This module is already cached |
1279 | $return[$modname] = $modlist[$course->id][$modname]; | |
1280 | continue; | |
1281 | } | |
1282 | ||
1283 | // Include the module lib | |
1284 | $libfile = "$CFG->dirroot/mod/$modname/lib.php"; | |
1285 | if (!file_exists($libfile)) { | |
1286 | continue; | |
1287 | } | |
1288 | include_once($libfile); | |
1289 | ||
1290 | // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!! | |
1291 | $gettypesfunc = $modname.'_get_types'; | |
1292 | if (function_exists($gettypesfunc)) { | |
56c0878b SC |
1293 | $types = $gettypesfunc(); |
1294 | if (is_array($types) && count($types) > 0) { | |
59155a3f ARN |
1295 | $group = new stdClass(); |
1296 | $group->name = $modname; | |
1297 | $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon')); | |
1298 | foreach($types as $type) { | |
1299 | if ($type->typestr === '--') { | |
1300 | continue; | |
1301 | } | |
1302 | if (strpos($type->typestr, '--') === 0) { | |
1303 | $group->title = str_replace('--', '', $type->typestr); | |
1304 | continue; | |
1305 | } | |
1306 | // Set the Sub Type metadata | |
1307 | $subtype = new stdClass(); | |
1308 | $subtype->title = $type->typestr; | |
1309 | $subtype->type = str_replace('&', '&', $type->type); | |
1310 | $subtype->name = preg_replace('/.*type=/', '', $subtype->type); | |
1311 | $subtype->archetype = $type->modclass; | |
1312 | ||
1313 | // The group archetype should match the subtype archetypes and all subtypes | |
1314 | // should have the same archetype | |
1315 | $group->archetype = $subtype->archetype; | |
1316 | ||
1317 | if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) { | |
1318 | $subtype->help = get_string('help' . $subtype->name, $modname); | |
1319 | } | |
cf003abc | 1320 | $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $subtype->name)); |
59155a3f ARN |
1321 | $group->types[] = $subtype; |
1322 | } | |
1323 | $modlist[$course->id][$modname] = $group; | |
1324 | } | |
1325 | } else { | |
1326 | $module = new stdClass(); | |
17079335 | 1327 | $module->title = $modnamestr; |
59155a3f | 1328 | $module->name = $modname; |
17079335 | 1329 | $module->link = new moodle_url($urlbase, array('add' => $modname)); |
59155a3f | 1330 | $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon')); |
01e0e704 ARN |
1331 | $sm = get_string_manager(); |
1332 | if ($sm->string_exists('modulename_help', $modname)) { | |
59155a3f | 1333 | $module->help = get_string('modulename_help', $modname); |
01e0e704 ARN |
1334 | if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs |
1335 | $link = get_string('modulename_link', $modname); | |
1336 | $linktext = get_string('morehelp'); | |
afe3566c | 1337 | $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink')); |
01e0e704 | 1338 | } |
59155a3f ARN |
1339 | } |
1340 | $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); | |
1341 | $modlist[$course->id][$modname] = $module; | |
1342 | } | |
56c0878b SC |
1343 | if (isset($modlist[$course->id][$modname])) { |
1344 | $return[$modname] = $modlist[$course->id][$modname]; | |
1345 | } else { | |
1346 | debugging("Invalid module metadata configuration for {$modname}"); | |
1347 | } | |
59155a3f ARN |
1348 | } |
1349 | ||
1350 | return $return; | |
1351 | } | |
1352 | ||
8ed5dd63 | 1353 | /** |
1354 | * Return the course category context for the category with id $categoryid, except | |
1355 | * that if $categoryid is 0, return the system context. | |
1356 | * | |
1357 | * @param integer $categoryid a category id or 0. | |
1358 | * @return object the corresponding context | |
1359 | */ | |
1360 | function get_category_or_system_context($categoryid) { | |
1361 | if ($categoryid) { | |
4658aec5 | 1362 | return context_coursecat::instance($categoryid, IGNORE_MISSING); |
8ed5dd63 | 1363 | } else { |
9a5e297b | 1364 | return context_system::instance(); |
8ed5dd63 | 1365 | } |
1366 | } | |
1367 | ||
cb6fec1f | 1368 | /** |
4e0b6025 MG |
1369 | * Returns full course categories trees to be used in html_writer::select() |
1370 | * | |
1371 | * Calls {@link coursecat::make_categories_list()} to build the tree and | |
1372 | * adds whitespace to denote nesting | |
1373 | * | |
1374 | * @return array array mapping coursecat id to the display name | |
cb6fec1f | 1375 | */ |
0705ff84 | 1376 | function make_categories_options() { |
4e0b6025 MG |
1377 | global $CFG; |
1378 | require_once($CFG->libdir. '/coursecatlib.php'); | |
a7a04f6d | 1379 | $cats = coursecat::make_categories_list('', 0, ' / '); |
0705ff84 | 1380 | foreach ($cats as $key => $value) { |
a7a04f6d MG |
1381 | // Prefix the value with the number of spaces equal to category depth (number of separators in the value). |
1382 | $cats[$key] = str_repeat(' ', substr_count($value, ' / ')). $value; | |
0705ff84 | 1383 | } |
1384 | return $cats; | |
1385 | } | |
c2cb4545 | 1386 | |
77eddcd5 | 1387 | /** |
1388 | * Print the buttons relating to course requests. | |
1389 | * | |
8e57a6df | 1390 | * @param object $context current page context. |
77eddcd5 | 1391 | */ |
8e57a6df | 1392 | function print_course_request_buttons($context) { |
b4531207 | 1393 | global $CFG, $DB, $OUTPUT; |
77eddcd5 | 1394 | if (empty($CFG->enablecourserequests)) { |
1395 | return; | |
1396 | } | |
8e57a6df | 1397 | if (!has_capability('moodle/course:create', $context) && has_capability('moodle/course:request', $context)) { |
77eddcd5 | 1398 | /// Print a button to request a new course |
8e57a6df | 1399 | echo $OUTPUT->single_button(new moodle_url('/course/request.php'), get_string('requestcourse'), 'get'); |
77eddcd5 | 1400 | } |
1401 | /// Print a button to manage pending requests | |
8e57a6df | 1402 | if ($context->contextlevel == CONTEXT_SYSTEM && has_capability('moodle/site:approvecourse', $context)) { |
5c2ed7e2 | 1403 | $disabled = !$DB->record_exists('course_request', array()); |
8e57a6df | 1404 | echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled)); |
77eddcd5 | 1405 | } |
1406 | } | |
1407 | ||
5048e034 | 1408 | /** |
1409 | * Does the user have permission to edit things in this category? | |
1410 | * | |
1411 | * @param integer $categoryid The id of the category we are showing, or 0 for system context. | |
1412 | * @return boolean has_any_capability(array(...), ...); in the appropriate context. | |
1413 | */ | |
1414 | function can_edit_in_category($categoryid = 0) { | |
1415 | $context = get_category_or_system_context($categoryid); | |
1416 | return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context); | |
1417 | } | |
1418 | ||
11b0c469 | 1419 | /// MODULE FUNCTIONS ///////////////////////////////////////////////////////////////// |
1420 | ||
1421 | function add_course_module($mod) { | |
cb6fec1f | 1422 | global $DB; |
11b0c469 | 1423 | |
e5dfd0f3 | 1424 | $mod->added = time(); |
53f4ad2c | 1425 | unset($mod->id); |
11b0c469 | 1426 | |
38b19bbc MG |
1427 | $cmid = $DB->insert_record("course_modules", $mod); |
1428 | rebuild_course_cache($mod->course, true); | |
1429 | return $cmid; | |
11b0c469 | 1430 | } |
1431 | ||
97928ddf | 1432 | /** |
4ede27b2 | 1433 | * Creates missing course section(s) and rebuilds course cache |
99e9f9a6 | 1434 | * |
b46be6ad | 1435 | * @param int|stdClass $courseorid course id or course object |
4ede27b2 MG |
1436 | * @param int|array $sections list of relative section numbers to create |
1437 | * @return bool if there were any sections created | |
97928ddf | 1438 | */ |
b46be6ad | 1439 | function course_create_sections_if_missing($courseorid, $sections) { |
cb6fec1f | 1440 | global $DB; |
4ede27b2 MG |
1441 | if (!is_array($sections)) { |
1442 | $sections = array($sections); | |
1443 | } | |
b46be6ad MG |
1444 | $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all()); |
1445 | if (is_object($courseorid)) { | |
1446 | $courseorid = $courseorid->id; | |
1447 | } | |
4ede27b2 MG |
1448 | $coursechanged = false; |
1449 | foreach ($sections as $sectionnum) { | |
1450 | if (!in_array($sectionnum, $existing)) { | |
1451 | $cw = new stdClass(); | |
b46be6ad | 1452 | $cw->course = $courseorid; |
4ede27b2 MG |
1453 | $cw->section = $sectionnum; |
1454 | $cw->summary = ''; | |
1455 | $cw->summaryformat = FORMAT_HTML; | |
1456 | $cw->sequence = ''; | |
1457 | $id = $DB->insert_record("course_sections", $cw); | |
1458 | $coursechanged = true; | |
1459 | } | |
1460 | } | |
1461 | if ($coursechanged) { | |
b46be6ad | 1462 | rebuild_course_cache($courseorid, true); |
4ede27b2 MG |
1463 | } |
1464 | return $coursechanged; | |
97928ddf | 1465 | } |
52f14061 | 1466 | |
ece966f0 | 1467 | /** |
722e6ba9 | 1468 | * Adds an existing module to the section |
ece966f0 | 1469 | * |
722e6ba9 MG |
1470 | * Updates both tables {course_sections} and {course_modules} |
1471 | * | |
5523c344 | 1472 | * Note: This function does not use modinfo PROVIDED that the section you are |
1473 | * adding the module to already exists. If the section does not exist, it will | |
1474 | * build modinfo if necessary and create the section. | |
1475 | * | |
722e6ba9 | 1476 | * @param int|stdClass $courseorid course id or course object |
46453565 | 1477 | * @param int $cmid id of the module already existing in course_modules table |
722e6ba9 MG |
1478 | * @param int $sectionnum relative number of the section (field course_sections.section) |
1479 | * If section does not exist it will be created | |
1480 | * @param int|stdClass $beforemod id or object with field id corresponding to the module | |
1481 | * before which the module needs to be included. Null for inserting in the | |
1482 | * end of the section | |
1483 | * @return int The course_sections ID where the module is inserted | |
ece966f0 | 1484 | */ |
46453565 | 1485 | function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) { |
722e6ba9 MG |
1486 | global $DB, $COURSE; |
1487 | if (is_object($beforemod)) { | |
1488 | $beforemod = $beforemod->id; | |
1489 | } | |
46453565 PS |
1490 | if (is_object($courseorid)) { |
1491 | $courseid = $courseorid->id; | |
1492 | } else { | |
1493 | $courseid = $courseorid; | |
1494 | } | |
46453565 | 1495 | // Do not try to use modinfo here, there is no guarantee it is valid! |
5523c344 | 1496 | $section = $DB->get_record('course_sections', |
1497 | array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING); | |
1498 | if (!$section) { | |
1499 | // This function call requires modinfo. | |
1500 | course_create_sections_if_missing($courseorid, $sectionnum); | |
1501 | $section = $DB->get_record('course_sections', | |
1502 | array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST); | |
1503 | } | |
1504 | ||
722e6ba9 | 1505 | $modarray = explode(",", trim($section->sequence)); |
281d730e | 1506 | if (empty($section->sequence)) { |
46453565 | 1507 | $newsequence = "$cmid"; |
722e6ba9 | 1508 | } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) { |
46453565 | 1509 | $insertarray = array($cmid, $beforemod); |
722e6ba9 MG |
1510 | array_splice($modarray, $key[0], 1, $insertarray); |
1511 | $newsequence = implode(",", $modarray); | |
1512 | } else { | |
46453565 | 1513 | $newsequence = "$section->sequence,$cmid"; |
722e6ba9 MG |
1514 | } |
1515 | $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id)); | |
46453565 | 1516 | $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid)); |
b46be6ad MG |
1517 | if (is_object($courseorid)) { |
1518 | rebuild_course_cache($courseorid->id, true); | |
1519 | } else { | |
1520 | rebuild_course_cache($courseorid, true); | |
1521 | } | |
722e6ba9 | 1522 | return $section->id; // Return course_sections ID that was used. |
11b0c469 | 1523 | } |
1524 | ||
48e535bc | 1525 | function set_coursemodule_groupmode($id, $groupmode) { |
cb6fec1f | 1526 | global $DB; |
38b19bbc MG |
1527 | $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST); |
1528 | if ($cm->groupmode != $groupmode) { | |
1529 | $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id)); | |
1530 | rebuild_course_cache($cm->course, true); | |
1531 | } | |
1532 | return ($cm->groupmode != $groupmode); | |
3d575e6f | 1533 | } |
1534 | ||
177d4abf | 1535 | function set_coursemodule_idnumber($id, $idnumber) { |
cb6fec1f | 1536 | global $DB; |
38b19bbc MG |
1537 | $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST); |
1538 | if ($cm->idnumber != $idnumber) { | |
1539 | $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id)); | |
1540 | rebuild_course_cache($cm->course, true); | |
1541 | } | |
1542 | return ($cm->idnumber != $idnumber); | |
177d4abf | 1543 | } |
4e781c7b | 1544 | |
02f66c42 | 1545 | /** |
00a34234 FM |
1546 | * Set the visibility of a module and inherent properties. |
1547 | * | |
1548 | * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered | |
1549 | * has been moved to {@link set_section_visible()} which was the only place from which | |
1550 | * the parameter was used. | |
1551 | * | |
1552 | * @param int $id of the module | |
1553 | * @param int $visible state of the module | |
1554 | * @return bool false when the module was not found, true otherwise | |
1555 | */ | |
1556 | function set_coursemodule_visible($id, $visible) { | |
f5e2602a | 1557 | global $DB, $CFG; |
0f078024 | 1558 | require_once($CFG->libdir.'/gradelib.php'); |
c5ceaeaf | 1559 | require_once($CFG->dirroot.'/calendar/lib.php'); |
0f078024 | 1560 | |
00a34234 FM |
1561 | // Trigger developer's attention when using the previously removed argument. |
1562 | if (func_num_args() > 2) { | |
1563 | debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides | |
1564 | has been removed.', DEBUG_DEVELOPER); | |
1565 | } | |
1566 | ||
cb6fec1f | 1567 | if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) { |
978abb42 | 1568 | return false; |
1569 | } | |
40fcc261 AD |
1570 | |
1571 | // Create events and propagate visibility to associated grade items if the value has changed. | |
1572 | // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades. | |
1573 | if ($cm->visible == $visible) { | |
1574 | return true; | |
1575 | } | |
1576 | ||
cb6fec1f | 1577 | if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) { |
978abb42 | 1578 | return false; |
1579 | } | |
cb6fec1f | 1580 | if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) { |
dcd338ff | 1581 | foreach($events as $event) { |
48e535bc | 1582 | if ($visible) { |
379cd2d4 EL |
1583 | $event = new calendar_event($event); |
1584 | $event->toggle_visibility(true); | |
48e535bc | 1585 | } else { |
379cd2d4 EL |
1586 | $event = new calendar_event($event); |
1587 | $event->toggle_visibility(false); | |
48e535bc | 1588 | } |
dcd338ff | 1589 | } |
1590 | } | |
f5e2602a | 1591 | |
40fcc261 | 1592 | // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there. |
0f078024 DM |
1593 | $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course)); |
1594 | if ($grade_items) { | |
1595 | foreach ($grade_items as $grade_item) { | |
1596 | $grade_item->set_hidden(!$visible); | |
1597 | } | |
f5e2602a | 1598 | } |
f685e830 | 1599 | |
00a34234 FM |
1600 | // Updating visible and visibleold to keep them in sync. Only changing a section visibility will |
1601 | // affect visibleold to allow for an original visibility restore. See set_section_visible(). | |
1602 | $cminfo = new stdClass(); | |
1603 | $cminfo->id = $id; | |
1604 | $cminfo->visible = $visible; | |
1605 | $cminfo->visibleold = $visible; | |
1606 | $DB->update_record('course_modules', $cminfo); | |
1607 | ||
38b19bbc MG |
1608 | rebuild_course_cache($cm->course, true); |
1609 | return true; | |
1acfbce5 | 1610 | } |
1611 | ||
cb6fec1f | 1612 | /** |
a347aee3 MN |
1613 | * This function will handles the whole deletion process of a module. This includes calling |
1614 | * the modules delete_instance function, deleting files, events, grades, conditional data, | |
1615 | * the data in the course_module and course_sections table and adding a module deletion | |
1616 | * event to the DB. | |
290130b3 | 1617 | * |
a347aee3 MN |
1618 | * @param int $cmid the course module id |
1619 | * @since 2.5 | |
290130b3 | 1620 | */ |
a347aee3 MN |
1621 | function course_delete_module($cmid) { |
1622 | global $CFG, $DB, $USER; | |
1623 | ||
f615fbab | 1624 | require_once($CFG->libdir.'/gradelib.php'); |
cae83708 | 1625 | require_once($CFG->dirroot.'/blog/lib.php'); |
c5ceaeaf | 1626 | require_once($CFG->dirroot.'/calendar/lib.php'); |
f615fbab | 1627 | |
a347aee3 MN |
1628 | // Get the course module. |
1629 | if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) { | |
290130b3 | 1630 | return true; |
1631 | } | |
a347aee3 MN |
1632 | |
1633 | // Get the module context. | |
1634 | $modcontext = context_module::instance($cm->id); | |
1635 | ||
1636 | // Get the course module name. | |
1637 | $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST); | |
1638 | ||
1639 | // Get the file location of the delete_instance function for this module. | |
1640 | $modlib = "$CFG->dirroot/mod/$modulename/lib.php"; | |
1641 | ||
1642 | // Include the file required to call the delete_instance function for this module. | |
1643 | if (file_exists($modlib)) { | |
1644 | require_once($modlib); | |
1645 | } else { | |
9a9cb741 MN |
1646 | throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null, |
1647 | "Cannot delete this module as the file mod/$modulename/lib.php is missing."); | |
a347aee3 MN |
1648 | } |
1649 | ||
9a9cb741 | 1650 | $deleteinstancefunction = $modulename . '_delete_instance'; |
a347aee3 | 1651 | |
9a9cb741 MN |
1652 | // Ensure the delete_instance function exists for this module. |
1653 | if (!function_exists($deleteinstancefunction)) { | |
1654 | throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null, | |
1655 | "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php."); | |
1656 | } | |
1657 | ||
1658 | // Call the delete_instance function, if it returns false throw an exception. | |
a347aee3 | 1659 | if (!$deleteinstancefunction($cm->instance)) { |
9a9cb741 MN |
1660 | throw new moodle_exception('cannotdeletemoduleinstance', '', '', null, |
1661 | "Cannot delete the module $modulename (instance)."); | |
a347aee3 MN |
1662 | } |
1663 | ||
1664 | // Remove all module files in case modules forget to do that. | |
1665 | $fs = get_file_storage(); | |
1666 | $fs->delete_area_files($modcontext->id); | |
1667 | ||
1668 | // Delete events from calendar. | |
1669 | if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) { | |
dcd338ff | 1670 | foreach($events as $event) { |
c5ceaeaf DP |
1671 | $calendarevent = calendar_event::load($event->id); |
1672 | $calendarevent->delete(); | |
dcd338ff | 1673 | } |
1674 | } | |
a347aee3 MN |
1675 | |
1676 | // Delete grade items, outcome items and grades attached to modules. | |
1677 | if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename, | |
1678 | 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) { | |
f615fbab | 1679 | foreach ($grade_items as $grade_item) { |
1680 | $grade_item->delete('moddelete'); | |
1681 | } | |
f615fbab | 1682 | } |
a347aee3 | 1683 | |
46e12372 SM |
1684 | // Delete completion and availability data; it is better to do this even if the |
1685 | // features are not turned on, in case they were turned on previously (these will be | |
a347aee3 | 1686 | // very quick on an empty table). |
46e12372 SM |
1687 | $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id)); |
1688 | $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id)); | |
a8f84c28 | 1689 | $DB->delete_records('course_modules_avail_fields', array('coursemoduleid' => $cm->id)); |
ede323e2 ARN |
1690 | $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id, |
1691 | 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY)); | |
06b3a6b2 | 1692 | |
a347aee3 | 1693 | // Delete the context. |
c592eea2 | 1694 | context_helper::delete_instance(CONTEXT_MODULE, $cm->id); |
a347aee3 MN |
1695 | |
1696 | // Delete the module from the course_modules table. | |
1697 | $DB->delete_records('course_modules', array('id' => $cm->id)); | |
1698 | ||
1699 | // Delete module from that section. | |
1700 | if (!delete_mod_from_section($cm->id, $cm->section)) { | |
9a9cb741 MN |
1701 | throw new moodle_exception('cannotdeletemodulefromsection', '', '', null, |
1702 | "Cannot delete the module $modulename (instance) from section."); | |
a347aee3 MN |
1703 | } |
1704 | ||
1705 | // Trigger a mod_deleted event with information about this module. | |
1706 | $eventdata = new stdClass(); | |
1707 | $eventdata->modulename = $modulename; | |
1708 | $eventdata->cmid = $cm->id; | |
1709 | $eventdata->courseid = $cm->course; | |
1710 | $eventdata->userid = $USER->id; | |
1711 | events_trigger('mod_deleted', $eventdata); | |
1712 | ||
1713 | add_to_log($cm->course, 'course', "delete mod", | |
1714 | "view.php?id=$cm->course", | |
1715 | "$modulename $cm->instance", $cm->id); | |
1716 | ||
38b19bbc | 1717 | rebuild_course_cache($cm->course, true); |
11b0c469 | 1718 | } |
1719 | ||
722e6ba9 | 1720 | function delete_mod_from_section($modid, $sectionid) { |
cb6fec1f | 1721 | global $DB; |
11b0c469 | 1722 | |
722e6ba9 | 1723 | if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) { |
11b0c469 | 1724 | |
e5dfd0f3 | 1725 | $modarray = explode(",", $section->sequence); |
11b0c469 | 1726 | |
722e6ba9 | 1727 | if ($key = array_keys ($modarray, $modid)) { |
11b0c469 | 1728 | array_splice($modarray, $key[0], 1); |
1729 | $newsequence = implode(",", $modarray); | |
38b19bbc MG |
1730 | $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id)); |
1731 | rebuild_course_cache($section->course, true); | |
1732 | return true; | |
11b0c469 | 1733 | } else { |
1734 | return false; | |
1735 | } | |
89adb174 | 1736 | |
11b0c469 | 1737 | } |
7977cffd | 1738 | return false; |
11b0c469 | 1739 | } |
1740 | ||
3440ec12 | 1741 | /** |
1742 | * Moves a section within a course, from a position to another. | |
1743 | * Be very careful: $section and $destination refer to section number, | |
1744 | * not id!. | |
1745 | * | |
1746 | * @param object $course | |
1747 | * @param int $section Section number (not id!!!) | |
1748 | * @param int $destination | |
1749 | * @return boolean Result | |
1750 | */ | |
1751 | function move_section_to($course, $section, $destination) { | |
1752 | /// Moves a whole course section up and down within the course | |
1753 | global $USER, $DB; | |
1754 | ||
ca255392 | 1755 | if (!$destination && $destination != 0) { |
3440ec12 | 1756 | return true; |
1757 | } | |
1758 | ||
850acb35 MG |
1759 | // compartibility with course formats using field 'numsections' |
1760 | $courseformatoptions = course_get_format($course)->get_format_options(); | |
1761 | if ((array_key_exists('numsections', $courseformatoptions) && | |
1762 | ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) { | |
3440ec12 | 1763 | return false; |
1764 | } | |
1765 | ||
1766 | // Get all sections for this course and re-order them (2 of them should now share the same section number) | |
1767 | if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id), | |
1768 | 'section ASC, id ASC', 'id, section')) { | |
1769 | return false; | |
1770 | } | |
1771 | ||
cf76b335 | 1772 | $movedsections = reorder_sections($sections, $section, $destination); |
3440ec12 | 1773 | |
cf76b335 | 1774 | // Update all sections. Do this in 2 steps to avoid breaking database |
1775 | // uniqueness constraint | |
1776 | $transaction = $DB->start_delegated_transaction(); | |
1777 | foreach ($movedsections as $id => $position) { | |
1778 | if ($sections[$id] !== $position) { | |
1779 | $DB->set_field('course_sections', 'section', -$position, array('id' => $id)); | |
1780 | } | |
1781 | } | |
e7e0f8d2 DP |
1782 | foreach ($movedsections as $id => $position) { |
1783 | if ($sections[$id] !== $position) { | |
1784 | $DB->set_field('course_sections', 'section', $position, array('id' => $id)); | |
1785 | } | |
3440ec12 | 1786 | } |
1787 | ||
15e2552f RK |
1788 | // If we move the highlighted section itself, then just highlight the destination. |
1789 | // Adjust the higlighted section location if we move something over it either direction. | |
1790 | if ($section == $course->marker) { | |
e7b6e6b9 | 1791 | course_set_marker($course->id, $destination); |
2365213f | 1792 | } elseif ($section > $course->marker && $course->marker >= $destination) { |
e7b6e6b9 | 1793 | course_set_marker($course->id, $course->marker+1); |
2365213f | 1794 | } elseif ($section < $course->marker && $course->marker <= $destination) { |
e7b6e6b9 | 1795 | course_set_marker($course->id, $course->marker-1); |
15e2552f RK |
1796 | } |
1797 | ||
cf76b335 | 1798 | $transaction->allow_commit(); |
38b19bbc | 1799 | rebuild_course_cache($course->id, true); |
3440ec12 | 1800 | return true; |
1801 | } | |
1802 | ||
1803 | /** | |
1804 | * Reordering algorithm for course sections. Given an array of section->section indexed by section->id, | |
1805 | * an original position number and a target position number, rebuilds the array so that the | |
1806 | * move is made without any duplication of section positions. | |
1807 | * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to | |
1808 | * insert a section before the first one, you must give 0 as the target (section 0 can never be moved). | |
1809 | * | |
1810 | * @param array $sections | |
1811 | * @param int $origin_position | |
1812 | * @param int $target_position | |
1813 | * @return array | |
1814 | */ | |
1815 | function reorder_sections($sections, $origin_position, $target_position) { | |
1816 | if (!is_array($sections)) { | |
1817 | return false; | |
1818 | } | |
1819 | ||
1820 | // We can't move section position 0 | |
1821 | if ($origin_position < 1) { | |
1822 | echo "We can't move section position 0"; | |
1823 | return false; | |
1824 | } | |
1825 | ||
1826 | // Locate origin section in sections array | |
1827 | if (!$origin_key = array_search($origin_position, $sections)) { | |
1828 | echo "searched position not in sections array"; | |
1829 | return false; // searched position not in sections array | |
1830 | } | |
1831 | ||
1832 | // Extract origin section | |
1833 | $origin_section = $sections[$origin_key]; | |
1834 | unset($sections[$origin_key]); | |
1835 | ||
1836 | // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!) | |
1837 | $found = false; | |
1838 | $append_array = array(); | |
1839 | foreach ($sections as $id => $position) { | |
1840 | if ($found) { | |
1841 | $append_array[$id] = $position; | |
1842 | unset($sections[$id]); | |
1843 | } | |
1844 | if ($position == $target_position) { | |
eb01aa2c RT |
1845 | if ($target_position < $origin_position) { |
1846 | $append_array[$id] = $position; | |
1847 | unset($sections[$id]); | |
1848 | } | |
3440ec12 | 1849 | $found = true; |
1850 | } | |
1851 | } | |
1852 | ||
1853 | // Append moved section | |
1854 | $sections[$origin_key] = $origin_section; | |
1855 | ||
1856 | // Append rest of array (if applicable) | |
1857 | if (!empty($append_array)) { | |
1858 | foreach ($append_array as $id => $position) { | |
1859 | $sections[$id] = $position; | |
1860 | } | |
1861 | } | |
1862 | ||
1863 | // Renumber positions | |
1864 | $position = 0; | |
1865 | foreach ($sections as $id => $p) { | |
1866 | $sections[$id] = $position; | |
1867 | $position++; | |
1868 | } | |
1869 | ||
1870 | return $sections; | |
1871 | ||
1872 | } | |
1873 | ||
cb6fec1f | 1874 | /** |
1875 | * Move the module object $mod to the specified $section | |
1876 | * If $beforemod exists then that is the module | |
1877 | * before which $modid should be inserted | |
d55f05ef MG |
1878 | * |
1879 | * @param stdClass|cm_info $mod | |
1880 | * @param stdClass|section_info $section | |
1881 | * @param int|stdClass $beforemod id or object with field id corresponding to the module | |
1882 | * before which the module needs to be included. Null for inserting in the | |
1883 | * end of the section | |
1884 | * @return int new value for module visibility (0 or 1) | |
cb6fec1f | 1885 | */ |
7977cffd | 1886 | function moveto_module($mod, $section, $beforemod=NULL) { |
a83dd077 | 1887 | global $OUTPUT, $DB; |
7977cffd | 1888 | |
d55f05ef MG |
1889 | // Current module visibility state - return value of this function. |
1890 | $modvisible = $mod->visible; | |
1891 | ||
1892 | // Remove original module from original section. | |
7977cffd | 1893 | if (! delete_mod_from_section($mod->id, $mod->section)) { |
e6db3026 | 1894 | echo $OUTPUT->notification("Could not delete module from existing section"); |
7977cffd | 1895 | } |
1896 | ||
d55f05ef | 1897 | // If moving to a hidden section then hide module. |
bb1592c8 CF |
1898 | if ($mod->section != $section->id) { |
1899 | if (!$section->visible && $mod->visible) { | |
d55f05ef MG |
1900 | // Module was visible but must become hidden after moving to hidden section. |
1901 | $modvisible = 0; | |
bb1592c8 CF |
1902 | set_coursemodule_visible($mod->id, 0); |
1903 | // Set visibleold to 1 so module will be visible when section is made visible. | |
1904 | $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id)); | |
1905 | } | |
1906 | if ($section->visible && !$mod->visible) { | |
d55f05ef | 1907 | // Hidden module was moved to the visible section, restore the module visibility from visibleold. |
bb1592c8 | 1908 | set_coursemodule_visible($mod->id, $mod->visibleold); |
d55f05ef | 1909 | $modvisible = $mod->visibleold; |
bb1592c8 | 1910 | } |
7977cffd | 1911 | } |
1912 | ||
d55f05ef | 1913 | // Add the module into the new section. |
722e6ba9 | 1914 | course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod); |
d55f05ef | 1915 | return $modvisible; |
7977cffd | 1916 | } |
1917 | ||
f558b291 MG |
1918 | /** |
1919 | * Returns the list of all editing actions that current user can perform on the module | |
1920 | * | |
1921 | * @param cm_info $mod The module to produce editing buttons for | |
1922 | * @param int $indent The current indenting (default -1 means no move left-right actions) | |
1923 | * @param int $sr The section to link back to (used for creating the links) | |
1924 | * @return array array of action_link or pix_icon objects | |
1925 | */ | |
1926 | function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { | |
1927 | global $COURSE, $SITE; | |
94361e02 | 1928 | |
3d575e6f | 1929 | static $str; |
1930 | ||
9a5e297b AA |
1931 | $coursecontext = context_course::instance($mod->course); |
1932 | $modcontext = context_module::instance($mod->id); | |
7749e187 | 1933 | |
af189935 PS |
1934 | $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign'); |
1935 | $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport'); | |
1936 | ||
cf69a00a | 1937 | // No permission to edit anything. |
af189935 | 1938 | if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) { |
f558b291 | 1939 | return array(); |
217a8ee9 | 1940 | } |
1941 | ||
af189935 PS |
1942 | $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext); |
1943 | ||
3d575e6f | 1944 | if (!isset($str)) { |
f558b291 MG |
1945 | $str = get_strings(array('delete', 'move', 'moveright', 'moveleft', |
1946 | 'update', 'duplicate', 'hide', 'show', 'edittitle'), 'moodle'); | |
1947 | $str->assign = get_string('assignroles', 'role'); | |
6dc5908e ARN |
1948 | $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone")); |
1949 | $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate")); | |
1950 | $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible")); | |
1951 | $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone")); | |
1952 | $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate")); | |
1953 | $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible")); | |
1acfbce5 | 1954 | } |
94361e02 | 1955 | |
af189935 | 1956 | $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey())); |
24e1eae4 | 1957 | |
f558b291 MG |
1958 | if ($sr !== null) { |
1959 | $baseurl->param('sr', $sr); | |
7749e187 SH |
1960 | } |
1961 | $actions = array(); | |
1962 | ||
cf69a00a | 1963 | // AJAX edit title. |
9ecb50e6 | 1964 | if ($mod->has_view() && $hasmanageactivities && |
f558b291 MG |
1965 | (($mod->course == $COURSE->id && course_ajax_enabled($COURSE)) || |
1966 | ($mod->course == SITEID && course_ajax_enabled($SITE)))) { | |
1967 | // we will not display link if we are on some other-course page (where we should not see this module anyway) | |
3665af78 | 1968 | $actions['title'] = new action_menu_link_secondary( |
7a9a07d2 | 1969 | new moodle_url($baseurl, array('update' => $mod->id)), |
cf69a00a | 1970 | new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')), |
e282c679 | 1971 | $str->edittitle, |
cf69a00a | 1972 | array('class' => 'editing_title', 'data-action' => 'edittitle') |
7a9a07d2 ARN |
1973 | ); |
1974 | } | |
1975 | ||
cf69a00a | 1976 | // Indent. |
af189935 | 1977 | if ($hasmanageactivities) { |
7749e187 SH |
1978 | if (right_to_left()) { // Exchange arrows on RTL |
1979 | $rightarrow = 't/left'; | |
1980 | $leftarrow = 't/right'; | |
1981 | } else { | |
1982 | $rightarrow = 't/right'; | |
1983 | $leftarrow = 't/left'; | |
1984 | } | |
1985 | ||
e282c679 | 1986 | $hiddenclass = 'hidden'; |
7749e187 | 1987 | if ($indent > 0) { |
e282c679 | 1988 | $hiddenclass = ''; |
7749e187 | 1989 | } |
3665af78 | 1990 | $actions['moveleft'] = new action_menu_link_secondary( |
e282c679 | 1991 | new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')), |
cf69a00a | 1992 | new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 1993 | $str->moveleft, |
cf69a00a | 1994 | array('class' => 'editing_moveleft ' . $hiddenclass, 'data-action' => 'moveleft') |
e282c679 SH |
1995 | ); |
1996 | $hiddenclass = 'hidden'; | |
7749e187 | 1997 | if ($indent >= 0) { |
e282c679 | 1998 | $hiddenclass = ''; |
7749e187 | 1999 | } |
3665af78 | 2000 | $actions['moveright'] = new action_menu_link_secondary( |
e282c679 | 2001 | new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')), |
cf69a00a | 2002 | new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2003 | $str->moveright, |
cf69a00a | 2004 | array('class' => 'editing_moveright ' . $hiddenclass, 'data-action' => 'moveright') |
e282c679 | 2005 | ); |
dc0dc7d5 | 2006 | } |
7749e187 | 2007 | |
cf69a00a | 2008 | // Move. |
af189935 | 2009 | if ($hasmanageactivities) { |
3665af78 | 2010 | $actions['move'] = new action_menu_link_primary( |
f558b291 | 2011 | new moodle_url($baseurl, array('copy' => $mod->id)), |
cf69a00a | 2012 | new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2013 | $str->move, |
cf69a00a | 2014 | array('class' => 'editing_move status', 'data-action' => 'move') |
f558b291 | 2015 | ); |
7749e187 SH |
2016 | } |
2017 | ||
cf69a00a | 2018 | // Update. |
af189935 | 2019 | if ($hasmanageactivities) { |
3665af78 | 2020 | $actions['update'] = new action_menu_link_secondary( |
af189935 | 2021 | new moodle_url($baseurl, array('update' => $mod->id)), |
cf69a00a | 2022 | new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2023 | $str->update, |
cf69a00a | 2024 | array('class' => 'editing_update', 'data-action' => 'update') |
af189935 PS |
2025 | ); |
2026 | } | |
7749e187 | 2027 | |
8a1a951f | 2028 | // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php) |
e5de4933 | 2029 | // Note that restoring on front page is never allowed. |
f558b291 MG |
2030 | if ($mod->course != SITEID && has_all_capabilities($dupecaps, $coursecontext) && |
2031 | plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) { | |
3665af78 | 2032 | $actions['duplicate'] = new action_menu_link_secondary( |
8645a28f | 2033 | new moodle_url($baseurl, array('duplicate' => $mod->id)), |
cf69a00a | 2034 | new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2035 | $str->duplicate, |
cf69a00a | 2036 | array('class' => 'editing_duplicate', 'data-action' => 'duplicate') |
8645a28f EL |
2037 | ); |
2038 | } | |
7749e187 | 2039 | |
cf69a00a | 2040 | // Delete. |
af189935 | 2041 | if ($hasmanageactivities) { |
3665af78 | 2042 | $actions['delete'] = new action_menu_link_secondary( |
af189935 | 2043 | new moodle_url($baseurl, array('delete' => $mod->id)), |
cf69a00a | 2044 | new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2045 | $str->delete, |
cf69a00a | 2046 | array('class' => 'editing_delete', 'data-action' => 'delete') |
af189935 PS |
2047 | ); |
2048 | } | |
7749e187 | 2049 | |
cf69a00a | 2050 | // Hide/Show. |
217a8ee9 | 2051 | if (has_capability('moodle/course:activityvisibility', $modcontext)) { |
2052 | if ($mod->visible) { | |
3665af78 | 2053 | $actions['hide'] = new action_menu_link_primary( |
7749e187 | 2054 | new moodle_url($baseurl, array('hide' => $mod->id)), |
cf69a00a | 2055 | new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2056 | $str->hide, |
cf69a00a | 2057 | array('class' => 'editing_hide', 'data-action' => 'hide') |
7749e187 | 2058 | ); |
217a8ee9 | 2059 | } else { |
3665af78 | 2060 | $actions['show'] = new action_menu_link_primary( |
7749e187 | 2061 | new moodle_url($baseurl, array('show' => $mod->id)), |
cf69a00a | 2062 | new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2063 | $str->show, |
cf69a00a | 2064 | array('class' => 'editing_show', 'data-action' => 'show') |
7749e187 | 2065 | ); |
217a8ee9 | 2066 | } |
7977cffd | 2067 | } |
530db4cd | 2068 | |
cf69a00a | 2069 | // Groupmode. |
5c016ab3 | 2070 | if ($hasmanageactivities and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) { |
6d04c4f2 | 2071 | if ($mod->effectivegroupmode == SEPARATEGROUPS) { |
e5de4933 | 2072 | $nextgroupmode = VISIBLEGROUPS; |
32d03b7b | 2073 | $grouptitle = $str->groupsseparate; |
6dc5908e | 2074 | $forcedgrouptitle = $str->forcedgroupsseparate; |
f558b291 | 2075 | $actionname = 'groupsseparate'; |
7749e187 | 2076 | $groupimage = 't/groups'; |
6d04c4f2 | 2077 | } else if ($mod->effectivegroupmode == VISIBLEGROUPS) { |
e5de4933 | 2078 | $nextgroupmode = NOGROUPS; |
32d03b7b | 2079 | $grouptitle = $str->groupsvisible; |
6dc5908e | 2080 | $forcedgrouptitle = $str->forcedgroupsvisible; |
f558b291 | 2081 | $actionname = 'groupsvisible'; |
7749e187 | 2082 | $groupimage = 't/groupv'; |
32d03b7b | 2083 | } else { |
e5de4933 | 2084 | $nextgroupmode = SEPARATEGROUPS; |
32d03b7b | 2085 | $grouptitle = $str->groupsnone; |
6dc5908e | 2086 | $forcedgrouptitle = $str->forcedgroupsnone; |
f558b291 | 2087 | $actionname = 'groupsnone'; |
7749e187 | 2088 | $groupimage = 't/groupn'; |
32d03b7b | 2089 | } |
5c016ab3 | 2090 | if (!$mod->coursegroupmodeforce) { |
3665af78 | 2091 | $actions[$actionname] = new action_menu_link_primary( |
e5de4933 | 2092 | new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)), |
cf69a00a | 2093 | new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')), |
e282c679 | 2094 | $grouptitle, |
e5de4933 | 2095 | array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode) |
7749e187 | 2096 | ); |
3d575e6f | 2097 | } else { |
25c605a5 | 2098 | $actions[$actionname] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('class' => 'iconsmall')); |
3d575e6f | 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 | ||
f558b291 | 2112 | return $actions; |
90845098 | 2113 | } |
2114 | ||
b61efafb | 2115 | /** |
264867fd | 2116 | * given a course object with shortname & fullname, this function will |
b61efafb | 2117 | * truncate the the number of chars allowed and add ... if it was too long |
2118 | */ | |
2119 | function course_format_name ($course,$max=100) { | |
264867fd | 2120 | |
9a5e297b | 2121 | $context = context_course::instance($course->id); |
8ebbb06a | 2122 | $shortname = format_string($course->shortname, true, array('context' => $context)); |
9a5e297b | 2123 | $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); |
d1e36240 | 2124 | $str = $shortname.': '. $fullname; |
2f1e464a | 2125 | if (core_text::strlen($str) <= $max) { |
b61efafb | 2126 | return $str; |
2127 | } | |
2128 | else { | |
2f1e464a | 2129 | return core_text::substr($str,0,$max-3).'...'; |
b61efafb | 2130 | } |
2131 | } | |
2132 | ||
0705ff84 | 2133 | /** |
9665ecd2 TH |
2134 | * Is the user allowed to add this type of module to this course? |
2135 | * @param object $course the course settings. Only $course->id is used. | |
2136 | * @param string $modname the module name. E.g. 'forum' or 'quiz'. | |
2137 | * @return bool whether the current user is allowed to add this type of module to this course. | |
0705ff84 | 2138 | */ |
9665ecd2 | 2139 | function course_allowed_module($course, $modname) { |
9665ecd2 TH |
2140 | if (is_numeric($modname)) { |
2141 | throw new coding_exception('Function course_allowed_module no longer | |
2142 | supports numeric module ids. Please update your code to pass the module name.'); | |
0705ff84 | 2143 | } |
264867fd | 2144 | |
9665ecd2 TH |
2145 | $capability = 'mod/' . $modname . ':addinstance'; |
2146 | if (!get_capability_info($capability)) { | |
2147 | // Debug warning that the capability does not exist, but no more than once per page. | |
2148 | static $warned = array(); | |
2149 | $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); | |
2150 | if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) { | |
2151 | debugging('The module ' . $modname . ' does not define the standard capability ' . | |
2152 | $capability , DEBUG_DEVELOPER); | |
2153 | $warned[$modname] = 1; | |
2154 | } | |
ddb0a19f | 2155 | |
9665ecd2 TH |
2156 | // If the capability does not exist, the module can always be added. |
2157 | return true; | |
0705ff84 | 2158 | } |
238c0dd9 | 2159 | |
9665ecd2 TH |
2160 | $coursecontext = context_course::instance($course->id); |
2161 | return has_capability($capability, $coursecontext); | |
0705ff84 | 2162 | } |
2163 | ||
cb6fec1f | 2164 | /** |
2165 | * Efficiently moves many courses around while maintaining | |
2166 | * sortorder in order. | |
2167 | * | |
e92c39ca PS |
2168 | * @param array $courseids is an array of course ids |
2169 | * @param int $categoryid | |
df997f84 | 2170 | * @return bool success |
cb6fec1f | 2171 | */ |
2172 | function move_courses($courseids, $categoryid) { | |
ed54cb41 | 2173 | global $DB; |
861efb19 | 2174 | |
df997f84 | 2175 | if (empty($courseids)) { |
ed54cb41 | 2176 | // Nothing to do. |
df997f84 PS |
2177 | return; |
2178 | } | |
264867fd | 2179 | |
ed54cb41 | 2180 | if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) { |
df997f84 PS |
2181 | return false; |
2182 | } | |
19f601d1 | 2183 | |
df997f84 | 2184 | $courseids = array_reverse($courseids); |
9a5e297b | 2185 | $newparent = context_coursecat::instance($category->id); |
df997f84 | 2186 | $i = 1; |
0cbe8111 | 2187 | |
aec8fe2f | 2188 | list($where, $params) = $DB->get_in_or_equal($courseids); |
30fe1181 MG |
2189 | $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params); |
2190 | foreach ($dbcourses as $dbcourse) { | |
2191 | $course = new stdClass(); | |
2192 | $course->id = $dbcourse->id; | |
2193 | $course->category = $category->id; | |
2194 | $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++; | |
2195 | if ($category->visible == 0) { | |
2196 | // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get | |
2197 | // to previous state if somebody unhides the category. | |
2198 | $course->visible = 0; | |
2199 | } | |
ed54cb41 | 2200 | |
30fe1181 | 2201 | $DB->update_record('course', $course); |
ed54cb41 | 2202 | |
30fe1181 MG |
2203 | // Store the context. |
2204 | $context = context_course::instance($course->id); | |
ed54cb41 | 2205 | |
30fe1181 MG |
2206 | // Update the course object we are passing to the event. |
2207 | $dbcourse->category = $course->category; | |
2208 | $dbcourse->sortorder = $course->sortorder; | |
2209 | if (isset($course->visible)) { | |
2210 | $dbcourse->visible = $course->visible; | |
264867fd | 2211 | } |
30fe1181 MG |
2212 | |
2213 | // Trigger a course updated event. | |
2214 | $event = \core\event\course_updated::create(array( | |
2215 | 'objectid' => $course->id, | |
2216 | 'context' => $context, | |
2217 | 'other' => array('shortname' => $dbcourse->shortname, | |
2218 | 'fullname' => $dbcourse->fullname) | |
2219 | )); | |
2220 | $event->add_record_snapshot('course', $dbcourse); | |
2221 | $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id)); | |
2222 | $event->trigger(); | |
2223 | ||
2224 | $context->update_moved($newparent); | |
0cbe8111 | 2225 | } |
df997f84 | 2226 | fix_course_sortorder(); |
eabbfa82 | 2227 | cache_helper::purge_by_event('changesincourse'); |
df997f84 | 2228 | |
861efb19 | 2229 | return true; |
2230 | } | |
2231 | ||
ae628043 | 2232 | /** |
ee7084e9 | 2233 | * Returns the display name of the given section that the course prefers |
7487c856 | 2234 | * |
ee7084e9 MG |
2235 | * Implementation of this function is provided by course format |
2236 | * @see format_base::get_section_name() | |
7487c856 | 2237 | * |
ee7084e9 MG |
2238 | * @param int|stdClass $courseorid The course to get the section name for (object or just course id) |
2239 | * @param int|stdClass $section Section object from database or just field course_sections.section | |
2240 | * @return string Display name that the course format prefers, e.g. "Week 2" | |
ae628043 | 2241 | */ |
ee7084e9 MG |
2242 | function get_section_name($courseorid, $section) { |
2243 | return course_get_format($courseorid)->get_section_name($section); | |
7487c856 SH |
2244 | } |
2245 | ||
2246 | /** | |
ee7084e9 | 2247 | * Tells if current course format uses sections |
1b048629 | 2248 | * |
7487c856 | 2249 | * @param string $format Course format ID e.g. 'weeks' $course->format |
ee7084e9 | 2250 | * @return bool |
7487c856 | 2251 | */ |
7487c856 | 2252 | function course_format_uses_sections($format) { |
ee7084e9 MG |
2253 | $course = new stdClass(); |
2254 | $course->format = $format; | |
2255 | return course_get_format($course)->uses_sections(); | |
ae628043 | 2256 | } |
2257 | ||
c0b5d925 DM |
2258 | /** |
2259 | * Returns the information about the ajax support in the given source format | |
2260 | * | |
2261 | * The returned object's property (boolean)capable indicates that | |
2262 | * the course format supports Moodle course ajax features. | |
2263 | * The property (array)testedbrowsers can be used as a parameter for {@see ajaxenabled()}. | |
2264 | * | |
2265 | * @param string $format | |
2266 | * @return stdClass | |
2267 | */ | |
2268 | function course_format_ajax_support($format) { | |
ee7084e9 MG |
2269 | $course = new stdClass(); |
2270 | $course->format = $format; | |
2271 | return course_get_format($course)->supports_ajax(); | |
c0b5d925 DM |
2272 | } |
2273 | ||
2585a68d | 2274 | /** |
2275 | * Can the current user delete this course? | |
8b449a39 | 2276 | * Course creators have exception, |
2277 | * 1 day after the creation they can sill delete the course. | |
2585a68d | 2278 | * @param int $courseid |
2279 | * @return boolean | |
2585a68d | 2280 | */ |
2281 | function can_delete_course($courseid) { | |
8b449a39 | 2282 | global $USER, $DB; |
2585a68d | 2283 | |
9a5e297b | 2284 | $context = context_course::instance($courseid); |
2585a68d | 2285 | |
8b449a39 | 2286 | if (has_capability('moodle/course:delete', $context)) { |
2287 | return true; | |
2288 | } | |
2289 | ||
2290 | // hack: now try to find out if creator created this course recently (1 day) | |
2291 | if (!has_capability('moodle/course:create', $context)) { | |
2292 | return false; | |
2293 | } | |
2294 | ||
2295 | $since = time() - 60*60*24; | |
2296 | ||
2297 | $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since); | |
2298 | $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since"; | |
2299 | ||
2300 | return $DB->record_exists_select('log', $select, $params); | |
2585a68d | 2301 | } |
2302 | ||
bef12c99 | 2303 | /** |
2304 | * Save the Your name for 'Some role' strings. | |
2305 | * | |
2306 | * @param integer $courseid the id of this course. | |
2307 | * @param array $data the data that came from the course settings form. | |
2308 | */ | |
2309 | function save_local_role_names($courseid, $data) { | |
2310 | global $DB; | |
9a5e297b | 2311 | $context = context_course::instance($courseid); |
bef12c99 | 2312 | |
2313 | foreach ($data as $fieldname => $value) { | |
df997f84 | 2314 | if (strpos($fieldname, 'role_') !== 0) { |
bef12c99 | 2315 | continue; |
2316 | } | |
2317 | list($ignored, $roleid) = explode('_', $fieldname); | |
2318 | ||
2319 | // make up our mind whether we want to delete, update or insert | |
2320 | if (!$value) { | |
2321 | $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid)); | |
2322 | ||
2323 | } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) { | |
2324 | $rolename->name = $value; | |
2325 | $DB->update_record('role_names', $rolename); | |
2326 | ||
2327 | } else { | |
2328 | $rolename = new stdClass; | |
2329 | $rolename->contextid = $context->id; | |
2330 | $rolename->roleid = $roleid; | |
2331 | $rolename->name = $value; | |
2332 | $DB->insert_record('role_names', $rolename); | |
2333 | } | |
2334 | } | |
2335 | } | |
2585a68d | 2336 | |
d1f8c1bd MG |
2337 | /** |
2338 | * Returns options to use in course overviewfiles filemanager | |
2339 | * | |
2340 | * @param null|stdClass|course_in_list|int $course either object that has 'id' property or just the course id; | |
2341 | * may be empty if course does not exist yet (course create form) | |
2342 | * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc. | |
2343 | * or null if overviewfiles are disabled | |
2344 | */ | |
2345 | function course_overviewfiles_options($course) { | |
2346 | global $CFG; | |
2347 | if (empty($CFG->courseoverviewfileslimit)) { | |
2348 | return null; | |
2349 | } | |
2350 | $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY); | |
2351 | if (in_array('*', $accepted_types) || empty($accepted_types)) { | |
2352 | $accepted_types = '*'; | |
2353 | } else { | |
2354 | // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered. | |
2355 | // Make sure extensions are prefixed with dot unless they are valid typegroups | |
2356 | foreach ($accepted_types as $i => $type) { | |
2357 | if (substr($type, 0, 1) !== '.') { | |
2358 | require_once($CFG->libdir. '/filelib.php'); | |
2359 | if (!count(file_get_typegroup('extension', $type))) { | |
2360 | // It does not start with dot and is not a valid typegroup, this is most likely extension. | |
2361 | $accepted_types[$i] = '.'. $type; | |
2362 | $corrected = true; | |
2363 | } | |
2364 | } | |
2365 | } | |
2366 | if (!empty($corrected)) { | |
2367 | set_config('courseoverviewfilesext', join(',', $accepted_types)); | |
2368 | } | |
2369 | } | |
2370 | $options = array( | |
2371 | 'maxfiles' => $CFG->courseoverviewfileslimit, | |
2372 | 'maxbytes' => $CFG->maxbytes, | |
2373 | 'subdirs' => 0, | |
2374 | 'accepted_types' => $accepted_types | |
2375 | ); | |
2376 | if (!empty($course->id)) { | |
2377 | $options['context'] = context_course::instance($course->id); | |
2378 | } else if (is_int($course) && $course > 0) { | |
2379 | $options['context'] = context_course::instance($course); | |
2380 | } | |
2381 | return $options; | |
2382 | } | |
2383 | ||
c3df0901 | 2384 | /** |
df997f84 PS |
2385 | * Create a course and either return a $course object |
2386 | * | |
2387 | * Please note this functions does not verify any access control, | |
2388 | * the calling code is responsible for all validation (usually it is the form definition). | |
bfefa87e | 2389 | * |
df997f84 | 2390 | * @param array $editoroptions course description editor options |
bfefa87e | 2391 | * @param object $data - all the data needed for an entry in the 'course' table |
df997f84 | 2392 | * @return object new course instance |
bfefa87e | 2393 | */ |
df997f84 | 2394 | function create_course($data, $editoroptions = NULL) { |
3c2fab94 | 2395 | global $DB; |
bfefa87e | 2396 | |
df997f84 PS |
2397 | //check the categoryid - must be given for all new courses |
2398 | $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST); | |
71b77297 | 2399 | |
9930e426 | 2400 | // Check if the shortname already exists. |
df997f84 PS |
2401 | if (!empty($data->shortname)) { |
2402 | if ($DB->record_exists('course', array('shortname' => $data->shortname))) { | |
9930e426 | 2403 | throw new moodle_exception('shortnametaken', '', '', $data->shortname); |
71b77297 | 2404 | } |
2405 | } | |
2406 | ||
9930e426 | 2407 | // Check if the idnumber already exists. |
df997f84 PS |
2408 | if (!empty($data->idnumber)) { |
2409 | if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) { | |
9930e426 | 2410 | throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber); |
71b77297 | 2411 | } |
2412 | } | |
2f48819b | 2413 | |
df997f84 PS |
2414 | $data->timecreated = time(); |
2415 | $data->timemodified = $data->timecreated; | |
71b77297 | 2416 | |
df997f84 PS |
2417 | // place at beginning of any category |
2418 | $data->sortorder = 0; | |
00f270bc | 2419 | |
df997f84 PS |
2420 | if ($editoroptions) { |
2421 | // summary text is updated later, we need context to store the files first | |
2422 | $data->summary = ''; | |
2423 | $data->summary_format = FORMAT_HTML; | |
2424 | } | |
2425 | ||
db1218a9 PS |
2426 | if (!isset($data->visible)) { |
2427 | // data not from form, add missing visibility info | |
54a01598 | 2428 | $data->visible = $category->visible; |
bfefa87e | 2429 | } |
db1218a9 | 2430 | $data->visibleold = $data->visible; |
2585a68d | 2431 | |
df997f84 | 2432 | $newcourseid = $DB->insert_record('course', $data); |
9a5e297b | 2433 | $context = context_course::instance($newcourseid, MUST_EXIST); |
bfefa87e | 2434 | |
df997f84 PS |
2435 | if ($editoroptions) { |
2436 | // Save the files used in the summary editor and store | |
64f93798 | 2437 | $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0); |
df997f84 PS |
2438 | $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid)); |
2439 | $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid)); | |
2440 | } | |
d1f8c1bd MG |
2441 | if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) { |
2442 | // Save the course overviewfiles | |
2443 | $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0); | |
2444 | } | |
bfefa87e | 2445 | |
fc79ede5 MG |
2446 | // update course format options |
2447 | course_get_format($newcourseid)->update_course_format_options($data); | |
2448 | ||
2449 | $course = course_get_format($newcourseid)->get_course(); | |
bfefa87e | 2450 | |
df997f84 PS |
2451 | // Setup the blocks |
2452 | blocks_add_default_course_blocks($course); | |
bfefa87e | 2453 | |
4ede27b2 MG |
2454 | // Create a default section. |
2455 | course_create_sections_if_missing($course, 0); | |
bfefa87e | 2456 | |
df997f84 | 2457 | fix_course_sortorder(); |
eabbfa82 MG |
2458 | // purge appropriate caches in case fix_course_sortorder() did not change anything |
2459 | cache_helper::purge_by_event('changesincourse'); | |
bfefa87e | 2460 | |
df997f84 | 2461 | // new context created - better mark it as dirty |
1de02d62 | 2462 | $context->mark_dirty(); |
bfefa87e | 2463 | |
df997f84 | 2464 | // Save any custom role names. |
e92c39ca | 2465 | save_local_role_names($course->id, (array)$data); |
bfefa87e | 2466 | |
df997f84 PS |
2467 | // set up enrolments |
2468 | enrol_course_updated(true, $course, $data); | |
bef12c99 | 2469 | |
3c2fab94 MN |
2470 | // Trigger a course created event. |
2471 | $event = \core\event\course_created::create(array( | |
2472 | 'objectid' => $course->id, | |
2473 | 'context' => context_course::instance($course->id), | |
2474 | 'other' => array('shortname' => $course->shortname, | |
2475 | 'fullname' => $course->fullname) | |
2476 | )); | |
2477 | $event->add_record_snapshot('course', $course); | |
2478 | $event->trigger(); | |
bfefa87e | 2479 | |
df997f84 | 2480 | return $course; |
bfefa87e | 2481 | } |
2482 | ||
c3df0901 | 2483 | /** |
df997f84 PS |
2484 | * Update a course. |
2485 | * | |
2486 | * Please note this functions does not verify any access control, | |
2487 | * the calling code is responsible for all validation (usually it is the form definition). | |
bfefa87e | 2488 | * |
2489 | * @param object $data - all the data needed for an entry in the 'course' table | |
df997f84 PS |
2490 | * @param array $editoroptions course description editor options |
2491 | * @return void | |
bfefa87e | 2492 | */ |
df997f84 | 2493 | function update_course($data, $editoroptions = NULL) { |
53a8e678 | 2494 | global $DB; |
bfefa87e | 2495 | |
df997f84 | 2496 | $data->timemodified = time(); |
ddb0a19f | 2497 | |
fc79ede5 | 2498 | $oldcourse = course_get_format($data->id)->get_course(); |
9a5e297b | 2499 | $context = context_course::instance($oldcourse->id); |
bfefa87e | 2500 | |
df997f84 | 2501 | if ($editoroptions) { |
64f93798 | 2502 | $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0); |
71b77297 | 2503 | } |
d1f8c1bd MG |
2504 | if ($overviewfilesoptions = course_overviewfiles_options($data->id)) { |
2505 | $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0); | |
2506 | } | |
71b77297 | 2507 | |
5536a561 FD |
2508 | // Check we don't have a duplicate shortname. |
2509 | if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) { | |
2510 | if ($DB->record_exists('course', array('shortname' => $data->shortname))) { | |
2511 | throw new moodle_exception('shortnametaken', '', '', $data->shortname); | |
2512 | } | |
2513 | } | |
2514 | ||
2515 | // Check we don't have a duplicate idnumber. | |
2516 | if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) { | |
2517 | if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) { | |
2518 | throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber); | |
2519 | } | |
2520 | } | |
2521 | ||
df997f84 PS |
2522 | if (!isset($data->category) or empty($data->category)) { |
2523 | // prevent nulls and 0 in category field | |
bfefa87e | 2524 | unset($data->category); |
2525 | } | |
b80a50f7 | 2526 | $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category); |
bfefa87e | 2527 | |
db1218a9 PS |
2528 | if (!isset($data->visible)) { |
2529 | // data not from form, add missing visibility info | |
df997f84 PS |
2530 | $data->visible = $oldcourse->visible; |
2531 | } | |
bfefa87e | 2532 | |
df997f84 PS |
2533 | if ($data->visible != $oldcourse->visible) { |
2534 | // reset the visibleold flag when manually hiding/unhiding course | |
2535 | $data->visibleold = $data->visible; | |
b55248d5 | 2536 | $changesincoursecat = true; |
df997f84 | 2537 | } else { |
a372aab5 | 2538 | if ($movecat) { |
df997f84 PS |
2539 | $newcategory = $DB->get_record('course_categories', array('id'=>$data->category)); |
2540 | if (empty($newcategory->visible)) { | |
2541 | // make sure when moving into hidden category the course is hidden automatically | |
2542 | $data->visible = 0; | |
2543 | } | |
a372aab5 | 2544 | } |
df997f84 | 2545 | } |
a372aab5 | 2546 | |
df997f84 | 2547 | // Update with the new data |
487caf6b | 2548 | $DB->update_record('course', $data); |
38b19bbc MG |
2549 | // make sure the modinfo cache is reset |
2550 | rebuild_course_cache($data->id); | |
bfefa87e | 2551 | |
fc79ede5 MG |
2552 | // update course format options with full course data |
2553 | course_get_format($data->id)->update_course_format_options($data, $oldcourse); | |
2554 | ||
df997f84 | 2555 | $course = $DB->get_record('course', array('id'=>$data->id)); |
bfefa87e | 2556 | |
df997f84 | 2557 | if ($movecat) { |
9a5e297b | 2558 | $newparent = context_coursecat::instance($course->category); |
2c5b0eb7 | 2559 | $context->update_moved($newparent); |
df997f84 | 2560 | } |
238c0dd9 | 2561 | |
24f824b9 MG |
2562 | if ($movecat || (isset($data->sortorder) && $oldcourse->sortorder != $data->sortorder)) { |
2563 | fix_course_sortorder(); | |
2564 | } | |
2565 | ||
eabbfa82 MG |
2566 | // purge appropriate caches in case fix_course_sortorder() did not change anything |
2567 | cache_helper::purge_by_event('changesincourse'); | |
b55248d5 | 2568 | if ($changesincoursecat) { |
b80a50f7 MG |
2569 | cache_helper::purge_by_event('changesincoursecat'); |
2570 | } | |
bfefa87e | 2571 | |
df997f84 PS |
2572 | // Test for and remove blocks which aren't appropriate anymore |
2573 | blocks_remove_inappropriate($course); | |
bfefa87e | 2574 | |
df997f84 PS |
2575 | // Save any custom role names. |
2576 | save_local_role_names($course->id, $data); | |
2577 | ||
2578 | // update enrol settings | |
2579 | enrol_course_updated(false, $course, $data); | |
2580 | ||
53a8e678 MN |
2581 | // Trigger a course updated event. |
2582 | $event = \core\event\course_updated::create(array( | |
2583 | 'objectid' => $course->id, | |
2584 | 'context' => $context, | |
2585 | 'other' => array('shortname' => $course->shortname, | |
2586 | 'fullname' => $course->fullname) | |
2587 | )); | |
2588 | $event->add_record_snapshot('course', $course); | |
2bf2f359 | 2589 | $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id)); |
53a8e678 | 2590 | $event->trigger(); |
fc79ede5 MG |
2591 | |
2592 | if ($oldcourse->format !== $course->format) { | |
2593 | // Remove all options stored for the previous format | |
2594 | // We assume that new course format migrated everything it needed watching trigger | |
2595 | // 'course_updated' and in method format_XXX::update_course_format_options() | |
2596 | $DB->delete_records('course_format_options', | |
2597 | array('courseid' => $course->id, 'format' => $oldcourse->format)); | |
2598 | } | |
bfefa87e | 2599 | } |
2585a68d | 2600 | |
07ab0c80 | 2601 | /** |
fcb4decd | 2602 | * Average number of participants |
07ab0c80 | 2603 | * @return integer |
2604 | */ | |
2605 | function average_number_of_participants() { | |
fcb4decd | 2606 | global $DB, $SITE; |
2607 | ||
2608 | //count total of enrolments for visible course (except front page) | |
2609 | $sql = 'SELECT COUNT(*) FROM ( | |
2610 | SELECT DISTINCT ue.userid, e.courseid | |
2611 | FROM {user_enrolments} ue, {enrol} e, {course} c | |
516c5eca | 2612 | WHERE ue.enrolid = e.id |
fcb4decd | 2613 | AND e.courseid <> :siteid |
2614 | AND c.id = e.courseid | |
9f247093 | 2615 | AND c.visible = 1) total'; |
fcb4decd | 2616 | $params = array('siteid' => $SITE->id); |
2617 | $enrolmenttotal = $DB->count_records_sql($sql, $params); | |
2618 | ||
2619 | ||
2620 | //count total of visible courses (minus front page) | |
2621 | $coursetotal = $DB->count_records('course', array('visible' => 1)); | |
2622 | $coursetotal = $coursetotal - 1 ; | |
2623 | ||
2624 | //average of enrolment | |
2625 | if (empty($coursetotal)) { | |
2626 | $participantaverage = 0; | |
2627 | } else { | |
2628 | $participantaverage = $enrolmenttotal / $coursetotal; | |
2629 | } | |
2630 | ||
2631 | return $participantaverage; | |
07ab0c80 | 2632 | } |
2633 | ||
2634 | /** | |
fcb4decd | 2635 | * Average number of course modules |
07ab0c80 | 2636 | * @return integer |
2637 | */ | |
2638 | function average_number_of_courses_modules() { | |
fcb4decd | 2639 | global $DB, $SITE; |
2640 | ||
2641 | //count total of visible course module (except front page) | |
2642 | $sql = 'SELECT COUNT(*) FROM ( | |
2643 | SELECT cm.course, cm.module | |
2644 | FROM {course} c, {course_modules} cm | |
516c5eca | 2645 | WHERE c.id = cm.course |
fcb4decd | 2646 | AND c.id <> :siteid |
2647 | AND cm.visible = 1 | |
9f247093 | 2648 | AND c.visible = 1) total'; |
fcb4decd | 2649 | $params = array('siteid' => $SITE->id); |
2650 | $moduletotal = $DB->count_records_sql($sql, $params); | |
2651 | ||
2652 | ||
2653 | //count total of visible courses (minus front page) | |
2654 | $coursetotal = $DB->count_records('course', array('visible' => 1)); | |
2655 | $coursetotal = $coursetotal - 1 ; | |
2656 | ||
2657 | //average of course module | |
2658 | if (empty($coursetotal)) { | |
2659 | $coursemoduleaverage = 0; | |
2660 | } else { | |
2661 | $coursemoduleaverage = $moduletotal / $coursetotal; | |
2662 | } | |
2663 | ||
2664 | return $coursemoduleaverage; | |
07ab0c80 | 2665 | } |
2666 | ||
8bdc9cac SH |
2667 | /** |
2668 | * This class pertains to course requests and contains methods associated with | |
2669 | * create, approving, and removing course requests. | |
2670 | * | |
64f93798 PS |
2671 | * Please note we do not allow embedded images here because there is no context |
2672 | * to store them with proper access control. | |
2673 | * | |
8bdc9cac SH |
2674 | * @copyright 2009 Sam Hemelryk |
2675 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
2676 | * @since Moodle 2.0 | |
2677 | * | |
2678 | * @property-read int $id | |
2679 | * @property-read string $fullname | |
2680 | * @property-read string $shortname | |
2681 | * @property-read string $summary | |
2682 | * @property-read int $summaryformat | |
2683 | * @property-read int $summarytrust | |
2684 | * @property-read string $reason | |
2685 | * @property-read int $requester | |
8bdc9cac SH |
2686 | */ |
2687 | class course_request { | |
2688 | ||
2689 | /** | |
2690 | * This is the stdClass that stores the properties for the course request | |
7e85563d | 2691 | * and is externally accessed through the __get magic method |
8bdc9cac SH |
2692 | * @var stdClass |
2693 | */ | |
2694 | protected $properties; | |
2695 | ||
2696 | /** | |
2697 | * An array of options for the summary editor used by course request forms. | |
2698 | * This is initially set by {@link summary_editor_options()} | |
2699 | * @var array | |
2700 | * @static | |
2701 | */ | |
2702 | protected static $summaryeditoroptions; | |
2703 | ||
8bdc9cac SH |
2704 | /** |
2705 | * Static function to prepare the summary editor for working with a course | |
2706 | * request. | |
2707 | * | |
2708 | * @static | |
2709 | * @param null|stdClass $data Optional, an object containing the default values | |
2710 | * for the form, these may be modified when preparing the | |
2711 | * editor so this should be called before creating the form | |
2712 | * @return stdClass An object that can be used to set the default values for | |
2713 | * an mforms form | |
2714 | */ | |
2715 | public static function prepare($data=null) { | |
2716 | if ($data === null) { | |
2717 | $data = new stdClass; | |
2718 | } | |
64f93798 | 2719 | $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options()); |
8bdc9cac SH |
2720 | return $data; |
2721 | } | |
2722 | ||
2723 | /** | |
2724 | * Static function to create a new course request when passed an array of properties | |
2725 | * for it. | |
2726 | * | |
2727 | * This function also handles saving any files that may have been used in the editor | |
2728 | * | |
2729 | * @static | |
2730 | * @param stdClass $data | |
2731 | * @return course_request The newly created course request | |
2732 | */ | |
2733 | public static function create($data) { | |
2734 | global $USER, $DB, $CFG; | |
2735 | $data->requester = $USER->id; | |
64f93798 | 2736 | |
d347f304 | 2737 | // Setting the default category if none set. |
59b9a140 FM |
2738 | if (empty($data->category) || empty($CFG->requestcategoryselection)) { |
2739 | $data->category = $CFG->defaultrequestcategory; | |
2740 | } | |
2741 | ||
64f93798 PS |
2742 | // Summary is a required field so copy the text over |
2743 | $data->summary = $data->summary_editor['text']; | |
2744 | $data->summaryformat = $data->summary_editor['format']; | |
2745 | ||
8bdc9cac | 2746 | $data->id = $DB->insert_record('course_request', $data); |
64f93798 | 2747 | |
8bdc9cac SH |
2748 | // Create a new course_request object and return it |
2749 | $request = new course_request($data); | |
2750 | ||
2751 | // Notify the admin if required. | |
adf176d7 | 2752 | if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) { |
aa6c1ced | 2753 | |
8bdc9cac SH |
2754 | $a = new stdClass; |
2755 | $a->link = "$CFG->wwwroot/course/pending.php"; | |
2756 | $a->user = fullname($USER); | |
2757 | $subject = get_string('courserequest'); | |
2758 | $message = get_string('courserequestnotifyemail', 'admin', $a); | |
2759 | foreach ($users as $user) { | |
2a63b636 | 2760 | $request->notify($user, $USER, 'courserequested', $subject, $message); |
8bdc9cac SH |
2761 | } |
2762 | } | |
2763 | ||
2764 | return $request; | |
2765 | } | |
2766 | ||
2767 | /** | |
2768 | * Returns an array of options to use with a summary editor | |
2769 | * | |
2770 | * @uses course_request::$summaryeditoroptions | |
2771 | * @return array An array of options to use with the editor | |
2772 | */ | |
2773 | public static function summary_editor_options() { | |
2774 | global $CFG; | |
2775 | if (self::$summaryeditoroptions === null) { | |
64f93798 | 2776 | self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0); |
8bdc9cac SH |
2777 | } |
2778 | return self::$summaryeditoroptions; | |
2779 | } | |
2780 | ||
8bdc9cac SH |
2781 | /** |
2782 | * Loads the properties for this course request object. Id is required and if | |
2783 | * only id is provided then we load the rest of the properties from the database | |
2784 | * | |
2785 | * @param stdClass|int $properties Either an object containing properties | |
2786 | * or the course_request id to load | |
2787 | */ | |
2788 | public function __construct($properties) { | |
2789 | global $DB; | |
2790 | if (empty($properties->id)) { | |
2791 | if (empty($properties)) { | |
2792 | throw new coding_exception('You must provide a course request id when creating a course_request object'); | |
2793 | } | |
2794 | $id = $properties; | |
2795 | $properties = new stdClass; | |
2796 | $properties->id = (int)$id; | |
2797 | unset($id); | |
2798 | } | |
2799 | if (empty($properties->requester)) { | |
2800 | if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) { | |
2801 | print_error('unknowncourserequest'); | |
2802 | } | |
2803 | } else { | |
2804 | $this->properties = $properties; | |
2805 | } | |
2806 | $this->properties->collision = null; | |
2807 | } | |
2808 | ||
2809 | /** | |
2810 | * Returns the requested property | |
2811 | * | |
2812 | * @param string $key | |
2813 | * @return mixed | |
2814 | */ | |
2815 | public function __get($key) { | |
8bdc9cac SH |
2816 | return $this->properties->$key; |
2817 | } | |
2818 | ||
2819 | /** | |
2820 | * Override this to ensure empty($request->blah) calls return a reliable answer... | |
2821 | * | |
2822 | * This is required because we define the __get method | |
2823 | * | |
2824 | * @param mixed $key | |
2825 | * @return bool True is it not empty, false otherwise | |
2826 | */ | |
2827 | public function __isset($key) { | |
2828 | return (!empty($this->properties->$key)); | |
2829 | } | |
2830 | ||
2831 | /** | |
2832 | * Returns the user who requested this course | |
2833 | * | |
2834 | * Uses a static var to cache the results and cut down the number of db queries | |
2835 | * | |
2836 | * @staticvar array $requesters An array of cached users | |
2837 | * @return stdClass The user who requested the course | |
2838 | */ | |
2839 | public function get_requester() { | |
2840 | global $DB; | |
2841 | static $requesters= array(); | |
2842 | if (!array_key_exists($this->properties->requester, $requesters)) { | |
2843 | $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester)); | |
2844 | } | |
2845 | return $requesters[$this->properties->requester]; | |
2846 | } | |
2847 | ||
2848 | /** | |
2849 | * Checks that the shortname used by the course does not conflict with any other | |
2850 | * courses that exist | |
2851 | * | |
2852 | * @param string|null $shortnamemark The string to append to the requests shortname | |
2853 | * should a conflict be found | |
2854 | * @return bool true is there is a conflict, false otherwise | |
2855 | */ | |
2856 | public function check_shortname_collision($shortnamemark = '[*]') { | |
2857 | global $DB; | |
2858 | ||
2859 | if ($this->properties->collision !== null) { | |
2860 | return $this->properties->collision; | |
2861 | } | |
2862 | ||
2863 | if (empty($this->properties->shortname)) { | |
2864 | debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER); | |
2865 | $this->properties->collision = false; | |
2866 | } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) { | |
2867 | if (!empty($shortnamemark)) { | |
2868 | $this->properties->shortname .= ' '.$shortnamemark; | |
2869 | } | |
2870 | $this->properties->collision = true; | |
2871 | } else { | |
2872 | $this->properties->collision = false; | |
2873 | } | |
2874 | return $this->properties->collision; | |
2875 | } | |
2876 | ||
2d8a275b MG |
2877 | /** |
2878 | * Returns the category where this course request should be created | |
2879 | * | |
2880 | * Note that we don't check here that user has a capability to view | |
2881 | * hidden categories if he has capabilities 'moodle/site:approvecourse' and | |
2882 | * 'moodle/course:changecategory' | |
2883 | * | |
2884 | * @return coursecat | |
2885 | */ | |
2886 | public function get_category() { | |
2887 | global $CFG; | |
2888 | require_once($CFG->libdir.'/coursecatlib.php'); | |
2889 | // If the category is not set, if the current user does not have the rights to change the category, or if the | |
2890 | // category does not exist, we set the default category to the course to be approved. | |
2891 | // The system level is used because the capability moodle/site:approvecourse is based on a system level. | |
2892 | if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) || | |
2893 | (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) { | |
2894 | $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true); | |
2895 | } | |
2896 | if (!$category) { | |
2897 | $category = coursecat::get_default(); | |
2898 | } | |
2899 | return $category; | |
2900 | } | |
2901 | ||
8bdc9cac SH |
2902 | /** |
2903 | * This function approves the request turning it into a course | |
2904 | * | |
2905 | * This function converts the course request into a course, at the same time | |
7e85563d | 2906 | * transferring any files used in the summary to the new course and then removing |
8bdc9cac SH |
2907 | * the course request and the files associated with it. |
2908 | * | |
2909 | * @return int The id of the course that was created from this request | |
2910 | */ | |
2911 | public function approve() { | |
2912 | global $CFG, $DB, $USER; | |
70740405 PS |
2913 | |
2914 | $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST); | |
2915 | ||
8bdc9cac SH |
2916 | $courseconfig = get_config('moodlecourse'); |
2917 | ||
2918 | // Transfer appropriate settings | |
70740405 PS |
2919 | $data = clone($this->properties); |
2920 | unset($data->id); | |
2921 | unset($data->reason); | |
2922 | unset($data->requester); | |
8bdc9cac SH |
2923 | |
2924 | // Set category | |
2d8a275b | 2925 | $category = $this->get_category(); |
70740405 | 2926 | $data->category = $category->id; |
8bdc9cac | 2927 | // Set misc settings |
70740405 | 2928 | $data->requested = 1; |
8bdc9cac SH |
2929 | |
2930 | // Apply course default settings | |
70740405 | 2931 | $data->format = $courseconfig->format; |
70740405 PS |
2932 | $data->newsitems = $courseconfig->newsitems; |
2933 | $data->showgrades = $courseconfig->showgrades; | |
2934 | $data->showreports = $courseconfig->showreports; | |
2935 | $data->maxbytes = $courseconfig->maxbytes; | |
2936 | $data->groupmode = $courseconfig->groupmode; | |
2937 | $data->groupmodeforce = $courseconfig->groupmodeforce; | |
2938 | $data->visible = $courseconfig->visible; | |
2939 | $data->visibleold = $data->visible; | |
2940 | $data->lang = $courseconfig->lang; | |
8bdc9cac | 2941 | |
70740405 | 2942 | $course = create_course($data); |
1f364c87 | 2943 | $context = context_course::instance($course->id, MUST_EXIST); |
8bdc9cac | 2944 | |
70740405 PS |
2945 | // add enrol instances |
2946 | if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) { | |
2947 | if ($manual = enrol_get_plugin('manual')) { | |
2948 | $manual->add_default_instance($course); | |
2949 | } | |
8bdc9cac | 2950 | } |
70740405 PS |
2951 | |
2952 | // enrol the requester as teacher if necessary | |
2953 | if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) { | |
2954 | enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid); | |
2955 | } | |
2956 | ||
2957 | $this->delete(); | |
2958 | ||
fbaea88f | 2959 | $a = new stdClass(); |
9a5e297b | 2960 | $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); |
70740405 PS |
2961 | $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id; |
2962 | $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a)); | |
2963 | ||
2964 | return $course->id; | |
8bdc9cac SH |
2965 | } |
2966 | ||
2967 | /** | |
2968 | * Reject a course request | |
2969 | * | |
2970 | * This function rejects a course request, emailing the requesting user the | |
2971 | * provided notice and then removing the request from the database | |
2972 | * | |
2973 | * @param string $notice The message to display to the user | |
2974 | */ | |
2975 | public function reject($notice) { | |
70740405 PS |
2976 | global $USER, $DB; |
2977 | $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST); | |
8bdc9cac SH |
2978 | $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice)); |
2979 | $this->delete(); | |
2980 | } | |
2981 | ||
2982 | /** | |
2983 | * Deletes the course request and any associated files | |
2984 | */ | |
2985 | public function delete() { | |
2986 | global $DB; | |
2987 | $DB->delete_records('course_request', array('id' => $this->properties->id)); | |
8bdc9cac SH |
2988 | } |
2989 | ||
2990 | /** | |
2991 | * Send a message from one user to another using events_trigger | |
2992 | * | |
2993 | * @param object $touser | |
2994 | * @param object $fromuser | |
2995 | * @param string $name | |
2996 | * @param string $subject | |
2997 | * @param string $message | |
2998 | */ | |
2999 | protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) { | |
fbaea88f | 3000 | $eventdata = new stdClass(); |
a1708181 | 3001 | $eventdata->component = 'moodle'; |
8bdc9cac SH |
3002 | $eventdata->name = $name; |
3003 | $eventdata->userfrom = $fromuser; | |
3004 | $eventdata->userto = $touser; | |
3005 | $eventdata->subject = $subject; | |
3006 | $eventdata->fullmessage = $message; | |
3007 | $eventdata->fullmessageformat = FORMAT_PLAIN; | |
3008 | $eventdata->fullmessagehtml = ''; | |
3009 | $eventdata->smallmessage = ''; | |
a1708181 | 3010 | $eventdata->notification = 1; |
7c7d3afa | 3011 | message_send($eventdata); |
8bdc9cac | 3012 | } |
5c34c4ba | 3013 | } |
b1627a92 DC |
3014 | |
3015 | /** | |
3016 | * Return a list of page types | |
3017 | * @param string $pagetype current page type | |
9dd85edf MG |
3018 | * @param context $parentcontext Block's parent context |
3019 | * @param context $currentcontext Current context of block | |
3020 | * @return array array of page types | |
b1627a92 | 3021 | */ |
b38e2e28 | 3022 | function course_page_type_list($pagetype, $parentcontext, $currentcontext) { |
9dd85edf MG |
3023 | if ($pagetype === 'course-index' || $pagetype === 'course-index-category') { |
3024 | // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page | |
3025 | $pagetypes = array('*' => get_string('page-x', 'pagetype'), | |
3026 | 'course-index-*' => get_string('page-course-index-x', 'pagetype'), | |
3027 | ); | |
3028 | } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) { | |
3029 | // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.) | |
3030 | $pagetypes = array('*' => get_string('page-x', 'pagetype')); | |
3031 | } else { | |
3032 | // Otherwise consider it a page inside a course even if $currentcontext is null | |
3033 | $pagetypes = array('*' => get_string('page-x', 'pagetype'), | |
3034 | 'course-*' => get_string('page-course-x', 'pagetype'), | |
3035 | 'course-view-*' => get_string('page-course-view-x', 'pagetype') | |
3036 | ); | |
b1627a92 | 3037 | } |
9dd85edf | 3038 | return $pagetypes; |
af27c69e | 3039 | } |
ebaa29d1 | 3040 | |
5720019d ARN |
3041 | /** |
3042 | * Determine whether course ajax should be enabled for the specified course | |
3043 | * | |
924c34df | 3044 | * @param stdClass $course The course to test against |
5720019d ARN |
3045 | * @return boolean Whether course ajax is enabled or note |
3046 | */ | |
3047 | function course_ajax_enabled($course) { | |
3048 | global $CFG, $PAGE, $SITE; | |
3049 | ||
3050 | // Ajax must be enabled globally | |
3051 | if (!$CFG->enableajax) { | |
3052 | return false; | |
3053 | } | |
3054 | ||
3055 | // The user must be editing for AJAX to be included | |
3056 | if (!$PAGE->user_is_editing()) { | |
3057 | return false; | |
3058 | } | |
3059 | ||
3060 | // Check that the theme suports | |
3061 | if (!$PAGE->theme->enablecourseajax) { | |
3062 | return false; | |
3063 | } | |
3064 | ||
3065 | // Check that the course format supports ajax functionality | |
3066 | // The site 'format' doesn't have information on course format support | |
3067 | if ($SITE->id !== $course->id) { | |
3068 | $courseformatajaxsupport = course_format_ajax_support($course->format); | |
3069 | if (!$courseformatajaxsupport->capable) { | |
3070 | return false; | |
3071 | } | |
3072 | } | |
3073 | ||
3074 | // All conditions have been met so course ajax should be enabled | |
3075 | return true; | |
3076 | } | |
3077 | ||
ebaa29d1 ARN |
3078 | /** |
3079 | * Include the relevant javascript and language strings for the resource | |
3080 | * toolbox YUI module | |
3081 | * | |
3082 | * @param integer $id The ID of the course being applied to | |
7b061512 SH |
3083 | * @param array $usedmodules An array containing the names of the modules in use on the page |
3084 | * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site | |
924c34df | 3085 | * @param stdClass $config An object containing configuration parameters for ajax modules including: |
ebaa29d1 ARN |
3086 | * * resourceurl The URL to post changes to for resource changes |
3087 | * * sectionurl The URL to post changes to for section changes | |
3088 | * * pageparams Additional parameters to pass through in the post | |
7b061512 | 3089 | * @return bool |
ebaa29d1 | 3090 | */ |
7b061512 | 3091 | function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) { |
52973369 | 3092 | global $CFG, $PAGE, $SITE; |
ebaa29d1 ARN |
3093 | |
3094 | // Ensure that ajax should be included | |
5720019d ARN |
3095 | if (!course_ajax_enabled($course)) { |
3096 | return false; | |
ebaa29d1 ARN |
3097 | } |
3098 | ||
3099 | if (!$config) { | |
3100 | $config = new stdClass(); | |
3101 | } | |
3102 | ||
3103 | // The URL to use for resource changes | |
3104 | if (!isset($config->resourceurl)) { | |
3105 | $config->resourceurl = '/course/rest.php'; | |
3106 | } | |
3107 | ||
3108 | // The URL to use for section changes | |
3109 | if (!isset($config->sectionurl)) { | |
3110 | $config->sectionurl = '/course/rest.php'; | |
3111 | } | |
3112 | ||
3113 | // Any additional parameters which need to be included on page submission | |
3114 | if (!isset($config->pageparams)) { | |
3115 | $config->pageparams = array(); | |
3116 | } | |
3117 | ||
3118 | // Include toolboxes | |
3119 | $PAGE->requires->yui_module('moodle-course-toolboxes', | |
3120 | 'M.course.init_resource_toolbox', | |
3121 | array(array( | |
3122 | 'courseid' => $course->id, | |
3123 | 'ajaxurl' => $config->resourceurl, | |
3124 | 'config' => $config, | |
3125 | )) | |
3126 | ); | |
3127 | $PAGE->requires->yui_module('moodle-course-toolboxes', | |
3128 | 'M.course.init_section_toolbox', | |
3129 | array(array( | |
3130 | 'courseid' => $course->id, | |
3131 | 'format' => $course->format, | |
3132 | 'ajaxurl' => $config->sectionurl, | |
3133 | 'config' => $config, | |
3134 | )) | |
3135 | ); | |
3136 | ||
15e2552f | 3137 | // Include course dragdrop |
5720019d | 3138 | if ($course->id != $SITE->id) { |
c77582fe | 3139 | $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop', |
15e2552f RK |
3140 | array(array( |
3141 | 'courseid' => $course->id, | |
56838156 | 3142 | 'ajaxurl' => $config->sectionurl, |
15e2552f RK |
3143 | 'config' => $config, |
3144 | )), null, true); | |
3145 | ||
c77582fe | 3146 | $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop', |
15e2552f RK |
3147 | array(array( |
3148 | 'courseid' => $course->id, | |
56838156 | 3149 | 'ajaxurl' => $config->resourceurl, |
15e2552f RK |
3150 | 'config' => $config, |
3151 | )), null, true); | |
56838156 RK |
3152 | } |
3153 | ||
ebaa29d1 ARN |
3154 | // Require various strings for the command toolbox |
3155 | $PAGE->requires->strings_for_js(array( | |
3156 | 'moveleft', | |
3157 | 'deletechecktype', | |
3158 | 'deletechecktypename', | |
7a9a07d2 ARN |
3159 | 'edittitle', |
3160 | 'edittitleinstructions', | |
ebaa29d1 ARN |
3161 | 'show', |
3162 | 'hide', | |
3163 | 'groupsnone', | |
3164 | 'groupsvisible', | |
3165 | 'groupsseparate', | |
3166 | 'clicktochangeinbrackets', | |
3167 | 'markthistopic', | |
3168 | 'markedthistopic', | |
15e2552f RK |
3169 | 'move', |
3170 | 'movesection', | |
dd66b6ab | 3171 | 'movecontent', |
bbb483b2 | 3172 | 'tocontent', |
dd66b6ab | 3173 | 'emptydragdropregion' |
ebaa29d1 ARN |
3174 | ), 'moodle'); |
3175 | ||
3176 | // Include format-specific strings | |
5720019d | 3177 | if ($course->id != $SITE->id) { |
ebaa29d1 ARN |
3178 | $PAGE->requires->strings_for_js(array( |
3179 | 'showfromothers', | |
3180 | 'hidefromothers', | |
3181 | ), 'format_' . $course->format); | |
3182 | } | |
3183 | ||
3184 | // For confirming resource deletion we need the name of the module in question | |
7b061512 | 3185 | foreach ($usedmodules as $module => $modname) { |
ebaa29d1 ARN |
3186 | $PAGE->requires->string_for_js('pluginname', $module); |
3187 | } | |
3aaa1843 | 3188 | |
32528f94 | 3189 | // Load drag and drop upload AJAX. |
52973369 | 3190 | require_once($CFG->dirroot.'/course/dnduploadlib.php'); |
7b061512 SH |
3191 | dndupload_add_to_course($course, $enabledmodules); |
3192 | ||
5720019d | 3193 | return true; |
ebaa29d1 | 3194 | } |
5218b9bb | 3195 | |
3776335c MG |
3196 | /** |
3197 | * Returns the sorted list of available course formats, filtered by enabled if necessary | |
3198 | * | |
3199 | * @param bool $enabledonly return only formats that are enabled | |
3200 | * @return array array of sorted format names | |
3201 | */ | |
3202 | function get_sorted_course_formats($enabledonly = false) { | |
3203 | global $CFG; | |
bd3b3bba | 3204 | $formats = core_component::get_plugin_list('format'); |
3776335c MG |
3205 | |
3206 | if (!empty($CFG->format_plugins_sortorder)) { | |
3207 | $order = explode(',', $CFG->format_plugins_sortorder); | |
3208 | $order = array_merge(array_intersect($order, array_keys($formats)), | |
3209 | array_diff(array_keys($formats), $order)); | |
3210 | } else { | |
3211 | $order = array_keys($formats); | |
3212 | } | |
3213 | if (!$enabledonly) { | |
3214 | return $order; | |
3215 | } | |
3216 | $sortedformats = array(); | |
3217 | foreach ($order as $formatname) { | |
3218 | if (!get_config('format_'.$formatname, 'disabled')) { | |
3219 | $sortedformats[] = $formatname; | |
3220 | } | |
3221 | } | |
3222 | return $sortedformats; | |
3223 | } | |
3224 | ||
5218b9bb DP |
3225 | /** |
3226 | * The URL to use for the specified course (with section) | |
3227 | * | |
ee7084e9 MG |
3228 | * @param int|stdClass $courseorid The course to get the section name for (either object or just course id) |
3229 | * @param int|stdClass $section Section object from database or just field course_sections.section | |
923451c5 MG |
3230 | * if omitted the course view page is returned |
3231 | * @param array $options options for view URL. At the moment core uses: | |
3232 | * 'navigation' (bool) if true and section has no separate page, the function returns null | |
3233 | * 'sr' (int) used by multipage formats to specify to which section to return | |
5218b9bb DP |
3234 | * @return moodle_url The url of course |
3235 | */ | |
ee7084e9 MG |
3236 | function course_get_url($courseorid, $section = null, $options = array()) { |
3237 | return course_get_format($courseorid)->get_view_url($section, $options); | |
5218b9bb | 3238 | } |
dd5d933f | 3239 | |
dd5d933f JM |
3240 | /** |
3241 | * Create a module. | |
7cb0ea2c | 3242 | * |
dd5d933f JM |
3243 | * It includes: |
3244 | * - capability checks and other checks | |
3245 | * - create the module from the module info | |
3246 | * | |
3247 | * @param object $module | |
3248 | * @return object the created module info | |
3249 | */ | |
3250 | function create_module($moduleinfo) { | |
74df2951 | 3251 | global $DB, $CFG; |
80fe0c19 JM |
3252 | |
3253 | require_once($CFG->dirroot . '/course/modlib.php'); | |
3254 | ||
dd5d933f JM |
3255 | // Check manadatory attributs. |
3256 | $mandatoryfields = array('modulename', 'course', 'section', 'visible'); | |
3257 | if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) { | |
3258 | $mandatoryfields[] = 'introeditor'; | |
3259 | } | |