Merge branch 'MDL-22504_drag_and_drop_upload_final' of https://github.com/davosmith...
[moodle.git] / course / lib.php
blob8b78b51870109848d93b13d3d994538489d5771f
1 <?php
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.
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/>.
18 /**
19 * Library of useful functions
21 * @copyright 1999 Martin Dougiamas http://dougiamas.com
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @package core
24 * @subpackage course
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir.'/completionlib.php');
30 require_once($CFG->libdir.'/filelib.php');
31 require_once($CFG->dirroot.'/course/dnduploadlib.php');
33 define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records
34 define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds
35 define('COURSE_MAX_SUMMARIES_PER_PAGE', 10); // courses
36 define('COURSE_MAX_COURSES_PER_DROPDOWN',1000); // max courses in log dropdown before switching to optional
37 define('COURSE_MAX_USERS_PER_DROPDOWN',1000); // max users in log dropdown before switching to optional
38 define('FRONTPAGENEWS', '0');
39 define('FRONTPAGECOURSELIST', '1');
40 define('FRONTPAGECATEGORYNAMES', '2');
41 define('FRONTPAGETOPICONLY', '3');
42 define('FRONTPAGECATEGORYCOMBO', '4');
43 define('FRONTPAGECOURSELIMIT', 200); // maximum number of courses displayed on the frontpage
44 define('EXCELROWS', 65535);
45 define('FIRSTUSEDEXCELROW', 3);
47 define('MOD_CLASS_ACTIVITY', 0);
48 define('MOD_CLASS_RESOURCE', 1);
50 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
51 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
53 function make_log_url($module, $url) {
54 switch ($module) {
55 case 'course':
56 if (strpos($url, 'report/') === 0) {
57 // there is only one report type, course reports are deprecated
58 $url = "/$url";
59 break;
61 case 'file':
62 case 'login':
63 case 'lib':
64 case 'admin':
65 case 'calendar':
66 case 'mnet course':
67 if (strpos($url, '../') === 0) {
68 $url = ltrim($url, '.');
69 } else {
70 $url = "/course/$url";
72 break;
73 case 'user':
74 case 'blog':
75 $url = "/$module/$url";
76 break;
77 case 'upload':
78 $url = $url;
79 break;
80 case 'coursetags':
81 $url = '/'.$url;
82 break;
83 case 'library':
84 case '':
85 $url = '/';
86 break;
87 case 'message':
88 $url = "/message/$url";
89 break;
90 case 'notes':
91 $url = "/notes/$url";
92 break;
93 case 'tag':
94 $url = "/tag/$url";
95 break;
96 case 'role':
97 $url = '/'.$url;
98 break;
99 default:
100 $url = "/mod/$module/$url";
101 break;
104 //now let's sanitise urls - there might be some ugly nasties:-(
105 $parts = explode('?', $url);
106 $script = array_shift($parts);
107 if (strpos($script, 'http') === 0) {
108 $script = clean_param($script, PARAM_URL);
109 } else {
110 $script = clean_param($script, PARAM_PATH);
113 $query = '';
114 if ($parts) {
115 $query = implode('', $parts);
116 $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
117 $parts = explode('&', $query);
118 $eq = urlencode('=');
119 foreach ($parts as $key=>$part) {
120 $part = urlencode(urldecode($part));
121 $part = str_replace($eq, '=', $part);
122 $parts[$key] = $part;
124 $query = '?'.implode('&amp;', $parts);
127 return $script.$query;
131 function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
132 $modname="", $modid=0, $modaction="", $groupid=0) {
133 global $CFG, $DB;
135 // It is assumed that $date is the GMT time of midnight for that day,
136 // and so the next 86400 seconds worth of logs are printed.
138 /// Setup for group handling.
140 // TODO: I don't understand group/context/etc. enough to be able to do
141 // something interesting with it here
142 // What is the context of a remote course?
144 /// If the group mode is separate, and this user does not have editing privileges,
145 /// then only the user's group can be viewed.
146 //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
147 // $groupid = get_current_group($course->id);
149 /// If this course doesn't have groups, no groupid can be specified.
150 //else if (!$course->groupmode) {
151 // $groupid = 0;
154 $groupid = 0;
156 $joins = array();
157 $where = '';
159 $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
160 FROM {mnet_log} l
161 LEFT JOIN {user} u ON l.userid = u.id
162 WHERE ";
163 $params = array();
165 $where .= "l.hostid = :hostid";
166 $params['hostid'] = $hostid;
168 // TODO: Is 1 really a magic number referring to the sitename?
169 if ($course != SITEID || $modid != 0) {
170 $where .= " AND l.course=:courseid";
171 $params['courseid'] = $course;
174 if ($modname) {
175 $where .= " AND l.module = :modname";
176 $params['modname'] = $modname;
179 if ('site_errors' === $modid) {
180 $where .= " AND ( l.action='error' OR l.action='infected' )";
181 } else if ($modid) {
182 //TODO: This assumes that modids are the same across sites... probably
183 //not true
184 $where .= " AND l.cmid = :modid";
185 $params['modid'] = $modid;
188 if ($modaction) {
189 $firstletter = substr($modaction, 0, 1);
190 if ($firstletter == '-') {
191 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
192 $params['modaction'] = '%'.substr($modaction, 1).'%';
193 } else {
194 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
195 $params['modaction'] = '%'.$modaction.'%';
199 if ($user) {
200 $where .= " AND l.userid = :user";
201 $params['user'] = $user;
204 if ($date) {
205 $enddate = $date + 86400;
206 $where .= " AND l.time > :date AND l.time < :enddate";
207 $params['date'] = $date;
208 $params['enddate'] = $enddate;
211 $result = array();
212 $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
213 if(!empty($result['totalcount'])) {
214 $where .= " ORDER BY $order";
215 $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
216 } else {
217 $result['logs'] = array();
219 return $result;
222 function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
223 $modname="", $modid=0, $modaction="", $groupid=0) {
224 global $DB, $SESSION, $USER;
225 // It is assumed that $date is the GMT time of midnight for that day,
226 // and so the next 86400 seconds worth of logs are printed.
228 /// Setup for group handling.
230 /// If the group mode is separate, and this user does not have editing privileges,
231 /// then only the user's group can be viewed.
232 if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
233 if (isset($SESSION->currentgroup[$course->id])) {
234 $groupid = $SESSION->currentgroup[$course->id];
235 } else {
236 $groupid = groups_get_all_groups($course->id, $USER->id);
237 if (is_array($groupid)) {
238 $groupid = array_shift(array_keys($groupid));
239 $SESSION->currentgroup[$course->id] = $groupid;
240 } else {
241 $groupid = 0;
245 /// If this course doesn't have groups, no groupid can be specified.
246 else if (!$course->groupmode) {
247 $groupid = 0;
250 $joins = array();
251 $params = array();
253 if ($course->id != SITEID || $modid != 0) {
254 $joins[] = "l.course = :courseid";
255 $params['courseid'] = $course->id;
258 if ($modname) {
259 $joins[] = "l.module = :modname";
260 $params['modname'] = $modname;
263 if ('site_errors' === $modid) {
264 $joins[] = "( l.action='error' OR l.action='infected' )";
265 } else if ($modid) {
266 $joins[] = "l.cmid = :modid";
267 $params['modid'] = $modid;
270 if ($modaction) {
271 $firstletter = substr($modaction, 0, 1);
272 if ($firstletter == '-') {
273 $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
274 $params['modaction'] = '%'.substr($modaction, 1).'%';
275 } else {
276 $joins[] = $DB->sql_like('l.action', ':modaction', false);
277 $params['modaction'] = '%'.$modaction.'%';
282 /// Getting all members of a group.
283 if ($groupid and !$user) {
284 if ($gusers = groups_get_members($groupid)) {
285 $gusers = array_keys($gusers);
286 $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
287 } else {
288 $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
291 else if ($user) {
292 $joins[] = "l.userid = :userid";
293 $params['userid'] = $user;
296 if ($date) {
297 $enddate = $date + 86400;
298 $joins[] = "l.time > :date AND l.time < :enddate";
299 $params['date'] = $date;
300 $params['enddate'] = $enddate;
303 $selector = implode(' AND ', $joins);
305 $totalcount = 0; // Initialise
306 $result = array();
307 $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
308 $result['totalcount'] = $totalcount;
309 return $result;
313 function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
314 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
316 global $CFG, $DB, $OUTPUT;
318 if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
319 $modname, $modid, $modaction, $groupid)) {
320 echo $OUTPUT->notification("No logs found!");
321 echo $OUTPUT->footer();
322 exit;
325 $courses = array();
327 if ($course->id == SITEID) {
328 $courses[0] = '';
329 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
330 foreach ($ccc as $cc) {
331 $courses[$cc->id] = $cc->shortname;
334 } else {
335 $courses[$course->id] = $course->shortname;
338 $totalcount = $logs['totalcount'];
339 $count=0;
340 $ldcache = array();
341 $tt = getdate(time());
342 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
344 $strftimedatetime = get_string("strftimedatetime");
346 echo "<div class=\"info\">\n";
347 print_string("displayingrecords", "", $totalcount);
348 echo "</div>\n";
350 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
352 $table = new html_table();
353 $table->classes = array('logtable','generalbox');
354 $table->align = array('right', 'left', 'left');
355 $table->head = array(
356 get_string('time'),
357 get_string('ip_address'),
358 get_string('fullnameuser'),
359 get_string('action'),
360 get_string('info')
362 $table->data = array();
364 if ($course->id == SITEID) {
365 array_unshift($table->align, 'left');
366 array_unshift($table->head, get_string('course'));
369 // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
370 if (empty($logs['logs'])) {
371 $logs['logs'] = array();
374 foreach ($logs['logs'] as $log) {
376 if (isset($ldcache[$log->module][$log->action])) {
377 $ld = $ldcache[$log->module][$log->action];
378 } else {
379 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
380 $ldcache[$log->module][$log->action] = $ld;
382 if ($ld && is_numeric($log->info)) {
383 // ugly hack to make sure fullname is shown correctly
384 if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
385 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
386 } else {
387 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
391 //Filter log->info
392 $log->info = format_string($log->info);
394 // If $log->url has been trimmed short by the db size restriction
395 // code in add_to_log, keep a note so we don't add a link to a broken url
396 $brokenurl=(textlib::strlen($log->url)==100 && textlib::substr($log->url,97)=='...');
398 $row = array();
399 if ($course->id == SITEID) {
400 if (empty($log->course)) {
401 $row[] = get_string('site');
402 } else {
403 $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
407 $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
409 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
410 $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
412 $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $course->id))));
414 $displayaction="$log->module $log->action";
415 if ($brokenurl) {
416 $row[] = $displayaction;
417 } else {
418 $link = make_log_url($log->module,$log->url);
419 $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
421 $row[] = $log->info;
422 $table->data[] = $row;
425 echo html_writer::table($table);
426 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
430 function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
431 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
433 global $CFG, $DB, $OUTPUT;
435 if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
436 $modname, $modid, $modaction, $groupid)) {
437 echo $OUTPUT->notification("No logs found!");
438 echo $OUTPUT->footer();
439 exit;
442 if ($course->id == SITEID) {
443 $courses[0] = '';
444 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
445 foreach ($ccc as $cc) {
446 $courses[$cc->id] = $cc->shortname;
451 $totalcount = $logs['totalcount'];
452 $count=0;
453 $ldcache = array();
454 $tt = getdate(time());
455 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
457 $strftimedatetime = get_string("strftimedatetime");
459 echo "<div class=\"info\">\n";
460 print_string("displayingrecords", "", $totalcount);
461 echo "</div>\n";
463 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
465 echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
466 echo "<tr>";
467 if ($course->id == SITEID) {
468 echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
470 echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
471 echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
472 echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
473 echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
474 echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
475 echo "</tr>\n";
477 if (empty($logs['logs'])) {
478 echo "</table>\n";
479 return;
482 $row = 1;
483 foreach ($logs['logs'] as $log) {
485 $log->info = $log->coursename;
486 $row = ($row + 1) % 2;
488 if (isset($ldcache[$log->module][$log->action])) {
489 $ld = $ldcache[$log->module][$log->action];
490 } else {
491 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
492 $ldcache[$log->module][$log->action] = $ld;
494 if (0 && $ld && !empty($log->info)) {
495 // ugly hack to make sure fullname is shown correctly
496 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
497 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
498 } else {
499 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
503 //Filter log->info
504 $log->info = format_string($log->info);
506 echo '<tr class="r'.$row.'">';
507 if ($course->id == SITEID) {
508 $courseshortname = format_string($courses[$log->course], true, array('context' => get_context_instance(CONTEXT_COURSE, SITEID)));
509 echo "<td class=\"r$row c0\" >\n";
510 echo " <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
511 echo "</td>\n";
513 echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
514 ' '.userdate($log->time, $strftimedatetime)."</td>\n";
515 echo "<td class=\"r$row c2\" >\n";
516 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
517 echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
518 echo "</td>\n";
519 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $course->id)));
520 echo "<td class=\"r$row c3\" >\n";
521 echo " <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
522 echo "</td>\n";
523 echo "<td class=\"r$row c4\">\n";
524 echo $log->action .': '.$log->module;
525 echo "</td>\n";;
526 echo "<td class=\"r$row c5\">{$log->info}</td>\n";
527 echo "</tr>\n";
529 echo "</table>\n";
531 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
535 function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
536 $modid, $modaction, $groupid) {
537 global $DB;
539 $text = get_string('course')."\t".get_string('time')."\t".get_string('ip_address')."\t".
540 get_string('fullnameuser')."\t".get_string('action')."\t".get_string('info');
542 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
543 $modname, $modid, $modaction, $groupid)) {
544 return false;
547 $courses = array();
549 if ($course->id == SITEID) {
550 $courses[0] = '';
551 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
552 foreach ($ccc as $cc) {
553 $courses[$cc->id] = $cc->shortname;
556 } else {
557 $courses[$course->id] = $course->shortname;
560 $count=0;
561 $ldcache = array();
562 $tt = getdate(time());
563 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
565 $strftimedatetime = get_string("strftimedatetime");
567 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
568 $filename .= '.txt';
569 header("Content-Type: application/download\n");
570 header("Content-Disposition: attachment; filename=\"$filename\"");
571 header("Expires: 0");
572 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
573 header("Pragma: public");
575 echo get_string('savedat').userdate(time(), $strftimedatetime)."\n";
576 echo $text."\n";
578 if (empty($logs['logs'])) {
579 return true;
582 foreach ($logs['logs'] as $log) {
583 if (isset($ldcache[$log->module][$log->action])) {
584 $ld = $ldcache[$log->module][$log->action];
585 } else {
586 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
587 $ldcache[$log->module][$log->action] = $ld;
589 if ($ld && !empty($log->info)) {
590 // ugly hack to make sure fullname is shown correctly
591 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
592 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
593 } else {
594 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
598 //Filter log->info
599 $log->info = format_string($log->info);
600 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
602 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
603 $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
604 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
605 $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action, $log->info);
606 $text = implode("\t", $row);
607 echo $text." \n";
609 return true;
613 function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
614 $modid, $modaction, $groupid) {
616 global $CFG, $DB;
618 require_once("$CFG->libdir/excellib.class.php");
620 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
621 $modname, $modid, $modaction, $groupid)) {
622 return false;
625 $courses = array();
627 if ($course->id == SITEID) {
628 $courses[0] = '';
629 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
630 foreach ($ccc as $cc) {
631 $courses[$cc->id] = $cc->shortname;
634 } else {
635 $courses[$course->id] = $course->shortname;
638 $count=0;
639 $ldcache = array();
640 $tt = getdate(time());
641 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
643 $strftimedatetime = get_string("strftimedatetime");
645 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
646 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
647 $filename .= '.xls';
649 $workbook = new MoodleExcelWorkbook('-');
650 $workbook->send($filename);
652 $worksheet = array();
653 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
654 get_string('fullnameuser'), get_string('action'), get_string('info'));
656 // Creating worksheets
657 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
658 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
659 $worksheet[$wsnumber] =& $workbook->add_worksheet($sheettitle);
660 $worksheet[$wsnumber]->set_column(1, 1, 30);
661 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
662 userdate(time(), $strftimedatetime));
663 $col = 0;
664 foreach ($headers as $item) {
665 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
666 $col++;
670 if (empty($logs['logs'])) {
671 $workbook->close();
672 return true;
675 $formatDate =& $workbook->add_format();
676 $formatDate->set_num_format(get_string('log_excel_date_format'));
678 $row = FIRSTUSEDEXCELROW;
679 $wsnumber = 1;
680 $myxls =& $worksheet[$wsnumber];
681 foreach ($logs['logs'] as $log) {
682 if (isset($ldcache[$log->module][$log->action])) {
683 $ld = $ldcache[$log->module][$log->action];
684 } else {
685 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
686 $ldcache[$log->module][$log->action] = $ld;
688 if ($ld && !empty($log->info)) {
689 // ugly hack to make sure fullname is shown correctly
690 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
691 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
692 } else {
693 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
697 // Filter log->info
698 $log->info = format_string($log->info);
699 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
701 if ($nroPages>1) {
702 if ($row > EXCELROWS) {
703 $wsnumber++;
704 $myxls =& $worksheet[$wsnumber];
705 $row = FIRSTUSEDEXCELROW;
709 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
711 $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
712 $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
713 $myxls->write($row, 2, $log->ip, '');
714 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
715 $myxls->write($row, 3, $fullname, '');
716 $myxls->write($row, 4, $log->module.' '.$log->action, '');
717 $myxls->write($row, 5, $log->info, '');
719 $row++;
722 $workbook->close();
723 return true;
726 function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
727 $modid, $modaction, $groupid) {
729 global $CFG, $DB;
731 require_once("$CFG->libdir/odslib.class.php");
733 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
734 $modname, $modid, $modaction, $groupid)) {
735 return false;
738 $courses = array();
740 if ($course->id == SITEID) {
741 $courses[0] = '';
742 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
743 foreach ($ccc as $cc) {
744 $courses[$cc->id] = $cc->shortname;
747 } else {
748 $courses[$course->id] = $course->shortname;
751 $count=0;
752 $ldcache = array();
753 $tt = getdate(time());
754 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
756 $strftimedatetime = get_string("strftimedatetime");
758 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
759 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
760 $filename .= '.ods';
762 $workbook = new MoodleODSWorkbook('-');
763 $workbook->send($filename);
765 $worksheet = array();
766 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
767 get_string('fullnameuser'), get_string('action'), get_string('info'));
769 // Creating worksheets
770 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
771 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
772 $worksheet[$wsnumber] =& $workbook->add_worksheet($sheettitle);
773 $worksheet[$wsnumber]->set_column(1, 1, 30);
774 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
775 userdate(time(), $strftimedatetime));
776 $col = 0;
777 foreach ($headers as $item) {
778 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
779 $col++;
783 if (empty($logs['logs'])) {
784 $workbook->close();
785 return true;
788 $formatDate =& $workbook->add_format();
789 $formatDate->set_num_format(get_string('log_excel_date_format'));
791 $row = FIRSTUSEDEXCELROW;
792 $wsnumber = 1;
793 $myxls =& $worksheet[$wsnumber];
794 foreach ($logs['logs'] as $log) {
795 if (isset($ldcache[$log->module][$log->action])) {
796 $ld = $ldcache[$log->module][$log->action];
797 } else {
798 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
799 $ldcache[$log->module][$log->action] = $ld;
801 if ($ld && !empty($log->info)) {
802 // ugly hack to make sure fullname is shown correctly
803 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
804 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
805 } else {
806 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
810 // Filter log->info
811 $log->info = format_string($log->info);
812 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
814 if ($nroPages>1) {
815 if ($row > EXCELROWS) {
816 $wsnumber++;
817 $myxls =& $worksheet[$wsnumber];
818 $row = FIRSTUSEDEXCELROW;
822 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
824 $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
825 $myxls->write_date($row, 1, $log->time);
826 $myxls->write_string($row, 2, $log->ip);
827 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
828 $myxls->write_string($row, 3, $fullname);
829 $myxls->write_string($row, 4, $log->module.' '.$log->action);
830 $myxls->write_string($row, 5, $log->info);
832 $row++;
835 $workbook->close();
836 return true;
840 function print_overview($courses, array $remote_courses=array()) {
841 global $CFG, $USER, $DB, $OUTPUT;
843 $htmlarray = array();
844 if ($modules = $DB->get_records('modules')) {
845 foreach ($modules as $mod) {
846 if (file_exists(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php')) {
847 include_once(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php');
848 $fname = $mod->name.'_print_overview';
849 if (function_exists($fname)) {
850 $fname($courses,$htmlarray);
855 foreach ($courses as $course) {
856 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
857 echo $OUTPUT->box_start('coursebox');
858 $attributes = array('title' => s($fullname));
859 if (empty($course->visible)) {
860 $attributes['class'] = 'dimmed';
862 echo $OUTPUT->heading(html_writer::link(
863 new moodle_url('/course/view.php', array('id' => $course->id)), $fullname, $attributes), 3);
864 if (array_key_exists($course->id,$htmlarray)) {
865 foreach ($htmlarray[$course->id] as $modname => $html) {
866 echo $html;
869 echo $OUTPUT->box_end();
872 if (!empty($remote_courses)) {
873 echo $OUTPUT->heading(get_string('remotecourses', 'mnet'));
875 foreach ($remote_courses as $course) {
876 echo $OUTPUT->box_start('coursebox');
877 $attributes = array('title' => s($course->fullname));
878 echo $OUTPUT->heading(html_writer::link(
879 new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
880 format_string($course->shortname),
881 $attributes) . ' (' . format_string($course->hostname) . ')', 3);
882 echo $OUTPUT->box_end();
888 * This function trawls through the logs looking for
889 * anything new since the user's last login
891 function print_recent_activity($course) {
892 // $course is an object
893 global $CFG, $USER, $SESSION, $DB, $OUTPUT;
895 $context = get_context_instance(CONTEXT_COURSE, $course->id);
897 $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
899 $timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
901 if (!isguestuser()) {
902 if (!empty($USER->lastcourseaccess[$course->id])) {
903 if ($USER->lastcourseaccess[$course->id] > $timestart) {
904 $timestart = $USER->lastcourseaccess[$course->id];
909 echo '<div class="activitydate">';
910 echo get_string('activitysince', '', userdate($timestart));
911 echo '</div>';
912 echo '<div class="activityhead">';
914 echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
916 echo "</div>\n";
918 $content = false;
920 /// Firstly, have there been any new enrolments?
922 $users = get_recent_enrolments($course->id, $timestart);
924 //Accessibility: new users now appear in an <OL> list.
925 if ($users) {
926 echo '<div class="newusers">';
927 echo $OUTPUT->heading(get_string("newusers").':', 3);
928 $content = true;
929 echo "<ol class=\"list\">\n";
930 foreach ($users as $user) {
931 $fullname = fullname($user, $viewfullnames);
932 echo '<li class="name"><a href="'."$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a></li>\n";
934 echo "</ol>\n</div>\n";
937 /// Next, have there been any modifications to the course structure?
939 $modinfo = get_fast_modinfo($course);
941 $changelist = array();
943 $logs = $DB->get_records_select('log', "time > ? AND course = ? AND
944 module = 'course' AND
945 (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
946 array($timestart, $course->id), "id ASC");
948 if ($logs) {
949 $actions = array('add mod', 'update mod', 'delete mod');
950 $newgones = array(); // added and later deleted items
951 foreach ($logs as $key => $log) {
952 if (!in_array($log->action, $actions)) {
953 continue;
955 $info = explode(' ', $log->info);
957 // note: in most cases I replaced hardcoding of label with use of
958 // $cm->has_view() but it was not possible to do this here because
959 // we don't necessarily have the $cm for it
960 if ($info[0] == 'label') { // Labels are ignored in recent activity
961 continue;
964 if (count($info) != 2) {
965 debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
966 continue;
969 $modname = $info[0];
970 $instanceid = $info[1];
972 if ($log->action == 'delete mod') {
973 // unfortunately we do not know if the mod was visible
974 if (!array_key_exists($log->info, $newgones)) {
975 $strdeleted = get_string('deletedactivity', 'moodle', get_string('modulename', $modname));
976 $changelist[$log->info] = array ('operation' => 'delete', 'text' => $strdeleted);
978 } else {
979 if (!isset($modinfo->instances[$modname][$instanceid])) {
980 if ($log->action == 'add mod') {
981 // do not display added and later deleted activities
982 $newgones[$log->info] = true;
984 continue;
986 $cm = $modinfo->instances[$modname][$instanceid];
987 if (!$cm->uservisible) {
988 continue;
991 if ($log->action == 'add mod') {
992 $stradded = get_string('added', 'moodle', get_string('modulename', $modname));
993 $changelist[$log->info] = array('operation' => 'add', 'text' => "$stradded:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
995 } else if ($log->action == 'update mod' and empty($changelist[$log->info])) {
996 $strupdated = get_string('updated', 'moodle', get_string('modulename', $modname));
997 $changelist[$log->info] = array('operation' => 'update', 'text' => "$strupdated:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
1003 if (!empty($changelist)) {
1004 echo $OUTPUT->heading(get_string("courseupdates").':', 3);
1005 $content = true;
1006 foreach ($changelist as $changeinfo => $change) {
1007 echo '<p class="activity">'.$change['text'].'</p>';
1011 /// Now display new things from each module
1013 $usedmodules = array();
1014 foreach($modinfo->cms as $cm) {
1015 if (isset($usedmodules[$cm->modname])) {
1016 continue;
1018 if (!$cm->uservisible) {
1019 continue;
1021 $usedmodules[$cm->modname] = $cm->modname;
1024 foreach ($usedmodules as $modname) { // Each module gets it's own logs and prints them
1025 if (file_exists($CFG->dirroot.'/mod/'.$modname.'/lib.php')) {
1026 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
1027 $print_recent_activity = $modname.'_print_recent_activity';
1028 if (function_exists($print_recent_activity)) {
1029 // NOTE: original $isteacher (second parameter below) was replaced with $viewfullnames!
1030 $content = $print_recent_activity($course, $viewfullnames, $timestart) || $content;
1032 } else {
1033 debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
1037 if (! $content) {
1038 echo '<p class="message">'.get_string('nothingnew').'</p>';
1043 * For a given course, returns an array of course activity objects
1044 * Each item in the array contains he following properties:
1046 function get_array_of_activities($courseid) {
1047 // cm - course module id
1048 // mod - name of the module (eg forum)
1049 // section - the number of the section (eg week or topic)
1050 // name - the name of the instance
1051 // visible - is the instance visible or not
1052 // groupingid - grouping id
1053 // groupmembersonly - is this instance visible to group members only
1054 // extra - contains extra string to include in any link
1055 global $CFG, $DB;
1056 if(!empty($CFG->enableavailability)) {
1057 require_once($CFG->libdir.'/conditionlib.php');
1060 $course = $DB->get_record('course', array('id'=>$courseid));
1062 if (empty($course)) {
1063 throw new moodle_exception('courseidnotfound');
1066 $mod = array();
1068 $rawmods = get_course_mods($courseid);
1069 if (empty($rawmods)) {
1070 return $mod; // always return array
1073 if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
1074 foreach ($sections as $section) {
1075 if (!empty($section->sequence)) {
1076 $sequence = explode(",", $section->sequence);
1077 foreach ($sequence as $seq) {
1078 if (empty($rawmods[$seq])) {
1079 continue;
1081 $mod[$seq] = new stdClass();
1082 $mod[$seq]->id = $rawmods[$seq]->instance;
1083 $mod[$seq]->cm = $rawmods[$seq]->id;
1084 $mod[$seq]->mod = $rawmods[$seq]->modname;
1086 // Oh dear. Inconsistent names left here for backward compatibility.
1087 $mod[$seq]->section = $section->section;
1088 $mod[$seq]->sectionid = $rawmods[$seq]->section;
1090 $mod[$seq]->module = $rawmods[$seq]->module;
1091 $mod[$seq]->added = $rawmods[$seq]->added;
1092 $mod[$seq]->score = $rawmods[$seq]->score;
1093 $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
1094 $mod[$seq]->visible = $rawmods[$seq]->visible;
1095 $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
1096 $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
1097 $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
1098 $mod[$seq]->groupmembersonly = $rawmods[$seq]->groupmembersonly;
1099 $mod[$seq]->indent = $rawmods[$seq]->indent;
1100 $mod[$seq]->completion = $rawmods[$seq]->completion;
1101 $mod[$seq]->extra = "";
1102 $mod[$seq]->completiongradeitemnumber =
1103 $rawmods[$seq]->completiongradeitemnumber;
1104 $mod[$seq]->completionview = $rawmods[$seq]->completionview;
1105 $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
1106 $mod[$seq]->availablefrom = $rawmods[$seq]->availablefrom;
1107 $mod[$seq]->availableuntil = $rawmods[$seq]->availableuntil;
1108 $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
1109 $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
1110 if (!empty($CFG->enableavailability)) {
1111 condition_info::fill_availability_conditions($rawmods[$seq]);
1112 $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
1113 $mod[$seq]->conditionsgrade = $rawmods[$seq]->conditionsgrade;
1116 $modname = $mod[$seq]->mod;
1117 $functionname = $modname."_get_coursemodule_info";
1119 if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
1120 continue;
1123 include_once("$CFG->dirroot/mod/$modname/lib.php");
1125 if ($hasfunction = function_exists($functionname)) {
1126 if ($info = $functionname($rawmods[$seq])) {
1127 if (!empty($info->icon)) {
1128 $mod[$seq]->icon = $info->icon;
1130 if (!empty($info->iconcomponent)) {
1131 $mod[$seq]->iconcomponent = $info->iconcomponent;
1133 if (!empty($info->name)) {
1134 $mod[$seq]->name = $info->name;
1136 if ($info instanceof cached_cm_info) {
1137 // When using cached_cm_info you can include three new fields
1138 // that aren't available for legacy code
1139 if (!empty($info->content)) {
1140 $mod[$seq]->content = $info->content;
1142 if (!empty($info->extraclasses)) {
1143 $mod[$seq]->extraclasses = $info->extraclasses;
1145 if (!empty($info->iconurl)) {
1146 $mod[$seq]->iconurl = $info->iconurl;
1148 if (!empty($info->onclick)) {
1149 $mod[$seq]->onclick = $info->onclick;
1151 if (!empty($info->customdata)) {
1152 $mod[$seq]->customdata = $info->customdata;
1154 } else {
1155 // When using a stdclass, the (horrible) deprecated ->extra field
1156 // is available for BC
1157 if (!empty($info->extra)) {
1158 $mod[$seq]->extra = $info->extra;
1163 // When there is no modname_get_coursemodule_info function,
1164 // but showdescriptions is enabled, then we use the 'intro'
1165 // and 'introformat' fields in the module table
1166 if (!$hasfunction && $rawmods[$seq]->showdescription) {
1167 if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
1168 array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
1169 // Set content from intro and introformat. Filters are disabled
1170 // because we filter it with format_text at display time
1171 $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
1172 $modvalues, $rawmods[$seq]->id, false);
1174 // To save making another query just below, put name in here
1175 $mod[$seq]->name = $modvalues->name;
1178 if (!isset($mod[$seq]->name)) {
1179 $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1182 // Minimise the database size by unsetting default options when they are
1183 // 'empty'. This list corresponds to code in the cm_info constructor.
1184 foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
1185 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
1186 'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom',
1187 'availableuntil', 'conditionscompletion', 'conditionsgrade',
1188 'completionview', 'completionexpected', 'score', 'showdescription')
1189 as $property) {
1190 if (property_exists($mod[$seq], $property) &&
1191 empty($mod[$seq]->{$property})) {
1192 unset($mod[$seq]->{$property});
1195 // Special case: this value is usually set to null, but may be 0
1196 if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
1197 is_null($mod[$seq]->completiongradeitemnumber)) {
1198 unset($mod[$seq]->completiongradeitemnumber);
1204 return $mod;
1209 * Returns a number of useful structures for course displays
1211 function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modnamesused) {
1212 global $CFG, $DB, $COURSE;
1214 $mods = array(); // course modules indexed by id
1215 $modnames = array(); // all course module names (except resource!)
1216 $modnamesplural= array(); // all course module names (plural form)
1217 $modnamesused = array(); // course module names used
1219 if ($allmods = $DB->get_records("modules")) {
1220 foreach ($allmods as $mod) {
1221 if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
1222 continue;
1224 if ($mod->visible) {
1225 $modnames[$mod->name] = get_string("modulename", "$mod->name");
1226 $modnamesplural[$mod->name] = get_string("modulenameplural", "$mod->name");
1229 collatorlib::asort($modnames);
1230 } else {
1231 print_error("nomodules", 'debug');
1234 $course = ($courseid==$COURSE->id) ? $COURSE : $DB->get_record('course',array('id'=>$courseid));
1235 $modinfo = get_fast_modinfo($course);
1237 if ($rawmods=$modinfo->cms) {
1238 foreach($rawmods as $mod) { // Index the mods
1239 if (empty($modnames[$mod->modname])) {
1240 continue;
1242 $mods[$mod->id] = $mod;
1243 $mods[$mod->id]->modfullname = $modnames[$mod->modname];
1244 if (!$mod->visible and !has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_COURSE, $courseid))) {
1245 continue;
1247 // Check groupings
1248 if (!groups_course_module_visible($mod)) {
1249 continue;
1251 $modnamesused[$mod->modname] = $modnames[$mod->modname];
1253 if ($modnamesused) {
1254 collatorlib::asort($modnamesused);
1260 * Returns an array of sections for the requested course id
1262 * This function stores the sections against the course id within a staticvar encase
1263 * of subsequent requests. This is used all over + in some standard libs and course
1264 * format callbacks so subsequent requests are a reality.
1266 * @staticvar array $coursesections
1267 * @param int $courseid
1268 * @return array Array of sections
1270 function get_all_sections($courseid) {
1271 global $DB;
1272 static $coursesections = array();
1273 if (!array_key_exists($courseid, $coursesections)) {
1274 $coursesections[$courseid] = $DB->get_records("course_sections", array("course"=>"$courseid"), "section",
1275 "section, id, course, name, summary, summaryformat, sequence, visible");
1277 return $coursesections[$courseid];
1281 * Set highlighted section. Only one section can be highlighted at the time.
1283 * @param int $courseid course id
1284 * @param int $marker highlight section with this number, 0 means remove higlightin
1285 * @return void
1287 function course_set_marker($courseid, $marker) {
1288 global $DB;
1289 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1293 * For a given course section, marks it visible or hidden,
1294 * and does the same for every activity in that section
1296 * @param int $courseid course id
1297 * @param int $sectionnumber The section number to adjust
1298 * @param int $visibility The new visibility
1299 * @return array A list of resources which were hidden in the section
1301 function set_section_visible($courseid, $sectionnumber, $visibility) {
1302 global $DB;
1304 $resourcestotoggle = array();
1305 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1306 $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1307 if (!empty($section->sequence)) {
1308 $modules = explode(",", $section->sequence);
1309 foreach ($modules as $moduleid) {
1310 set_coursemodule_visible($moduleid, $visibility, true);
1313 rebuild_course_cache($courseid);
1315 // Determine which modules are visible for AJAX update
1316 if (!empty($modules)) {
1317 list($insql, $params) = $DB->get_in_or_equal($modules);
1318 $select = 'id ' . $insql . ' AND visible = ?';
1319 array_push($params, $visibility);
1320 if (!$visibility) {
1321 $select .= ' AND visibleold = 1';
1323 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1326 return $resourcestotoggle;
1330 * Obtains shared data that is used in print_section when displaying a
1331 * course-module entry.
1333 * Calls format_text or format_string as appropriate, and obtains the correct icon.
1335 * This data is also used in other areas of the code.
1336 * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
1337 * @param object $course Moodle course object
1338 * @return array An array with the following values in this order:
1339 * $content (optional extra content for after link),
1340 * $instancename (text of link)
1342 function get_print_section_cm_text(cm_info $cm, $course) {
1343 global $OUTPUT;
1345 // Get content from modinfo if specified. Content displays either
1346 // in addition to the standard link (below), or replaces it if
1347 // the link is turned off by setting ->url to null.
1348 if (($content = $cm->get_content()) !== '') {
1349 // Improve filter performance by preloading filter setttings for all
1350 // activities on the course (this does nothing if called multiple
1351 // times)
1352 filter_preload_activities($cm->get_modinfo());
1354 // Get module context
1355 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1356 $labelformatoptions = new stdClass();
1357 $labelformatoptions->noclean = true;
1358 $labelformatoptions->overflowdiv = true;
1359 $labelformatoptions->context = $modulecontext;
1360 $content = format_text($content, FORMAT_HTML, $labelformatoptions);
1361 } else {
1362 $content = '';
1365 // Get course context
1366 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1367 $stringoptions = new stdClass;
1368 $stringoptions->context = $coursecontext;
1369 $instancename = format_string($cm->name, true, $stringoptions);
1370 return array($content, $instancename);
1374 * Prints a section full of activity modules
1376 function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn = false) {
1377 global $CFG, $USER, $DB, $PAGE, $OUTPUT;
1379 static $initialised;
1381 static $groupbuttons;
1382 static $groupbuttonslink;
1383 static $isediting;
1384 static $ismoving;
1385 static $strmovehere;
1386 static $strmovefull;
1387 static $strunreadpostsone;
1388 static $groupings;
1389 static $modulenames;
1391 if (!isset($initialised)) {
1392 $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
1393 $groupbuttonslink = (!$course->groupmodeforce);
1394 $isediting = $PAGE->user_is_editing();
1395 $ismoving = $isediting && ismoving($course->id);
1396 if ($ismoving) {
1397 $strmovehere = get_string("movehere");
1398 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
1400 $modulenames = array();
1401 $initialised = true;
1404 $modinfo = get_fast_modinfo($course);
1405 $completioninfo = new completion_info($course);
1407 //Accessibility: replace table with list <ul>, but don't output empty list.
1408 if (!empty($section->sequence)) {
1410 // Fix bug #5027, don't want style=\"width:$width\".
1411 echo "<ul class=\"section img-text\">\n";
1412 $sectionmods = explode(",", $section->sequence);
1414 foreach ($sectionmods as $modnumber) {
1415 if (empty($mods[$modnumber])) {
1416 continue;
1420 * @var cm_info
1422 $mod = $mods[$modnumber];
1424 if ($ismoving and $mod->id == $USER->activitycopy) {
1425 // do not display moving mod
1426 continue;
1429 if (isset($modinfo->cms[$modnumber])) {
1430 // We can continue (because it will not be displayed at all)
1431 // if:
1432 // 1) The activity is not visible to users
1433 // and
1434 // 2a) The 'showavailability' option is not set (if that is set,
1435 // we need to display the activity so we can show
1436 // availability info)
1437 // or
1438 // 2b) The 'availableinfo' is empty, i.e. the activity was
1439 // hidden in a way that leaves no info, such as using the
1440 // eye icon.
1441 if (!$modinfo->cms[$modnumber]->uservisible &&
1442 (empty($modinfo->cms[$modnumber]->showavailability) ||
1443 empty($modinfo->cms[$modnumber]->availableinfo))) {
1444 // visibility shortcut
1445 continue;
1447 } else {
1448 if (!file_exists("$CFG->dirroot/mod/$mod->modname/lib.php")) {
1449 // module not installed
1450 continue;
1452 if (!coursemodule_visible_for_user($mod) &&
1453 empty($mod->showavailability)) {
1454 // full visibility check
1455 continue;
1459 if (!isset($modulenames[$mod->modname])) {
1460 $modulenames[$mod->modname] = get_string('modulename', $mod->modname);
1462 $modulename = $modulenames[$mod->modname];
1464 // In some cases the activity is visible to user, but it is
1465 // dimmed. This is done if viewhiddenactivities is true and if:
1466 // 1. the activity is not visible, or
1467 // 2. the activity has dates set which do not include current, or
1468 // 3. the activity has any other conditions set (regardless of whether
1469 // current user meets them)
1470 $canviewhidden = has_capability(
1471 'moodle/course:viewhiddenactivities',
1472 get_context_instance(CONTEXT_MODULE, $mod->id));
1473 $accessiblebutdim = false;
1474 if ($canviewhidden) {
1475 $accessiblebutdim = !$mod->visible;
1476 if (!empty($CFG->enableavailability)) {
1477 $accessiblebutdim = $accessiblebutdim ||
1478 $mod->availablefrom > time() ||
1479 ($mod->availableuntil && $mod->availableuntil < time()) ||
1480 count($mod->conditionsgrade) > 0 ||
1481 count($mod->conditionscompletion) > 0;
1485 $liclasses = array();
1486 $liclasses[] = 'activity';
1487 $liclasses[] = $mod->modname;
1488 $liclasses[] = 'modtype_'.$mod->modname;
1489 $extraclasses = $mod->get_extra_classes();
1490 if ($extraclasses) {
1491 $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
1493 echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
1494 if ($ismoving) {
1495 echo '<a title="'.$strmovefull.'"'.
1496 ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
1497 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1498 ' alt="'.$strmovehere.'" /></a><br />
1502 $classes = array('mod-indent');
1503 if (!empty($mod->indent)) {
1504 $classes[] = 'mod-indent-'.$mod->indent;
1505 if ($mod->indent > 15) {
1506 $classes[] = 'mod-indent-huge';
1509 echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
1511 // Get data about this course-module
1512 list($content, $instancename) =
1513 get_print_section_cm_text($modinfo->cms[$modnumber], $course);
1515 //Accessibility: for files get description via icon, this is very ugly hack!
1516 $altname = '';
1517 $altname = $mod->modfullname;
1518 if (!empty($customicon)) {
1519 $archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1520 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
1521 $mimetype = mimeinfo_from_icon('type', $customicon);
1522 $altname = get_mimetype_description($mimetype);
1525 // Avoid unnecessary duplication: if e.g. a forum name already
1526 // includes the word forum (or Forum, etc) then it is unhelpful
1527 // to include that in the accessible description that is added.
1528 if (false !== strpos(textlib::strtolower($instancename),
1529 textlib::strtolower($altname))) {
1530 $altname = '';
1532 // File type after name, for alphabetic lists (screen reader).
1533 if ($altname) {
1534 $altname = get_accesshide(' '.$altname);
1537 // We may be displaying this just in order to show information
1538 // about visibility, without the actual link
1539 $contentpart = '';
1540 if ($mod->uservisible) {
1541 // Nope - in this case the link is fully working for user
1542 $linkclasses = '';
1543 $textclasses = '';
1544 if ($accessiblebutdim) {
1545 $linkclasses .= ' dimmed';
1546 $textclasses .= ' dimmed_text';
1547 $accesstext = '<span class="accesshide">'.
1548 get_string('hiddenfromstudents').': </span>';
1549 } else {
1550 $accesstext = '';
1552 if ($linkclasses) {
1553 $linkcss = 'class="' . trim($linkclasses) . '" ';
1554 } else {
1555 $linkcss = '';
1557 if ($textclasses) {
1558 $textcss = 'class="' . trim($textclasses) . '" ';
1559 } else {
1560 $textcss = '';
1563 // Get on-click attribute value if specified
1564 $onclick = $mod->get_on_click();
1565 if ($onclick) {
1566 $onclick = ' onclick="' . $onclick . '"';
1569 if ($url = $mod->get_url()) {
1570 // Display link itself
1571 echo '<a ' . $linkcss . $mod->extra . $onclick .
1572 ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
1573 '" class="activityicon" alt="' .
1574 $modulename . '" /> ' .
1575 $accesstext . '<span class="instancename">' .
1576 $instancename . $altname . '</span></a>';
1578 // If specified, display extra content after link
1579 if ($content) {
1580 $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) .
1581 '">' . $content . '</div>';
1583 } else {
1584 // No link, so display only content
1585 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1586 $accesstext . $content . '</div>';
1589 if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
1590 if (!isset($groupings)) {
1591 $groupings = groups_get_all_groupings($course->id);
1593 echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
1595 } else {
1596 $textclasses = $extraclasses;
1597 $textclasses .= ' dimmed_text';
1598 if ($textclasses) {
1599 $textcss = 'class="' . trim($textclasses) . '" ';
1600 } else {
1601 $textcss = '';
1603 $accesstext = '<span class="accesshide">' .
1604 get_string('notavailableyet', 'condition') .
1605 ': </span>';
1607 if ($url = $mod->get_url()) {
1608 // Display greyed-out text of link
1609 echo '<div ' . $textcss . $mod->extra .
1610 ' >' . '<img src="' . $mod->get_icon_url() .
1611 '" class="activityicon" alt="' .
1612 $modulename .
1613 '" /> <span>'. $instancename . $altname .
1614 '</span></div>';
1616 // Do not display content after link when it is greyed out like this.
1617 } else {
1618 // No link, so display only content (also greyed)
1619 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1620 $accesstext . $content . '</div>';
1624 // Module can put text after the link (e.g. forum unread)
1625 echo $mod->get_after_link();
1627 // If there is content but NO link (eg label), then display the
1628 // content here (BEFORE any icons). In this case cons must be
1629 // displayed after the content so that it makes more sense visually
1630 // and for accessibility reasons, e.g. if you have a one-line label
1631 // it should work similarly (at least in terms of ordering) to an
1632 // activity.
1633 if (empty($url)) {
1634 echo $contentpart;
1637 if ($isediting) {
1638 if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
1639 if (! $mod->groupmodelink = $groupbuttonslink) {
1640 $mod->groupmode = $course->groupmode;
1643 } else {
1644 $mod->groupmode = false;
1646 echo '&nbsp;&nbsp;';
1648 if ($sectionreturn) {
1649 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
1650 } else {
1651 echo make_editing_buttons($mod, $absolute, true, $mod->indent, 0);
1653 echo $mod->get_after_edit_icons();
1656 // Completion
1657 $completion = $hidecompletion
1658 ? COMPLETION_TRACKING_NONE
1659 : $completioninfo->is_enabled($mod);
1660 if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
1661 !isguestuser() && $mod->uservisible) {
1662 $completiondata = $completioninfo->get_data($mod,true);
1663 $completionicon = '';
1664 if ($isediting) {
1665 switch ($completion) {
1666 case COMPLETION_TRACKING_MANUAL :
1667 $completionicon = 'manual-enabled'; break;
1668 case COMPLETION_TRACKING_AUTOMATIC :
1669 $completionicon = 'auto-enabled'; break;
1670 default: // wtf
1672 } else if ($completion==COMPLETION_TRACKING_MANUAL) {
1673 switch($completiondata->completionstate) {
1674 case COMPLETION_INCOMPLETE:
1675 $completionicon = 'manual-n'; break;
1676 case COMPLETION_COMPLETE:
1677 $completionicon = 'manual-y'; break;
1679 } else { // Automatic
1680 switch($completiondata->completionstate) {
1681 case COMPLETION_INCOMPLETE:
1682 $completionicon = 'auto-n'; break;
1683 case COMPLETION_COMPLETE:
1684 $completionicon = 'auto-y'; break;
1685 case COMPLETION_COMPLETE_PASS:
1686 $completionicon = 'auto-pass'; break;
1687 case COMPLETION_COMPLETE_FAIL:
1688 $completionicon = 'auto-fail'; break;
1691 if ($completionicon) {
1692 $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
1693 $imgalt = s(get_string('completion-alt-'.$completionicon, 'completion', $mod->name));
1694 if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
1695 $imgtitle = s(get_string('completion-title-'.$completionicon, 'completion', $mod->name));
1696 $newstate =
1697 $completiondata->completionstate==COMPLETION_COMPLETE
1698 ? COMPLETION_INCOMPLETE
1699 : COMPLETION_COMPLETE;
1700 // In manual mode the icon is a toggle form...
1702 // If this completion state is used by the
1703 // conditional activities system, we need to turn
1704 // off the JS.
1705 if (!empty($CFG->enableavailability) &&
1706 condition_info::completion_value_used_as_condition($course, $mod)) {
1707 $extraclass = ' preventjs';
1708 } else {
1709 $extraclass = '';
1711 echo "
1712 <form class='togglecompletion$extraclass' method='post' action='".$CFG->wwwroot."/course/togglecompletion.php'><div>
1713 <input type='hidden' name='id' value='{$mod->id}' />
1714 <input type='hidden' name='modulename' value='".s($mod->name)."' />
1715 <input type='hidden' name='sesskey' value='".sesskey()."' />
1716 <input type='hidden' name='completionstate' value='$newstate' />
1717 <input type='image' src='$imgsrc' alt='$imgalt' title='$imgtitle' />
1718 </div></form>";
1719 } else {
1720 // In auto mode, or when editing, the icon is just an image
1721 echo "<span class='autocompletion'>";
1722 echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
1727 // If there is content AND a link, then display the content here
1728 // (AFTER any icons). Otherwise it was displayed before
1729 if (!empty($url)) {
1730 echo $contentpart;
1733 // Show availability information (for someone who isn't allowed to
1734 // see the activity itself, or for staff)
1735 if (!$mod->uservisible) {
1736 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
1737 } else if ($canviewhidden && !empty($CFG->enableavailability)) {
1738 $ci = new condition_info($mod);
1739 $fullinfo = $ci->get_full_information();
1740 if($fullinfo) {
1741 echo '<div class="availabilityinfo">'.get_string($mod->showavailability
1742 ? 'userrestriction_visible'
1743 : 'userrestriction_hidden','condition',
1744 $fullinfo).'</div>';
1748 echo html_writer::end_tag('div');
1749 echo html_writer::end_tag('li')."\n";
1752 } elseif ($ismoving) {
1753 echo "<ul class=\"section\">\n";
1756 if ($ismoving) {
1757 echo '<li><a title="'.$strmovefull.'"'.
1758 ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
1759 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1760 ' alt="'.$strmovehere.'" /></a></li>
1763 if (!empty($section->sequence) || $ismoving) {
1764 echo "</ul><!--class='section'-->\n\n";
1769 * Prints the menus to add activities and resources.
1771 function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false) {
1772 global $CFG, $OUTPUT;
1774 // check to see if user can add menus
1775 if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id))) {
1776 return false;
1779 // Retrieve all modules with associated metadata
1780 $modules = get_module_metadata($course, $modnames);
1782 // We'll sort resources and activities into two lists
1783 $resources = array();
1784 $activities = array();
1786 // We need to add the section section to the link for each module
1787 $sectionlink = '&section=' . $section;
1789 foreach ($modules as $module) {
1790 if (isset($module->types)) {
1791 // This module has a subtype
1792 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1793 $subtypes = array();
1794 foreach ($module->types as $subtype) {
1795 $subtypes[$subtype->link . $sectionlink] = $subtype->title;
1798 // Sort module subtypes into the list
1799 if (!empty($module->title)) {
1800 // This grouping has a name
1801 if ($module->archetype == MOD_CLASS_RESOURCE) {
1802 $resources[] = array($module->title=>$subtypes);
1803 } else {
1804 $activities[] = array($module->title=>$subtypes);
1806 } else {
1807 // This grouping does not have a name
1808 if ($module->archetype == MOD_CLASS_RESOURCE) {
1809 $resources = array_merge($resources, $subtypes);
1810 } else {
1811 $activities = array_merge($activities, $subtypes);
1814 } else {
1815 // This module has no subtypes
1816 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
1817 $resources[$module->link . $sectionlink] = $module->title;
1818 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
1819 // System modules cannot be added by user, do not add to dropdown
1820 } else {
1821 $activities[$module->link . $sectionlink] = $module->title;
1826 $straddactivity = get_string('addactivity');
1827 $straddresource = get_string('addresource');
1829 $output = '<div class="section_add_menus">';
1831 if (!$vertical) {
1832 $output .= '<div class="horizontal">';
1835 if (!empty($resources)) {
1836 $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
1837 $select->set_help_icon('resources');
1838 $output .= $OUTPUT->render($select);
1841 if (!empty($activities)) {
1842 $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
1843 $select->set_help_icon('activities');
1844 $output .= $OUTPUT->render($select);
1847 if (!$vertical) {
1848 $output .= '</div>';
1851 $output .= '</div>';
1853 if ($return) {
1854 return $output;
1855 } else {
1856 echo $output;
1861 * Retrieve all metadata for the requested modules
1863 * @param object $course The Course
1864 * @param array $modnames An array containing the list of modules and their
1865 * names
1866 * @return array A list of stdClass objects containing metadata about each
1867 * module
1869 function get_module_metadata($course, $modnames) {
1870 global $CFG, $OUTPUT;
1872 // get_module_metadata will be called once per section on the page and courses may show
1873 // different modules to one another
1874 static $modlist = array();
1875 if (!isset($modlist[$course->id])) {
1876 $modlist[$course->id] = array();
1879 $return = array();
1880 $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&add=';
1881 foreach($modnames as $modname => $modnamestr) {
1882 if (!course_allowed_module($course, $modname)) {
1883 continue;
1885 if (isset($modlist[$modname])) {
1886 // This module is already cached
1887 $return[$modname] = $modlist[$course->id][$modname];
1888 continue;
1891 // Include the module lib
1892 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1893 if (!file_exists($libfile)) {
1894 continue;
1896 include_once($libfile);
1898 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1899 $gettypesfunc = $modname.'_get_types';
1900 if (function_exists($gettypesfunc)) {
1901 if ($types = $gettypesfunc()) {
1902 $group = new stdClass();
1903 $group->name = $modname;
1904 $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1905 foreach($types as $type) {
1906 if ($type->typestr === '--') {
1907 continue;
1909 if (strpos($type->typestr, '--') === 0) {
1910 $group->title = str_replace('--', '', $type->typestr);
1911 continue;
1913 // Set the Sub Type metadata
1914 $subtype = new stdClass();
1915 $subtype->title = $type->typestr;
1916 $subtype->type = str_replace('&amp;', '&', $type->type);
1917 $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1918 $subtype->archetype = $type->modclass;
1920 // The group archetype should match the subtype archetypes and all subtypes
1921 // should have the same archetype
1922 $group->archetype = $subtype->archetype;
1924 if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1925 $subtype->help = get_string('help' . $subtype->name, $modname);
1927 $subtype->link = $urlbase . $subtype->type;
1928 $group->types[] = $subtype;
1930 $modlist[$course->id][$modname] = $group;
1932 } else {
1933 $module = new stdClass();
1934 $module->title = get_string('modulename', $modname);
1935 $module->name = $modname;
1936 $module->link = $urlbase . $modname;
1937 $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1938 if (get_string_manager()->string_exists('modulename_help', $modname)) {
1939 $module->help = get_string('modulename_help', $modname);
1941 $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1942 $modlist[$course->id][$modname] = $module;
1944 $return[$modname] = $modlist[$course->id][$modname];
1947 return $return;
1951 * Return the course category context for the category with id $categoryid, except
1952 * that if $categoryid is 0, return the system context.
1954 * @param integer $categoryid a category id or 0.
1955 * @return object the corresponding context
1957 function get_category_or_system_context($categoryid) {
1958 if ($categoryid) {
1959 return get_context_instance(CONTEXT_COURSECAT, $categoryid);
1960 } else {
1961 return get_context_instance(CONTEXT_SYSTEM);
1966 * Gets the child categories of a given courses category. Uses a static cache
1967 * to make repeat calls efficient.
1969 * @param int $parentid the id of a course category.
1970 * @return array all the child course categories.
1972 function get_child_categories($parentid) {
1973 static $allcategories = null;
1975 // only fill in this variable the first time
1976 if (null == $allcategories) {
1977 $allcategories = array();
1979 $categories = get_categories();
1980 foreach ($categories as $category) {
1981 if (empty($allcategories[$category->parent])) {
1982 $allcategories[$category->parent] = array();
1984 $allcategories[$category->parent][] = $category;
1988 if (empty($allcategories[$parentid])) {
1989 return array();
1990 } else {
1991 return $allcategories[$parentid];
1996 * This function recursively travels the categories, building up a nice list
1997 * for display. It also makes an array that list all the parents for each
1998 * category.
2000 * For example, if you have a tree of categories like:
2001 * Miscellaneous (id = 1)
2002 * Subcategory (id = 2)
2003 * Sub-subcategory (id = 4)
2004 * Other category (id = 3)
2005 * Then after calling this function you will have
2006 * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
2007 * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
2008 * 3 => 'Other category');
2009 * $parents = array(2 => array(1), 4 => array(1, 2));
2011 * If you specify $requiredcapability, then only categories where the current
2012 * user has that capability will be added to $list, although all categories
2013 * will still be added to $parents, and if you only have $requiredcapability
2014 * in a child category, not the parent, then the child catgegory will still be
2015 * included.
2017 * If you specify the option $excluded, then that category, and all its children,
2018 * are omitted from the tree. This is useful when you are doing something like
2019 * moving categories, where you do not want to allow people to move a category
2020 * to be the child of itself.
2022 * @param array $list For output, accumulates an array categoryid => full category path name
2023 * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
2024 * @param string/array $requiredcapability if given, only categories where the current
2025 * user has this capability will be added to $list. Can also be an array of capabilities,
2026 * in which case they are all required.
2027 * @param integer $excludeid Omit this category and its children from the lists built.
2028 * @param object $category Build the tree starting at this category - otherwise starts at the top level.
2029 * @param string $path For internal use, as part of recursive calls.
2031 function make_categories_list(&$list, &$parents, $requiredcapability = '',
2032 $excludeid = 0, $category = NULL, $path = "") {
2034 // initialize the arrays if needed
2035 if (!is_array($list)) {
2036 $list = array();
2038 if (!is_array($parents)) {
2039 $parents = array();
2042 if (empty($category)) {
2043 // Start at the top level.
2044 $category = new stdClass;
2045 $category->id = 0;
2046 } else {
2047 // This is the excluded category, don't include it.
2048 if ($excludeid > 0 && $excludeid == $category->id) {
2049 return;
2052 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2053 $categoryname = format_string($category->name, true, array('context' => $context));
2055 // Update $path.
2056 if ($path) {
2057 $path = $path.' / '.$categoryname;
2058 } else {
2059 $path = $categoryname;
2062 // Add this category to $list, if the permissions check out.
2063 if (empty($requiredcapability)) {
2064 $list[$category->id] = $path;
2066 } else {
2067 $requiredcapability = (array)$requiredcapability;
2068 if (has_all_capabilities($requiredcapability, $context)) {
2069 $list[$category->id] = $path;
2074 // Add all the children recursively, while updating the parents array.
2075 if ($categories = get_child_categories($category->id)) {
2076 foreach ($categories as $cat) {
2077 if (!empty($category->id)) {
2078 if (isset($parents[$category->id])) {
2079 $parents[$cat->id] = $parents[$category->id];
2081 $parents[$cat->id][] = $category->id;
2083 make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
2089 * This function generates a structured array of courses and categories.
2091 * The depth of categories is limited by $CFG->maxcategorydepth however there
2092 * is no limit on the number of courses!
2094 * Suitable for use with the course renderers course_category_tree method:
2095 * $renderer = $PAGE->get_renderer('core','course');
2096 * echo $renderer->course_category_tree(get_course_category_tree());
2098 * @global moodle_database $DB
2099 * @param int $id
2100 * @param int $depth
2102 function get_course_category_tree($id = 0, $depth = 0) {
2103 global $DB, $CFG;
2104 $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM));
2105 $categories = get_child_categories($id);
2106 $categoryids = array();
2107 foreach ($categories as $key => &$category) {
2108 if (!$category->visible && !$viewhiddencats) {
2109 unset($categories[$key]);
2110 continue;
2112 $categoryids[$category->id] = $category;
2113 if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
2114 list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
2115 foreach ($subcategories as $subid=>$subcat) {
2116 $categoryids[$subid] = $subcat;
2118 $category->courses = array();
2122 if ($depth > 0) {
2123 // This is a recursive call so return the required array
2124 return array($categories, $categoryids);
2127 // The depth is 0 this function has just been called so we can finish it off
2129 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
2130 list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
2131 $sql = "SELECT
2132 c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
2133 $ccselect
2134 FROM {course} c
2135 $ccjoin
2136 WHERE c.category $catsql ORDER BY c.sortorder ASC";
2137 if ($courses = $DB->get_records_sql($sql, $catparams)) {
2138 // loop throught them
2139 foreach ($courses as $course) {
2140 if ($course->id == SITEID) {
2141 continue;
2143 context_instance_preload($course);
2144 if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
2145 $categoryids[$course->category]->courses[$course->id] = $course;
2149 return $categories;
2153 * Recursive function to print out all the categories in a nice format
2154 * with or without courses included
2156 function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
2157 global $CFG;
2159 // maxcategorydepth == 0 meant no limit
2160 if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
2161 return;
2164 if (!$displaylist) {
2165 make_categories_list($displaylist, $parentslist);
2168 if ($category) {
2169 if ($category->visible or has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM))) {
2170 print_category_info($category, $depth, $showcourses);
2171 } else {
2172 return; // Don't bother printing children of invisible categories
2175 } else {
2176 $category = new stdClass();
2177 $category->id = "0";
2180 if ($categories = get_child_categories($category->id)) { // Print all the children recursively
2181 $countcats = count($categories);
2182 $count = 0;
2183 $first = true;
2184 $last = false;
2185 foreach ($categories as $cat) {
2186 $count++;
2187 if ($count == $countcats) {
2188 $last = true;
2190 $up = $first ? false : true;
2191 $down = $last ? false : true;
2192 $first = false;
2194 print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
2200 * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
2202 function make_categories_options() {
2203 make_categories_list($cats,$parents);
2204 foreach ($cats as $key => $value) {
2205 if (array_key_exists($key,$parents)) {
2206 if ($indent = count($parents[$key])) {
2207 for ($i = 0; $i < $indent; $i++) {
2208 $cats[$key] = '&nbsp;'.$cats[$key];
2213 return $cats;
2217 * Gets the name of a course to be displayed when showing a list of courses.
2218 * By default this is just $course->fullname but user can configure it. The
2219 * result of this function should be passed through print_string.
2220 * @param object $course Moodle course object
2221 * @return string Display name of course (either fullname or short + fullname)
2223 function get_course_display_name_for_list($course) {
2224 global $CFG;
2225 if (!empty($CFG->courselistshortnames)) {
2226 return $course->shortname . ' ' .$course->fullname;
2227 } else {
2228 return $course->fullname;
2233 * Prints the category info in indented fashion
2234 * This function is only used by print_whole_category_list() above
2236 function print_category_info($category, $depth=0, $showcourses = false) {
2237 global $CFG, $DB, $OUTPUT;
2239 $strsummary = get_string('summary');
2241 $catlinkcss = null;
2242 if (!$category->visible) {
2243 $catlinkcss = array('class'=>'dimmed');
2245 static $coursecount = null;
2246 if (null === $coursecount) {
2247 // only need to check this once
2248 $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
2251 if ($showcourses and $coursecount) {
2252 $catimage = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
2253 } else {
2254 $catimage = "&nbsp;";
2257 $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
2258 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2259 $fullname = format_string($category->name, true, array('context' => $context));
2261 if ($showcourses and $coursecount) {
2262 echo '<div class="categorylist clearfix">';
2263 $cat = '';
2264 $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
2265 $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2266 $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
2268 $html = '';
2269 if ($depth > 0) {
2270 for ($i=0; $i< $depth; $i++) {
2271 $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
2272 $cat = '';
2274 } else {
2275 $html = $cat;
2277 echo html_writer::tag('div', $html, array('class'=>'category'));
2278 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2280 // does the depth exceed maxcategorydepth
2281 // maxcategorydepth == 0 or unset meant no limit
2282 $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
2283 if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
2284 foreach ($courses as $course) {
2285 $linkcss = null;
2286 if (!$course->visible) {
2287 $linkcss = array('class'=>'dimmed');
2290 $coursename = get_course_display_name_for_list($course);
2291 $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
2293 // print enrol info
2294 $courseicon = '';
2295 if ($icons = enrol_get_course_info_icons($course)) {
2296 foreach ($icons as $pix_icon) {
2297 $courseicon = $OUTPUT->render($pix_icon).' ';
2301 $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
2303 if ($course->summary) {
2304 $link = new moodle_url('/course/info.php?id='.$course->id);
2305 $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
2306 new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
2307 array('title'=>$strsummary));
2309 $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
2312 $html = '';
2313 for ($i=0; $i <= $depth; $i++) {
2314 $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
2315 $coursecontent = '';
2317 echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
2320 echo '</div>';
2321 } else {
2322 echo '<div class="categorylist">';
2323 $html = '';
2324 $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2325 if (count($courses) > 0) {
2326 $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
2329 if ($depth > 0) {
2330 for ($i=0; $i< $depth; $i++) {
2331 $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
2332 $cat = '';
2334 } else {
2335 $html = $cat;
2338 echo html_writer::tag('div', $html, array('class'=>'category'));
2339 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2340 echo '</div>';
2345 * Print the buttons relating to course requests.
2347 * @param object $systemcontext the system context.
2349 function print_course_request_buttons($systemcontext) {
2350 global $CFG, $DB, $OUTPUT;
2351 if (empty($CFG->enablecourserequests)) {
2352 return;
2354 if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
2355 /// Print a button to request a new course
2356 echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
2358 /// Print a button to manage pending requests
2359 if (has_capability('moodle/site:approvecourse', $systemcontext)) {
2360 $disabled = !$DB->record_exists('course_request', array());
2361 echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
2366 * Does the user have permission to edit things in this category?
2368 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2369 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
2371 function can_edit_in_category($categoryid = 0) {
2372 $context = get_category_or_system_context($categoryid);
2373 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
2377 * Prints the turn editing on/off button on course/index.php or course/category.php.
2379 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2380 * @return string HTML of the editing button, or empty string, if this user is not allowed
2381 * to see it.
2383 function update_category_button($categoryid = 0) {
2384 global $CFG, $PAGE, $OUTPUT;
2386 // Check permissions.
2387 if (!can_edit_in_category($categoryid)) {
2388 return '';
2391 // Work out the appropriate action.
2392 if ($PAGE->user_is_editing()) {
2393 $label = get_string('turneditingoff');
2394 $edit = 'off';
2395 } else {
2396 $label = get_string('turneditingon');
2397 $edit = 'on';
2400 // Generate the button HTML.
2401 $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
2402 if ($categoryid) {
2403 $options['id'] = $categoryid;
2404 $page = 'category.php';
2405 } else {
2406 $page = 'index.php';
2408 return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
2412 * Category is 0 (for all courses) or an object
2414 function print_courses($category) {
2415 global $CFG, $OUTPUT;
2417 if (!is_object($category) && $category==0) {
2418 $categories = get_child_categories(0); // Parent = 0 ie top-level categories only
2419 if (is_array($categories) && count($categories) == 1) {
2420 $category = array_shift($categories);
2421 $courses = get_courses_wmanagers($category->id,
2422 'c.sortorder ASC',
2423 array('summary','summaryformat'));
2424 } else {
2425 $courses = get_courses_wmanagers('all',
2426 'c.sortorder ASC',
2427 array('summary','summaryformat'));
2429 unset($categories);
2430 } else {
2431 $courses = get_courses_wmanagers($category->id,
2432 'c.sortorder ASC',
2433 array('summary','summaryformat'));
2436 if ($courses) {
2437 echo html_writer::start_tag('ul', array('class'=>'unlist'));
2438 foreach ($courses as $course) {
2439 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2440 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2441 echo html_writer::start_tag('li');
2442 print_course($course);
2443 echo html_writer::end_tag('li');
2446 echo html_writer::end_tag('ul');
2447 } else {
2448 echo $OUTPUT->heading(get_string("nocoursesyet"));
2449 $context = get_context_instance(CONTEXT_SYSTEM);
2450 if (has_capability('moodle/course:create', $context)) {
2451 $options = array();
2452 if (!empty($category->id)) {
2453 $options['category'] = $category->id;
2454 } else {
2455 $options['category'] = $CFG->defaultrequestcategory;
2457 echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
2458 echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
2459 echo html_writer::end_tag('div');
2465 * Print a description of a course, suitable for browsing in a list.
2467 * @param object $course the course object.
2468 * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
2470 function print_course($course, $highlightterms = '') {
2471 global $CFG, $USER, $DB, $OUTPUT;
2473 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2475 // Rewrite file URLs so that they are correct
2476 $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
2478 echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
2479 echo html_writer::start_tag('div', array('class'=>'info'));
2480 echo html_writer::start_tag('h3', array('class'=>'name'));
2482 $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
2484 $coursename = get_course_display_name_for_list($course);
2485 $linktext = highlight($highlightterms, format_string($coursename));
2486 $linkparams = array('title'=>get_string('entercourse'));
2487 if (empty($course->visible)) {
2488 $linkparams['class'] = 'dimmed';
2490 echo html_writer::link($linkhref, $linktext, $linkparams);
2491 echo html_writer::end_tag('h3');
2493 /// first find all roles that are supposed to be displayed
2494 if (!empty($CFG->coursecontact)) {
2495 $managerroles = explode(',', $CFG->coursecontact);
2496 $namesarray = array();
2497 $rusers = array();
2499 if (!isset($course->managers)) {
2500 $rusers = get_role_users($managerroles, $context, true,
2501 'ra.id AS raid, u.id, u.username, u.firstname, u.lastname,
2502 r.name AS rolename, r.sortorder, r.id AS roleid',
2503 'r.sortorder ASC, u.lastname ASC');
2504 } else {
2505 // use the managers array if we have it for perf reasosn
2506 // populate the datastructure like output of get_role_users();
2507 foreach ($course->managers as $manager) {
2508 $u = new stdClass();
2509 $u = $manager->user;
2510 $u->roleid = $manager->roleid;
2511 $u->rolename = $manager->rolename;
2513 $rusers[] = $u;
2517 /// Rename some of the role names if needed
2518 if (isset($context)) {
2519 $aliasnames = $DB->get_records('role_names', array('contextid'=>$context->id), '', 'roleid,contextid,name');
2522 $namesarray = array();
2523 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
2524 foreach ($rusers as $ra) {
2525 if (isset($namesarray[$ra->id])) {
2526 // only display a user once with the higest sortorder role
2527 continue;
2530 if (isset($aliasnames[$ra->roleid])) {
2531 $ra->rolename = $aliasnames[$ra->roleid]->name;
2534 $fullname = fullname($ra, $canviewfullnames);
2535 $namesarray[$ra->id] = format_string($ra->rolename).': '.
2536 html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
2539 if (!empty($namesarray)) {
2540 echo html_writer::start_tag('ul', array('class'=>'teachers'));
2541 foreach ($namesarray as $name) {
2542 echo html_writer::tag('li', $name);
2544 echo html_writer::end_tag('ul');
2547 echo html_writer::end_tag('div'); // End of info div
2549 echo html_writer::start_tag('div', array('class'=>'summary'));
2550 $options = new stdClass();
2551 $options->noclean = true;
2552 $options->para = false;
2553 $options->overflowdiv = true;
2554 if (!isset($course->summaryformat)) {
2555 $course->summaryformat = FORMAT_MOODLE;
2557 echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options, $course->id));
2558 if ($icons = enrol_get_course_info_icons($course)) {
2559 echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
2560 foreach ($icons as $icon) {
2561 echo $OUTPUT->render($icon);
2563 echo html_writer::end_tag('div'); // End of enrolmenticons div
2565 echo html_writer::end_tag('div'); // End of summary div
2566 echo html_writer::end_tag('div'); // End of coursebox div
2570 * Prints custom user information on the home page.
2571 * Over time this can include all sorts of information
2573 function print_my_moodle() {
2574 global $USER, $CFG, $DB, $OUTPUT;
2576 if (!isloggedin() or isguestuser()) {
2577 print_error('nopermissions', '', '', 'See My Moodle');
2580 $courses = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
2581 $rhosts = array();
2582 $rcourses = array();
2583 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
2584 $rcourses = get_my_remotecourses($USER->id);
2585 $rhosts = get_my_remotehosts();
2588 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
2590 if (!empty($courses)) {
2591 echo '<ul class="unlist">';
2592 foreach ($courses as $course) {
2593 if ($course->id == SITEID) {
2594 continue;
2596 echo '<li>';
2597 print_course($course);
2598 echo "</li>\n";
2600 echo "</ul>\n";
2603 // MNET
2604 if (!empty($rcourses)) {
2605 // at the IDP, we know of all the remote courses
2606 foreach ($rcourses as $course) {
2607 print_remote_course($course, "100%");
2609 } elseif (!empty($rhosts)) {
2610 // non-IDP, we know of all the remote servers, but not courses
2611 foreach ($rhosts as $host) {
2612 print_remote_host($host, "100%");
2615 unset($course);
2616 unset($host);
2618 if ($DB->count_records("course") > (count($courses) + 1) ) { // Some courses not being displayed
2619 echo "<table width=\"100%\"><tr><td align=\"center\">";
2620 print_course_search("", false, "short");
2621 echo "</td><td align=\"center\">";
2622 echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
2623 echo "</td></tr></table>\n";
2626 } else {
2627 if ($DB->count_records("course_categories") > 1) {
2628 echo $OUTPUT->box_start("categorybox");
2629 print_whole_category_list();
2630 echo $OUTPUT->box_end();
2631 } else {
2632 print_courses(0);
2638 function print_course_search($value="", $return=false, $format="plain") {
2639 global $CFG;
2640 static $count = 0;
2642 $count++;
2644 $id = 'coursesearch';
2646 if ($count > 1) {
2647 $id .= $count;
2650 $strsearchcourses= get_string("searchcourses");
2652 if ($format == 'plain') {
2653 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2654 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2655 $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
2656 $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
2657 $output .= '<input type="submit" value="'.get_string('go').'" />';
2658 $output .= '</fieldset></form>';
2659 } else if ($format == 'short') {
2660 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2661 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2662 $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
2663 $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2664 $output .= '<input type="submit" value="'.get_string('go').'" />';
2665 $output .= '</fieldset></form>';
2666 } else if ($format == 'navbar') {
2667 $output = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2668 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2669 $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
2670 $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2671 $output .= '<input type="submit" value="'.get_string('go').'" />';
2672 $output .= '</fieldset></form>';
2675 if ($return) {
2676 return $output;
2678 echo $output;
2681 function print_remote_course($course, $width="100%") {
2682 global $CFG, $USER;
2684 $linkcss = '';
2686 $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}";
2688 echo '<div class="coursebox remotecoursebox clearfix">';
2689 echo '<div class="info">';
2690 echo '<div class="name"><a title="'.get_string('entercourse').'"'.
2691 $linkcss.' href="'.$url.'">'
2692 . format_string($course->fullname) .'</a><br />'
2693 . format_string($course->hostname) . ' : '
2694 . format_string($course->cat_name) . ' : '
2695 . format_string($course->shortname). '</div>';
2696 echo '</div><div class="summary">';
2697 $options = new stdClass();
2698 $options->noclean = true;
2699 $options->para = false;
2700 $options->overflowdiv = true;
2701 echo format_text($course->summary, $course->summaryformat, $options);
2702 echo '</div>';
2703 echo '</div>';
2706 function print_remote_host($host, $width="100%") {
2707 global $OUTPUT;
2709 $linkcss = '';
2711 echo '<div class="coursebox clearfix">';
2712 echo '<div class="info">';
2713 echo '<div class="name">';
2714 echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
2715 echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
2716 . s($host['name']).'</a> - ';
2717 echo $host['count'] . ' ' . get_string('courses');
2718 echo '</div>';
2719 echo '</div>';
2720 echo '</div>';
2724 /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
2726 function add_course_module($mod) {
2727 global $DB;
2729 $mod->added = time();
2730 unset($mod->id);
2732 return $DB->insert_record("course_modules", $mod);
2736 * Returns course section - creates new if does not exist yet.
2737 * @param int $relative section number
2738 * @param int $courseid
2739 * @return object $course_section object
2741 function get_course_section($section, $courseid) {
2742 global $DB;
2744 if ($cw = $DB->get_record("course_sections", array("section"=>$section, "course"=>$courseid))) {
2745 return $cw;
2747 $cw = new stdClass();
2748 $cw->course = $courseid;
2749 $cw->section = $section;
2750 $cw->summary = "";
2751 $cw->summaryformat = FORMAT_HTML;
2752 $cw->sequence = "";
2753 $id = $DB->insert_record("course_sections", $cw);
2754 return $DB->get_record("course_sections", array("id"=>$id));
2757 * Given a full mod object with section and course already defined, adds this module to that section.
2759 * @param object $mod
2760 * @param int $beforemod An existing ID which we will insert the new module before
2761 * @return int The course_sections ID where the mod is inserted
2763 function add_mod_to_section($mod, $beforemod=NULL) {
2764 global $DB;
2766 if ($section = $DB->get_record("course_sections", array("course"=>$mod->course, "section"=>$mod->section))) {
2768 $section->sequence = trim($section->sequence);
2770 if (empty($section->sequence)) {
2771 $newsequence = "$mod->coursemodule";
2773 } else if ($beforemod) {
2774 $modarray = explode(",", $section->sequence);
2776 if ($key = array_keys($modarray, $beforemod->id)) {
2777 $insertarray = array($mod->id, $beforemod->id);
2778 array_splice($modarray, $key[0], 1, $insertarray);
2779 $newsequence = implode(",", $modarray);
2781 } else { // Just tack it on the end anyway
2782 $newsequence = "$section->sequence,$mod->coursemodule";
2785 } else {
2786 $newsequence = "$section->sequence,$mod->coursemodule";
2789 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2790 return $section->id; // Return course_sections ID that was used.
2792 } else { // Insert a new record
2793 $section = new stdClass();
2794 $section->course = $mod->course;
2795 $section->section = $mod->section;
2796 $section->summary = "";
2797 $section->summaryformat = FORMAT_HTML;
2798 $section->sequence = $mod->coursemodule;
2799 return $DB->insert_record("course_sections", $section);
2803 function set_coursemodule_groupmode($id, $groupmode) {
2804 global $DB;
2805 return $DB->set_field("course_modules", "groupmode", $groupmode, array("id"=>$id));
2808 function set_coursemodule_idnumber($id, $idnumber) {
2809 global $DB;
2810 return $DB->set_field("course_modules", "idnumber", $idnumber, array("id"=>$id));
2814 * $prevstateoverrides = true will set the visibility of the course module
2815 * to what is defined in visibleold. This enables us to remember the current
2816 * visibility when making a whole section hidden, so that when we toggle
2817 * that section back to visible, we are able to return the visibility of
2818 * the course module back to what it was originally.
2820 function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
2821 global $DB, $CFG;
2822 require_once($CFG->libdir.'/gradelib.php');
2824 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2825 return false;
2827 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
2828 return false;
2830 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2831 foreach($events as $event) {
2832 if ($visible) {
2833 show_event($event);
2834 } else {
2835 hide_event($event);
2840 // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
2841 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
2842 if ($grade_items) {
2843 foreach ($grade_items as $grade_item) {
2844 $grade_item->set_hidden(!$visible);
2848 if ($prevstateoverrides) {
2849 if ($visible == '0') {
2850 // Remember the current visible state so we can toggle this back.
2851 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id'=>$id));
2852 } else {
2853 // Get the previous saved visible states.
2854 return $DB->set_field('course_modules', 'visible', $cm->visibleold, array('id'=>$id));
2857 return $DB->set_field("course_modules", "visible", $visible, array("id"=>$id));
2861 * Delete a course module and any associated data at the course level (events)
2862 * Until 1.5 this function simply marked a deleted flag ... now it
2863 * deletes it completely.
2866 function delete_course_module($id) {
2867 global $CFG, $DB;
2868 require_once($CFG->libdir.'/gradelib.php');
2869 require_once($CFG->dirroot.'/blog/lib.php');
2871 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2872 return true;
2874 $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
2875 //delete events from calendar
2876 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2877 foreach($events as $event) {
2878 delete_event($event->id);
2881 //delete grade items, outcome items and grades attached to modules
2882 if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
2883 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
2884 foreach ($grade_items as $grade_item) {
2885 $grade_item->delete('moddelete');
2888 // Delete completion and availability data; it is better to do this even if the
2889 // features are not turned on, in case they were turned on previously (these will be
2890 // very quick on an empty table)
2891 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
2892 $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
2893 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
2894 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
2896 delete_context(CONTEXT_MODULE, $cm->id);
2897 return $DB->delete_records('course_modules', array('id'=>$cm->id));
2900 function delete_mod_from_section($mod, $section) {
2901 global $DB;
2903 if ($section = $DB->get_record("course_sections", array("id"=>$section)) ) {
2905 $modarray = explode(",", $section->sequence);
2907 if ($key = array_keys ($modarray, $mod)) {
2908 array_splice($modarray, $key[0], 1);
2909 $newsequence = implode(",", $modarray);
2910 return $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2911 } else {
2912 return false;
2916 return false;
2920 * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
2922 * @param object $course course object
2923 * @param int $section Section number (not id!!!)
2924 * @param int $move (-1 or 1)
2925 * @return boolean true if section moved successfully
2927 function move_section($course, $section, $move) {
2928 /// Moves a whole course section up and down within the course
2929 global $USER, $DB;
2931 if (!$move) {
2932 return true;
2935 $sectiondest = $section + $move;
2937 if ($sectiondest > $course->numsections or $sectiondest < 1) {
2938 return false;
2941 if (!$sectionrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$section))) {
2942 return false;
2945 if (!$sectiondestrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$sectiondest))) {
2946 return false;
2949 // Three-step change ensures that the section always remains unique (there is
2950 // a unique index now)
2951 $DB->set_field("course_sections", "section", -$sectiondest, array("id"=>$sectionrecord->id));
2952 $DB->set_field("course_sections", "section", $section, array("id"=>$sectiondestrecord->id));
2953 $DB->set_field("course_sections", "section", $sectiondest, array("id"=>$sectionrecord->id));
2955 // Update highlighting if the move affects highlighted section
2956 if ($course->marker == $section) {
2957 course_set_marker($course->id, $sectiondest);
2958 } elseif ($course->marker == $sectiondest) {
2959 course_set_marker($course->id, $section);
2963 // Fix order if needed. The database prevents duplicate sections, but it is
2964 // possible there could be a gap in the numbering.
2965 $sections = $DB->get_records('course_sections', array('course'=>$course->id), 'section ASC');
2966 $n = 0;
2967 foreach ($sections as $section) {
2968 if ($section->section != $n) {
2969 $DB->set_field('course_sections', 'section', $n, array('id'=>$section->id));
2971 $n++;
2973 return true;
2977 * Moves a section within a course, from a position to another.
2978 * Be very careful: $section and $destination refer to section number,
2979 * not id!.
2981 * @param object $course
2982 * @param int $section Section number (not id!!!)
2983 * @param int $destination
2984 * @return boolean Result
2986 function move_section_to($course, $section, $destination) {
2987 /// Moves a whole course section up and down within the course
2988 global $USER, $DB;
2990 if (!$destination && $destination != 0) {
2991 return true;
2994 if ($destination > $course->numsections) {
2995 return false;
2998 // Get all sections for this course and re-order them (2 of them should now share the same section number)
2999 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
3000 'section ASC, id ASC', 'id, section')) {
3001 return false;
3004 $movedsections = reorder_sections($sections, $section, $destination);
3006 // Update all sections. Do this in 2 steps to avoid breaking database
3007 // uniqueness constraint
3008 $transaction = $DB->start_delegated_transaction();
3009 foreach ($movedsections as $id => $position) {
3010 if ($sections[$id] !== $position) {
3011 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
3014 foreach ($movedsections as $id => $position) {
3015 if ($sections[$id] !== $position) {
3016 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
3020 // Adjust destination to reflect the actual section
3021 $moveup = false;
3022 if ($section > $destination) {
3023 $destination++;
3024 $moveup = true;
3027 // If we move the highlighted section itself, then just highlight the destination.
3028 // Adjust the higlighted section location if we move something over it either direction.
3029 if ($section == $course->marker) {
3030 course_set_marker($course->id, $destination);
3031 } elseif ($moveup && $section > $course->marker && $course->marker >= $destination) {
3032 course_set_marker($course->id, $course->marker+1);
3033 } elseif (!$moveup && $section < $course->marker && $course->marker <= $destination) {
3034 course_set_marker($course->id, $course->marker-1);
3037 $transaction->allow_commit();
3038 return true;
3042 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
3043 * an original position number and a target position number, rebuilds the array so that the
3044 * move is made without any duplication of section positions.
3045 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
3046 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
3048 * @param array $sections
3049 * @param int $origin_position
3050 * @param int $target_position
3051 * @return array
3053 function reorder_sections($sections, $origin_position, $target_position) {
3054 if (!is_array($sections)) {
3055 return false;
3058 // We can't move section position 0
3059 if ($origin_position < 1) {
3060 echo "We can't move section position 0";
3061 return false;
3064 // Locate origin section in sections array
3065 if (!$origin_key = array_search($origin_position, $sections)) {
3066 echo "searched position not in sections array";
3067 return false; // searched position not in sections array
3070 // Extract origin section
3071 $origin_section = $sections[$origin_key];
3072 unset($sections[$origin_key]);
3074 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
3075 $found = false;
3076 $append_array = array();
3077 foreach ($sections as $id => $position) {
3078 if ($found) {
3079 $append_array[$id] = $position;
3080 unset($sections[$id]);
3082 if ($position == $target_position) {
3083 $found = true;
3087 // Append moved section
3088 $sections[$origin_key] = $origin_section;
3090 // Append rest of array (if applicable)
3091 if (!empty($append_array)) {
3092 foreach ($append_array as $id => $position) {
3093 $sections[$id] = $position;
3097 // Renumber positions
3098 $position = 0;
3099 foreach ($sections as $id => $p) {
3100 $sections[$id] = $position;
3101 $position++;
3104 return $sections;
3109 * Move the module object $mod to the specified $section
3110 * If $beforemod exists then that is the module
3111 * before which $modid should be inserted
3112 * All parameters are objects
3114 function moveto_module($mod, $section, $beforemod=NULL) {
3115 global $DB, $OUTPUT;
3117 /// Remove original module from original section
3118 if (! delete_mod_from_section($mod->id, $mod->section)) {
3119 echo $OUTPUT->notification("Could not delete module from existing section");
3122 /// Update module itself if necessary
3124 if ($mod->section != $section->id) {
3125 $mod->section = $section->id;
3126 $DB->update_record("course_modules", $mod);
3127 // if moving to a hidden section then hide module
3128 if (!$section->visible) {
3129 set_coursemodule_visible($mod->id, 0);
3133 /// Add the module into the new section
3135 $mod->course = $section->course;
3136 $mod->section = $section->section; // need relative reference
3137 $mod->coursemodule = $mod->id;
3139 if (! add_mod_to_section($mod, $beforemod)) {
3140 return false;
3143 return true;
3147 * Produces the editing buttons for a module
3149 * @global core_renderer $OUTPUT
3150 * @staticvar type $str
3151 * @param stdClass $mod The module to produce editing buttons for
3152 * @param bool $absolute_ignored ignored - all links are absolute
3153 * @param bool $moveselect If true a move seleciton process is used (default true)
3154 * @param int $indent The current indenting
3155 * @param int $section The section to link back to
3156 * @return string XHTML for the editing buttons
3158 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
3159 global $CFG, $OUTPUT, $COURSE;
3161 static $str;
3163 $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
3164 $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
3166 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
3167 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
3169 // no permission to edit anything
3170 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
3171 return false;
3174 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
3176 if (!isset($str)) {
3177 $str = new stdClass;
3178 $str->assign = get_string("assignroles", 'role');
3179 $str->delete = get_string("delete");
3180 $str->move = get_string("move");
3181 $str->moveup = get_string("moveup");
3182 $str->movedown = get_string("movedown");
3183 $str->moveright = get_string("moveright");
3184 $str->moveleft = get_string("moveleft");
3185 $str->update = get_string("update");
3186 $str->duplicate = get_string("duplicate");
3187 $str->hide = get_string("hide");
3188 $str->show = get_string("show");
3189 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
3190 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
3191 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
3192 $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
3193 $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
3194 $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
3195 $str->edittitle = get_string('edittitle', 'moodle');
3198 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
3200 if ($section >= 0) {
3201 $baseurl->param('sr', $section);
3203 $actions = array();
3205 // AJAX edit title
3206 if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
3207 $actions[] = new action_link(
3208 new moodle_url($baseurl, array('update' => $mod->id)),
3209 new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')),
3210 null,
3211 array('class' => 'editing_title', 'title' => $str->edittitle)
3215 // leftright
3216 if ($hasmanageactivities) {
3217 if (right_to_left()) { // Exchange arrows on RTL
3218 $rightarrow = 't/left';
3219 $leftarrow = 't/right';
3220 } else {
3221 $rightarrow = 't/right';
3222 $leftarrow = 't/left';
3225 if ($indent > 0) {
3226 $actions[] = new action_link(
3227 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
3228 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall')),
3229 null,
3230 array('class' => 'editing_moveleft', 'title' => $str->moveleft)
3233 if ($indent >= 0) {
3234 $actions[] = new action_link(
3235 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
3236 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall')),
3237 null,
3238 array('class' => 'editing_moveright', 'title' => $str->moveright)
3243 // move
3244 if ($hasmanageactivities) {
3245 if ($moveselect) {
3246 $actions[] = new action_link(
3247 new moodle_url($baseurl, array('copy' => $mod->id)),
3248 new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall')),
3249 null,
3250 array('class' => 'editing_move', 'title' => $str->move)
3252 } else {
3253 $actions[] = new action_link(
3254 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
3255 new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall')),
3256 null,
3257 array('class' => 'editing_moveup', 'title' => $str->moveup)
3259 $actions[] = new action_link(
3260 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
3261 new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall')),
3262 null,
3263 array('class' => 'editing_movedown', 'title' => $str->movedown)
3268 // Update
3269 if ($hasmanageactivities) {
3270 $actions[] = new action_link(
3271 new moodle_url($baseurl, array('update' => $mod->id)),
3272 new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
3273 null,
3274 array('class' => 'editing_update', 'title' => $str->update)
3278 // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
3279 if (has_all_capabilities($dupecaps, $coursecontext)) {
3280 $actions[] = new action_link(
3281 new moodle_url($baseurl, array('duplicate' => $mod->id)),
3282 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
3283 null,
3284 array('class' => 'editing_duplicate', 'title' => $str->duplicate)
3288 // Delete
3289 if ($hasmanageactivities) {
3290 $actions[] = new action_link(
3291 new moodle_url($baseurl, array('delete' => $mod->id)),
3292 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
3293 null,
3294 array('class' => 'editing_delete', 'title' => $str->delete)
3298 // hideshow
3299 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
3300 if ($mod->visible) {
3301 $actions[] = new action_link(
3302 new moodle_url($baseurl, array('hide' => $mod->id)),
3303 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall')),
3304 null,
3305 array('class' => 'editing_hide', 'title' => $str->hide)
3307 } else {
3308 $actions[] = new action_link(
3309 new moodle_url($baseurl, array('show' => $mod->id)),
3310 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall')),
3311 null,
3312 array('class' => 'editing_show', 'title' => $str->show)
3317 // groupmode
3318 if ($hasmanageactivities and $mod->groupmode !== false) {
3319 if ($mod->groupmode == SEPARATEGROUPS) {
3320 $groupmode = 0;
3321 $grouptitle = $str->groupsseparate;
3322 $forcedgrouptitle = $str->forcedgroupsseparate;
3323 $groupclass = 'editing_groupsseparate';
3324 $groupimage = 't/groups';
3325 } else if ($mod->groupmode == VISIBLEGROUPS) {
3326 $groupmode = 1;
3327 $grouptitle = $str->groupsvisible;
3328 $forcedgrouptitle = $str->forcedgroupsvisible;
3329 $groupclass = 'editing_groupsvisible';
3330 $groupimage = 't/groupv';
3331 } else {
3332 $groupmode = 2;
3333 $grouptitle = $str->groupsnone;
3334 $forcedgrouptitle = $str->forcedgroupsnone;
3335 $groupclass = 'editing_groupsnone';
3336 $groupimage = 't/groupn';
3338 if ($mod->groupmodelink) {
3339 $actions[] = new action_link(
3340 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
3341 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
3342 null,
3343 array('class' => $groupclass, 'title' => $grouptitle)
3345 } else {
3346 $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
3350 // Assign
3351 if (has_capability('moodle/role:assign', $modcontext)){
3352 $actions[] = new action_link(
3353 new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
3354 new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall')),
3355 null,
3356 array('class' => 'editing_assign', 'title' => $str->assign)
3360 $output = html_writer::start_tag('span', array('class' => 'commands'));
3361 foreach ($actions as $action) {
3362 if ($action instanceof renderable) {
3363 $output .= $OUTPUT->render($action);
3364 } else {
3365 $output .= $action;
3368 $output .= html_writer::end_tag('span');
3369 return $output;
3373 * given a course object with shortname & fullname, this function will
3374 * truncate the the number of chars allowed and add ... if it was too long
3376 function course_format_name ($course,$max=100) {
3378 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3379 $shortname = format_string($course->shortname, true, array('context' => $context));
3380 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
3381 $str = $shortname.': '. $fullname;
3382 if (textlib::strlen($str) <= $max) {
3383 return $str;
3385 else {
3386 return textlib::substr($str,0,$max-3).'...';
3391 * Is the user allowed to add this type of module to this course?
3392 * @param object $course the course settings. Only $course->id is used.
3393 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
3394 * @return bool whether the current user is allowed to add this type of module to this course.
3396 function course_allowed_module($course, $modname) {
3397 global $DB;
3399 if (is_numeric($modname)) {
3400 throw new coding_exception('Function course_allowed_module no longer
3401 supports numeric module ids. Please update your code to pass the module name.');
3404 $capability = 'mod/' . $modname . ':addinstance';
3405 if (!get_capability_info($capability)) {
3406 // Debug warning that the capability does not exist, but no more than once per page.
3407 static $warned = array();
3408 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
3409 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
3410 debugging('The module ' . $modname . ' does not define the standard capability ' .
3411 $capability , DEBUG_DEVELOPER);
3412 $warned[$modname] = 1;
3415 // If the capability does not exist, the module can always be added.
3416 return true;
3419 $coursecontext = context_course::instance($course->id);
3420 return has_capability($capability, $coursecontext);
3424 * Recursively delete category including all subcategories and courses.
3425 * @param stdClass $category
3426 * @param boolean $showfeedback display some notices
3427 * @return array return deleted courses
3429 function category_delete_full($category, $showfeedback=true) {
3430 global $CFG, $DB;
3431 require_once($CFG->libdir.'/gradelib.php');
3432 require_once($CFG->libdir.'/questionlib.php');
3433 require_once($CFG->dirroot.'/cohort/lib.php');
3435 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3436 foreach ($children as $childcat) {
3437 category_delete_full($childcat, $showfeedback);
3441 $deletedcourses = array();
3442 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
3443 foreach ($courses as $course) {
3444 if (!delete_course($course, false)) {
3445 throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
3447 $deletedcourses[] = $course;
3451 // move or delete cohorts in this context
3452 cohort_delete_category($category);
3454 // now delete anything that may depend on course category context
3455 grade_course_category_delete($category->id, 0, $showfeedback);
3456 if (!question_delete_course_category($category, 0, $showfeedback)) {
3457 throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
3460 // finally delete the category and it's context
3461 $DB->delete_records('course_categories', array('id'=>$category->id));
3462 delete_context(CONTEXT_COURSECAT, $category->id);
3464 events_trigger('course_category_deleted', $category);
3466 return $deletedcourses;
3470 * Delete category, but move contents to another category.
3471 * @param object $ccategory
3472 * @param int $newparentid category id
3473 * @return bool status
3475 function category_delete_move($category, $newparentid, $showfeedback=true) {
3476 global $CFG, $DB, $OUTPUT;
3477 require_once($CFG->libdir.'/gradelib.php');
3478 require_once($CFG->libdir.'/questionlib.php');
3479 require_once($CFG->dirroot.'/cohort/lib.php');
3481 if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
3482 return false;
3485 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3486 foreach ($children as $childcat) {
3487 move_category($childcat, $newparentcat);
3491 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
3492 if (!move_courses(array_keys($courses), $newparentid)) {
3493 echo $OUTPUT->notification("Error moving courses");
3494 return false;
3496 echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
3499 // move or delete cohorts in this context
3500 cohort_delete_category($category);
3502 // now delete anything that may depend on course category context
3503 grade_course_category_delete($category->id, $newparentid, $showfeedback);
3504 if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
3505 echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
3506 return false;
3509 // finally delete the category and it's context
3510 $DB->delete_records('course_categories', array('id'=>$category->id));
3511 delete_context(CONTEXT_COURSECAT, $category->id);
3513 events_trigger('course_category_deleted', $category);
3515 echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
3517 return true;
3521 * Efficiently moves many courses around while maintaining
3522 * sortorder in order.
3524 * @param array $courseids is an array of course ids
3525 * @param int $categoryid
3526 * @return bool success
3528 function move_courses($courseids, $categoryid) {
3529 global $CFG, $DB, $OUTPUT;
3531 if (empty($courseids)) {
3532 // nothing to do
3533 return;
3536 if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
3537 return false;
3540 $courseids = array_reverse($courseids);
3541 $newparent = get_context_instance(CONTEXT_COURSECAT, $category->id);
3542 $i = 1;
3544 foreach ($courseids as $courseid) {
3545 if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
3546 $course = new stdClass();
3547 $course->id = $courseid;
3548 $course->category = $category->id;
3549 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
3550 if ($category->visible == 0) {
3551 // hide the course when moving into hidden category,
3552 // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
3553 $course->visible = 0;
3556 $DB->update_record('course', $course);
3558 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3559 context_moved($context, $newparent);
3562 fix_course_sortorder();
3564 return true;
3568 * Hide course category and child course and subcategories
3569 * @param stdClass $category
3570 * @return void
3572 function course_category_hide($category) {
3573 global $DB;
3575 $category->visible = 0;
3576 $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
3577 $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
3578 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($category->id)); // store visible flag so that we can return to it if we immediately unhide
3579 $DB->set_field('course', 'visible', 0, array('category' => $category->id));
3580 // get all child categories and hide too
3581 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3582 foreach ($subcats as $cat) {
3583 $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
3584 $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
3585 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
3586 $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
3592 * Show course category and child course and subcategories
3593 * @param stdClass $category
3594 * @return void
3596 function course_category_show($category) {
3597 global $DB;
3599 $category->visible = 1;
3600 $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
3601 $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
3602 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
3603 // get all child categories and unhide too
3604 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3605 foreach ($subcats as $cat) {
3606 if ($cat->visibleold) {
3607 $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
3609 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
3615 * Efficiently moves a category - NOTE that this can have
3616 * a huge impact access-control-wise...
3618 function move_category($category, $newparentcat) {
3619 global $CFG, $DB;
3621 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
3623 $hidecat = false;
3624 if (empty($newparentcat->id)) {
3625 $DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id));
3627 $newparent = get_context_instance(CONTEXT_SYSTEM);
3629 } else {
3630 $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id));
3631 $newparent = get_context_instance(CONTEXT_COURSECAT, $newparentcat->id);
3633 if (!$newparentcat->visible and $category->visible) {
3634 // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
3635 $hidecat = true;
3639 context_moved($context, $newparent);
3641 // now make it last in new category
3642 $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
3644 // and fix the sortorders
3645 fix_course_sortorder();
3647 if ($hidecat) {
3648 course_category_hide($category);
3653 * Returns the display name of the given section that the course prefers.
3655 * This function utilizes a callback that can be implemented within the course
3656 * formats lib.php file to customize the display name that is used to reference
3657 * the section.
3659 * By default (if callback is not defined) the method
3660 * {@see get_numeric_section_name} is called instead.
3662 * @param stdClass $course The course to get the section name for
3663 * @param stdClass $section Section object from database
3664 * @return Display name that the course format prefers, e.g. "Week 2"
3666 * @see get_generic_section_name
3668 function get_section_name(stdClass $course, stdClass $section) {
3669 global $CFG;
3671 /// Inelegant hack for bug 3408
3672 if ($course->format == 'site') {
3673 return get_string('site');
3676 // Use course formatter callback if it exists
3677 $namingfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
3678 $namingfunction = 'callback_'.$course->format.'_get_section_name';
3679 if (!function_exists($namingfunction) && file_exists($namingfile)) {
3680 require_once $namingfile;
3682 if (function_exists($namingfunction)) {
3683 return $namingfunction($course, $section);
3686 // else, default behavior:
3687 return get_generic_section_name($course->format, $section);
3691 * Gets the generic section name for a courses section.
3693 * @param string $format Course format ID e.g. 'weeks' $course->format
3694 * @param stdClass $section Section object from database
3695 * @return Display name that the course format prefers, e.g. "Week 2"
3697 function get_generic_section_name($format, stdClass $section) {
3698 return get_string('sectionname', "format_$format") . ' ' . $section->section;
3702 function course_format_uses_sections($format) {
3703 global $CFG;
3705 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3706 $featurefunction = 'callback_'.$format.'_uses_sections';
3707 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3708 require_once $featurefile;
3710 if (function_exists($featurefunction)) {
3711 return $featurefunction();
3714 return false;
3718 * Returns the information about the ajax support in the given source format
3720 * The returned object's property (boolean)capable indicates that
3721 * the course format supports Moodle course ajax features.
3722 * The property (array)testedbrowsers can be used as a parameter for {@see ajaxenabled()}.
3724 * @param string $format
3725 * @return stdClass
3727 function course_format_ajax_support($format) {
3728 global $CFG;
3730 // set up default values
3731 $ajaxsupport = new stdClass();
3732 $ajaxsupport->capable = false;
3733 $ajaxsupport->testedbrowsers = array();
3735 // get the information from the course format library
3736 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3737 $featurefunction = 'callback_'.$format.'_ajax_support';
3738 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3739 require_once $featurefile;
3741 if (function_exists($featurefunction)) {
3742 $formatsupport = $featurefunction();
3743 if (isset($formatsupport->capable)) {
3744 $ajaxsupport->capable = $formatsupport->capable;
3746 if (is_array($formatsupport->testedbrowsers)) {
3747 $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers;
3751 return $ajaxsupport;
3755 * Can the current user delete this course?
3756 * Course creators have exception,
3757 * 1 day after the creation they can sill delete the course.
3758 * @param int $courseid
3759 * @return boolean
3761 function can_delete_course($courseid) {
3762 global $USER, $DB;
3764 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3766 if (has_capability('moodle/course:delete', $context)) {
3767 return true;
3770 // hack: now try to find out if creator created this course recently (1 day)
3771 if (!has_capability('moodle/course:create', $context)) {
3772 return false;
3775 $since = time() - 60*60*24;
3777 $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
3778 $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
3780 return $DB->record_exists_select('log', $select, $params);
3784 * Save the Your name for 'Some role' strings.
3786 * @param integer $courseid the id of this course.
3787 * @param array $data the data that came from the course settings form.
3789 function save_local_role_names($courseid, $data) {
3790 global $DB;
3791 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3793 foreach ($data as $fieldname => $value) {
3794 if (strpos($fieldname, 'role_') !== 0) {
3795 continue;
3797 list($ignored, $roleid) = explode('_', $fieldname);
3799 // make up our mind whether we want to delete, update or insert
3800 if (!$value) {
3801 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
3803 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
3804 $rolename->name = $value;
3805 $DB->update_record('role_names', $rolename);
3807 } else {
3808 $rolename = new stdClass;
3809 $rolename->contextid = $context->id;
3810 $rolename->roleid = $roleid;
3811 $rolename->name = $value;
3812 $DB->insert_record('role_names', $rolename);
3818 * Create a course and either return a $course object
3820 * Please note this functions does not verify any access control,
3821 * the calling code is responsible for all validation (usually it is the form definition).
3823 * @param array $editoroptions course description editor options
3824 * @param object $data - all the data needed for an entry in the 'course' table
3825 * @return object new course instance
3827 function create_course($data, $editoroptions = NULL) {
3828 global $CFG, $DB;
3830 //check the categoryid - must be given for all new courses
3831 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
3833 //check if the shortname already exist
3834 if (!empty($data->shortname)) {
3835 if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
3836 throw new moodle_exception('shortnametaken');
3840 //check if the id number already exist
3841 if (!empty($data->idnumber)) {
3842 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
3843 throw new moodle_exception('idnumbertaken');
3847 $data->timecreated = time();
3848 $data->timemodified = $data->timecreated;
3850 // place at beginning of any category
3851 $data->sortorder = 0;
3853 if ($editoroptions) {
3854 // summary text is updated later, we need context to store the files first
3855 $data->summary = '';
3856 $data->summary_format = FORMAT_HTML;
3859 if (!isset($data->visible)) {
3860 // data not from form, add missing visibility info
3861 $data->visible = $category->visible;
3863 $data->visibleold = $data->visible;
3865 $newcourseid = $DB->insert_record('course', $data);
3866 $context = get_context_instance(CONTEXT_COURSE, $newcourseid, MUST_EXIST);
3868 if ($editoroptions) {
3869 // Save the files used in the summary editor and store
3870 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3871 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
3872 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
3875 $course = $DB->get_record('course', array('id'=>$newcourseid));
3877 // Setup the blocks
3878 blocks_add_default_course_blocks($course);
3880 $section = new stdClass();
3881 $section->course = $course->id; // Create a default section.
3882 $section->section = 0;
3883 $section->summaryformat = FORMAT_HTML;
3884 $DB->insert_record('course_sections', $section);
3886 fix_course_sortorder();
3888 // new context created - better mark it as dirty
3889 mark_context_dirty($context->path);
3891 // Save any custom role names.
3892 save_local_role_names($course->id, (array)$data);
3894 // set up enrolments
3895 enrol_course_updated(true, $course, $data);
3897 add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
3899 // Trigger events
3900 events_trigger('course_created', $course);
3902 return $course;
3906 * Create a new course category and marks the context as dirty
3908 * This function does not set the sortorder for the new category and
3909 * @see{fix_course_sortorder} should be called after creating a new course
3910 * category
3912 * Please note that this function does not verify access control.
3914 * @param object $category All of the data required for an entry in the course_categories table
3915 * @return object new course category
3917 function create_course_category($category) {
3918 global $DB;
3920 $category->timemodified = time();
3921 $category->id = $DB->insert_record('course_categories', $category);
3922 $category = $DB->get_record('course_categories', array('id' => $category->id));
3924 // We should mark the context as dirty
3925 $category->context = context_coursecat::instance($category->id);
3926 $category->context->mark_dirty();
3928 return $category;
3932 * Update a course.
3934 * Please note this functions does not verify any access control,
3935 * the calling code is responsible for all validation (usually it is the form definition).
3937 * @param object $data - all the data needed for an entry in the 'course' table
3938 * @param array $editoroptions course description editor options
3939 * @return void
3941 function update_course($data, $editoroptions = NULL) {
3942 global $CFG, $DB;
3944 $data->timemodified = time();
3946 $oldcourse = $DB->get_record('course', array('id'=>$data->id), '*', MUST_EXIST);
3947 $context = get_context_instance(CONTEXT_COURSE, $oldcourse->id);
3949 if ($editoroptions) {
3950 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3953 if (!isset($data->category) or empty($data->category)) {
3954 // prevent nulls and 0 in category field
3955 unset($data->category);
3957 $movecat = (isset($data->category) and $oldcourse->category != $data->category);
3959 if (!isset($data->visible)) {
3960 // data not from form, add missing visibility info
3961 $data->visible = $oldcourse->visible;
3964 if ($data->visible != $oldcourse->visible) {
3965 // reset the visibleold flag when manually hiding/unhiding course
3966 $data->visibleold = $data->visible;
3967 } else {
3968 if ($movecat) {
3969 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
3970 if (empty($newcategory->visible)) {
3971 // make sure when moving into hidden category the course is hidden automatically
3972 $data->visible = 0;
3977 // Update with the new data
3978 $DB->update_record('course', $data);
3980 $course = $DB->get_record('course', array('id'=>$data->id));
3982 if ($movecat) {
3983 $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category);
3984 context_moved($context, $newparent);
3987 fix_course_sortorder();
3989 // Test for and remove blocks which aren't appropriate anymore
3990 blocks_remove_inappropriate($course);
3992 // Save any custom role names.
3993 save_local_role_names($course->id, $data);
3995 // update enrol settings
3996 enrol_course_updated(false, $course, $data);
3998 add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
4000 // Trigger events
4001 events_trigger('course_updated', $course);
4005 * Average number of participants
4006 * @return integer
4008 function average_number_of_participants() {
4009 global $DB, $SITE;
4011 //count total of enrolments for visible course (except front page)
4012 $sql = 'SELECT COUNT(*) FROM (
4013 SELECT DISTINCT ue.userid, e.courseid
4014 FROM {user_enrolments} ue, {enrol} e, {course} c
4015 WHERE ue.enrolid = e.id
4016 AND e.courseid <> :siteid
4017 AND c.id = e.courseid
4018 AND c.visible = 1) as total';
4019 $params = array('siteid' => $SITE->id);
4020 $enrolmenttotal = $DB->count_records_sql($sql, $params);
4023 //count total of visible courses (minus front page)
4024 $coursetotal = $DB->count_records('course', array('visible' => 1));
4025 $coursetotal = $coursetotal - 1 ;
4027 //average of enrolment
4028 if (empty($coursetotal)) {
4029 $participantaverage = 0;
4030 } else {
4031 $participantaverage = $enrolmenttotal / $coursetotal;
4034 return $participantaverage;
4038 * Average number of course modules
4039 * @return integer
4041 function average_number_of_courses_modules() {
4042 global $DB, $SITE;
4044 //count total of visible course module (except front page)
4045 $sql = 'SELECT COUNT(*) FROM (
4046 SELECT cm.course, cm.module
4047 FROM {course} c, {course_modules} cm
4048 WHERE c.id = cm.course
4049 AND c.id <> :siteid
4050 AND cm.visible = 1
4051 AND c.visible = 1) as total';
4052 $params = array('siteid' => $SITE->id);
4053 $moduletotal = $DB->count_records_sql($sql, $params);
4056 //count total of visible courses (minus front page)
4057 $coursetotal = $DB->count_records('course', array('visible' => 1));
4058 $coursetotal = $coursetotal - 1 ;
4060 //average of course module
4061 if (empty($coursetotal)) {
4062 $coursemoduleaverage = 0;
4063 } else {
4064 $coursemoduleaverage = $moduletotal / $coursetotal;
4067 return $coursemoduleaverage;
4071 * This class pertains to course requests and contains methods associated with
4072 * create, approving, and removing course requests.
4074 * Please note we do not allow embedded images here because there is no context
4075 * to store them with proper access control.
4077 * @copyright 2009 Sam Hemelryk
4078 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4079 * @since Moodle 2.0
4081 * @property-read int $id
4082 * @property-read string $fullname
4083 * @property-read string $shortname
4084 * @property-read string $summary
4085 * @property-read int $summaryformat
4086 * @property-read int $summarytrust
4087 * @property-read string $reason
4088 * @property-read int $requester
4090 class course_request {
4093 * This is the stdClass that stores the properties for the course request
4094 * and is externally accessed through the __get magic method
4095 * @var stdClass
4097 protected $properties;
4100 * An array of options for the summary editor used by course request forms.
4101 * This is initially set by {@link summary_editor_options()}
4102 * @var array
4103 * @static
4105 protected static $summaryeditoroptions;
4108 * Static function to prepare the summary editor for working with a course
4109 * request.
4111 * @static
4112 * @param null|stdClass $data Optional, an object containing the default values
4113 * for the form, these may be modified when preparing the
4114 * editor so this should be called before creating the form
4115 * @return stdClass An object that can be used to set the default values for
4116 * an mforms form
4118 public static function prepare($data=null) {
4119 if ($data === null) {
4120 $data = new stdClass;
4122 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
4123 return $data;
4127 * Static function to create a new course request when passed an array of properties
4128 * for it.
4130 * This function also handles saving any files that may have been used in the editor
4132 * @static
4133 * @param stdClass $data
4134 * @return course_request The newly created course request
4136 public static function create($data) {
4137 global $USER, $DB, $CFG;
4138 $data->requester = $USER->id;
4140 // Summary is a required field so copy the text over
4141 $data->summary = $data->summary_editor['text'];
4142 $data->summaryformat = $data->summary_editor['format'];
4144 $data->id = $DB->insert_record('course_request', $data);
4146 // Create a new course_request object and return it
4147 $request = new course_request($data);
4149 // Notify the admin if required.
4150 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
4152 $a = new stdClass;
4153 $a->link = "$CFG->wwwroot/course/pending.php";
4154 $a->user = fullname($USER);
4155 $subject = get_string('courserequest');
4156 $message = get_string('courserequestnotifyemail', 'admin', $a);
4157 foreach ($users as $user) {
4158 $request->notify($user, $USER, 'courserequested', $subject, $message);
4162 return $request;
4166 * Returns an array of options to use with a summary editor
4168 * @uses course_request::$summaryeditoroptions
4169 * @return array An array of options to use with the editor
4171 public static function summary_editor_options() {
4172 global $CFG;
4173 if (self::$summaryeditoroptions === null) {
4174 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
4176 return self::$summaryeditoroptions;
4180 * Loads the properties for this course request object. Id is required and if
4181 * only id is provided then we load the rest of the properties from the database
4183 * @param stdClass|int $properties Either an object containing properties
4184 * or the course_request id to load
4186 public function __construct($properties) {
4187 global $DB;
4188 if (empty($properties->id)) {
4189 if (empty($properties)) {
4190 throw new coding_exception('You must provide a course request id when creating a course_request object');
4192 $id = $properties;
4193 $properties = new stdClass;
4194 $properties->id = (int)$id;
4195 unset($id);
4197 if (empty($properties->requester)) {
4198 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
4199 print_error('unknowncourserequest');
4201 } else {
4202 $this->properties = $properties;
4204 $this->properties->collision = null;
4208 * Returns the requested property
4210 * @param string $key
4211 * @return mixed
4213 public function __get($key) {
4214 return $this->properties->$key;
4218 * Override this to ensure empty($request->blah) calls return a reliable answer...
4220 * This is required because we define the __get method
4222 * @param mixed $key
4223 * @return bool True is it not empty, false otherwise
4225 public function __isset($key) {
4226 return (!empty($this->properties->$key));
4230 * Returns the user who requested this course
4232 * Uses a static var to cache the results and cut down the number of db queries
4234 * @staticvar array $requesters An array of cached users
4235 * @return stdClass The user who requested the course
4237 public function get_requester() {
4238 global $DB;
4239 static $requesters= array();
4240 if (!array_key_exists($this->properties->requester, $requesters)) {
4241 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
4243 return $requesters[$this->properties->requester];
4247 * Checks that the shortname used by the course does not conflict with any other
4248 * courses that exist
4250 * @param string|null $shortnamemark The string to append to the requests shortname
4251 * should a conflict be found
4252 * @return bool true is there is a conflict, false otherwise
4254 public function check_shortname_collision($shortnamemark = '[*]') {
4255 global $DB;
4257 if ($this->properties->collision !== null) {
4258 return $this->properties->collision;
4261 if (empty($this->properties->shortname)) {
4262 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
4263 $this->properties->collision = false;
4264 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
4265 if (!empty($shortnamemark)) {
4266 $this->properties->shortname .= ' '.$shortnamemark;
4268 $this->properties->collision = true;
4269 } else {
4270 $this->properties->collision = false;
4272 return $this->properties->collision;
4276 * This function approves the request turning it into a course
4278 * This function converts the course request into a course, at the same time
4279 * transferring any files used in the summary to the new course and then removing
4280 * the course request and the files associated with it.
4282 * @return int The id of the course that was created from this request
4284 public function approve() {
4285 global $CFG, $DB, $USER;
4287 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
4289 $category = get_course_category($CFG->defaultrequestcategory);
4290 $courseconfig = get_config('moodlecourse');
4292 // Transfer appropriate settings
4293 $data = clone($this->properties);
4294 unset($data->id);
4295 unset($data->reason);
4296 unset($data->requester);
4298 // Set category
4299 $data->category = $category->id;
4300 $data->sortorder = $category->sortorder; // place as the first in category
4302 // Set misc settings
4303 $data->requested = 1;
4305 // Apply course default settings
4306 $data->format = $courseconfig->format;
4307 $data->numsections = $courseconfig->numsections;
4308 $data->hiddensections = $courseconfig->hiddensections;
4309 $data->newsitems = $courseconfig->newsitems;
4310 $data->showgrades = $courseconfig->showgrades;
4311 $data->showreports = $courseconfig->showreports;
4312 $data->maxbytes = $courseconfig->maxbytes;
4313 $data->groupmode = $courseconfig->groupmode;
4314 $data->groupmodeforce = $courseconfig->groupmodeforce;
4315 $data->visible = $courseconfig->visible;
4316 $data->visibleold = $data->visible;
4317 $data->lang = $courseconfig->lang;
4319 $course = create_course($data);
4320 $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
4322 // add enrol instances
4323 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
4324 if ($manual = enrol_get_plugin('manual')) {
4325 $manual->add_default_instance($course);
4329 // enrol the requester as teacher if necessary
4330 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
4331 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
4334 $this->delete();
4336 $a = new stdClass();
4337 $a->name = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
4338 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
4339 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
4341 return $course->id;
4345 * Reject a course request
4347 * This function rejects a course request, emailing the requesting user the
4348 * provided notice and then removing the request from the database
4350 * @param string $notice The message to display to the user
4352 public function reject($notice) {
4353 global $USER, $DB;
4354 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
4355 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
4356 $this->delete();
4360 * Deletes the course request and any associated files
4362 public function delete() {
4363 global $DB;
4364 $DB->delete_records('course_request', array('id' => $this->properties->id));
4368 * Send a message from one user to another using events_trigger
4370 * @param object $touser
4371 * @param object $fromuser
4372 * @param string $name
4373 * @param string $subject
4374 * @param string $message
4376 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
4377 $eventdata = new stdClass();
4378 $eventdata->component = 'moodle';
4379 $eventdata->name = $name;
4380 $eventdata->userfrom = $fromuser;
4381 $eventdata->userto = $touser;
4382 $eventdata->subject = $subject;
4383 $eventdata->fullmessage = $message;
4384 $eventdata->fullmessageformat = FORMAT_PLAIN;
4385 $eventdata->fullmessagehtml = '';
4386 $eventdata->smallmessage = '';
4387 $eventdata->notification = 1;
4388 message_send($eventdata);
4393 * Return a list of page types
4394 * @param string $pagetype current page type
4395 * @param stdClass $parentcontext Block's parent context
4396 * @param stdClass $currentcontext Current context of block
4398 function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
4399 // if above course context ,display all course fomats
4400 list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
4401 if ($course->id == SITEID) {
4402 return array('*'=>get_string('page-x', 'pagetype'));
4403 } else {
4404 return array('*'=>get_string('page-x', 'pagetype'),
4405 'course-*'=>get_string('page-course-x', 'pagetype'),
4406 'course-view-*'=>get_string('page-course-view-x', 'pagetype')
4412 * Determine whether course ajax should be enabled for the specified course
4414 * @param stdClass $course The course to test against
4415 * @return boolean Whether course ajax is enabled or note
4417 function course_ajax_enabled($course) {
4418 global $CFG, $PAGE, $SITE;
4420 // Ajax must be enabled globally
4421 if (!$CFG->enableajax) {
4422 return false;
4425 // The user must be editing for AJAX to be included
4426 if (!$PAGE->user_is_editing()) {
4427 return false;
4430 // Check that the theme suports
4431 if (!$PAGE->theme->enablecourseajax) {
4432 return false;
4435 // Check that the course format supports ajax functionality
4436 // The site 'format' doesn't have information on course format support
4437 if ($SITE->id !== $course->id) {
4438 $courseformatajaxsupport = course_format_ajax_support($course->format);
4439 if (!$courseformatajaxsupport->capable) {
4440 return false;
4444 // All conditions have been met so course ajax should be enabled
4445 return true;
4449 * Include the relevant javascript and language strings for the resource
4450 * toolbox YUI module
4452 * @param integer $id The ID of the course being applied to
4453 * @param array $usedmodules An array containing the names of the modules in use on the page
4454 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
4455 * @param stdClass $config An object containing configuration parameters for ajax modules including:
4456 * * resourceurl The URL to post changes to for resource changes
4457 * * sectionurl The URL to post changes to for section changes
4458 * * pageparams Additional parameters to pass through in the post
4459 * @return bool
4461 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
4462 global $PAGE, $SITE;
4464 // Ensure that ajax should be included
4465 if (!course_ajax_enabled($course)) {
4466 return false;
4469 if (!$config) {
4470 $config = new stdClass();
4473 // The URL to use for resource changes
4474 if (!isset($config->resourceurl)) {
4475 $config->resourceurl = '/course/rest.php';
4478 // The URL to use for section changes
4479 if (!isset($config->sectionurl)) {
4480 $config->sectionurl = '/course/rest.php';
4483 // Any additional parameters which need to be included on page submission
4484 if (!isset($config->pageparams)) {
4485 $config->pageparams = array();
4488 // Include toolboxes
4489 $PAGE->requires->yui_module('moodle-course-toolboxes',
4490 'M.course.init_resource_toolbox',
4491 array(array(
4492 'courseid' => $course->id,
4493 'ajaxurl' => $config->resourceurl,
4494 'config' => $config,
4497 $PAGE->requires->yui_module('moodle-course-toolboxes',
4498 'M.course.init_section_toolbox',
4499 array(array(
4500 'courseid' => $course->id,
4501 'format' => $course->format,
4502 'ajaxurl' => $config->sectionurl,
4503 'config' => $config,
4507 // Include course dragdrop
4508 if ($course->id != $SITE->id) {
4509 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
4510 array(array(
4511 'courseid' => $course->id,
4512 'ajaxurl' => $config->sectionurl,
4513 'config' => $config,
4514 )), null, true);
4516 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
4517 array(array(
4518 'courseid' => $course->id,
4519 'ajaxurl' => $config->resourceurl,
4520 'config' => $config,
4521 )), null, true);
4524 // Include blocks dragdrop
4525 $params = array(
4526 'courseid' => $course->id,
4527 'pagetype' => $PAGE->pagetype,
4528 'pagelayout' => $PAGE->pagelayout,
4529 'regions' => $PAGE->blocks->get_regions(),
4531 $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
4533 // Require various strings for the command toolbox
4534 $PAGE->requires->strings_for_js(array(
4535 'moveleft',
4536 'deletechecktype',
4537 'deletechecktypename',
4538 'edittitle',
4539 'edittitleinstructions',
4540 'show',
4541 'hide',
4542 'groupsnone',
4543 'groupsvisible',
4544 'groupsseparate',
4545 'clicktochangeinbrackets',
4546 'markthistopic',
4547 'markedthistopic',
4548 'move',
4549 'movesection',
4550 ), 'moodle');
4552 // Include format-specific strings
4553 if ($course->id != $SITE->id) {
4554 $PAGE->requires->strings_for_js(array(
4555 'showfromothers',
4556 'hidefromothers',
4557 ), 'format_' . $course->format);
4560 // For confirming resource deletion we need the name of the module in question
4561 foreach ($usedmodules as $module => $modname) {
4562 $PAGE->requires->string_for_js('pluginname', $module);
4565 // Load drag and drop upload AJAX.
4566 dndupload_add_to_course($course, $enabledmodules);
4568 return true;
4572 * The URL to use for the specified course (with section)
4574 * @param stdClass $course The course to get the section name for
4575 * @param int $sectionno The section number to return a link to
4576 * @return moodle_url The url of course
4578 function course_get_url($course, $sectionno = null) {
4579 $url = new moodle_url('/course/view.php', array('id' => $course->id));
4581 if (!is_null($sectionno)) {
4582 if ($course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
4583 $url->param('section', $sectionno);
4584 } else {
4585 $url->set_anchor('section-'.$sectionno);
4589 return $url;