MDL-30617 Add a javascript interface for adding modules to a course
[moodle.git] / course / lib.php
blob9499824d729126aebeb4bb0461e40eace0c823b0
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 * Note: Since Moodle 2.3, it is more efficient to get this data by calling
1267 * get_fast_modinfo, then using $modinfo->get_section_info or get_section_info_all.
1269 * @staticvar array $coursesections
1270 * @param int $courseid
1271 * @return array Array of sections
1273 function get_all_sections($courseid) {
1274 global $DB;
1275 static $coursesections = array();
1276 if (!array_key_exists($courseid, $coursesections)) {
1277 $coursesections[$courseid] = $DB->get_records("course_sections", array("course"=>"$courseid"), "section",
1278 'section, id, course, name, summary, summaryformat, sequence, visible, ' .
1279 'availablefrom, availableuntil, showavailability, groupingid');
1281 return $coursesections[$courseid];
1285 * Set highlighted section. Only one section can be highlighted at the time.
1287 * @param int $courseid course id
1288 * @param int $marker highlight section with this number, 0 means remove higlightin
1289 * @return void
1291 function course_set_marker($courseid, $marker) {
1292 global $DB;
1293 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1297 * For a given course section, marks it visible or hidden,
1298 * and does the same for every activity in that section
1300 * @param int $courseid course id
1301 * @param int $sectionnumber The section number to adjust
1302 * @param int $visibility The new visibility
1303 * @return array A list of resources which were hidden in the section
1305 function set_section_visible($courseid, $sectionnumber, $visibility) {
1306 global $DB;
1308 $resourcestotoggle = array();
1309 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1310 $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1311 if (!empty($section->sequence)) {
1312 $modules = explode(",", $section->sequence);
1313 foreach ($modules as $moduleid) {
1314 set_coursemodule_visible($moduleid, $visibility, true);
1317 rebuild_course_cache($courseid);
1319 // Determine which modules are visible for AJAX update
1320 if (!empty($modules)) {
1321 list($insql, $params) = $DB->get_in_or_equal($modules);
1322 $select = 'id ' . $insql . ' AND visible = ?';
1323 array_push($params, $visibility);
1324 if (!$visibility) {
1325 $select .= ' AND visibleold = 1';
1327 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1330 return $resourcestotoggle;
1334 * Obtains shared data that is used in print_section when displaying a
1335 * course-module entry.
1337 * Calls format_text or format_string as appropriate, and obtains the correct icon.
1339 * This data is also used in other areas of the code.
1340 * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
1341 * @param object $course Moodle course object
1342 * @return array An array with the following values in this order:
1343 * $content (optional extra content for after link),
1344 * $instancename (text of link)
1346 function get_print_section_cm_text(cm_info $cm, $course) {
1347 global $OUTPUT;
1349 // Get content from modinfo if specified. Content displays either
1350 // in addition to the standard link (below), or replaces it if
1351 // the link is turned off by setting ->url to null.
1352 if (($content = $cm->get_content()) !== '') {
1353 // Improve filter performance by preloading filter setttings for all
1354 // activities on the course (this does nothing if called multiple
1355 // times)
1356 filter_preload_activities($cm->get_modinfo());
1358 // Get module context
1359 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1360 $labelformatoptions = new stdClass();
1361 $labelformatoptions->noclean = true;
1362 $labelformatoptions->overflowdiv = true;
1363 $labelformatoptions->context = $modulecontext;
1364 $content = format_text($content, FORMAT_HTML, $labelformatoptions);
1365 } else {
1366 $content = '';
1369 // Get course context
1370 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1371 $stringoptions = new stdClass;
1372 $stringoptions->context = $coursecontext;
1373 $instancename = format_string($cm->name, true, $stringoptions);
1374 return array($content, $instancename);
1378 * Prints a section full of activity modules
1380 function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn = false) {
1381 global $CFG, $USER, $DB, $PAGE, $OUTPUT;
1383 static $initialised;
1385 static $groupbuttons;
1386 static $groupbuttonslink;
1387 static $isediting;
1388 static $ismoving;
1389 static $strmovehere;
1390 static $strmovefull;
1391 static $strunreadpostsone;
1392 static $modulenames;
1394 if (!isset($initialised)) {
1395 $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
1396 $groupbuttonslink = (!$course->groupmodeforce);
1397 $isediting = $PAGE->user_is_editing();
1398 $ismoving = $isediting && ismoving($course->id);
1399 if ($ismoving) {
1400 $strmovehere = get_string("movehere");
1401 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
1403 $modulenames = array();
1404 $initialised = true;
1407 $modinfo = get_fast_modinfo($course);
1408 $completioninfo = new completion_info($course);
1410 //Accessibility: replace table with list <ul>, but don't output empty list.
1411 if (!empty($section->sequence)) {
1413 // Fix bug #5027, don't want style=\"width:$width\".
1414 echo "<ul class=\"section img-text\">\n";
1415 $sectionmods = explode(",", $section->sequence);
1417 foreach ($sectionmods as $modnumber) {
1418 if (empty($mods[$modnumber])) {
1419 continue;
1423 * @var cm_info
1425 $mod = $mods[$modnumber];
1427 if ($ismoving and $mod->id == $USER->activitycopy) {
1428 // do not display moving mod
1429 continue;
1432 if (isset($modinfo->cms[$modnumber])) {
1433 // We can continue (because it will not be displayed at all)
1434 // if:
1435 // 1) The activity is not visible to users
1436 // and
1437 // 2a) The 'showavailability' option is not set (if that is set,
1438 // we need to display the activity so we can show
1439 // availability info)
1440 // or
1441 // 2b) The 'availableinfo' is empty, i.e. the activity was
1442 // hidden in a way that leaves no info, such as using the
1443 // eye icon.
1444 if (!$modinfo->cms[$modnumber]->uservisible &&
1445 (empty($modinfo->cms[$modnumber]->showavailability) ||
1446 empty($modinfo->cms[$modnumber]->availableinfo))) {
1447 // visibility shortcut
1448 continue;
1450 } else {
1451 if (!file_exists("$CFG->dirroot/mod/$mod->modname/lib.php")) {
1452 // module not installed
1453 continue;
1455 if (!coursemodule_visible_for_user($mod) &&
1456 empty($mod->showavailability)) {
1457 // full visibility check
1458 continue;
1462 if (!isset($modulenames[$mod->modname])) {
1463 $modulenames[$mod->modname] = get_string('modulename', $mod->modname);
1465 $modulename = $modulenames[$mod->modname];
1467 // In some cases the activity is visible to user, but it is
1468 // dimmed. This is done if viewhiddenactivities is true and if:
1469 // 1. the activity is not visible, or
1470 // 2. the activity has dates set which do not include current, or
1471 // 3. the activity has any other conditions set (regardless of whether
1472 // current user meets them)
1473 $canviewhidden = has_capability(
1474 'moodle/course:viewhiddenactivities',
1475 get_context_instance(CONTEXT_MODULE, $mod->id));
1476 $accessiblebutdim = false;
1477 if ($canviewhidden) {
1478 $accessiblebutdim = !$mod->visible;
1479 if (!empty($CFG->enableavailability)) {
1480 $accessiblebutdim = $accessiblebutdim ||
1481 $mod->availablefrom > time() ||
1482 ($mod->availableuntil && $mod->availableuntil < time()) ||
1483 count($mod->conditionsgrade) > 0 ||
1484 count($mod->conditionscompletion) > 0;
1488 $liclasses = array();
1489 $liclasses[] = 'activity';
1490 $liclasses[] = $mod->modname;
1491 $liclasses[] = 'modtype_'.$mod->modname;
1492 $extraclasses = $mod->get_extra_classes();
1493 if ($extraclasses) {
1494 $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
1496 echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
1497 if ($ismoving) {
1498 echo '<a title="'.$strmovefull.'"'.
1499 ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
1500 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1501 ' alt="'.$strmovehere.'" /></a><br />
1505 $classes = array('mod-indent');
1506 if (!empty($mod->indent)) {
1507 $classes[] = 'mod-indent-'.$mod->indent;
1508 if ($mod->indent > 15) {
1509 $classes[] = 'mod-indent-huge';
1512 echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
1514 // Get data about this course-module
1515 list($content, $instancename) =
1516 get_print_section_cm_text($modinfo->cms[$modnumber], $course);
1518 //Accessibility: for files get description via icon, this is very ugly hack!
1519 $altname = '';
1520 $altname = $mod->modfullname;
1521 if (!empty($customicon)) {
1522 $archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1523 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
1524 $mimetype = mimeinfo_from_icon('type', $customicon);
1525 $altname = get_mimetype_description($mimetype);
1528 // Avoid unnecessary duplication: if e.g. a forum name already
1529 // includes the word forum (or Forum, etc) then it is unhelpful
1530 // to include that in the accessible description that is added.
1531 if (false !== strpos(textlib::strtolower($instancename),
1532 textlib::strtolower($altname))) {
1533 $altname = '';
1535 // File type after name, for alphabetic lists (screen reader).
1536 if ($altname) {
1537 $altname = get_accesshide(' '.$altname);
1540 // We may be displaying this just in order to show information
1541 // about visibility, without the actual link
1542 $contentpart = '';
1543 if ($mod->uservisible) {
1544 // Nope - in this case the link is fully working for user
1545 $linkclasses = '';
1546 $textclasses = '';
1547 if ($accessiblebutdim) {
1548 $linkclasses .= ' dimmed';
1549 $textclasses .= ' dimmed_text';
1550 $accesstext = '<span class="accesshide">'.
1551 get_string('hiddenfromstudents').': </span>';
1552 } else {
1553 $accesstext = '';
1555 if ($linkclasses) {
1556 $linkcss = 'class="' . trim($linkclasses) . '" ';
1557 } else {
1558 $linkcss = '';
1560 if ($textclasses) {
1561 $textcss = 'class="' . trim($textclasses) . '" ';
1562 } else {
1563 $textcss = '';
1566 // Get on-click attribute value if specified
1567 $onclick = $mod->get_on_click();
1568 if ($onclick) {
1569 $onclick = ' onclick="' . $onclick . '"';
1572 if ($url = $mod->get_url()) {
1573 // Display link itself
1574 echo '<a ' . $linkcss . $mod->extra . $onclick .
1575 ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
1576 '" class="activityicon" alt="' .
1577 $modulename . '" /> ' .
1578 $accesstext . '<span class="instancename">' .
1579 $instancename . $altname . '</span></a>';
1581 // If specified, display extra content after link
1582 if ($content) {
1583 $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) .
1584 '">' . $content . '</div>';
1586 } else {
1587 // No link, so display only content
1588 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1589 $accesstext . $content . '</div>';
1592 if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
1593 $groupings = groups_get_all_groupings($course->id);
1594 echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
1596 } else {
1597 $textclasses = $extraclasses;
1598 $textclasses .= ' dimmed_text';
1599 if ($textclasses) {
1600 $textcss = 'class="' . trim($textclasses) . '" ';
1601 } else {
1602 $textcss = '';
1604 $accesstext = '<span class="accesshide">' .
1605 get_string('notavailableyet', 'condition') .
1606 ': </span>';
1608 if ($url = $mod->get_url()) {
1609 // Display greyed-out text of link
1610 echo '<div ' . $textcss . $mod->extra .
1611 ' >' . '<img src="' . $mod->get_icon_url() .
1612 '" class="activityicon" alt="' .
1613 $modulename .
1614 '" /> <span>'. $instancename . $altname .
1615 '</span></div>';
1617 // Do not display content after link when it is greyed out like this.
1618 } else {
1619 // No link, so display only content (also greyed)
1620 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1621 $accesstext . $content . '</div>';
1625 // Module can put text after the link (e.g. forum unread)
1626 echo $mod->get_after_link();
1628 // If there is content but NO link (eg label), then display the
1629 // content here (BEFORE any icons). In this case cons must be
1630 // displayed after the content so that it makes more sense visually
1631 // and for accessibility reasons, e.g. if you have a one-line label
1632 // it should work similarly (at least in terms of ordering) to an
1633 // activity.
1634 if (empty($url)) {
1635 echo $contentpart;
1638 if ($isediting) {
1639 if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
1640 if (! $mod->groupmodelink = $groupbuttonslink) {
1641 $mod->groupmode = $course->groupmode;
1644 } else {
1645 $mod->groupmode = false;
1647 echo '&nbsp;&nbsp;';
1649 if ($sectionreturn) {
1650 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
1651 } else {
1652 echo make_editing_buttons($mod, $absolute, true, $mod->indent, 0);
1654 echo $mod->get_after_edit_icons();
1657 // Completion
1658 $completion = $hidecompletion
1659 ? COMPLETION_TRACKING_NONE
1660 : $completioninfo->is_enabled($mod);
1661 if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
1662 !isguestuser() && $mod->uservisible) {
1663 $completiondata = $completioninfo->get_data($mod,true);
1664 $completionicon = '';
1665 if ($isediting) {
1666 switch ($completion) {
1667 case COMPLETION_TRACKING_MANUAL :
1668 $completionicon = 'manual-enabled'; break;
1669 case COMPLETION_TRACKING_AUTOMATIC :
1670 $completionicon = 'auto-enabled'; break;
1671 default: // wtf
1673 } else if ($completion==COMPLETION_TRACKING_MANUAL) {
1674 switch($completiondata->completionstate) {
1675 case COMPLETION_INCOMPLETE:
1676 $completionicon = 'manual-n'; break;
1677 case COMPLETION_COMPLETE:
1678 $completionicon = 'manual-y'; break;
1680 } else { // Automatic
1681 switch($completiondata->completionstate) {
1682 case COMPLETION_INCOMPLETE:
1683 $completionicon = 'auto-n'; break;
1684 case COMPLETION_COMPLETE:
1685 $completionicon = 'auto-y'; break;
1686 case COMPLETION_COMPLETE_PASS:
1687 $completionicon = 'auto-pass'; break;
1688 case COMPLETION_COMPLETE_FAIL:
1689 $completionicon = 'auto-fail'; break;
1692 if ($completionicon) {
1693 $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
1694 $imgalt = s(get_string('completion-alt-'.$completionicon, 'completion', $mod->name));
1695 if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
1696 $imgtitle = s(get_string('completion-title-'.$completionicon, 'completion', $mod->name));
1697 $newstate =
1698 $completiondata->completionstate==COMPLETION_COMPLETE
1699 ? COMPLETION_INCOMPLETE
1700 : COMPLETION_COMPLETE;
1701 // In manual mode the icon is a toggle form...
1703 // If this completion state is used by the
1704 // conditional activities system, we need to turn
1705 // off the JS.
1706 if (!empty($CFG->enableavailability) &&
1707 condition_info::completion_value_used_as_condition($course, $mod)) {
1708 $extraclass = ' preventjs';
1709 } else {
1710 $extraclass = '';
1712 echo "
1713 <form class='togglecompletion$extraclass' method='post' action='".$CFG->wwwroot."/course/togglecompletion.php'><div>
1714 <input type='hidden' name='id' value='{$mod->id}' />
1715 <input type='hidden' name='modulename' value='".s($mod->name)."' />
1716 <input type='hidden' name='sesskey' value='".sesskey()."' />
1717 <input type='hidden' name='completionstate' value='$newstate' />
1718 <input type='image' src='$imgsrc' alt='$imgalt' title='$imgtitle' />
1719 </div></form>";
1720 } else {
1721 // In auto mode, or when editing, the icon is just an image
1722 echo "<span class='autocompletion'>";
1723 echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
1728 // If there is content AND a link, then display the content here
1729 // (AFTER any icons). Otherwise it was displayed before
1730 if (!empty($url)) {
1731 echo $contentpart;
1734 // Show availability information (for someone who isn't allowed to
1735 // see the activity itself, or for staff)
1736 if (!$mod->uservisible) {
1737 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
1738 } else if ($canviewhidden && !empty($CFG->enableavailability)) {
1739 $ci = new condition_info($mod);
1740 $fullinfo = $ci->get_full_information();
1741 if($fullinfo) {
1742 echo '<div class="availabilityinfo">'.get_string($mod->showavailability
1743 ? 'userrestriction_visible'
1744 : 'userrestriction_hidden','condition',
1745 $fullinfo).'</div>';
1749 echo html_writer::end_tag('div');
1750 echo html_writer::end_tag('li')."\n";
1753 } elseif ($ismoving) {
1754 echo "<ul class=\"section\">\n";
1757 if ($ismoving) {
1758 echo '<li><a title="'.$strmovefull.'"'.
1759 ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
1760 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1761 ' alt="'.$strmovehere.'" /></a></li>
1764 if (!empty($section->sequence) || $ismoving) {
1765 echo "</ul><!--class='section'-->\n\n";
1770 * Prints the menus to add activities and resources.
1772 function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false) {
1773 global $CFG, $OUTPUT;
1775 // check to see if user can add menus
1776 if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id))) {
1777 return false;
1780 // Retrieve all modules with associated metadata
1781 $modules = get_module_metadata($course, $modnames);
1783 // We'll sort resources and activities into two lists
1784 $resources = array();
1785 $activities = array();
1787 // We need to add the section section to the link for each module
1788 $sectionlink = '&section=' . $section;
1790 foreach ($modules as $module) {
1791 if (isset($module->types)) {
1792 // This module has a subtype
1793 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1794 $subtypes = array();
1795 foreach ($module->types as $subtype) {
1796 $subtypes[$subtype->link . $sectionlink] = $subtype->title;
1799 // Sort module subtypes into the list
1800 if (!empty($module->title)) {
1801 // This grouping has a name
1802 if ($module->archetype == MOD_CLASS_RESOURCE) {
1803 $resources[] = array($module->title=>$subtypes);
1804 } else {
1805 $activities[] = array($module->title=>$subtypes);
1807 } else {
1808 // This grouping does not have a name
1809 if ($module->archetype == MOD_CLASS_RESOURCE) {
1810 $resources = array_merge($resources, $subtypes);
1811 } else {
1812 $activities = array_merge($activities, $subtypes);
1815 } else {
1816 // This module has no subtypes
1817 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
1818 $resources[$module->link . $sectionlink] = $module->title;
1819 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
1820 // System modules cannot be added by user, do not add to dropdown
1821 } else {
1822 $activities[$module->link . $sectionlink] = $module->title;
1827 $straddactivity = get_string('addactivity');
1828 $straddresource = get_string('addresource');
1830 $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
1832 if (!$vertical) {
1833 $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
1836 if (!empty($resources)) {
1837 $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
1838 $select->set_help_icon('resources');
1839 $output .= $OUTPUT->render($select);
1842 if (!empty($activities)) {
1843 $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
1844 $select->set_help_icon('activities');
1845 $output .= $OUTPUT->render($select);
1848 if (!$vertical) {
1849 $output .= html_writer::end_tag('div');
1852 $output .= html_writer::end_tag('div');
1854 if (course_ajax_enabled($course)) {
1855 $straddeither = get_string('addresourceoractivity');
1856 // The module chooser link
1857 $modchooser = '<div class="sectionaddmodule">';
1858 $modchooser .= '<div class="section_add_menus"><a class="sectionmodchooserlink" href="#">';
1859 $modchooser .= '<img alt="'.$straddeither.'" src="'.$OUTPUT->pix_url('t/add').'" class="activityicon">&nbsp;';
1860 $modchooser .= '<span class="instancename">'.$straddeither.'</span>';
1861 $modchooser .= '</a></div></div>';
1863 // Wrap the normal output in a noscript div
1864 $usemodchooser = get_user_preferences('usemodchooser', 1);
1865 if ($usemodchooser) {
1866 $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
1867 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
1868 } else {
1869 $output = html_writer::tag('div', $output, array('class' => 'visibleifjs addresourcedropdown'));
1870 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hiddenifjs addresourcemodchooser'));
1872 $output = $modchooser . $output;
1875 if ($return) {
1876 return $output;
1877 } else {
1878 echo $output;
1883 * Retrieve all metadata for the requested modules
1885 * @param object $course The Course
1886 * @param array $modnames An array containing the list of modules and their
1887 * names
1888 * @return array A list of stdClass objects containing metadata about each
1889 * module
1891 function get_module_metadata($course, $modnames) {
1892 global $CFG, $OUTPUT;
1894 // get_module_metadata will be called once per section on the page and courses may show
1895 // different modules to one another
1896 static $modlist = array();
1897 if (!isset($modlist[$course->id])) {
1898 $modlist[$course->id] = array();
1901 $return = array();
1902 $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&add=';
1903 foreach($modnames as $modname => $modnamestr) {
1904 if (!course_allowed_module($course, $modname)) {
1905 continue;
1907 if (isset($modlist[$modname])) {
1908 // This module is already cached
1909 $return[$modname] = $modlist[$course->id][$modname];
1910 continue;
1913 // Include the module lib
1914 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1915 if (!file_exists($libfile)) {
1916 continue;
1918 include_once($libfile);
1920 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1921 $gettypesfunc = $modname.'_get_types';
1922 if (function_exists($gettypesfunc)) {
1923 if ($types = $gettypesfunc()) {
1924 $group = new stdClass();
1925 $group->name = $modname;
1926 $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1927 foreach($types as $type) {
1928 if ($type->typestr === '--') {
1929 continue;
1931 if (strpos($type->typestr, '--') === 0) {
1932 $group->title = str_replace('--', '', $type->typestr);
1933 continue;
1935 // Set the Sub Type metadata
1936 $subtype = new stdClass();
1937 $subtype->title = $type->typestr;
1938 $subtype->type = str_replace('&amp;', '&', $type->type);
1939 $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1940 $subtype->archetype = $type->modclass;
1942 // The group archetype should match the subtype archetypes and all subtypes
1943 // should have the same archetype
1944 $group->archetype = $subtype->archetype;
1946 if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1947 $subtype->help = get_string('help' . $subtype->name, $modname);
1949 $subtype->link = $urlbase . $subtype->type;
1950 $group->types[] = $subtype;
1952 $modlist[$course->id][$modname] = $group;
1954 } else {
1955 $module = new stdClass();
1956 $module->title = get_string('modulename', $modname);
1957 $module->name = $modname;
1958 $module->link = $urlbase . $modname;
1959 $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1960 $sm = get_string_manager();
1961 if ($sm->string_exists('modulename_help', $modname)) {
1962 $module->help = get_string('modulename_help', $modname);
1963 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs
1964 $link = get_string('modulename_link', $modname);
1965 $linktext = get_string('morehelp');
1966 $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
1969 $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1970 $modlist[$course->id][$modname] = $module;
1972 $return[$modname] = $modlist[$course->id][$modname];
1975 return $return;
1979 * Return the course category context for the category with id $categoryid, except
1980 * that if $categoryid is 0, return the system context.
1982 * @param integer $categoryid a category id or 0.
1983 * @return object the corresponding context
1985 function get_category_or_system_context($categoryid) {
1986 if ($categoryid) {
1987 return get_context_instance(CONTEXT_COURSECAT, $categoryid);
1988 } else {
1989 return get_context_instance(CONTEXT_SYSTEM);
1994 * Gets the child categories of a given courses category. Uses a static cache
1995 * to make repeat calls efficient.
1997 * @param int $parentid the id of a course category.
1998 * @return array all the child course categories.
2000 function get_child_categories($parentid) {
2001 static $allcategories = null;
2003 // only fill in this variable the first time
2004 if (null == $allcategories) {
2005 $allcategories = array();
2007 $categories = get_categories();
2008 foreach ($categories as $category) {
2009 if (empty($allcategories[$category->parent])) {
2010 $allcategories[$category->parent] = array();
2012 $allcategories[$category->parent][] = $category;
2016 if (empty($allcategories[$parentid])) {
2017 return array();
2018 } else {
2019 return $allcategories[$parentid];
2024 * This function recursively travels the categories, building up a nice list
2025 * for display. It also makes an array that list all the parents for each
2026 * category.
2028 * For example, if you have a tree of categories like:
2029 * Miscellaneous (id = 1)
2030 * Subcategory (id = 2)
2031 * Sub-subcategory (id = 4)
2032 * Other category (id = 3)
2033 * Then after calling this function you will have
2034 * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
2035 * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
2036 * 3 => 'Other category');
2037 * $parents = array(2 => array(1), 4 => array(1, 2));
2039 * If you specify $requiredcapability, then only categories where the current
2040 * user has that capability will be added to $list, although all categories
2041 * will still be added to $parents, and if you only have $requiredcapability
2042 * in a child category, not the parent, then the child catgegory will still be
2043 * included.
2045 * If you specify the option $excluded, then that category, and all its children,
2046 * are omitted from the tree. This is useful when you are doing something like
2047 * moving categories, where you do not want to allow people to move a category
2048 * to be the child of itself.
2050 * @param array $list For output, accumulates an array categoryid => full category path name
2051 * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
2052 * @param string/array $requiredcapability if given, only categories where the current
2053 * user has this capability will be added to $list. Can also be an array of capabilities,
2054 * in which case they are all required.
2055 * @param integer $excludeid Omit this category and its children from the lists built.
2056 * @param object $category Build the tree starting at this category - otherwise starts at the top level.
2057 * @param string $path For internal use, as part of recursive calls.
2059 function make_categories_list(&$list, &$parents, $requiredcapability = '',
2060 $excludeid = 0, $category = NULL, $path = "") {
2062 // initialize the arrays if needed
2063 if (!is_array($list)) {
2064 $list = array();
2066 if (!is_array($parents)) {
2067 $parents = array();
2070 if (empty($category)) {
2071 // Start at the top level.
2072 $category = new stdClass;
2073 $category->id = 0;
2074 } else {
2075 // This is the excluded category, don't include it.
2076 if ($excludeid > 0 && $excludeid == $category->id) {
2077 return;
2080 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2081 $categoryname = format_string($category->name, true, array('context' => $context));
2083 // Update $path.
2084 if ($path) {
2085 $path = $path.' / '.$categoryname;
2086 } else {
2087 $path = $categoryname;
2090 // Add this category to $list, if the permissions check out.
2091 if (empty($requiredcapability)) {
2092 $list[$category->id] = $path;
2094 } else {
2095 $requiredcapability = (array)$requiredcapability;
2096 if (has_all_capabilities($requiredcapability, $context)) {
2097 $list[$category->id] = $path;
2102 // Add all the children recursively, while updating the parents array.
2103 if ($categories = get_child_categories($category->id)) {
2104 foreach ($categories as $cat) {
2105 if (!empty($category->id)) {
2106 if (isset($parents[$category->id])) {
2107 $parents[$cat->id] = $parents[$category->id];
2109 $parents[$cat->id][] = $category->id;
2111 make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
2117 * This function generates a structured array of courses and categories.
2119 * The depth of categories is limited by $CFG->maxcategorydepth however there
2120 * is no limit on the number of courses!
2122 * Suitable for use with the course renderers course_category_tree method:
2123 * $renderer = $PAGE->get_renderer('core','course');
2124 * echo $renderer->course_category_tree(get_course_category_tree());
2126 * @global moodle_database $DB
2127 * @param int $id
2128 * @param int $depth
2130 function get_course_category_tree($id = 0, $depth = 0) {
2131 global $DB, $CFG;
2132 $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM));
2133 $categories = get_child_categories($id);
2134 $categoryids = array();
2135 foreach ($categories as $key => &$category) {
2136 if (!$category->visible && !$viewhiddencats) {
2137 unset($categories[$key]);
2138 continue;
2140 $categoryids[$category->id] = $category;
2141 if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
2142 list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
2143 foreach ($subcategories as $subid=>$subcat) {
2144 $categoryids[$subid] = $subcat;
2146 $category->courses = array();
2150 if ($depth > 0) {
2151 // This is a recursive call so return the required array
2152 return array($categories, $categoryids);
2155 // The depth is 0 this function has just been called so we can finish it off
2157 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
2158 list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
2159 $sql = "SELECT
2160 c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
2161 $ccselect
2162 FROM {course} c
2163 $ccjoin
2164 WHERE c.category $catsql ORDER BY c.sortorder ASC";
2165 if ($courses = $DB->get_records_sql($sql, $catparams)) {
2166 // loop throught them
2167 foreach ($courses as $course) {
2168 if ($course->id == SITEID) {
2169 continue;
2171 context_instance_preload($course);
2172 if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
2173 $categoryids[$course->category]->courses[$course->id] = $course;
2177 return $categories;
2181 * Recursive function to print out all the categories in a nice format
2182 * with or without courses included
2184 function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
2185 global $CFG;
2187 // maxcategorydepth == 0 meant no limit
2188 if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
2189 return;
2192 if (!$displaylist) {
2193 make_categories_list($displaylist, $parentslist);
2196 if ($category) {
2197 if ($category->visible or has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM))) {
2198 print_category_info($category, $depth, $showcourses);
2199 } else {
2200 return; // Don't bother printing children of invisible categories
2203 } else {
2204 $category = new stdClass();
2205 $category->id = "0";
2208 if ($categories = get_child_categories($category->id)) { // Print all the children recursively
2209 $countcats = count($categories);
2210 $count = 0;
2211 $first = true;
2212 $last = false;
2213 foreach ($categories as $cat) {
2214 $count++;
2215 if ($count == $countcats) {
2216 $last = true;
2218 $up = $first ? false : true;
2219 $down = $last ? false : true;
2220 $first = false;
2222 print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
2228 * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
2230 function make_categories_options() {
2231 make_categories_list($cats,$parents);
2232 foreach ($cats as $key => $value) {
2233 if (array_key_exists($key,$parents)) {
2234 if ($indent = count($parents[$key])) {
2235 for ($i = 0; $i < $indent; $i++) {
2236 $cats[$key] = '&nbsp;'.$cats[$key];
2241 return $cats;
2245 * Gets the name of a course to be displayed when showing a list of courses.
2246 * By default this is just $course->fullname but user can configure it. The
2247 * result of this function should be passed through print_string.
2248 * @param object $course Moodle course object
2249 * @return string Display name of course (either fullname or short + fullname)
2251 function get_course_display_name_for_list($course) {
2252 global $CFG;
2253 if (!empty($CFG->courselistshortnames)) {
2254 return $course->shortname . ' ' .$course->fullname;
2255 } else {
2256 return $course->fullname;
2261 * Prints the category info in indented fashion
2262 * This function is only used by print_whole_category_list() above
2264 function print_category_info($category, $depth=0, $showcourses = false) {
2265 global $CFG, $DB, $OUTPUT;
2267 $strsummary = get_string('summary');
2269 $catlinkcss = null;
2270 if (!$category->visible) {
2271 $catlinkcss = array('class'=>'dimmed');
2273 static $coursecount = null;
2274 if (null === $coursecount) {
2275 // only need to check this once
2276 $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
2279 if ($showcourses and $coursecount) {
2280 $catimage = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
2281 } else {
2282 $catimage = "&nbsp;";
2285 $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
2286 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2287 $fullname = format_string($category->name, true, array('context' => $context));
2289 if ($showcourses and $coursecount) {
2290 echo '<div class="categorylist clearfix">';
2291 $cat = '';
2292 $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
2293 $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2294 $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
2296 $html = '';
2297 if ($depth > 0) {
2298 for ($i=0; $i< $depth; $i++) {
2299 $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
2300 $cat = '';
2302 } else {
2303 $html = $cat;
2305 echo html_writer::tag('div', $html, array('class'=>'category'));
2306 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2308 // does the depth exceed maxcategorydepth
2309 // maxcategorydepth == 0 or unset meant no limit
2310 $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
2311 if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
2312 foreach ($courses as $course) {
2313 $linkcss = null;
2314 if (!$course->visible) {
2315 $linkcss = array('class'=>'dimmed');
2318 $coursename = get_course_display_name_for_list($course);
2319 $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
2321 // print enrol info
2322 $courseicon = '';
2323 if ($icons = enrol_get_course_info_icons($course)) {
2324 foreach ($icons as $pix_icon) {
2325 $courseicon = $OUTPUT->render($pix_icon).' ';
2329 $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
2331 if ($course->summary) {
2332 $link = new moodle_url('/course/info.php?id='.$course->id);
2333 $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
2334 new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
2335 array('title'=>$strsummary));
2337 $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
2340 $html = '';
2341 for ($i=0; $i <= $depth; $i++) {
2342 $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
2343 $coursecontent = '';
2345 echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
2348 echo '</div>';
2349 } else {
2350 echo '<div class="categorylist">';
2351 $html = '';
2352 $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2353 if (count($courses) > 0) {
2354 $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
2357 if ($depth > 0) {
2358 for ($i=0; $i< $depth; $i++) {
2359 $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
2360 $cat = '';
2362 } else {
2363 $html = $cat;
2366 echo html_writer::tag('div', $html, array('class'=>'category'));
2367 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2368 echo '</div>';
2373 * Print the buttons relating to course requests.
2375 * @param object $systemcontext the system context.
2377 function print_course_request_buttons($systemcontext) {
2378 global $CFG, $DB, $OUTPUT;
2379 if (empty($CFG->enablecourserequests)) {
2380 return;
2382 if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
2383 /// Print a button to request a new course
2384 echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
2386 /// Print a button to manage pending requests
2387 if (has_capability('moodle/site:approvecourse', $systemcontext)) {
2388 $disabled = !$DB->record_exists('course_request', array());
2389 echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
2394 * Does the user have permission to edit things in this category?
2396 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2397 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
2399 function can_edit_in_category($categoryid = 0) {
2400 $context = get_category_or_system_context($categoryid);
2401 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
2405 * Prints the turn editing on/off button on course/index.php or course/category.php.
2407 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2408 * @return string HTML of the editing button, or empty string, if this user is not allowed
2409 * to see it.
2411 function update_category_button($categoryid = 0) {
2412 global $CFG, $PAGE, $OUTPUT;
2414 // Check permissions.
2415 if (!can_edit_in_category($categoryid)) {
2416 return '';
2419 // Work out the appropriate action.
2420 if ($PAGE->user_is_editing()) {
2421 $label = get_string('turneditingoff');
2422 $edit = 'off';
2423 } else {
2424 $label = get_string('turneditingon');
2425 $edit = 'on';
2428 // Generate the button HTML.
2429 $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
2430 if ($categoryid) {
2431 $options['id'] = $categoryid;
2432 $page = 'category.php';
2433 } else {
2434 $page = 'index.php';
2436 return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
2440 * Category is 0 (for all courses) or an object
2442 function print_courses($category) {
2443 global $CFG, $OUTPUT;
2445 if (!is_object($category) && $category==0) {
2446 $categories = get_child_categories(0); // Parent = 0 ie top-level categories only
2447 if (is_array($categories) && count($categories) == 1) {
2448 $category = array_shift($categories);
2449 $courses = get_courses_wmanagers($category->id,
2450 'c.sortorder ASC',
2451 array('summary','summaryformat'));
2452 } else {
2453 $courses = get_courses_wmanagers('all',
2454 'c.sortorder ASC',
2455 array('summary','summaryformat'));
2457 unset($categories);
2458 } else {
2459 $courses = get_courses_wmanagers($category->id,
2460 'c.sortorder ASC',
2461 array('summary','summaryformat'));
2464 if ($courses) {
2465 echo html_writer::start_tag('ul', array('class'=>'unlist'));
2466 foreach ($courses as $course) {
2467 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2468 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2469 echo html_writer::start_tag('li');
2470 print_course($course);
2471 echo html_writer::end_tag('li');
2474 echo html_writer::end_tag('ul');
2475 } else {
2476 echo $OUTPUT->heading(get_string("nocoursesyet"));
2477 $context = get_context_instance(CONTEXT_SYSTEM);
2478 if (has_capability('moodle/course:create', $context)) {
2479 $options = array();
2480 if (!empty($category->id)) {
2481 $options['category'] = $category->id;
2482 } else {
2483 $options['category'] = $CFG->defaultrequestcategory;
2485 echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
2486 echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
2487 echo html_writer::end_tag('div');
2493 * Print a description of a course, suitable for browsing in a list.
2495 * @param object $course the course object.
2496 * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
2498 function print_course($course, $highlightterms = '') {
2499 global $CFG, $USER, $DB, $OUTPUT;
2501 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2503 // Rewrite file URLs so that they are correct
2504 $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
2506 echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
2507 echo html_writer::start_tag('div', array('class'=>'info'));
2508 echo html_writer::start_tag('h3', array('class'=>'name'));
2510 $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
2512 $coursename = get_course_display_name_for_list($course);
2513 $linktext = highlight($highlightterms, format_string($coursename));
2514 $linkparams = array('title'=>get_string('entercourse'));
2515 if (empty($course->visible)) {
2516 $linkparams['class'] = 'dimmed';
2518 echo html_writer::link($linkhref, $linktext, $linkparams);
2519 echo html_writer::end_tag('h3');
2521 /// first find all roles that are supposed to be displayed
2522 if (!empty($CFG->coursecontact)) {
2523 $managerroles = explode(',', $CFG->coursecontact);
2524 $namesarray = array();
2525 $rusers = array();
2527 if (!isset($course->managers)) {
2528 $rusers = get_role_users($managerroles, $context, true,
2529 'ra.id AS raid, u.id, u.username, u.firstname, u.lastname,
2530 r.name AS rolename, r.sortorder, r.id AS roleid',
2531 'r.sortorder ASC, u.lastname ASC');
2532 } else {
2533 // use the managers array if we have it for perf reasosn
2534 // populate the datastructure like output of get_role_users();
2535 foreach ($course->managers as $manager) {
2536 $u = new stdClass();
2537 $u = $manager->user;
2538 $u->roleid = $manager->roleid;
2539 $u->rolename = $manager->rolename;
2541 $rusers[] = $u;
2545 /// Rename some of the role names if needed
2546 if (isset($context)) {
2547 $aliasnames = $DB->get_records('role_names', array('contextid'=>$context->id), '', 'roleid,contextid,name');
2550 $namesarray = array();
2551 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
2552 foreach ($rusers as $ra) {
2553 if (isset($namesarray[$ra->id])) {
2554 // only display a user once with the higest sortorder role
2555 continue;
2558 if (isset($aliasnames[$ra->roleid])) {
2559 $ra->rolename = $aliasnames[$ra->roleid]->name;
2562 $fullname = fullname($ra, $canviewfullnames);
2563 $namesarray[$ra->id] = format_string($ra->rolename).': '.
2564 html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
2567 if (!empty($namesarray)) {
2568 echo html_writer::start_tag('ul', array('class'=>'teachers'));
2569 foreach ($namesarray as $name) {
2570 echo html_writer::tag('li', $name);
2572 echo html_writer::end_tag('ul');
2575 echo html_writer::end_tag('div'); // End of info div
2577 echo html_writer::start_tag('div', array('class'=>'summary'));
2578 $options = new stdClass();
2579 $options->noclean = true;
2580 $options->para = false;
2581 $options->overflowdiv = true;
2582 if (!isset($course->summaryformat)) {
2583 $course->summaryformat = FORMAT_MOODLE;
2585 echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options, $course->id));
2586 if ($icons = enrol_get_course_info_icons($course)) {
2587 echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
2588 foreach ($icons as $icon) {
2589 echo $OUTPUT->render($icon);
2591 echo html_writer::end_tag('div'); // End of enrolmenticons div
2593 echo html_writer::end_tag('div'); // End of summary div
2594 echo html_writer::end_tag('div'); // End of coursebox div
2598 * Prints custom user information on the home page.
2599 * Over time this can include all sorts of information
2601 function print_my_moodle() {
2602 global $USER, $CFG, $DB, $OUTPUT;
2604 if (!isloggedin() or isguestuser()) {
2605 print_error('nopermissions', '', '', 'See My Moodle');
2608 $courses = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
2609 $rhosts = array();
2610 $rcourses = array();
2611 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
2612 $rcourses = get_my_remotecourses($USER->id);
2613 $rhosts = get_my_remotehosts();
2616 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
2618 if (!empty($courses)) {
2619 echo '<ul class="unlist">';
2620 foreach ($courses as $course) {
2621 if ($course->id == SITEID) {
2622 continue;
2624 echo '<li>';
2625 print_course($course);
2626 echo "</li>\n";
2628 echo "</ul>\n";
2631 // MNET
2632 if (!empty($rcourses)) {
2633 // at the IDP, we know of all the remote courses
2634 foreach ($rcourses as $course) {
2635 print_remote_course($course, "100%");
2637 } elseif (!empty($rhosts)) {
2638 // non-IDP, we know of all the remote servers, but not courses
2639 foreach ($rhosts as $host) {
2640 print_remote_host($host, "100%");
2643 unset($course);
2644 unset($host);
2646 if ($DB->count_records("course") > (count($courses) + 1) ) { // Some courses not being displayed
2647 echo "<table width=\"100%\"><tr><td align=\"center\">";
2648 print_course_search("", false, "short");
2649 echo "</td><td align=\"center\">";
2650 echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
2651 echo "</td></tr></table>\n";
2654 } else {
2655 if ($DB->count_records("course_categories") > 1) {
2656 echo $OUTPUT->box_start("categorybox");
2657 print_whole_category_list();
2658 echo $OUTPUT->box_end();
2659 } else {
2660 print_courses(0);
2666 function print_course_search($value="", $return=false, $format="plain") {
2667 global $CFG;
2668 static $count = 0;
2670 $count++;
2672 $id = 'coursesearch';
2674 if ($count > 1) {
2675 $id .= $count;
2678 $strsearchcourses= get_string("searchcourses");
2680 if ($format == 'plain') {
2681 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2682 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2683 $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
2684 $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
2685 $output .= '<input type="submit" value="'.get_string('go').'" />';
2686 $output .= '</fieldset></form>';
2687 } else if ($format == 'short') {
2688 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2689 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2690 $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
2691 $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2692 $output .= '<input type="submit" value="'.get_string('go').'" />';
2693 $output .= '</fieldset></form>';
2694 } else if ($format == 'navbar') {
2695 $output = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2696 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2697 $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
2698 $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2699 $output .= '<input type="submit" value="'.get_string('go').'" />';
2700 $output .= '</fieldset></form>';
2703 if ($return) {
2704 return $output;
2706 echo $output;
2709 function print_remote_course($course, $width="100%") {
2710 global $CFG, $USER;
2712 $linkcss = '';
2714 $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}";
2716 echo '<div class="coursebox remotecoursebox clearfix">';
2717 echo '<div class="info">';
2718 echo '<div class="name"><a title="'.get_string('entercourse').'"'.
2719 $linkcss.' href="'.$url.'">'
2720 . format_string($course->fullname) .'</a><br />'
2721 . format_string($course->hostname) . ' : '
2722 . format_string($course->cat_name) . ' : '
2723 . format_string($course->shortname). '</div>';
2724 echo '</div><div class="summary">';
2725 $options = new stdClass();
2726 $options->noclean = true;
2727 $options->para = false;
2728 $options->overflowdiv = true;
2729 echo format_text($course->summary, $course->summaryformat, $options);
2730 echo '</div>';
2731 echo '</div>';
2734 function print_remote_host($host, $width="100%") {
2735 global $OUTPUT;
2737 $linkcss = '';
2739 echo '<div class="coursebox clearfix">';
2740 echo '<div class="info">';
2741 echo '<div class="name">';
2742 echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
2743 echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
2744 . s($host['name']).'</a> - ';
2745 echo $host['count'] . ' ' . get_string('courses');
2746 echo '</div>';
2747 echo '</div>';
2748 echo '</div>';
2752 /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
2754 function add_course_module($mod) {
2755 global $DB;
2757 $mod->added = time();
2758 unset($mod->id);
2760 return $DB->insert_record("course_modules", $mod);
2764 * Returns course section - creates new if does not exist yet.
2765 * @param int $relative section number
2766 * @param int $courseid
2767 * @return object $course_section object
2769 function get_course_section($section, $courseid) {
2770 global $DB;
2772 if ($cw = $DB->get_record("course_sections", array("section"=>$section, "course"=>$courseid))) {
2773 return $cw;
2775 $cw = new stdClass();
2776 $cw->course = $courseid;
2777 $cw->section = $section;
2778 $cw->summary = "";
2779 $cw->summaryformat = FORMAT_HTML;
2780 $cw->sequence = "";
2781 $id = $DB->insert_record("course_sections", $cw);
2782 rebuild_course_cache($courseid, true);
2783 return $DB->get_record("course_sections", array("id"=>$id));
2787 * Given a full mod object with section and course already defined, adds this module to that section.
2789 * @param object $mod
2790 * @param int $beforemod An existing ID which we will insert the new module before
2791 * @return int The course_sections ID where the mod is inserted
2793 function add_mod_to_section($mod, $beforemod=NULL) {
2794 global $DB;
2796 if ($section = $DB->get_record("course_sections", array("course"=>$mod->course, "section"=>$mod->section))) {
2798 $section->sequence = trim($section->sequence);
2800 if (empty($section->sequence)) {
2801 $newsequence = "$mod->coursemodule";
2803 } else if ($beforemod) {
2804 $modarray = explode(",", $section->sequence);
2806 if ($key = array_keys($modarray, $beforemod->id)) {
2807 $insertarray = array($mod->id, $beforemod->id);
2808 array_splice($modarray, $key[0], 1, $insertarray);
2809 $newsequence = implode(",", $modarray);
2811 } else { // Just tack it on the end anyway
2812 $newsequence = "$section->sequence,$mod->coursemodule";
2815 } else {
2816 $newsequence = "$section->sequence,$mod->coursemodule";
2819 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2820 return $section->id; // Return course_sections ID that was used.
2822 } else { // Insert a new record
2823 $section = new stdClass();
2824 $section->course = $mod->course;
2825 $section->section = $mod->section;
2826 $section->summary = "";
2827 $section->summaryformat = FORMAT_HTML;
2828 $section->sequence = $mod->coursemodule;
2829 return $DB->insert_record("course_sections", $section);
2833 function set_coursemodule_groupmode($id, $groupmode) {
2834 global $DB;
2835 return $DB->set_field("course_modules", "groupmode", $groupmode, array("id"=>$id));
2838 function set_coursemodule_idnumber($id, $idnumber) {
2839 global $DB;
2840 return $DB->set_field("course_modules", "idnumber", $idnumber, array("id"=>$id));
2844 * $prevstateoverrides = true will set the visibility of the course module
2845 * to what is defined in visibleold. This enables us to remember the current
2846 * visibility when making a whole section hidden, so that when we toggle
2847 * that section back to visible, we are able to return the visibility of
2848 * the course module back to what it was originally.
2850 function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
2851 global $DB, $CFG;
2852 require_once($CFG->libdir.'/gradelib.php');
2854 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2855 return false;
2857 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
2858 return false;
2860 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2861 foreach($events as $event) {
2862 if ($visible) {
2863 show_event($event);
2864 } else {
2865 hide_event($event);
2870 // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
2871 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
2872 if ($grade_items) {
2873 foreach ($grade_items as $grade_item) {
2874 $grade_item->set_hidden(!$visible);
2878 if ($prevstateoverrides) {
2879 if ($visible == '0') {
2880 // Remember the current visible state so we can toggle this back.
2881 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id'=>$id));
2882 } else {
2883 // Get the previous saved visible states.
2884 return $DB->set_field('course_modules', 'visible', $cm->visibleold, array('id'=>$id));
2887 return $DB->set_field("course_modules", "visible", $visible, array("id"=>$id));
2891 * Delete a course module and any associated data at the course level (events)
2892 * Until 1.5 this function simply marked a deleted flag ... now it
2893 * deletes it completely.
2896 function delete_course_module($id) {
2897 global $CFG, $DB;
2898 require_once($CFG->libdir.'/gradelib.php');
2899 require_once($CFG->dirroot.'/blog/lib.php');
2901 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2902 return true;
2904 $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
2905 //delete events from calendar
2906 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2907 foreach($events as $event) {
2908 delete_event($event->id);
2911 //delete grade items, outcome items and grades attached to modules
2912 if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
2913 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
2914 foreach ($grade_items as $grade_item) {
2915 $grade_item->delete('moddelete');
2918 // Delete completion and availability data; it is better to do this even if the
2919 // features are not turned on, in case they were turned on previously (these will be
2920 // very quick on an empty table)
2921 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
2922 $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
2923 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
2924 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
2926 delete_context(CONTEXT_MODULE, $cm->id);
2927 return $DB->delete_records('course_modules', array('id'=>$cm->id));
2930 function delete_mod_from_section($mod, $section) {
2931 global $DB;
2933 if ($section = $DB->get_record("course_sections", array("id"=>$section)) ) {
2935 $modarray = explode(",", $section->sequence);
2937 if ($key = array_keys ($modarray, $mod)) {
2938 array_splice($modarray, $key[0], 1);
2939 $newsequence = implode(",", $modarray);
2940 return $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2941 } else {
2942 return false;
2946 return false;
2950 * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
2952 * @param object $course course object
2953 * @param int $section Section number (not id!!!)
2954 * @param int $move (-1 or 1)
2955 * @return boolean true if section moved successfully
2957 function move_section($course, $section, $move) {
2958 /// Moves a whole course section up and down within the course
2959 global $USER, $DB;
2961 if (!$move) {
2962 return true;
2965 $sectiondest = $section + $move;
2967 if ($sectiondest > $course->numsections or $sectiondest < 1) {
2968 return false;
2971 if (!$sectionrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$section))) {
2972 return false;
2975 if (!$sectiondestrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$sectiondest))) {
2976 return false;
2979 // Three-step change ensures that the section always remains unique (there is
2980 // a unique index now)
2981 $DB->set_field("course_sections", "section", -$sectiondest, array("id"=>$sectionrecord->id));
2982 $DB->set_field("course_sections", "section", $section, array("id"=>$sectiondestrecord->id));
2983 $DB->set_field("course_sections", "section", $sectiondest, array("id"=>$sectionrecord->id));
2985 // Update highlighting if the move affects highlighted section
2986 if ($course->marker == $section) {
2987 course_set_marker($course->id, $sectiondest);
2988 } elseif ($course->marker == $sectiondest) {
2989 course_set_marker($course->id, $section);
2993 // Fix order if needed. The database prevents duplicate sections, but it is
2994 // possible there could be a gap in the numbering.
2995 $sections = $DB->get_records('course_sections', array('course'=>$course->id), 'section ASC');
2996 $n = 0;
2997 foreach ($sections as $section) {
2998 if ($section->section != $n) {
2999 $DB->set_field('course_sections', 'section', $n, array('id'=>$section->id));
3001 $n++;
3003 return true;
3007 * Moves a section within a course, from a position to another.
3008 * Be very careful: $section and $destination refer to section number,
3009 * not id!.
3011 * @param object $course
3012 * @param int $section Section number (not id!!!)
3013 * @param int $destination
3014 * @return boolean Result
3016 function move_section_to($course, $section, $destination) {
3017 /// Moves a whole course section up and down within the course
3018 global $USER, $DB;
3020 if (!$destination && $destination != 0) {
3021 return true;
3024 if ($destination > $course->numsections) {
3025 return false;
3028 // Get all sections for this course and re-order them (2 of them should now share the same section number)
3029 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
3030 'section ASC, id ASC', 'id, section')) {
3031 return false;
3034 $movedsections = reorder_sections($sections, $section, $destination);
3036 // Update all sections. Do this in 2 steps to avoid breaking database
3037 // uniqueness constraint
3038 $transaction = $DB->start_delegated_transaction();
3039 foreach ($movedsections as $id => $position) {
3040 if ($sections[$id] !== $position) {
3041 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
3044 foreach ($movedsections as $id => $position) {
3045 if ($sections[$id] !== $position) {
3046 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
3050 // Adjust destination to reflect the actual section
3051 $moveup = false;
3052 if ($section > $destination) {
3053 $destination++;
3054 $moveup = true;
3057 // If we move the highlighted section itself, then just highlight the destination.
3058 // Adjust the higlighted section location if we move something over it either direction.
3059 if ($section == $course->marker) {
3060 course_set_marker($course->id, $destination);
3061 } elseif ($moveup && $section > $course->marker && $course->marker >= $destination) {
3062 course_set_marker($course->id, $course->marker+1);
3063 } elseif (!$moveup && $section < $course->marker && $course->marker <= $destination) {
3064 course_set_marker($course->id, $course->marker-1);
3067 $transaction->allow_commit();
3068 return true;
3072 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
3073 * an original position number and a target position number, rebuilds the array so that the
3074 * move is made without any duplication of section positions.
3075 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
3076 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
3078 * @param array $sections
3079 * @param int $origin_position
3080 * @param int $target_position
3081 * @return array
3083 function reorder_sections($sections, $origin_position, $target_position) {
3084 if (!is_array($sections)) {
3085 return false;
3088 // We can't move section position 0
3089 if ($origin_position < 1) {
3090 echo "We can't move section position 0";
3091 return false;
3094 // Locate origin section in sections array
3095 if (!$origin_key = array_search($origin_position, $sections)) {
3096 echo "searched position not in sections array";
3097 return false; // searched position not in sections array
3100 // Extract origin section
3101 $origin_section = $sections[$origin_key];
3102 unset($sections[$origin_key]);
3104 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
3105 $found = false;
3106 $append_array = array();
3107 foreach ($sections as $id => $position) {
3108 if ($found) {
3109 $append_array[$id] = $position;
3110 unset($sections[$id]);
3112 if ($position == $target_position) {
3113 $found = true;
3117 // Append moved section
3118 $sections[$origin_key] = $origin_section;
3120 // Append rest of array (if applicable)
3121 if (!empty($append_array)) {
3122 foreach ($append_array as $id => $position) {
3123 $sections[$id] = $position;
3127 // Renumber positions
3128 $position = 0;
3129 foreach ($sections as $id => $p) {
3130 $sections[$id] = $position;
3131 $position++;
3134 return $sections;
3139 * Move the module object $mod to the specified $section
3140 * If $beforemod exists then that is the module
3141 * before which $modid should be inserted
3142 * All parameters are objects
3144 function moveto_module($mod, $section, $beforemod=NULL) {
3145 global $DB, $OUTPUT;
3147 /// Remove original module from original section
3148 if (! delete_mod_from_section($mod->id, $mod->section)) {
3149 echo $OUTPUT->notification("Could not delete module from existing section");
3152 /// Update module itself if necessary
3154 if ($mod->section != $section->id) {
3155 $mod->section = $section->id;
3156 $DB->update_record("course_modules", $mod);
3157 // if moving to a hidden section then hide module
3158 if (!$section->visible) {
3159 set_coursemodule_visible($mod->id, 0);
3163 /// Add the module into the new section
3165 $mod->course = $section->course;
3166 $mod->section = $section->section; // need relative reference
3167 $mod->coursemodule = $mod->id;
3169 if (! add_mod_to_section($mod, $beforemod)) {
3170 return false;
3173 return true;
3177 * Produces the editing buttons for a module
3179 * @global core_renderer $OUTPUT
3180 * @staticvar type $str
3181 * @param stdClass $mod The module to produce editing buttons for
3182 * @param bool $absolute_ignored ignored - all links are absolute
3183 * @param bool $moveselect If true a move seleciton process is used (default true)
3184 * @param int $indent The current indenting
3185 * @param int $section The section to link back to
3186 * @return string XHTML for the editing buttons
3188 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
3189 global $CFG, $OUTPUT, $COURSE;
3191 static $str;
3193 $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
3194 $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
3196 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
3197 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
3199 // no permission to edit anything
3200 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
3201 return false;
3204 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
3206 if (!isset($str)) {
3207 $str = new stdClass;
3208 $str->assign = get_string("assignroles", 'role');
3209 $str->delete = get_string("delete");
3210 $str->move = get_string("move");
3211 $str->moveup = get_string("moveup");
3212 $str->movedown = get_string("movedown");
3213 $str->moveright = get_string("moveright");
3214 $str->moveleft = get_string("moveleft");
3215 $str->update = get_string("update");
3216 $str->duplicate = get_string("duplicate");
3217 $str->hide = get_string("hide");
3218 $str->show = get_string("show");
3219 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
3220 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
3221 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
3222 $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
3223 $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
3224 $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
3225 $str->edittitle = get_string('edittitle', 'moodle');
3228 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
3230 if ($section >= 0) {
3231 $baseurl->param('sr', $section);
3233 $actions = array();
3235 // AJAX edit title
3236 if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
3237 $actions[] = new action_link(
3238 new moodle_url($baseurl, array('update' => $mod->id)),
3239 new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')),
3240 null,
3241 array('class' => 'editing_title', 'title' => $str->edittitle)
3245 // leftright
3246 if ($hasmanageactivities) {
3247 if (right_to_left()) { // Exchange arrows on RTL
3248 $rightarrow = 't/left';
3249 $leftarrow = 't/right';
3250 } else {
3251 $rightarrow = 't/right';
3252 $leftarrow = 't/left';
3255 if ($indent > 0) {
3256 $actions[] = new action_link(
3257 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
3258 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall')),
3259 null,
3260 array('class' => 'editing_moveleft', 'title' => $str->moveleft)
3263 if ($indent >= 0) {
3264 $actions[] = new action_link(
3265 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
3266 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall')),
3267 null,
3268 array('class' => 'editing_moveright', 'title' => $str->moveright)
3273 // move
3274 if ($hasmanageactivities) {
3275 if ($moveselect) {
3276 $actions[] = new action_link(
3277 new moodle_url($baseurl, array('copy' => $mod->id)),
3278 new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall')),
3279 null,
3280 array('class' => 'editing_move', 'title' => $str->move)
3282 } else {
3283 $actions[] = new action_link(
3284 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
3285 new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall')),
3286 null,
3287 array('class' => 'editing_moveup', 'title' => $str->moveup)
3289 $actions[] = new action_link(
3290 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
3291 new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall')),
3292 null,
3293 array('class' => 'editing_movedown', 'title' => $str->movedown)
3298 // Update
3299 if ($hasmanageactivities) {
3300 $actions[] = new action_link(
3301 new moodle_url($baseurl, array('update' => $mod->id)),
3302 new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
3303 null,
3304 array('class' => 'editing_update', 'title' => $str->update)
3308 // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
3309 if (has_all_capabilities($dupecaps, $coursecontext)) {
3310 $actions[] = new action_link(
3311 new moodle_url($baseurl, array('duplicate' => $mod->id)),
3312 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
3313 null,
3314 array('class' => 'editing_duplicate', 'title' => $str->duplicate)
3318 // Delete
3319 if ($hasmanageactivities) {
3320 $actions[] = new action_link(
3321 new moodle_url($baseurl, array('delete' => $mod->id)),
3322 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
3323 null,
3324 array('class' => 'editing_delete', 'title' => $str->delete)
3328 // hideshow
3329 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
3330 if ($mod->visible) {
3331 $actions[] = new action_link(
3332 new moodle_url($baseurl, array('hide' => $mod->id)),
3333 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall')),
3334 null,
3335 array('class' => 'editing_hide', 'title' => $str->hide)
3337 } else {
3338 $actions[] = new action_link(
3339 new moodle_url($baseurl, array('show' => $mod->id)),
3340 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall')),
3341 null,
3342 array('class' => 'editing_show', 'title' => $str->show)
3347 // groupmode
3348 if ($hasmanageactivities and $mod->groupmode !== false) {
3349 if ($mod->groupmode == SEPARATEGROUPS) {
3350 $groupmode = 0;
3351 $grouptitle = $str->groupsseparate;
3352 $forcedgrouptitle = $str->forcedgroupsseparate;
3353 $groupclass = 'editing_groupsseparate';
3354 $groupimage = 't/groups';
3355 } else if ($mod->groupmode == VISIBLEGROUPS) {
3356 $groupmode = 1;
3357 $grouptitle = $str->groupsvisible;
3358 $forcedgrouptitle = $str->forcedgroupsvisible;
3359 $groupclass = 'editing_groupsvisible';
3360 $groupimage = 't/groupv';
3361 } else {
3362 $groupmode = 2;
3363 $grouptitle = $str->groupsnone;
3364 $forcedgrouptitle = $str->forcedgroupsnone;
3365 $groupclass = 'editing_groupsnone';
3366 $groupimage = 't/groupn';
3368 if ($mod->groupmodelink) {
3369 $actions[] = new action_link(
3370 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
3371 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
3372 null,
3373 array('class' => $groupclass, 'title' => $grouptitle)
3375 } else {
3376 $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
3380 // Assign
3381 if (has_capability('moodle/role:assign', $modcontext)){
3382 $actions[] = new action_link(
3383 new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
3384 new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall')),
3385 null,
3386 array('class' => 'editing_assign', 'title' => $str->assign)
3390 $output = html_writer::start_tag('span', array('class' => 'commands'));
3391 foreach ($actions as $action) {
3392 if ($action instanceof renderable) {
3393 $output .= $OUTPUT->render($action);
3394 } else {
3395 $output .= $action;
3398 $output .= html_writer::end_tag('span');
3399 return $output;
3403 * given a course object with shortname & fullname, this function will
3404 * truncate the the number of chars allowed and add ... if it was too long
3406 function course_format_name ($course,$max=100) {
3408 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3409 $shortname = format_string($course->shortname, true, array('context' => $context));
3410 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
3411 $str = $shortname.': '. $fullname;
3412 if (textlib::strlen($str) <= $max) {
3413 return $str;
3415 else {
3416 return textlib::substr($str,0,$max-3).'...';
3421 * Is the user allowed to add this type of module to this course?
3422 * @param object $course the course settings. Only $course->id is used.
3423 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
3424 * @return bool whether the current user is allowed to add this type of module to this course.
3426 function course_allowed_module($course, $modname) {
3427 global $DB;
3429 if (is_numeric($modname)) {
3430 throw new coding_exception('Function course_allowed_module no longer
3431 supports numeric module ids. Please update your code to pass the module name.');
3434 $capability = 'mod/' . $modname . ':addinstance';
3435 if (!get_capability_info($capability)) {
3436 // Debug warning that the capability does not exist, but no more than once per page.
3437 static $warned = array();
3438 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
3439 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
3440 debugging('The module ' . $modname . ' does not define the standard capability ' .
3441 $capability , DEBUG_DEVELOPER);
3442 $warned[$modname] = 1;
3445 // If the capability does not exist, the module can always be added.
3446 return true;
3449 $coursecontext = context_course::instance($course->id);
3450 return has_capability($capability, $coursecontext);
3454 * Recursively delete category including all subcategories and courses.
3455 * @param stdClass $category
3456 * @param boolean $showfeedback display some notices
3457 * @return array return deleted courses
3459 function category_delete_full($category, $showfeedback=true) {
3460 global $CFG, $DB;
3461 require_once($CFG->libdir.'/gradelib.php');
3462 require_once($CFG->libdir.'/questionlib.php');
3463 require_once($CFG->dirroot.'/cohort/lib.php');
3465 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3466 foreach ($children as $childcat) {
3467 category_delete_full($childcat, $showfeedback);
3471 $deletedcourses = array();
3472 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
3473 foreach ($courses as $course) {
3474 if (!delete_course($course, false)) {
3475 throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
3477 $deletedcourses[] = $course;
3481 // move or delete cohorts in this context
3482 cohort_delete_category($category);
3484 // now delete anything that may depend on course category context
3485 grade_course_category_delete($category->id, 0, $showfeedback);
3486 if (!question_delete_course_category($category, 0, $showfeedback)) {
3487 throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
3490 // finally delete the category and it's context
3491 $DB->delete_records('course_categories', array('id'=>$category->id));
3492 delete_context(CONTEXT_COURSECAT, $category->id);
3494 events_trigger('course_category_deleted', $category);
3496 return $deletedcourses;
3500 * Delete category, but move contents to another category.
3501 * @param object $ccategory
3502 * @param int $newparentid category id
3503 * @return bool status
3505 function category_delete_move($category, $newparentid, $showfeedback=true) {
3506 global $CFG, $DB, $OUTPUT;
3507 require_once($CFG->libdir.'/gradelib.php');
3508 require_once($CFG->libdir.'/questionlib.php');
3509 require_once($CFG->dirroot.'/cohort/lib.php');
3511 if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
3512 return false;
3515 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3516 foreach ($children as $childcat) {
3517 move_category($childcat, $newparentcat);
3521 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
3522 if (!move_courses(array_keys($courses), $newparentid)) {
3523 echo $OUTPUT->notification("Error moving courses");
3524 return false;
3526 echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
3529 // move or delete cohorts in this context
3530 cohort_delete_category($category);
3532 // now delete anything that may depend on course category context
3533 grade_course_category_delete($category->id, $newparentid, $showfeedback);
3534 if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
3535 echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
3536 return false;
3539 // finally delete the category and it's context
3540 $DB->delete_records('course_categories', array('id'=>$category->id));
3541 delete_context(CONTEXT_COURSECAT, $category->id);
3543 events_trigger('course_category_deleted', $category);
3545 echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
3547 return true;
3551 * Efficiently moves many courses around while maintaining
3552 * sortorder in order.
3554 * @param array $courseids is an array of course ids
3555 * @param int $categoryid
3556 * @return bool success
3558 function move_courses($courseids, $categoryid) {
3559 global $CFG, $DB, $OUTPUT;
3561 if (empty($courseids)) {
3562 // nothing to do
3563 return;
3566 if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
3567 return false;
3570 $courseids = array_reverse($courseids);
3571 $newparent = get_context_instance(CONTEXT_COURSECAT, $category->id);
3572 $i = 1;
3574 foreach ($courseids as $courseid) {
3575 if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
3576 $course = new stdClass();
3577 $course->id = $courseid;
3578 $course->category = $category->id;
3579 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
3580 if ($category->visible == 0) {
3581 // hide the course when moving into hidden category,
3582 // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
3583 $course->visible = 0;
3586 $DB->update_record('course', $course);
3588 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3589 context_moved($context, $newparent);
3592 fix_course_sortorder();
3594 return true;
3598 * Hide course category and child course and subcategories
3599 * @param stdClass $category
3600 * @return void
3602 function course_category_hide($category) {
3603 global $DB;
3605 $category->visible = 0;
3606 $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
3607 $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
3608 $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
3609 $DB->set_field('course', 'visible', 0, array('category' => $category->id));
3610 // get all child categories and hide too
3611 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3612 foreach ($subcats as $cat) {
3613 $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
3614 $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
3615 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
3616 $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
3622 * Show course category and child course and subcategories
3623 * @param stdClass $category
3624 * @return void
3626 function course_category_show($category) {
3627 global $DB;
3629 $category->visible = 1;
3630 $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
3631 $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
3632 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
3633 // get all child categories and unhide too
3634 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3635 foreach ($subcats as $cat) {
3636 if ($cat->visibleold) {
3637 $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
3639 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
3645 * Efficiently moves a category - NOTE that this can have
3646 * a huge impact access-control-wise...
3648 function move_category($category, $newparentcat) {
3649 global $CFG, $DB;
3651 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
3653 $hidecat = false;
3654 if (empty($newparentcat->id)) {
3655 $DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id));
3657 $newparent = get_context_instance(CONTEXT_SYSTEM);
3659 } else {
3660 $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id));
3661 $newparent = get_context_instance(CONTEXT_COURSECAT, $newparentcat->id);
3663 if (!$newparentcat->visible and $category->visible) {
3664 // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
3665 $hidecat = true;
3669 context_moved($context, $newparent);
3671 // now make it last in new category
3672 $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
3674 // and fix the sortorders
3675 fix_course_sortorder();
3677 if ($hidecat) {
3678 course_category_hide($category);
3683 * Returns the display name of the given section that the course prefers.
3685 * This function utilizes a callback that can be implemented within the course
3686 * formats lib.php file to customize the display name that is used to reference
3687 * the section.
3689 * By default (if callback is not defined) the method
3690 * {@see get_numeric_section_name} is called instead.
3692 * @param stdClass $course The course to get the section name for
3693 * @param stdClass $section Section object from database
3694 * @return Display name that the course format prefers, e.g. "Week 2"
3696 * @see get_generic_section_name
3698 function get_section_name(stdClass $course, stdClass $section) {
3699 global $CFG;
3701 /// Inelegant hack for bug 3408
3702 if ($course->format == 'site') {
3703 return get_string('site');
3706 // Use course formatter callback if it exists
3707 $namingfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
3708 $namingfunction = 'callback_'.$course->format.'_get_section_name';
3709 if (!function_exists($namingfunction) && file_exists($namingfile)) {
3710 require_once $namingfile;
3712 if (function_exists($namingfunction)) {
3713 return $namingfunction($course, $section);
3716 // else, default behavior:
3717 return get_generic_section_name($course->format, $section);
3721 * Gets the generic section name for a courses section.
3723 * @param string $format Course format ID e.g. 'weeks' $course->format
3724 * @param stdClass $section Section object from database
3725 * @return Display name that the course format prefers, e.g. "Week 2"
3727 function get_generic_section_name($format, stdClass $section) {
3728 return get_string('sectionname', "format_$format") . ' ' . $section->section;
3732 function course_format_uses_sections($format) {
3733 global $CFG;
3735 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3736 $featurefunction = 'callback_'.$format.'_uses_sections';
3737 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3738 require_once $featurefile;
3740 if (function_exists($featurefunction)) {
3741 return $featurefunction();
3744 return false;
3748 * Returns the information about the ajax support in the given source format
3750 * The returned object's property (boolean)capable indicates that
3751 * the course format supports Moodle course ajax features.
3752 * The property (array)testedbrowsers can be used as a parameter for {@see ajaxenabled()}.
3754 * @param string $format
3755 * @return stdClass
3757 function course_format_ajax_support($format) {
3758 global $CFG;
3760 // set up default values
3761 $ajaxsupport = new stdClass();
3762 $ajaxsupport->capable = false;
3763 $ajaxsupport->testedbrowsers = array();
3765 // get the information from the course format library
3766 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3767 $featurefunction = 'callback_'.$format.'_ajax_support';
3768 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3769 require_once $featurefile;
3771 if (function_exists($featurefunction)) {
3772 $formatsupport = $featurefunction();
3773 if (isset($formatsupport->capable)) {
3774 $ajaxsupport->capable = $formatsupport->capable;
3776 if (is_array($formatsupport->testedbrowsers)) {
3777 $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers;
3781 return $ajaxsupport;
3785 * Can the current user delete this course?
3786 * Course creators have exception,
3787 * 1 day after the creation they can sill delete the course.
3788 * @param int $courseid
3789 * @return boolean
3791 function can_delete_course($courseid) {
3792 global $USER, $DB;
3794 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3796 if (has_capability('moodle/course:delete', $context)) {
3797 return true;
3800 // hack: now try to find out if creator created this course recently (1 day)
3801 if (!has_capability('moodle/course:create', $context)) {
3802 return false;
3805 $since = time() - 60*60*24;
3807 $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
3808 $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
3810 return $DB->record_exists_select('log', $select, $params);
3814 * Save the Your name for 'Some role' strings.
3816 * @param integer $courseid the id of this course.
3817 * @param array $data the data that came from the course settings form.
3819 function save_local_role_names($courseid, $data) {
3820 global $DB;
3821 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3823 foreach ($data as $fieldname => $value) {
3824 if (strpos($fieldname, 'role_') !== 0) {
3825 continue;
3827 list($ignored, $roleid) = explode('_', $fieldname);
3829 // make up our mind whether we want to delete, update or insert
3830 if (!$value) {
3831 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
3833 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
3834 $rolename->name = $value;
3835 $DB->update_record('role_names', $rolename);
3837 } else {
3838 $rolename = new stdClass;
3839 $rolename->contextid = $context->id;
3840 $rolename->roleid = $roleid;
3841 $rolename->name = $value;
3842 $DB->insert_record('role_names', $rolename);
3848 * Create a course and either return a $course object
3850 * Please note this functions does not verify any access control,
3851 * the calling code is responsible for all validation (usually it is the form definition).
3853 * @param array $editoroptions course description editor options
3854 * @param object $data - all the data needed for an entry in the 'course' table
3855 * @return object new course instance
3857 function create_course($data, $editoroptions = NULL) {
3858 global $CFG, $DB;
3860 //check the categoryid - must be given for all new courses
3861 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
3863 //check if the shortname already exist
3864 if (!empty($data->shortname)) {
3865 if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
3866 throw new moodle_exception('shortnametaken');
3870 //check if the id number already exist
3871 if (!empty($data->idnumber)) {
3872 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
3873 throw new moodle_exception('idnumbertaken');
3877 $data->timecreated = time();
3878 $data->timemodified = $data->timecreated;
3880 // place at beginning of any category
3881 $data->sortorder = 0;
3883 if ($editoroptions) {
3884 // summary text is updated later, we need context to store the files first
3885 $data->summary = '';
3886 $data->summary_format = FORMAT_HTML;
3889 if (!isset($data->visible)) {
3890 // data not from form, add missing visibility info
3891 $data->visible = $category->visible;
3893 $data->visibleold = $data->visible;
3895 $newcourseid = $DB->insert_record('course', $data);
3896 $context = get_context_instance(CONTEXT_COURSE, $newcourseid, MUST_EXIST);
3898 if ($editoroptions) {
3899 // Save the files used in the summary editor and store
3900 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3901 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
3902 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
3905 $course = $DB->get_record('course', array('id'=>$newcourseid));
3907 // Setup the blocks
3908 blocks_add_default_course_blocks($course);
3910 $section = new stdClass();
3911 $section->course = $course->id; // Create a default section.
3912 $section->section = 0;
3913 $section->summaryformat = FORMAT_HTML;
3914 $DB->insert_record('course_sections', $section);
3916 fix_course_sortorder();
3918 // new context created - better mark it as dirty
3919 mark_context_dirty($context->path);
3921 // Save any custom role names.
3922 save_local_role_names($course->id, (array)$data);
3924 // set up enrolments
3925 enrol_course_updated(true, $course, $data);
3927 add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
3929 // Trigger events
3930 events_trigger('course_created', $course);
3932 return $course;
3936 * Create a new course category and marks the context as dirty
3938 * This function does not set the sortorder for the new category and
3939 * @see{fix_course_sortorder} should be called after creating a new course
3940 * category
3942 * Please note that this function does not verify access control.
3944 * @param object $category All of the data required for an entry in the course_categories table
3945 * @return object new course category
3947 function create_course_category($category) {
3948 global $DB;
3950 $category->timemodified = time();
3951 $category->id = $DB->insert_record('course_categories', $category);
3952 $category = $DB->get_record('course_categories', array('id' => $category->id));
3954 // We should mark the context as dirty
3955 $category->context = context_coursecat::instance($category->id);
3956 $category->context->mark_dirty();
3958 return $category;
3962 * Update a course.
3964 * Please note this functions does not verify any access control,
3965 * the calling code is responsible for all validation (usually it is the form definition).
3967 * @param object $data - all the data needed for an entry in the 'course' table
3968 * @param array $editoroptions course description editor options
3969 * @return void
3971 function update_course($data, $editoroptions = NULL) {
3972 global $CFG, $DB;
3974 $data->timemodified = time();
3976 $oldcourse = $DB->get_record('course', array('id'=>$data->id), '*', MUST_EXIST);
3977 $context = get_context_instance(CONTEXT_COURSE, $oldcourse->id);
3979 if ($editoroptions) {
3980 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3983 if (!isset($data->category) or empty($data->category)) {
3984 // prevent nulls and 0 in category field
3985 unset($data->category);
3987 $movecat = (isset($data->category) and $oldcourse->category != $data->category);
3989 if (!isset($data->visible)) {
3990 // data not from form, add missing visibility info
3991 $data->visible = $oldcourse->visible;
3994 if ($data->visible != $oldcourse->visible) {
3995 // reset the visibleold flag when manually hiding/unhiding course
3996 $data->visibleold = $data->visible;
3997 } else {
3998 if ($movecat) {
3999 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
4000 if (empty($newcategory->visible)) {
4001 // make sure when moving into hidden category the course is hidden automatically
4002 $data->visible = 0;
4007 // Update with the new data
4008 $DB->update_record('course', $data);
4010 $course = $DB->get_record('course', array('id'=>$data->id));
4012 if ($movecat) {
4013 $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category);
4014 context_moved($context, $newparent);
4017 fix_course_sortorder();
4019 // Test for and remove blocks which aren't appropriate anymore
4020 blocks_remove_inappropriate($course);
4022 // Save any custom role names.
4023 save_local_role_names($course->id, $data);
4025 // update enrol settings
4026 enrol_course_updated(false, $course, $data);
4028 add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
4030 // Trigger events
4031 events_trigger('course_updated', $course);
4035 * Average number of participants
4036 * @return integer
4038 function average_number_of_participants() {
4039 global $DB, $SITE;
4041 //count total of enrolments for visible course (except front page)
4042 $sql = 'SELECT COUNT(*) FROM (
4043 SELECT DISTINCT ue.userid, e.courseid
4044 FROM {user_enrolments} ue, {enrol} e, {course} c
4045 WHERE ue.enrolid = e.id
4046 AND e.courseid <> :siteid
4047 AND c.id = e.courseid
4048 AND c.visible = 1) as total';
4049 $params = array('siteid' => $SITE->id);
4050 $enrolmenttotal = $DB->count_records_sql($sql, $params);
4053 //count total of visible courses (minus front page)
4054 $coursetotal = $DB->count_records('course', array('visible' => 1));
4055 $coursetotal = $coursetotal - 1 ;
4057 //average of enrolment
4058 if (empty($coursetotal)) {
4059 $participantaverage = 0;
4060 } else {
4061 $participantaverage = $enrolmenttotal / $coursetotal;
4064 return $participantaverage;
4068 * Average number of course modules
4069 * @return integer
4071 function average_number_of_courses_modules() {
4072 global $DB, $SITE;
4074 //count total of visible course module (except front page)
4075 $sql = 'SELECT COUNT(*) FROM (
4076 SELECT cm.course, cm.module
4077 FROM {course} c, {course_modules} cm
4078 WHERE c.id = cm.course
4079 AND c.id <> :siteid
4080 AND cm.visible = 1
4081 AND c.visible = 1) as total';
4082 $params = array('siteid' => $SITE->id);
4083 $moduletotal = $DB->count_records_sql($sql, $params);
4086 //count total of visible courses (minus front page)
4087 $coursetotal = $DB->count_records('course', array('visible' => 1));
4088 $coursetotal = $coursetotal - 1 ;
4090 //average of course module
4091 if (empty($coursetotal)) {
4092 $coursemoduleaverage = 0;
4093 } else {
4094 $coursemoduleaverage = $moduletotal / $coursetotal;
4097 return $coursemoduleaverage;
4101 * This class pertains to course requests and contains methods associated with
4102 * create, approving, and removing course requests.
4104 * Please note we do not allow embedded images here because there is no context
4105 * to store them with proper access control.
4107 * @copyright 2009 Sam Hemelryk
4108 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4109 * @since Moodle 2.0
4111 * @property-read int $id
4112 * @property-read string $fullname
4113 * @property-read string $shortname
4114 * @property-read string $summary
4115 * @property-read int $summaryformat
4116 * @property-read int $summarytrust
4117 * @property-read string $reason
4118 * @property-read int $requester
4120 class course_request {
4123 * This is the stdClass that stores the properties for the course request
4124 * and is externally accessed through the __get magic method
4125 * @var stdClass
4127 protected $properties;
4130 * An array of options for the summary editor used by course request forms.
4131 * This is initially set by {@link summary_editor_options()}
4132 * @var array
4133 * @static
4135 protected static $summaryeditoroptions;
4138 * Static function to prepare the summary editor for working with a course
4139 * request.
4141 * @static
4142 * @param null|stdClass $data Optional, an object containing the default values
4143 * for the form, these may be modified when preparing the
4144 * editor so this should be called before creating the form
4145 * @return stdClass An object that can be used to set the default values for
4146 * an mforms form
4148 public static function prepare($data=null) {
4149 if ($data === null) {
4150 $data = new stdClass;
4152 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
4153 return $data;
4157 * Static function to create a new course request when passed an array of properties
4158 * for it.
4160 * This function also handles saving any files that may have been used in the editor
4162 * @static
4163 * @param stdClass $data
4164 * @return course_request The newly created course request
4166 public static function create($data) {
4167 global $USER, $DB, $CFG;
4168 $data->requester = $USER->id;
4170 // Summary is a required field so copy the text over
4171 $data->summary = $data->summary_editor['text'];
4172 $data->summaryformat = $data->summary_editor['format'];
4174 $data->id = $DB->insert_record('course_request', $data);
4176 // Create a new course_request object and return it
4177 $request = new course_request($data);
4179 // Notify the admin if required.
4180 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
4182 $a = new stdClass;
4183 $a->link = "$CFG->wwwroot/course/pending.php";
4184 $a->user = fullname($USER);
4185 $subject = get_string('courserequest');
4186 $message = get_string('courserequestnotifyemail', 'admin', $a);
4187 foreach ($users as $user) {
4188 $request->notify($user, $USER, 'courserequested', $subject, $message);
4192 return $request;
4196 * Returns an array of options to use with a summary editor
4198 * @uses course_request::$summaryeditoroptions
4199 * @return array An array of options to use with the editor
4201 public static function summary_editor_options() {
4202 global $CFG;
4203 if (self::$summaryeditoroptions === null) {
4204 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
4206 return self::$summaryeditoroptions;
4210 * Loads the properties for this course request object. Id is required and if
4211 * only id is provided then we load the rest of the properties from the database
4213 * @param stdClass|int $properties Either an object containing properties
4214 * or the course_request id to load
4216 public function __construct($properties) {
4217 global $DB;
4218 if (empty($properties->id)) {
4219 if (empty($properties)) {
4220 throw new coding_exception('You must provide a course request id when creating a course_request object');
4222 $id = $properties;
4223 $properties = new stdClass;
4224 $properties->id = (int)$id;
4225 unset($id);
4227 if (empty($properties->requester)) {
4228 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
4229 print_error('unknowncourserequest');
4231 } else {
4232 $this->properties = $properties;
4234 $this->properties->collision = null;
4238 * Returns the requested property
4240 * @param string $key
4241 * @return mixed
4243 public function __get($key) {
4244 return $this->properties->$key;
4248 * Override this to ensure empty($request->blah) calls return a reliable answer...
4250 * This is required because we define the __get method
4252 * @param mixed $key
4253 * @return bool True is it not empty, false otherwise
4255 public function __isset($key) {
4256 return (!empty($this->properties->$key));
4260 * Returns the user who requested this course
4262 * Uses a static var to cache the results and cut down the number of db queries
4264 * @staticvar array $requesters An array of cached users
4265 * @return stdClass The user who requested the course
4267 public function get_requester() {
4268 global $DB;
4269 static $requesters= array();
4270 if (!array_key_exists($this->properties->requester, $requesters)) {
4271 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
4273 return $requesters[$this->properties->requester];
4277 * Checks that the shortname used by the course does not conflict with any other
4278 * courses that exist
4280 * @param string|null $shortnamemark The string to append to the requests shortname
4281 * should a conflict be found
4282 * @return bool true is there is a conflict, false otherwise
4284 public function check_shortname_collision($shortnamemark = '[*]') {
4285 global $DB;
4287 if ($this->properties->collision !== null) {
4288 return $this->properties->collision;
4291 if (empty($this->properties->shortname)) {
4292 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
4293 $this->properties->collision = false;
4294 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
4295 if (!empty($shortnamemark)) {
4296 $this->properties->shortname .= ' '.$shortnamemark;
4298 $this->properties->collision = true;
4299 } else {
4300 $this->properties->collision = false;
4302 return $this->properties->collision;
4306 * This function approves the request turning it into a course
4308 * This function converts the course request into a course, at the same time
4309 * transferring any files used in the summary to the new course and then removing
4310 * the course request and the files associated with it.
4312 * @return int The id of the course that was created from this request
4314 public function approve() {
4315 global $CFG, $DB, $USER;
4317 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
4319 $category = get_course_category($CFG->defaultrequestcategory);
4320 $courseconfig = get_config('moodlecourse');
4322 // Transfer appropriate settings
4323 $data = clone($this->properties);
4324 unset($data->id);
4325 unset($data->reason);
4326 unset($data->requester);
4328 // Set category
4329 $data->category = $category->id;
4330 $data->sortorder = $category->sortorder; // place as the first in category
4332 // Set misc settings
4333 $data->requested = 1;
4335 // Apply course default settings
4336 $data->format = $courseconfig->format;
4337 $data->numsections = $courseconfig->numsections;
4338 $data->hiddensections = $courseconfig->hiddensections;
4339 $data->newsitems = $courseconfig->newsitems;
4340 $data->showgrades = $courseconfig->showgrades;
4341 $data->showreports = $courseconfig->showreports;
4342 $data->maxbytes = $courseconfig->maxbytes;
4343 $data->groupmode = $courseconfig->groupmode;
4344 $data->groupmodeforce = $courseconfig->groupmodeforce;
4345 $data->visible = $courseconfig->visible;
4346 $data->visibleold = $data->visible;
4347 $data->lang = $courseconfig->lang;
4349 $course = create_course($data);
4350 $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
4352 // add enrol instances
4353 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
4354 if ($manual = enrol_get_plugin('manual')) {
4355 $manual->add_default_instance($course);
4359 // enrol the requester as teacher if necessary
4360 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
4361 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
4364 $this->delete();
4366 $a = new stdClass();
4367 $a->name = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
4368 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
4369 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
4371 return $course->id;
4375 * Reject a course request
4377 * This function rejects a course request, emailing the requesting user the
4378 * provided notice and then removing the request from the database
4380 * @param string $notice The message to display to the user
4382 public function reject($notice) {
4383 global $USER, $DB;
4384 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
4385 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
4386 $this->delete();
4390 * Deletes the course request and any associated files
4392 public function delete() {
4393 global $DB;
4394 $DB->delete_records('course_request', array('id' => $this->properties->id));
4398 * Send a message from one user to another using events_trigger
4400 * @param object $touser
4401 * @param object $fromuser
4402 * @param string $name
4403 * @param string $subject
4404 * @param string $message
4406 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
4407 $eventdata = new stdClass();
4408 $eventdata->component = 'moodle';
4409 $eventdata->name = $name;
4410 $eventdata->userfrom = $fromuser;
4411 $eventdata->userto = $touser;
4412 $eventdata->subject = $subject;
4413 $eventdata->fullmessage = $message;
4414 $eventdata->fullmessageformat = FORMAT_PLAIN;
4415 $eventdata->fullmessagehtml = '';
4416 $eventdata->smallmessage = '';
4417 $eventdata->notification = 1;
4418 message_send($eventdata);
4423 * Return a list of page types
4424 * @param string $pagetype current page type
4425 * @param stdClass $parentcontext Block's parent context
4426 * @param stdClass $currentcontext Current context of block
4428 function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
4429 // if above course context ,display all course fomats
4430 list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
4431 if ($course->id == SITEID) {
4432 return array('*'=>get_string('page-x', 'pagetype'));
4433 } else {
4434 return array('*'=>get_string('page-x', 'pagetype'),
4435 'course-*'=>get_string('page-course-x', 'pagetype'),
4436 'course-view-*'=>get_string('page-course-view-x', 'pagetype')
4442 * Determine whether course ajax should be enabled for the specified course
4444 * @param stdClass $course The course to test against
4445 * @return boolean Whether course ajax is enabled or note
4447 function course_ajax_enabled($course) {
4448 global $CFG, $PAGE, $SITE;
4450 // Ajax must be enabled globally
4451 if (!$CFG->enableajax) {
4452 return false;
4455 // The user must be editing for AJAX to be included
4456 if (!$PAGE->user_is_editing()) {
4457 return false;
4460 // Check that the theme suports
4461 if (!$PAGE->theme->enablecourseajax) {
4462 return false;
4465 // Check that the course format supports ajax functionality
4466 // The site 'format' doesn't have information on course format support
4467 if ($SITE->id !== $course->id) {
4468 $courseformatajaxsupport = course_format_ajax_support($course->format);
4469 if (!$courseformatajaxsupport->capable) {
4470 return false;
4474 // All conditions have been met so course ajax should be enabled
4475 return true;
4479 * Include the relevant javascript and language strings for the resource
4480 * toolbox YUI module
4482 * @param integer $id The ID of the course being applied to
4483 * @param array $usedmodules An array containing the names of the modules in use on the page
4484 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
4485 * @param stdClass $config An object containing configuration parameters for ajax modules including:
4486 * * resourceurl The URL to post changes to for resource changes
4487 * * sectionurl The URL to post changes to for section changes
4488 * * pageparams Additional parameters to pass through in the post
4489 * @return bool
4491 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
4492 global $PAGE, $SITE;
4494 // Ensure that ajax should be included
4495 if (!course_ajax_enabled($course)) {
4496 return false;
4499 if (!$config) {
4500 $config = new stdClass();
4503 // The URL to use for resource changes
4504 if (!isset($config->resourceurl)) {
4505 $config->resourceurl = '/course/rest.php';
4508 // The URL to use for section changes
4509 if (!isset($config->sectionurl)) {
4510 $config->sectionurl = '/course/rest.php';
4513 // Any additional parameters which need to be included on page submission
4514 if (!isset($config->pageparams)) {
4515 $config->pageparams = array();
4518 // Include toolboxes
4519 $PAGE->requires->yui_module('moodle-course-toolboxes',
4520 'M.course.init_resource_toolbox',
4521 array(array(
4522 'courseid' => $course->id,
4523 'ajaxurl' => $config->resourceurl,
4524 'config' => $config,
4527 $PAGE->requires->yui_module('moodle-course-toolboxes',
4528 'M.course.init_section_toolbox',
4529 array(array(
4530 'courseid' => $course->id,
4531 'format' => $course->format,
4532 'ajaxurl' => $config->sectionurl,
4533 'config' => $config,
4537 // Include course dragdrop
4538 if ($course->id != $SITE->id) {
4539 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
4540 array(array(
4541 'courseid' => $course->id,
4542 'ajaxurl' => $config->sectionurl,
4543 'config' => $config,
4544 )), null, true);
4546 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
4547 array(array(
4548 'courseid' => $course->id,
4549 'ajaxurl' => $config->resourceurl,
4550 'config' => $config,
4551 )), null, true);
4554 // Include blocks dragdrop
4555 $params = array(
4556 'courseid' => $course->id,
4557 'pagetype' => $PAGE->pagetype,
4558 'pagelayout' => $PAGE->pagelayout,
4559 'regions' => $PAGE->blocks->get_regions(),
4561 $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
4563 // Require various strings for the command toolbox
4564 $PAGE->requires->strings_for_js(array(
4565 'moveleft',
4566 'deletechecktype',
4567 'deletechecktypename',
4568 'edittitle',
4569 'edittitleinstructions',
4570 'show',
4571 'hide',
4572 'groupsnone',
4573 'groupsvisible',
4574 'groupsseparate',
4575 'clicktochangeinbrackets',
4576 'markthistopic',
4577 'markedthistopic',
4578 'move',
4579 'movesection',
4580 ), 'moodle');
4582 // Include format-specific strings
4583 if ($course->id != $SITE->id) {
4584 $PAGE->requires->strings_for_js(array(
4585 'showfromothers',
4586 'hidefromothers',
4587 ), 'format_' . $course->format);
4590 // For confirming resource deletion we need the name of the module in question
4591 foreach ($usedmodules as $module => $modname) {
4592 $PAGE->requires->string_for_js('pluginname', $module);
4595 // Load drag and drop upload AJAX.
4596 dndupload_add_to_course($course, $enabledmodules);
4598 // Add the module chooser
4599 $PAGE->requires->yui_module('moodle-course-modchooser',
4600 'M.course.init_chooser',
4601 array(array('courseid' => $course->id))
4603 $PAGE->requires->strings_for_js(array(
4604 'addresourceoractivity',
4605 'modchooserenable',
4606 'modchooserdisable',
4607 ), 'moodle');
4609 return true;
4613 * The URL to use for the specified course (with section)
4615 * @param stdClass $course The course to get the section name for
4616 * @param int $sectionno The section number to return a link to
4617 * @return moodle_url The url of course
4619 function course_get_url($course, $sectionno = null) {
4620 $url = new moodle_url('/course/view.php', array('id' => $course->id));
4622 if (!is_null($sectionno)) {
4623 if ($course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
4624 $url->param('section', $sectionno);
4625 } else {
4626 $url->set_anchor('section-'.$sectionno);
4630 return $url;