3 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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
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) {
56 if (strpos($url, 'report/') === 0) {
57 // there is only one report type, course reports are deprecated
67 if (strpos($url, '../') === 0) {
68 $url = ltrim($url, '.');
70 $url = "/course/$url";
75 $url = "/$module/$url";
88 $url = "/message/$url";
100 $url = "/mod/$module/$url";
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);
110 $script = clean_param($script, PARAM_PATH);
115 $query = implode('', $parts);
116 $query = str_replace('&', '&', $query); // both & and & 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('&', $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) {
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) {
159 $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
161 LEFT JOIN {user} u ON l.userid = u.id
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;
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' )";
182 //TODO: This assumes that modids are the same across sites... probably
184 $where .= " AND l.cmid = :modid";
185 $params['modid'] = $modid;
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).'%';
194 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
195 $params['modaction'] = '%'.$modaction.'%';
200 $where .= " AND l.userid = :user";
201 $params['user'] = $user;
205 $enddate = $date + 86400;
206 $where .= " AND l.time > :date AND l.time < :enddate";
207 $params['date'] = $date;
208 $params['enddate'] = $enddate;
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);
217 $result['logs'] = array();
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];
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;
245 /// If this course doesn't have groups, no groupid can be specified.
246 else if (!$course->groupmode) {
253 if ($course->id != SITEID || $modid != 0) {
254 $joins[] = "l.course = :courseid";
255 $params['courseid'] = $course->id;
259 $joins[] = "l.module = :modname";
260 $params['modname'] = $modname;
263 if ('site_errors' === $modid) {
264 $joins[] = "( l.action='error' OR l.action='infected' )";
266 $joins[] = "l.cmid = :modid";
267 $params['modid'] = $modid;
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).'%';
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) . ')';
288 $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
292 $joins[] = "l.userid = :userid";
293 $params['userid'] = $user;
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
307 $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
308 $result['totalcount'] = $totalcount;
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();
327 if ($course->id == SITEID) {
329 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
330 foreach ($ccc as $cc) {
331 $courses[$cc->id] = $cc->shortname;
335 $courses[$course->id] = $course->shortname;
338 $totalcount = $logs['totalcount'];
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);
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(
357 get_string('ip_address'),
358 get_string('fullnameuser'),
359 get_string('action'),
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];
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);
387 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$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)=='...');
399 if ($course->id == SITEID) {
400 if (empty($log->course)) {
401 $row[] = get_string('site');
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";
416 $row[] = $displayaction;
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));
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();
442 if ($course->id == SITEID) {
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'];
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);
463 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
465 echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
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";
477 if (empty($logs['logs'])) {
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];
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);
499 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$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";
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)));
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";
523 echo "<td class=\"r$row c4\">\n";
524 echo $log->action .': '.$log->module;
526 echo "<td class=\"r$row c5\">{$log->info}</td>\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) {
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)) {
549 if ($course->id == SITEID) {
551 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
552 foreach ($ccc as $cc) {
553 $courses[$cc->id] = $cc->shortname;
557 $courses[$course->id] = $course->shortname;
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);
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";
578 if (empty($logs['logs'])) {
582 foreach ($logs['logs'] as $log) {
583 if (isset($ldcache[$log->module][$log->action])) {
584 $ld = $ldcache[$log->module][$log->action];
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);
594 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$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);
613 function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
614 $modid, $modaction, $groupid) {
618 require_once("$CFG->libdir/excellib.class.php");
620 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
621 $modname, $modid, $modaction, $groupid)) {
627 if ($course->id == SITEID) {
629 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
630 foreach ($ccc as $cc) {
631 $courses[$cc->id] = $cc->shortname;
635 $courses[$course->id] = $course->shortname;
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);
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));
664 foreach ($headers as $item) {
665 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
670 if (empty($logs['logs'])) {
675 $formatDate =& $workbook->add_format();
676 $formatDate->set_num_format(get_string('log_excel_date_format'));
678 $row = FIRSTUSEDEXCELROW;
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];
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);
693 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
698 $log->info = format_string($log->info);
699 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
702 if ($row > EXCELROWS) {
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, '');
726 function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
727 $modid, $modaction, $groupid) {
731 require_once("$CFG->libdir/odslib.class.php");
733 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
734 $modname, $modid, $modaction, $groupid)) {
740 if ($course->id == SITEID) {
742 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
743 foreach ($ccc as $cc) {
744 $courses[$cc->id] = $cc->shortname;
748 $courses[$course->id] = $course->shortname;
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);
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));
777 foreach ($headers as $item) {
778 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
783 if (empty($logs['logs'])) {
788 $formatDate =& $workbook->add_format();
789 $formatDate->set_num_format(get_string('log_excel_date_format'));
791 $row = FIRSTUSEDEXCELROW;
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];
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);
806 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
811 $log->info = format_string($log->info);
812 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
815 if ($row > EXCELROWS) {
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);
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) {
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));
912 echo '<div class="activityhead">';
914 echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
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.
926 echo '<div class="newusers">';
927 echo $OUTPUT->heading(get_string("newusers").':', 3);
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&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");
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)) {
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
964 if (count($info) != 2) {
965 debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
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);
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;
986 $cm = $modinfo->instances[$modname][$instanceid];
987 if (!$cm->uservisible) {
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);
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])) {
1018 if (!$cm->uservisible) {
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;
1033 debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
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
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');
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])) {
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")) {
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;
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')
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);
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")) {
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);
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])) {
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))) {
1248 if (!groups_course_module_visible($mod)) {
1251 $modnamesused[$mod->modname] = $modnames[$mod->modname];
1253 if ($modnamesused) {
1254 collatorlib::asort($modnamesused);
1260 * Returns an array of sections for the requested course id
1262 * This function stores the sections against the course id within a staticvar encase
1263 * of subsequent requests. This is used all over + in some standard libs and course
1264 * format callbacks so subsequent requests are a reality.
1266 * @staticvar array $coursesections
1267 * @param int $courseid
1268 * @return array Array of sections
1270 function get_all_sections($courseid) {
1272 static $coursesections = array();
1273 if (!array_key_exists($courseid, $coursesections)) {
1274 $coursesections[$courseid] = $DB->get_records("course_sections", array("course"=>"$courseid"), "section",
1275 "section, id, course, name, summary, summaryformat, sequence, visible");
1277 return $coursesections[$courseid];
1281 * Set highlighted section. Only one section can be highlighted at the time.
1283 * @param int $courseid course id
1284 * @param int $marker highlight section with this number, 0 means remove higlightin
1287 function course_set_marker($courseid, $marker) {
1289 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1293 * For a given course section, marks it visible or hidden,
1294 * and does the same for every activity in that section
1296 * @param int $courseid course id
1297 * @param int $sectionnumber The section number to adjust
1298 * @param int $visibility The new visibility
1299 * @return array A list of resources which were hidden in the section
1301 function set_section_visible($courseid, $sectionnumber, $visibility) {
1304 $resourcestotoggle = array();
1305 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1306 $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1307 if (!empty($section->sequence)) {
1308 $modules = explode(",", $section->sequence);
1309 foreach ($modules as $moduleid) {
1310 set_coursemodule_visible($moduleid, $visibility, true);
1313 rebuild_course_cache($courseid);
1315 // Determine which modules are visible for AJAX update
1316 if (!empty($modules)) {
1317 list($insql, $params) = $DB->get_in_or_equal($modules);
1318 $select = 'id ' . $insql . ' AND visible = ?';
1319 array_push($params, $visibility);
1321 $select .= ' AND visibleold = 1';
1323 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1326 return $resourcestotoggle;
1330 * Obtains shared data that is used in print_section when displaying a
1331 * course-module entry.
1333 * Calls format_text or format_string as appropriate, and obtains the correct icon.
1335 * This data is also used in other areas of the code.
1336 * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
1337 * @param object $course Moodle course object
1338 * @return array An array with the following values in this order:
1339 * $content (optional extra content for after link),
1340 * $instancename (text of link)
1342 function get_print_section_cm_text(cm_info $cm, $course) {
1345 // Get content from modinfo if specified. Content displays either
1346 // in addition to the standard link (below), or replaces it if
1347 // the link is turned off by setting ->url to null.
1348 if (($content = $cm->get_content()) !== '') {
1349 // Improve filter performance by preloading filter setttings for all
1350 // activities on the course (this does nothing if called multiple
1352 filter_preload_activities($cm->get_modinfo());
1354 // Get module context
1355 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1356 $labelformatoptions = new stdClass();
1357 $labelformatoptions->noclean = true;
1358 $labelformatoptions->overflowdiv = true;
1359 $labelformatoptions->context = $modulecontext;
1360 $content = format_text($content, FORMAT_HTML, $labelformatoptions);
1365 // Get course context
1366 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1367 $stringoptions = new stdClass;
1368 $stringoptions->context = $coursecontext;
1369 $instancename = format_string($cm->name, true, $stringoptions);
1370 return array($content, $instancename);
1374 * Prints a section full of activity modules
1376 function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn = false) {
1377 global $CFG, $USER, $DB, $PAGE, $OUTPUT;
1379 static $initialised;
1381 static $groupbuttons;
1382 static $groupbuttonslink;
1385 static $strmovehere;
1386 static $strmovefull;
1387 static $strunreadpostsone;
1389 static $modulenames;
1391 if (!isset($initialised)) {
1392 $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
1393 $groupbuttonslink = (!$course->groupmodeforce);
1394 $isediting = $PAGE->user_is_editing();
1395 $ismoving = $isediting && ismoving($course->id);
1397 $strmovehere = get_string("movehere");
1398 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
1400 $modulenames = array();
1401 $initialised = true;
1404 $modinfo = get_fast_modinfo($course);
1405 $completioninfo = new completion_info($course);
1407 //Accessibility: replace table with list <ul>, but don't output empty list.
1408 if (!empty($section->sequence)) {
1410 // Fix bug #5027, don't want style=\"width:$width\".
1411 echo "<ul class=\"section img-text\">\n";
1412 $sectionmods = explode(",", $section->sequence);
1414 foreach ($sectionmods as $modnumber) {
1415 if (empty($mods[$modnumber])) {
1422 $mod = $mods[$modnumber];
1424 if ($ismoving and $mod->id == $USER->activitycopy) {
1425 // do not display moving mod
1429 if (isset($modinfo->cms[$modnumber])) {
1430 // We can continue (because it will not be displayed at all)
1432 // 1) The activity is not visible to users
1434 // 2a) The 'showavailability' option is not set (if that is set,
1435 // we need to display the activity so we can show
1436 // availability info)
1438 // 2b) The 'availableinfo' is empty, i.e. the activity was
1439 // hidden in a way that leaves no info, such as using the
1441 if (!$modinfo->cms[$modnumber]->uservisible &&
1442 (empty($modinfo->cms[$modnumber]->showavailability) ||
1443 empty($modinfo->cms[$modnumber]->availableinfo))) {
1444 // visibility shortcut
1448 if (!file_exists("$CFG->dirroot/mod/$mod->modname/lib.php")) {
1449 // module not installed
1452 if (!coursemodule_visible_for_user($mod) &&
1453 empty($mod->showavailability)) {
1454 // full visibility check
1459 if (!isset($modulenames[$mod->modname])) {
1460 $modulenames[$mod->modname] = get_string('modulename', $mod->modname);
1462 $modulename = $modulenames[$mod->modname];
1464 // In some cases the activity is visible to user, but it is
1465 // dimmed. This is done if viewhiddenactivities is true and if:
1466 // 1. the activity is not visible, or
1467 // 2. the activity has dates set which do not include current, or
1468 // 3. the activity has any other conditions set (regardless of whether
1469 // current user meets them)
1470 $canviewhidden = has_capability(
1471 'moodle/course:viewhiddenactivities',
1472 get_context_instance(CONTEXT_MODULE, $mod->id));
1473 $accessiblebutdim = false;
1474 if ($canviewhidden) {
1475 $accessiblebutdim = !$mod->visible;
1476 if (!empty($CFG->enableavailability)) {
1477 $accessiblebutdim = $accessiblebutdim ||
1478 $mod->availablefrom > time() ||
1479 ($mod->availableuntil && $mod->availableuntil < time()) ||
1480 count($mod->conditionsgrade) > 0 ||
1481 count($mod->conditionscompletion) > 0;
1485 $liclasses = array();
1486 $liclasses[] = 'activity';
1487 $liclasses[] = $mod->modname;
1488 $liclasses[] = 'modtype_'.$mod->modname;
1489 $extraclasses = $mod->get_extra_classes();
1490 if ($extraclasses) {
1491 $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
1493 echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
1495 echo '<a title="'.$strmovefull.'"'.
1496 ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&sesskey='.sesskey().'">'.
1497 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1498 ' alt="'.$strmovehere.'" /></a><br />
1502 $classes = array('mod-indent');
1503 if (!empty($mod->indent)) {
1504 $classes[] = 'mod-indent-'.$mod->indent;
1505 if ($mod->indent > 15) {
1506 $classes[] = 'mod-indent-huge';
1509 echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
1511 // Get data about this course-module
1512 list($content, $instancename) =
1513 get_print_section_cm_text($modinfo->cms[$modnumber], $course);
1515 //Accessibility: for files get description via icon, this is very ugly hack!
1517 $altname = $mod->modfullname;
1518 if (!empty($customicon)) {
1519 $archetype = plugin_supports('mod', $mod->modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1520 if ($archetype == MOD_ARCHETYPE_RESOURCE) {
1521 $mimetype = mimeinfo_from_icon('type', $customicon);
1522 $altname = get_mimetype_description($mimetype);
1525 // Avoid unnecessary duplication: if e.g. a forum name already
1526 // includes the word forum (or Forum, etc) then it is unhelpful
1527 // to include that in the accessible description that is added.
1528 if (false !== strpos(textlib::strtolower($instancename),
1529 textlib::strtolower($altname))) {
1532 // File type after name, for alphabetic lists (screen reader).
1534 $altname = get_accesshide(' '.$altname);
1537 // We may be displaying this just in order to show information
1538 // about visibility, without the actual link
1540 if ($mod->uservisible) {
1541 // Nope - in this case the link is fully working for user
1544 if ($accessiblebutdim) {
1545 $linkclasses .= ' dimmed';
1546 $textclasses .= ' dimmed_text';
1547 $accesstext = '<span class="accesshide">'.
1548 get_string('hiddenfromstudents').': </span>';
1553 $linkcss = 'class="' . trim($linkclasses) . '" ';
1558 $textcss = 'class="' . trim($textclasses) . '" ';
1563 // Get on-click attribute value if specified
1564 $onclick = $mod->get_on_click();
1566 $onclick = ' onclick="' . $onclick . '"';
1569 if ($url = $mod->get_url()) {
1570 // Display link itself
1571 echo '<a ' . $linkcss . $mod->extra . $onclick .
1572 ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
1573 '" class="activityicon" alt="' .
1574 $modulename . '" /> ' .
1575 $accesstext . '<span class="instancename">' .
1576 $instancename . $altname . '</span></a>';
1578 // If specified, display extra content after link
1580 $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) .
1581 '">' . $content . '</div>';
1584 // No link, so display only content
1585 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1586 $accesstext . $content . '</div>';
1589 if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
1590 if (!isset($groupings)) {
1591 $groupings = groups_get_all_groupings($course->id);
1593 echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
1596 $textclasses = $extraclasses;
1597 $textclasses .= ' dimmed_text';
1599 $textcss = 'class="' . trim($textclasses) . '" ';
1603 $accesstext = '<span class="accesshide">' .
1604 get_string('notavailableyet', 'condition') .
1607 if ($url = $mod->get_url()) {
1608 // Display greyed-out text of link
1609 echo '<div ' . $textcss . $mod->extra .
1610 ' >' . '<img src="' . $mod->get_icon_url() .
1611 '" class="activityicon" alt="' .
1613 '" /> <span>'. $instancename . $altname .
1616 // Do not display content after link when it is greyed out like this.
1618 // No link, so display only content (also greyed)
1619 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1620 $accesstext . $content . '</div>';
1624 // Module can put text after the link (e.g. forum unread)
1625 echo $mod->get_after_link();
1627 // If there is content but NO link (eg label), then display the
1628 // content here (BEFORE any icons). In this case cons must be
1629 // displayed after the content so that it makes more sense visually
1630 // and for accessibility reasons, e.g. if you have a one-line label
1631 // it should work similarly (at least in terms of ordering) to an
1638 if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
1639 if (! $mod->groupmodelink = $groupbuttonslink) {
1640 $mod->groupmode = $course->groupmode;
1644 $mod->groupmode = false;
1646 echo ' ';
1648 if ($sectionreturn) {
1649 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
1651 echo make_editing_buttons($mod, $absolute, true, $mod->indent, 0);
1653 echo $mod->get_after_edit_icons();
1657 $completion = $hidecompletion
1658 ? COMPLETION_TRACKING_NONE
1659 : $completioninfo->is_enabled($mod);
1660 if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
1661 !isguestuser() && $mod->uservisible) {
1662 $completiondata = $completioninfo->get_data($mod,true);
1663 $completionicon = '';
1665 switch ($completion) {
1666 case COMPLETION_TRACKING_MANUAL :
1667 $completionicon = 'manual-enabled'; break;
1668 case COMPLETION_TRACKING_AUTOMATIC :
1669 $completionicon = 'auto-enabled'; break;
1672 } else if ($completion==COMPLETION_TRACKING_MANUAL) {
1673 switch($completiondata->completionstate) {
1674 case COMPLETION_INCOMPLETE:
1675 $completionicon = 'manual-n'; break;
1676 case COMPLETION_COMPLETE:
1677 $completionicon = 'manual-y'; break;
1679 } else { // Automatic
1680 switch($completiondata->completionstate) {
1681 case COMPLETION_INCOMPLETE:
1682 $completionicon = 'auto-n'; break;
1683 case COMPLETION_COMPLETE:
1684 $completionicon = 'auto-y'; break;
1685 case COMPLETION_COMPLETE_PASS:
1686 $completionicon = 'auto-pass'; break;
1687 case COMPLETION_COMPLETE_FAIL:
1688 $completionicon = 'auto-fail'; break;
1691 if ($completionicon) {
1692 $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
1693 $imgalt = s(get_string('completion-alt-'.$completionicon, 'completion', $mod->name));
1694 if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
1695 $imgtitle = s(get_string('completion-title-'.$completionicon, 'completion', $mod->name));
1697 $completiondata->completionstate==COMPLETION_COMPLETE
1698 ? COMPLETION_INCOMPLETE
1699 : COMPLETION_COMPLETE;
1700 // In manual mode the icon is a toggle form...
1702 // If this completion state is used by the
1703 // conditional activities system, we need to turn
1705 if (!empty($CFG->enableavailability) &&
1706 condition_info::completion_value_used_as_condition($course, $mod)) {
1707 $extraclass = ' preventjs';
1712 <form class='togglecompletion$extraclass' method='post' action='".$CFG->wwwroot."/course/togglecompletion.php'><div>
1713 <input type='hidden' name='id' value='{$mod->id}' />
1714 <input type='hidden' name='modulename' value='".s($mod->name)."' />
1715 <input type='hidden' name='sesskey' value='".sesskey()."' />
1716 <input type='hidden' name='completionstate' value='$newstate' />
1717 <input type='image' src='$imgsrc' alt='$imgalt' title='$imgtitle' />
1720 // In auto mode, or when editing, the icon is just an image
1721 echo "<span class='autocompletion'>";
1722 echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
1727 // If there is content AND a link, then display the content here
1728 // (AFTER any icons). Otherwise it was displayed before
1733 // Show availability information (for someone who isn't allowed to
1734 // see the activity itself, or for staff)
1735 if (!$mod->uservisible) {
1736 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
1737 } else if ($canviewhidden && !empty($CFG->enableavailability)) {
1738 $ci = new condition_info($mod);
1739 $fullinfo = $ci->get_full_information();
1741 echo '<div class="availabilityinfo">'.get_string($mod->showavailability
1742 ? 'userrestriction_visible'
1743 : 'userrestriction_hidden','condition',
1744 $fullinfo).'</div>';
1748 echo html_writer::end_tag('div');
1749 echo html_writer::end_tag('li')."\n";
1752 } elseif ($ismoving) {
1753 echo "<ul class=\"section\">\n";
1757 echo '<li><a title="'.$strmovefull.'"'.
1758 ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&sesskey='.sesskey().'">'.
1759 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1760 ' alt="'.$strmovehere.'" /></a></li>
1763 if (!empty($section->sequence) || $ismoving) {
1764 echo "</ul><!--class='section'-->\n\n";
1769 * Prints the menus to add activities and resources.
1771 function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false) {
1772 global $CFG, $OUTPUT;
1774 // check to see if user can add menus
1775 if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id))) {
1779 // Retrieve all modules with associated metadata
1780 $modules = get_module_metadata($course, $modnames);
1782 // We'll sort resources and activities into two lists
1783 $resources = array();
1784 $activities = array();
1786 // We need to add the section section to the link for each module
1787 $sectionlink = '§ion=' . $section;
1789 foreach ($modules as $module) {
1790 if (isset($module->types)) {
1791 // This module has a subtype
1792 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1793 $subtypes = array();
1794 foreach ($module->types as $subtype) {
1795 $subtypes[$subtype->link . $sectionlink] = $subtype->title;
1798 // Sort module subtypes into the list
1799 if (!empty($module->title)) {
1800 // This grouping has a name
1801 if ($module->archetype == MOD_CLASS_RESOURCE) {
1802 $resources[] = array($module->title=>$subtypes);
1804 $activities[] = array($module->title=>$subtypes);
1807 // This grouping does not have a name
1808 if ($module->archetype == MOD_CLASS_RESOURCE) {
1809 $resources = array_merge($resources, $subtypes);
1811 $activities = array_merge($activities, $subtypes);
1815 // This module has no subtypes
1816 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
1817 $resources[$module->link . $sectionlink] = $module->title;
1818 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
1819 // System modules cannot be added by user, do not add to dropdown
1821 $activities[$module->link . $sectionlink] = $module->title;
1826 $straddactivity = get_string('addactivity');
1827 $straddresource = get_string('addresource');
1829 $output = '<div class="section_add_menus">';
1832 $output .= '<div class="horizontal">';
1835 if (!empty($resources)) {
1836 $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
1837 $select->set_help_icon('resources');
1838 $output .= $OUTPUT->render($select);
1841 if (!empty($activities)) {
1842 $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
1843 $select->set_help_icon('activities');
1844 $output .= $OUTPUT->render($select);
1848 $output .= '</div>';
1851 $output .= '</div>';
1861 * Retrieve all metadata for the requested modules
1863 * @param object $course The Course
1864 * @param array $modnames An array containing the list of modules and their
1866 * @return array A list of stdClass objects containing metadata about each
1869 function get_module_metadata($course, $modnames) {
1870 global $CFG, $OUTPUT;
1872 // get_module_metadata will be called once per section on the page and courses may show
1873 // different modules to one another
1874 static $modlist = array();
1875 if (!isset($modlist[$course->id])) {
1876 $modlist[$course->id] = array();
1880 $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&add=';
1881 foreach($modnames as $modname => $modnamestr) {
1882 if (!course_allowed_module($course, $modname)) {
1885 if (isset($modlist[$modname])) {
1886 // This module is already cached
1887 $return[$modname] = $modlist[$course->id][$modname];
1891 // Include the module lib
1892 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1893 if (!file_exists($libfile)) {
1896 include_once($libfile);
1898 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1899 $gettypesfunc = $modname.'_get_types';
1900 if (function_exists($gettypesfunc)) {
1901 if ($types = $gettypesfunc()) {
1902 $group = new stdClass();
1903 $group->name = $modname;
1904 $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1905 foreach($types as $type) {
1906 if ($type->typestr === '--') {
1909 if (strpos($type->typestr, '--') === 0) {
1910 $group->title = str_replace('--', '', $type->typestr);
1913 // Set the Sub Type metadata
1914 $subtype = new stdClass();
1915 $subtype->title = $type->typestr;
1916 $subtype->type = str_replace('&', '&', $type->type);
1917 $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1918 $subtype->archetype = $type->modclass;
1920 // The group archetype should match the subtype archetypes and all subtypes
1921 // should have the same archetype
1922 $group->archetype = $subtype->archetype;
1924 if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1925 $subtype->help = get_string('help' . $subtype->name, $modname);
1927 $subtype->link = $urlbase . $subtype->type;
1928 $group->types[] = $subtype;
1930 $modlist[$course->id][$modname] = $group;
1933 $module = new stdClass();
1934 $module->title = get_string('modulename', $modname);
1935 $module->name = $modname;
1936 $module->link = $urlbase . $modname;
1937 $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1938 if (get_string_manager()->string_exists('modulename_help', $modname)) {
1939 $module->help = get_string('modulename_help', $modname);
1941 $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1942 $modlist[$course->id][$modname] = $module;
1944 $return[$modname] = $modlist[$course->id][$modname];
1951 * Return the course category context for the category with id $categoryid, except
1952 * that if $categoryid is 0, return the system context.
1954 * @param integer $categoryid a category id or 0.
1955 * @return object the corresponding context
1957 function get_category_or_system_context($categoryid) {
1959 return get_context_instance(CONTEXT_COURSECAT, $categoryid);
1961 return get_context_instance(CONTEXT_SYSTEM);
1966 * Gets the child categories of a given courses category. Uses a static cache
1967 * to make repeat calls efficient.
1969 * @param int $parentid the id of a course category.
1970 * @return array all the child course categories.
1972 function get_child_categories($parentid) {
1973 static $allcategories = null;
1975 // only fill in this variable the first time
1976 if (null == $allcategories) {
1977 $allcategories = array();
1979 $categories = get_categories();
1980 foreach ($categories as $category) {
1981 if (empty($allcategories[$category->parent])) {
1982 $allcategories[$category->parent] = array();
1984 $allcategories[$category->parent][] = $category;
1988 if (empty($allcategories[$parentid])) {
1991 return $allcategories[$parentid];
1996 * This function recursively travels the categories, building up a nice list
1997 * for display. It also makes an array that list all the parents for each
2000 * For example, if you have a tree of categories like:
2001 * Miscellaneous (id = 1)
2002 * Subcategory (id = 2)
2003 * Sub-subcategory (id = 4)
2004 * Other category (id = 3)
2005 * Then after calling this function you will have
2006 * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
2007 * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
2008 * 3 => 'Other category');
2009 * $parents = array(2 => array(1), 4 => array(1, 2));
2011 * If you specify $requiredcapability, then only categories where the current
2012 * user has that capability will be added to $list, although all categories
2013 * will still be added to $parents, and if you only have $requiredcapability
2014 * in a child category, not the parent, then the child catgegory will still be
2017 * If you specify the option $excluded, then that category, and all its children,
2018 * are omitted from the tree. This is useful when you are doing something like
2019 * moving categories, where you do not want to allow people to move a category
2020 * to be the child of itself.
2022 * @param array $list For output, accumulates an array categoryid => full category path name
2023 * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
2024 * @param string/array $requiredcapability if given, only categories where the current
2025 * user has this capability will be added to $list. Can also be an array of capabilities,
2026 * in which case they are all required.
2027 * @param integer $excludeid Omit this category and its children from the lists built.
2028 * @param object $category Build the tree starting at this category - otherwise starts at the top level.
2029 * @param string $path For internal use, as part of recursive calls.
2031 function make_categories_list(&$list, &$parents, $requiredcapability = '',
2032 $excludeid = 0, $category = NULL, $path = "") {
2034 // initialize the arrays if needed
2035 if (!is_array($list)) {
2038 if (!is_array($parents)) {
2042 if (empty($category)) {
2043 // Start at the top level.
2044 $category = new stdClass;
2047 // This is the excluded category, don't include it.
2048 if ($excludeid > 0 && $excludeid == $category->id) {
2052 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2053 $categoryname = format_string($category->name, true, array('context' => $context));
2057 $path = $path.' / '.$categoryname;
2059 $path = $categoryname;
2062 // Add this category to $list, if the permissions check out.
2063 if (empty($requiredcapability)) {
2064 $list[$category->id] = $path;
2067 $requiredcapability = (array)$requiredcapability;
2068 if (has_all_capabilities($requiredcapability, $context)) {
2069 $list[$category->id] = $path;
2074 // Add all the children recursively, while updating the parents array.
2075 if ($categories = get_child_categories($category->id)) {
2076 foreach ($categories as $cat) {
2077 if (!empty($category->id)) {
2078 if (isset($parents[$category->id])) {
2079 $parents[$cat->id] = $parents[$category->id];
2081 $parents[$cat->id][] = $category->id;
2083 make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
2089 * This function generates a structured array of courses and categories.
2091 * The depth of categories is limited by $CFG->maxcategorydepth however there
2092 * is no limit on the number of courses!
2094 * Suitable for use with the course renderers course_category_tree method:
2095 * $renderer = $PAGE->get_renderer('core','course');
2096 * echo $renderer->course_category_tree(get_course_category_tree());
2098 * @global moodle_database $DB
2102 function get_course_category_tree($id = 0, $depth = 0) {
2104 $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM));
2105 $categories = get_child_categories($id);
2106 $categoryids = array();
2107 foreach ($categories as $key => &$category) {
2108 if (!$category->visible && !$viewhiddencats) {
2109 unset($categories[$key]);
2112 $categoryids[$category->id] = $category;
2113 if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
2114 list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
2115 foreach ($subcategories as $subid=>$subcat) {
2116 $categoryids[$subid] = $subcat;
2118 $category->courses = array();
2123 // This is a recursive call so return the required array
2124 return array($categories, $categoryids);
2127 // The depth is 0 this function has just been called so we can finish it off
2129 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
2130 list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
2132 c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
2136 WHERE c.category $catsql ORDER BY c.sortorder ASC";
2137 if ($courses = $DB->get_records_sql($sql, $catparams)) {
2138 // loop throught them
2139 foreach ($courses as $course) {
2140 if ($course->id == SITEID) {
2143 context_instance_preload($course);
2144 if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
2145 $categoryids[$course->category]->courses[$course->id] = $course;
2153 * Recursive function to print out all the categories in a nice format
2154 * with or without courses included
2156 function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
2159 // maxcategorydepth == 0 meant no limit
2160 if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
2164 if (!$displaylist) {
2165 make_categories_list($displaylist, $parentslist);
2169 if ($category->visible or has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM))) {
2170 print_category_info($category, $depth, $showcourses);
2172 return; // Don't bother printing children of invisible categories
2176 $category = new stdClass();
2177 $category->id = "0";
2180 if ($categories = get_child_categories($category->id)) { // Print all the children recursively
2181 $countcats = count($categories);
2185 foreach ($categories as $cat) {
2187 if ($count == $countcats) {
2190 $up = $first ? false : true;
2191 $down = $last ? false : true;
2194 print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
2200 * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
2202 function make_categories_options() {
2203 make_categories_list($cats,$parents);
2204 foreach ($cats as $key => $value) {
2205 if (array_key_exists($key,$parents)) {
2206 if ($indent = count($parents[$key])) {
2207 for ($i = 0; $i < $indent; $i++) {
2208 $cats[$key] = ' '.$cats[$key];
2217 * Gets the name of a course to be displayed when showing a list of courses.
2218 * By default this is just $course->fullname but user can configure it. The
2219 * result of this function should be passed through print_string.
2220 * @param object $course Moodle course object
2221 * @return string Display name of course (either fullname or short + fullname)
2223 function get_course_display_name_for_list($course) {
2225 if (!empty($CFG->courselistshortnames)) {
2226 return $course->shortname . ' ' .$course->fullname;
2228 return $course->fullname;
2233 * Prints the category info in indented fashion
2234 * This function is only used by print_whole_category_list() above
2236 function print_category_info($category, $depth=0, $showcourses = false) {
2237 global $CFG, $DB, $OUTPUT;
2239 $strsummary = get_string('summary');
2242 if (!$category->visible) {
2243 $catlinkcss = array('class'=>'dimmed');
2245 static $coursecount = null;
2246 if (null === $coursecount) {
2247 // only need to check this once
2248 $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
2251 if ($showcourses and $coursecount) {
2252 $catimage = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
2254 $catimage = " ";
2257 $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
2258 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2259 $fullname = format_string($category->name, true, array('context' => $context));
2261 if ($showcourses and $coursecount) {
2262 echo '<div class="categorylist clearfix">';
2264 $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
2265 $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2266 $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
2270 for ($i=0; $i< $depth; $i++) {
2271 $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
2277 echo html_writer::tag('div', $html, array('class'=>'category'));
2278 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2280 // does the depth exceed maxcategorydepth
2281 // maxcategorydepth == 0 or unset meant no limit
2282 $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
2283 if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
2284 foreach ($courses as $course) {
2286 if (!$course->visible) {
2287 $linkcss = array('class'=>'dimmed');
2290 $coursename = get_course_display_name_for_list($course);
2291 $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
2295 if ($icons = enrol_get_course_info_icons($course)) {
2296 foreach ($icons as $pix_icon) {
2297 $courseicon = $OUTPUT->render($pix_icon).' ';
2301 $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
2303 if ($course->summary) {
2304 $link = new moodle_url('/course/info.php?id='.$course->id);
2305 $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
2306 new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
2307 array('title'=>$strsummary));
2309 $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
2313 for ($i=0; $i <= $depth; $i++) {
2314 $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
2315 $coursecontent = '';
2317 echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
2322 echo '<div class="categorylist">';
2324 $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2325 if (count($courses) > 0) {
2326 $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
2330 for ($i=0; $i< $depth; $i++) {
2331 $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
2338 echo html_writer::tag('div', $html, array('class'=>'category'));
2339 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2345 * Print the buttons relating to course requests.
2347 * @param object $systemcontext the system context.
2349 function print_course_request_buttons($systemcontext) {
2350 global $CFG, $DB, $OUTPUT;
2351 if (empty($CFG->enablecourserequests)) {
2354 if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
2355 /// Print a button to request a new course
2356 echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
2358 /// Print a button to manage pending requests
2359 if (has_capability('moodle/site:approvecourse', $systemcontext)) {
2360 $disabled = !$DB->record_exists('course_request', array());
2361 echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
2366 * Does the user have permission to edit things in this category?
2368 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2369 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
2371 function can_edit_in_category($categoryid = 0) {
2372 $context = get_category_or_system_context($categoryid);
2373 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
2377 * Prints the turn editing on/off button on course/index.php or course/category.php.
2379 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2380 * @return string HTML of the editing button, or empty string, if this user is not allowed
2383 function update_category_button($categoryid = 0) {
2384 global $CFG, $PAGE, $OUTPUT;
2386 // Check permissions.
2387 if (!can_edit_in_category($categoryid)) {
2391 // Work out the appropriate action.
2392 if ($PAGE->user_is_editing()) {
2393 $label = get_string('turneditingoff');
2396 $label = get_string('turneditingon');
2400 // Generate the button HTML.
2401 $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
2403 $options['id'] = $categoryid;
2404 $page = 'category.php';
2406 $page = 'index.php';
2408 return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
2412 * Category is 0 (for all courses) or an object
2414 function print_courses($category) {
2415 global $CFG, $OUTPUT;
2417 if (!is_object($category) && $category==0) {
2418 $categories = get_child_categories(0); // Parent = 0 ie top-level categories only
2419 if (is_array($categories) && count($categories) == 1) {
2420 $category = array_shift($categories);
2421 $courses = get_courses_wmanagers($category->id,
2423 array('summary','summaryformat'));
2425 $courses = get_courses_wmanagers('all',
2427 array('summary','summaryformat'));
2431 $courses = get_courses_wmanagers($category->id,
2433 array('summary','summaryformat'));
2437 echo html_writer::start_tag('ul', array('class'=>'unlist'));
2438 foreach ($courses as $course) {
2439 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2440 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2441 echo html_writer::start_tag('li');
2442 print_course($course);
2443 echo html_writer::end_tag('li');
2446 echo html_writer::end_tag('ul');
2448 echo $OUTPUT->heading(get_string("nocoursesyet"));
2449 $context = get_context_instance(CONTEXT_SYSTEM);
2450 if (has_capability('moodle/course:create', $context)) {
2452 if (!empty($category->id)) {
2453 $options['category'] = $category->id;
2455 $options['category'] = $CFG->defaultrequestcategory;
2457 echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
2458 echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
2459 echo html_writer::end_tag('div');
2465 * Print a description of a course, suitable for browsing in a list.
2467 * @param object $course the course object.
2468 * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
2470 function print_course($course, $highlightterms = '') {
2471 global $CFG, $USER, $DB, $OUTPUT;
2473 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2475 // Rewrite file URLs so that they are correct
2476 $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
2478 echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
2479 echo html_writer::start_tag('div', array('class'=>'info'));
2480 echo html_writer::start_tag('h3', array('class'=>'name'));
2482 $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
2484 $coursename = get_course_display_name_for_list($course);
2485 $linktext = highlight($highlightterms, format_string($coursename));
2486 $linkparams = array('title'=>get_string('entercourse'));
2487 if (empty($course->visible)) {
2488 $linkparams['class'] = 'dimmed';
2490 echo html_writer::link($linkhref, $linktext, $linkparams);
2491 echo html_writer::end_tag('h3');
2493 /// first find all roles that are supposed to be displayed
2494 if (!empty($CFG->coursecontact)) {
2495 $managerroles = explode(',', $CFG->coursecontact);
2496 $namesarray = array();
2499 if (!isset($course->managers)) {
2500 $rusers = get_role_users($managerroles, $context, true,
2501 'ra.id AS raid, u.id, u.username, u.firstname, u.lastname,
2502 r.name AS rolename, r.sortorder, r.id AS roleid',
2503 'r.sortorder ASC, u.lastname ASC');
2505 // use the managers array if we have it for perf reasosn
2506 // populate the datastructure like output of get_role_users();
2507 foreach ($course->managers as $manager) {
2508 $u = new stdClass();
2509 $u = $manager->user;
2510 $u->roleid = $manager->roleid;
2511 $u->rolename = $manager->rolename;
2517 /// Rename some of the role names if needed
2518 if (isset($context)) {
2519 $aliasnames = $DB->get_records('role_names', array('contextid'=>$context->id), '', 'roleid,contextid,name');
2522 $namesarray = array();
2523 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
2524 foreach ($rusers as $ra) {
2525 if (isset($namesarray[$ra->id])) {
2526 // only display a user once with the higest sortorder role
2530 if (isset($aliasnames[$ra->roleid])) {
2531 $ra->rolename = $aliasnames[$ra->roleid]->name;
2534 $fullname = fullname($ra, $canviewfullnames);
2535 $namesarray[$ra->id] = format_string($ra->rolename).': '.
2536 html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
2539 if (!empty($namesarray)) {
2540 echo html_writer::start_tag('ul', array('class'=>'teachers'));
2541 foreach ($namesarray as $name) {
2542 echo html_writer::tag('li', $name);
2544 echo html_writer::end_tag('ul');
2547 echo html_writer::end_tag('div'); // End of info div
2549 echo html_writer::start_tag('div', array('class'=>'summary'));
2550 $options = new stdClass();
2551 $options->noclean = true;
2552 $options->para = false;
2553 $options->overflowdiv = true;
2554 if (!isset($course->summaryformat)) {
2555 $course->summaryformat = FORMAT_MOODLE;
2557 echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options, $course->id));
2558 if ($icons = enrol_get_course_info_icons($course)) {
2559 echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
2560 foreach ($icons as $icon) {
2561 echo $OUTPUT->render($icon);
2563 echo html_writer::end_tag('div'); // End of enrolmenticons div
2565 echo html_writer::end_tag('div'); // End of summary div
2566 echo html_writer::end_tag('div'); // End of coursebox div
2570 * Prints custom user information on the home page.
2571 * Over time this can include all sorts of information
2573 function print_my_moodle() {
2574 global $USER, $CFG, $DB, $OUTPUT;
2576 if (!isloggedin() or isguestuser()) {
2577 print_error('nopermissions', '', '', 'See My Moodle');
2580 $courses = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
2582 $rcourses = array();
2583 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
2584 $rcourses = get_my_remotecourses($USER->id);
2585 $rhosts = get_my_remotehosts();
2588 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
2590 if (!empty($courses)) {
2591 echo '<ul class="unlist">';
2592 foreach ($courses as $course) {
2593 if ($course->id == SITEID) {
2597 print_course($course);
2604 if (!empty($rcourses)) {
2605 // at the IDP, we know of all the remote courses
2606 foreach ($rcourses as $course) {
2607 print_remote_course($course, "100%");
2609 } elseif (!empty($rhosts)) {
2610 // non-IDP, we know of all the remote servers, but not courses
2611 foreach ($rhosts as $host) {
2612 print_remote_host($host, "100%");
2618 if ($DB->count_records("course") > (count($courses) + 1) ) { // Some courses not being displayed
2619 echo "<table width=\"100%\"><tr><td align=\"center\">";
2620 print_course_search("", false, "short");
2621 echo "</td><td align=\"center\">";
2622 echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
2623 echo "</td></tr></table>\n";
2627 if ($DB->count_records("course_categories") > 1) {
2628 echo $OUTPUT->box_start("categorybox");
2629 print_whole_category_list();
2630 echo $OUTPUT->box_end();
2638 function print_course_search($value="", $return=false, $format="plain") {
2644 $id = 'coursesearch';
2650 $strsearchcourses= get_string("searchcourses");
2652 if ($format == 'plain') {
2653 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2654 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2655 $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
2656 $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
2657 $output .= '<input type="submit" value="'.get_string('go').'" />';
2658 $output .= '</fieldset></form>';
2659 } else if ($format == 'short') {
2660 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2661 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2662 $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
2663 $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2664 $output .= '<input type="submit" value="'.get_string('go').'" />';
2665 $output .= '</fieldset></form>';
2666 } else if ($format == 'navbar') {
2667 $output = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2668 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2669 $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
2670 $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2671 $output .= '<input type="submit" value="'.get_string('go').'" />';
2672 $output .= '</fieldset></form>';
2681 function print_remote_course($course, $width="100%") {
2686 $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&wantsurl=/course/view.php?id={$course->remoteid}";
2688 echo '<div class="coursebox remotecoursebox clearfix">';
2689 echo '<div class="info">';
2690 echo '<div class="name"><a title="'.get_string('entercourse').'"'.
2691 $linkcss.' href="'.$url.'">'
2692 . format_string($course->fullname) .'</a><br />'
2693 . format_string($course->hostname) . ' : '
2694 . format_string($course->cat_name) . ' : '
2695 . format_string($course->shortname). '</div>';
2696 echo '</div><div class="summary">';
2697 $options = new stdClass();
2698 $options->noclean = true;
2699 $options->para = false;
2700 $options->overflowdiv = true;
2701 echo format_text($course->summary, $course->summaryformat, $options);
2706 function print_remote_host($host, $width="100%") {
2711 echo '<div class="coursebox clearfix">';
2712 echo '<div class="info">';
2713 echo '<div class="name">';
2714 echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
2715 echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
2716 . s($host['name']).'</a> - ';
2717 echo $host['count'] . ' ' . get_string('courses');
2724 /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
2726 function add_course_module($mod) {
2729 $mod->added = time();
2732 return $DB->insert_record("course_modules", $mod);
2736 * Returns course section - creates new if does not exist yet.
2737 * @param int $relative section number
2738 * @param int $courseid
2739 * @return object $course_section object
2741 function get_course_section($section, $courseid) {
2744 if ($cw = $DB->get_record("course_sections", array("section"=>$section, "course"=>$courseid))) {
2747 $cw = new stdClass();
2748 $cw->course = $courseid;
2749 $cw->section = $section;
2751 $cw->summaryformat = FORMAT_HTML;
2753 $id = $DB->insert_record("course_sections", $cw);
2754 return $DB->get_record("course_sections", array("id"=>$id));
2757 * Given a full mod object with section and course already defined, adds this module to that section.
2759 * @param object $mod
2760 * @param int $beforemod An existing ID which we will insert the new module before
2761 * @return int The course_sections ID where the mod is inserted
2763 function add_mod_to_section($mod, $beforemod=NULL) {
2766 if ($section = $DB->get_record("course_sections", array("course"=>$mod->course, "section"=>$mod->section))) {
2768 $section->sequence = trim($section->sequence);
2770 if (empty($section->sequence)) {
2771 $newsequence = "$mod->coursemodule";
2773 } else if ($beforemod) {
2774 $modarray = explode(",", $section->sequence);
2776 if ($key = array_keys($modarray, $beforemod->id)) {
2777 $insertarray = array($mod->id, $beforemod->id);
2778 array_splice($modarray, $key[0], 1, $insertarray);
2779 $newsequence = implode(",", $modarray);
2781 } else { // Just tack it on the end anyway
2782 $newsequence = "$section->sequence,$mod->coursemodule";
2786 $newsequence = "$section->sequence,$mod->coursemodule";
2789 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2790 return $section->id; // Return course_sections ID that was used.
2792 } else { // Insert a new record
2793 $section = new stdClass();
2794 $section->course = $mod->course;
2795 $section->section = $mod->section;
2796 $section->summary = "";
2797 $section->summaryformat = FORMAT_HTML;
2798 $section->sequence = $mod->coursemodule;
2799 return $DB->insert_record("course_sections", $section);
2803 function set_coursemodule_groupmode($id, $groupmode) {
2805 return $DB->set_field("course_modules", "groupmode", $groupmode, array("id"=>$id));
2808 function set_coursemodule_idnumber($id, $idnumber) {
2810 return $DB->set_field("course_modules", "idnumber", $idnumber, array("id"=>$id));
2814 * $prevstateoverrides = true will set the visibility of the course module
2815 * to what is defined in visibleold. This enables us to remember the current
2816 * visibility when making a whole section hidden, so that when we toggle
2817 * that section back to visible, we are able to return the visibility of
2818 * the course module back to what it was originally.
2820 function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
2822 require_once($CFG->libdir.'/gradelib.php');
2824 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2827 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
2830 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2831 foreach($events as $event) {
2840 // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
2841 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
2843 foreach ($grade_items as $grade_item) {
2844 $grade_item->set_hidden(!$visible);
2848 if ($prevstateoverrides) {
2849 if ($visible == '0') {
2850 // Remember the current visible state so we can toggle this back.
2851 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id'=>$id));
2853 // Get the previous saved visible states.
2854 return $DB->set_field('course_modules', 'visible', $cm->visibleold, array('id'=>$id));
2857 return $DB->set_field("course_modules", "visible", $visible, array("id"=>$id));
2861 * Delete a course module and any associated data at the course level (events)
2862 * Until 1.5 this function simply marked a deleted flag ... now it
2863 * deletes it completely.
2866 function delete_course_module($id) {
2868 require_once($CFG->libdir.'/gradelib.php');
2869 require_once($CFG->dirroot.'/blog/lib.php');
2871 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2874 $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
2875 //delete events from calendar
2876 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2877 foreach($events as $event) {
2878 delete_event($event->id);
2881 //delete grade items, outcome items and grades attached to modules
2882 if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
2883 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
2884 foreach ($grade_items as $grade_item) {
2885 $grade_item->delete('moddelete');
2888 // Delete completion and availability data; it is better to do this even if the
2889 // features are not turned on, in case they were turned on previously (these will be
2890 // very quick on an empty table)
2891 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
2892 $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
2893 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
2894 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
2896 delete_context(CONTEXT_MODULE, $cm->id);
2897 return $DB->delete_records('course_modules', array('id'=>$cm->id));
2900 function delete_mod_from_section($mod, $section) {
2903 if ($section = $DB->get_record("course_sections", array("id"=>$section)) ) {
2905 $modarray = explode(",", $section->sequence);
2907 if ($key = array_keys ($modarray, $mod)) {
2908 array_splice($modarray, $key[0], 1);
2909 $newsequence = implode(",", $modarray);
2910 return $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2920 * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
2922 * @param object $course course object
2923 * @param int $section Section number (not id!!!)
2924 * @param int $move (-1 or 1)
2925 * @return boolean true if section moved successfully
2927 function move_section($course, $section, $move) {
2928 /// Moves a whole course section up and down within the course
2935 $sectiondest = $section + $move;
2937 if ($sectiondest > $course->numsections or $sectiondest < 1) {
2941 if (!$sectionrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$section))) {
2945 if (!$sectiondestrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$sectiondest))) {
2949 // Three-step change ensures that the section always remains unique (there is
2950 // a unique index now)
2951 $DB->set_field("course_sections", "section", -$sectiondest, array("id"=>$sectionrecord->id));
2952 $DB->set_field("course_sections", "section", $section, array("id"=>$sectiondestrecord->id));
2953 $DB->set_field("course_sections", "section", $sectiondest, array("id"=>$sectionrecord->id));
2955 // Update highlighting if the move affects highlighted section
2956 if ($course->marker == $section) {
2957 course_set_marker($course->id, $sectiondest);
2958 } elseif ($course->marker == $sectiondest) {
2959 course_set_marker($course->id, $section);
2963 // Fix order if needed. The database prevents duplicate sections, but it is
2964 // possible there could be a gap in the numbering.
2965 $sections = $DB->get_records('course_sections', array('course'=>$course->id), 'section ASC');
2967 foreach ($sections as $section) {
2968 if ($section->section != $n) {
2969 $DB->set_field('course_sections', 'section', $n, array('id'=>$section->id));
2977 * Moves a section within a course, from a position to another.
2978 * Be very careful: $section and $destination refer to section number,
2981 * @param object $course
2982 * @param int $section Section number (not id!!!)
2983 * @param int $destination
2984 * @return boolean Result
2986 function move_section_to($course, $section, $destination) {
2987 /// Moves a whole course section up and down within the course
2990 if (!$destination && $destination != 0) {
2994 if ($destination > $course->numsections) {
2998 // Get all sections for this course and re-order them (2 of them should now share the same section number)
2999 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
3000 'section ASC, id ASC', 'id, section')) {
3004 $movedsections = reorder_sections($sections, $section, $destination);
3006 // Update all sections. Do this in 2 steps to avoid breaking database
3007 // uniqueness constraint
3008 $transaction = $DB->start_delegated_transaction();
3009 foreach ($movedsections as $id => $position) {
3010 if ($sections[$id] !== $position) {
3011 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
3014 foreach ($movedsections as $id => $position) {
3015 if ($sections[$id] !== $position) {
3016 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
3020 // Adjust destination to reflect the actual section
3022 if ($section > $destination) {
3027 // If we move the highlighted section itself, then just highlight the destination.
3028 // Adjust the higlighted section location if we move something over it either direction.
3029 if ($section == $course->marker) {
3030 course_set_marker($course->id, $destination);
3031 } elseif ($moveup && $section > $course->marker && $course->marker >= $destination) {
3032 course_set_marker($course->id, $course->marker+1);
3033 } elseif (!$moveup && $section < $course->marker && $course->marker <= $destination) {
3034 course_set_marker($course->id, $course->marker-1);
3037 $transaction->allow_commit();
3042 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
3043 * an original position number and a target position number, rebuilds the array so that the
3044 * move is made without any duplication of section positions.
3045 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
3046 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
3048 * @param array $sections
3049 * @param int $origin_position
3050 * @param int $target_position
3053 function reorder_sections($sections, $origin_position, $target_position) {
3054 if (!is_array($sections)) {
3058 // We can't move section position 0
3059 if ($origin_position < 1) {
3060 echo "We can't move section position 0";
3064 // Locate origin section in sections array
3065 if (!$origin_key = array_search($origin_position, $sections)) {
3066 echo "searched position not in sections array";
3067 return false; // searched position not in sections array
3070 // Extract origin section
3071 $origin_section = $sections[$origin_key];
3072 unset($sections[$origin_key]);
3074 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
3076 $append_array = array();
3077 foreach ($sections as $id => $position) {
3079 $append_array[$id] = $position;
3080 unset($sections[$id]);
3082 if ($position == $target_position) {
3087 // Append moved section
3088 $sections[$origin_key] = $origin_section;
3090 // Append rest of array (if applicable)
3091 if (!empty($append_array)) {
3092 foreach ($append_array as $id => $position) {
3093 $sections[$id] = $position;
3097 // Renumber positions
3099 foreach ($sections as $id => $p) {
3100 $sections[$id] = $position;
3109 * Move the module object $mod to the specified $section
3110 * If $beforemod exists then that is the module
3111 * before which $modid should be inserted
3112 * All parameters are objects
3114 function moveto_module($mod, $section, $beforemod=NULL) {
3115 global $DB, $OUTPUT;
3117 /// Remove original module from original section
3118 if (! delete_mod_from_section($mod->id, $mod->section)) {
3119 echo $OUTPUT->notification("Could not delete module from existing section");
3122 /// Update module itself if necessary
3124 if ($mod->section != $section->id) {
3125 $mod->section = $section->id;
3126 $DB->update_record("course_modules", $mod);
3127 // if moving to a hidden section then hide module
3128 if (!$section->visible) {
3129 set_coursemodule_visible($mod->id, 0);
3133 /// Add the module into the new section
3135 $mod->course = $section->course;
3136 $mod->section = $section->section; // need relative reference
3137 $mod->coursemodule = $mod->id;
3139 if (! add_mod_to_section($mod, $beforemod)) {
3147 * Produces the editing buttons for a module
3149 * @global core_renderer $OUTPUT
3150 * @staticvar type $str
3151 * @param stdClass $mod The module to produce editing buttons for
3152 * @param bool $absolute_ignored ignored - all links are absolute
3153 * @param bool $moveselect If true a move seleciton process is used (default true)
3154 * @param int $indent The current indenting
3155 * @param int $section The section to link back to
3156 * @return string XHTML for the editing buttons
3158 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
3159 global $CFG, $OUTPUT, $COURSE;
3163 $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
3164 $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
3166 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
3167 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
3169 // no permission to edit anything
3170 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
3174 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
3177 $str = new stdClass;
3178 $str->assign = get_string("assignroles", 'role');
3179 $str->delete = get_string("delete");
3180 $str->move = get_string("move");
3181 $str->moveup = get_string("moveup");
3182 $str->movedown = get_string("movedown");
3183 $str->moveright = get_string("moveright");
3184 $str->moveleft = get_string("moveleft");
3185 $str->update = get_string("update");
3186 $str->duplicate = get_string("duplicate");
3187 $str->hide = get_string("hide");
3188 $str->show = get_string("show");
3189 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
3190 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
3191 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
3192 $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
3193 $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
3194 $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
3195 $str->edittitle = get_string('edittitle', 'moodle');
3198 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
3200 if ($section >= 0) {
3201 $baseurl->param('sr', $section);
3206 if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
3207 $actions[] = new action_link(
3208 new moodle_url($baseurl, array('update' => $mod->id)),
3209 new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')),
3211 array('class' => 'editing_title', 'title' => $str->edittitle)
3216 if ($hasmanageactivities) {
3217 if (right_to_left()) { // Exchange arrows on RTL
3218 $rightarrow = 't/left';
3219 $leftarrow = 't/right';
3221 $rightarrow = 't/right';
3222 $leftarrow = 't/left';
3226 $actions[] = new action_link(
3227 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
3228 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall')),
3230 array('class' => 'editing_moveleft', 'title' => $str->moveleft)
3234 $actions[] = new action_link(
3235 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
3236 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall')),
3238 array('class' => 'editing_moveright', 'title' => $str->moveright)
3244 if ($hasmanageactivities) {
3246 $actions[] = new action_link(
3247 new moodle_url($baseurl, array('copy' => $mod->id)),
3248 new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall')),
3250 array('class' => 'editing_move', 'title' => $str->move)
3253 $actions[] = new action_link(
3254 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
3255 new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall')),
3257 array('class' => 'editing_moveup', 'title' => $str->moveup)
3259 $actions[] = new action_link(
3260 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
3261 new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall')),
3263 array('class' => 'editing_movedown', 'title' => $str->movedown)
3269 if ($hasmanageactivities) {
3270 $actions[] = new action_link(
3271 new moodle_url($baseurl, array('update' => $mod->id)),
3272 new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
3274 array('class' => 'editing_update', 'title' => $str->update)
3278 // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
3279 if (has_all_capabilities($dupecaps, $coursecontext)) {
3280 $actions[] = new action_link(
3281 new moodle_url($baseurl, array('duplicate' => $mod->id)),
3282 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
3284 array('class' => 'editing_duplicate', 'title' => $str->duplicate)
3289 if ($hasmanageactivities) {
3290 $actions[] = new action_link(
3291 new moodle_url($baseurl, array('delete' => $mod->id)),
3292 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
3294 array('class' => 'editing_delete', 'title' => $str->delete)
3299 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
3300 if ($mod->visible) {
3301 $actions[] = new action_link(
3302 new moodle_url($baseurl, array('hide' => $mod->id)),
3303 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall')),
3305 array('class' => 'editing_hide', 'title' => $str->hide)
3308 $actions[] = new action_link(
3309 new moodle_url($baseurl, array('show' => $mod->id)),
3310 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall')),
3312 array('class' => 'editing_show', 'title' => $str->show)
3318 if ($hasmanageactivities and $mod->groupmode !== false) {
3319 if ($mod->groupmode == SEPARATEGROUPS) {
3321 $grouptitle = $str->groupsseparate;
3322 $forcedgrouptitle = $str->forcedgroupsseparate;
3323 $groupclass = 'editing_groupsseparate';
3324 $groupimage = 't/groups';
3325 } else if ($mod->groupmode == VISIBLEGROUPS) {
3327 $grouptitle = $str->groupsvisible;
3328 $forcedgrouptitle = $str->forcedgroupsvisible;
3329 $groupclass = 'editing_groupsvisible';
3330 $groupimage = 't/groupv';
3333 $grouptitle = $str->groupsnone;
3334 $forcedgrouptitle = $str->forcedgroupsnone;
3335 $groupclass = 'editing_groupsnone';
3336 $groupimage = 't/groupn';
3338 if ($mod->groupmodelink) {
3339 $actions[] = new action_link(
3340 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
3341 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
3343 array('class' => $groupclass, 'title' => $grouptitle)
3346 $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
3351 if (has_capability('moodle/role:assign', $modcontext)){
3352 $actions[] = new action_link(
3353 new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
3354 new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall')),
3356 array('class' => 'editing_assign', 'title' => $str->assign)
3360 $output = html_writer::start_tag('span', array('class' => 'commands'));
3361 foreach ($actions as $action) {
3362 if ($action instanceof renderable) {
3363 $output .= $OUTPUT->render($action);
3368 $output .= html_writer::end_tag('span');
3373 * given a course object with shortname & fullname, this function will
3374 * truncate the the number of chars allowed and add ... if it was too long
3376 function course_format_name ($course,$max=100) {
3378 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3379 $shortname = format_string($course->shortname, true, array('context' => $context));
3380 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
3381 $str = $shortname.': '. $fullname;
3382 if (textlib::strlen($str) <= $max) {
3386 return textlib::substr($str,0,$max-3).'...';
3391 * Is the user allowed to add this type of module to this course?
3392 * @param object $course the course settings. Only $course->id is used.
3393 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
3394 * @return bool whether the current user is allowed to add this type of module to this course.
3396 function course_allowed_module($course, $modname) {
3399 if (is_numeric($modname)) {
3400 throw new coding_exception('Function course_allowed_module no longer
3401 supports numeric module ids. Please update your code to pass the module name.');
3404 $capability = 'mod/' . $modname . ':addinstance';
3405 if (!get_capability_info($capability)) {
3406 // Debug warning that the capability does not exist, but no more than once per page.
3407 static $warned = array();
3408 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
3409 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
3410 debugging('The module ' . $modname . ' does not define the standard capability ' .
3411 $capability , DEBUG_DEVELOPER);
3412 $warned[$modname] = 1;
3415 // If the capability does not exist, the module can always be added.
3419 $coursecontext = context_course::instance($course->id);
3420 return has_capability($capability, $coursecontext);
3424 * Recursively delete category including all subcategories and courses.
3425 * @param stdClass $category
3426 * @param boolean $showfeedback display some notices
3427 * @return array return deleted courses
3429 function category_delete_full($category, $showfeedback=true) {
3431 require_once($CFG->libdir.'/gradelib.php');
3432 require_once($CFG->libdir.'/questionlib.php');
3433 require_once($CFG->dirroot.'/cohort/lib.php');
3435 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3436 foreach ($children as $childcat) {
3437 category_delete_full($childcat, $showfeedback);
3441 $deletedcourses = array();
3442 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
3443 foreach ($courses as $course) {
3444 if (!delete_course($course, false)) {
3445 throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
3447 $deletedcourses[] = $course;
3451 // move or delete cohorts in this context
3452 cohort_delete_category($category);
3454 // now delete anything that may depend on course category context
3455 grade_course_category_delete($category->id, 0, $showfeedback);
3456 if (!question_delete_course_category($category, 0, $showfeedback)) {
3457 throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
3460 // finally delete the category and it's context
3461 $DB->delete_records('course_categories', array('id'=>$category->id));
3462 delete_context(CONTEXT_COURSECAT, $category->id);
3464 events_trigger('course_category_deleted', $category);
3466 return $deletedcourses;
3470 * Delete category, but move contents to another category.
3471 * @param object $ccategory
3472 * @param int $newparentid category id
3473 * @return bool status
3475 function category_delete_move($category, $newparentid, $showfeedback=true) {
3476 global $CFG, $DB, $OUTPUT;
3477 require_once($CFG->libdir.'/gradelib.php');
3478 require_once($CFG->libdir.'/questionlib.php');
3479 require_once($CFG->dirroot.'/cohort/lib.php');
3481 if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
3485 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3486 foreach ($children as $childcat) {
3487 move_category($childcat, $newparentcat);
3491 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {