MDL-40275 core Improve documentation for block_base::has_config()
[moodle.git] / course / lib.php
blob057b17925b0a801a09f1baaa086f17110a2da115
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * Library of useful functions
21 * @copyright 1999 Martin Dougiamas http://dougiamas.com
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @package core
24 * @subpackage course
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir.'/completionlib.php');
30 require_once($CFG->libdir.'/filelib.php');
31 require_once($CFG->dirroot.'/course/dnduploadlib.php');
33 define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records
34 define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds
35 define('COURSE_MAX_SUMMARIES_PER_PAGE', 10); // courses
36 define('COURSE_MAX_COURSES_PER_DROPDOWN',1000); // max courses in log dropdown before switching to optional
37 define('COURSE_MAX_USERS_PER_DROPDOWN',1000); // max users in log dropdown before switching to optional
38 define('FRONTPAGENEWS', '0');
39 define('FRONTPAGECOURSELIST', '1');
40 define('FRONTPAGECATEGORYNAMES', '2');
41 define('FRONTPAGETOPICONLY', '3');
42 define('FRONTPAGECATEGORYCOMBO', '4');
43 define('FRONTPAGECOURSELIMIT', 200); // maximum number of courses displayed on the frontpage
44 define('EXCELROWS', 65535);
45 define('FIRSTUSEDEXCELROW', 3);
47 define('MOD_CLASS_ACTIVITY', 0);
48 define('MOD_CLASS_RESOURCE', 1);
50 function make_log_url($module, $url) {
51 switch ($module) {
52 case 'course':
53 if (strpos($url, 'report/') === 0) {
54 // there is only one report type, course reports are deprecated
55 $url = "/$url";
56 break;
58 case 'file':
59 case 'login':
60 case 'lib':
61 case 'admin':
62 case 'calendar':
63 case 'category':
64 case 'mnet course':
65 if (strpos($url, '../') === 0) {
66 $url = ltrim($url, '.');
67 } else {
68 $url = "/course/$url";
70 break;
71 case 'user':
72 case 'blog':
73 $url = "/$module/$url";
74 break;
75 case 'upload':
76 $url = $url;
77 break;
78 case 'coursetags':
79 $url = '/'.$url;
80 break;
81 case 'library':
82 case '':
83 $url = '/';
84 break;
85 case 'message':
86 $url = "/message/$url";
87 break;
88 case 'notes':
89 $url = "/notes/$url";
90 break;
91 case 'tag':
92 $url = "/tag/$url";
93 break;
94 case 'role':
95 $url = '/'.$url;
96 break;
97 case 'grade':
98 $url = "/grade/$url";
99 break;
100 default:
101 $url = "/mod/$module/$url";
102 break;
105 //now let's sanitise urls - there might be some ugly nasties:-(
106 $parts = explode('?', $url);
107 $script = array_shift($parts);
108 if (strpos($script, 'http') === 0) {
109 $script = clean_param($script, PARAM_URL);
110 } else {
111 $script = clean_param($script, PARAM_PATH);
114 $query = '';
115 if ($parts) {
116 $query = implode('', $parts);
117 $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
118 $parts = explode('&', $query);
119 $eq = urlencode('=');
120 foreach ($parts as $key=>$part) {
121 $part = urlencode(urldecode($part));
122 $part = str_replace($eq, '=', $part);
123 $parts[$key] = $part;
125 $query = '?'.implode('&amp;', $parts);
128 return $script.$query;
132 function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
133 $modname="", $modid=0, $modaction="", $groupid=0) {
134 global $CFG, $DB;
136 // It is assumed that $date is the GMT time of midnight for that day,
137 // and so the next 86400 seconds worth of logs are printed.
139 /// Setup for group handling.
141 // TODO: I don't understand group/context/etc. enough to be able to do
142 // something interesting with it here
143 // What is the context of a remote course?
145 /// If the group mode is separate, and this user does not have editing privileges,
146 /// then only the user's group can be viewed.
147 //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
148 // $groupid = get_current_group($course->id);
150 /// If this course doesn't have groups, no groupid can be specified.
151 //else if (!$course->groupmode) {
152 // $groupid = 0;
155 $groupid = 0;
157 $joins = array();
158 $where = '';
160 $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
161 FROM {mnet_log} l
162 LEFT JOIN {user} u ON l.userid = u.id
163 WHERE ";
164 $params = array();
166 $where .= "l.hostid = :hostid";
167 $params['hostid'] = $hostid;
169 // TODO: Is 1 really a magic number referring to the sitename?
170 if ($course != SITEID || $modid != 0) {
171 $where .= " AND l.course=:courseid";
172 $params['courseid'] = $course;
175 if ($modname) {
176 $where .= " AND l.module = :modname";
177 $params['modname'] = $modname;
180 if ('site_errors' === $modid) {
181 $where .= " AND ( l.action='error' OR l.action='infected' )";
182 } else if ($modid) {
183 //TODO: This assumes that modids are the same across sites... probably
184 //not true
185 $where .= " AND l.cmid = :modid";
186 $params['modid'] = $modid;
189 if ($modaction) {
190 $firstletter = substr($modaction, 0, 1);
191 if ($firstletter == '-') {
192 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
193 $params['modaction'] = '%'.substr($modaction, 1).'%';
194 } else {
195 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
196 $params['modaction'] = '%'.$modaction.'%';
200 if ($user) {
201 $where .= " AND l.userid = :user";
202 $params['user'] = $user;
205 if ($date) {
206 $enddate = $date + 86400;
207 $where .= " AND l.time > :date AND l.time < :enddate";
208 $params['date'] = $date;
209 $params['enddate'] = $enddate;
212 $result = array();
213 $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
214 if(!empty($result['totalcount'])) {
215 $where .= " ORDER BY $order";
216 $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
217 } else {
218 $result['logs'] = array();
220 return $result;
223 function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
224 $modname="", $modid=0, $modaction="", $groupid=0) {
225 global $DB, $SESSION, $USER;
226 // It is assumed that $date is the GMT time of midnight for that day,
227 // and so the next 86400 seconds worth of logs are printed.
229 /// Setup for group handling.
231 /// If the group mode is separate, and this user does not have editing privileges,
232 /// then only the user's group can be viewed.
233 if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
234 if (isset($SESSION->currentgroup[$course->id])) {
235 $groupid = $SESSION->currentgroup[$course->id];
236 } else {
237 $groupid = groups_get_all_groups($course->id, $USER->id);
238 if (is_array($groupid)) {
239 $groupid = array_shift(array_keys($groupid));
240 $SESSION->currentgroup[$course->id] = $groupid;
241 } else {
242 $groupid = 0;
246 /// If this course doesn't have groups, no groupid can be specified.
247 else if (!$course->groupmode) {
248 $groupid = 0;
251 $joins = array();
252 $params = array();
254 if ($course->id != SITEID || $modid != 0) {
255 $joins[] = "l.course = :courseid";
256 $params['courseid'] = $course->id;
259 if ($modname) {
260 $joins[] = "l.module = :modname";
261 $params['modname'] = $modname;
264 if ('site_errors' === $modid) {
265 $joins[] = "( l.action='error' OR l.action='infected' )";
266 } else if ($modid) {
267 $joins[] = "l.cmid = :modid";
268 $params['modid'] = $modid;
271 if ($modaction) {
272 $firstletter = substr($modaction, 0, 1);
273 if ($firstletter == '-') {
274 $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
275 $params['modaction'] = '%'.substr($modaction, 1).'%';
276 } else {
277 $joins[] = $DB->sql_like('l.action', ':modaction', false);
278 $params['modaction'] = '%'.$modaction.'%';
283 /// Getting all members of a group.
284 if ($groupid and !$user) {
285 if ($gusers = groups_get_members($groupid)) {
286 $gusers = array_keys($gusers);
287 $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
288 } else {
289 $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
292 else if ($user) {
293 $joins[] = "l.userid = :userid";
294 $params['userid'] = $user;
297 if ($date) {
298 $enddate = $date + 86400;
299 $joins[] = "l.time > :date AND l.time < :enddate";
300 $params['date'] = $date;
301 $params['enddate'] = $enddate;
304 $selector = implode(' AND ', $joins);
306 $totalcount = 0; // Initialise
307 $result = array();
308 $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
309 $result['totalcount'] = $totalcount;
310 return $result;
314 function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
315 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
317 global $CFG, $DB, $OUTPUT;
319 if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
320 $modname, $modid, $modaction, $groupid)) {
321 echo $OUTPUT->notification("No logs found!");
322 echo $OUTPUT->footer();
323 exit;
326 $courses = array();
328 if ($course->id == SITEID) {
329 $courses[0] = '';
330 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
331 foreach ($ccc as $cc) {
332 $courses[$cc->id] = $cc->shortname;
335 } else {
336 $courses[$course->id] = $course->shortname;
339 $totalcount = $logs['totalcount'];
340 $count=0;
341 $ldcache = array();
342 $tt = getdate(time());
343 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
345 $strftimedatetime = get_string("strftimedatetime");
347 echo "<div class=\"info\">\n";
348 print_string("displayingrecords", "", $totalcount);
349 echo "</div>\n";
351 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
353 $table = new html_table();
354 $table->classes = array('logtable','generalbox');
355 $table->align = array('right', 'left', 'left');
356 $table->head = array(
357 get_string('time'),
358 get_string('ip_address'),
359 get_string('fullnameuser'),
360 get_string('action'),
361 get_string('info')
363 $table->data = array();
365 if ($course->id == SITEID) {
366 array_unshift($table->align, 'left');
367 array_unshift($table->head, get_string('course'));
370 // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
371 if (empty($logs['logs'])) {
372 $logs['logs'] = array();
375 foreach ($logs['logs'] as $log) {
377 if (isset($ldcache[$log->module][$log->action])) {
378 $ld = $ldcache[$log->module][$log->action];
379 } else {
380 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
381 $ldcache[$log->module][$log->action] = $ld;
383 if ($ld && is_numeric($log->info)) {
384 // ugly hack to make sure fullname is shown correctly
385 if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
386 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
387 } else {
388 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
392 //Filter log->info
393 $log->info = format_string($log->info);
395 // If $log->url has been trimmed short by the db size restriction
396 // code in add_to_log, keep a note so we don't add a link to a broken url
397 $brokenurl=(textlib::strlen($log->url)==100 && textlib::substr($log->url,97)=='...');
399 $row = array();
400 if ($course->id == SITEID) {
401 if (empty($log->course)) {
402 $row[] = get_string('site');
403 } else {
404 $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
408 $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
410 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
411 $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
413 $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))));
415 $displayaction="$log->module $log->action";
416 if ($brokenurl) {
417 $row[] = $displayaction;
418 } else {
419 $link = make_log_url($log->module,$log->url);
420 $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
422 $row[] = $log->info;
423 $table->data[] = $row;
426 echo html_writer::table($table);
427 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
431 function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
432 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
434 global $CFG, $DB, $OUTPUT;
436 if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
437 $modname, $modid, $modaction, $groupid)) {
438 echo $OUTPUT->notification("No logs found!");
439 echo $OUTPUT->footer();
440 exit;
443 if ($course->id == SITEID) {
444 $courses[0] = '';
445 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
446 foreach ($ccc as $cc) {
447 $courses[$cc->id] = $cc->shortname;
452 $totalcount = $logs['totalcount'];
453 $count=0;
454 $ldcache = array();
455 $tt = getdate(time());
456 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
458 $strftimedatetime = get_string("strftimedatetime");
460 echo "<div class=\"info\">\n";
461 print_string("displayingrecords", "", $totalcount);
462 echo "</div>\n";
464 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
466 echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
467 echo "<tr>";
468 if ($course->id == SITEID) {
469 echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
471 echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
472 echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
473 echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
474 echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
475 echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
476 echo "</tr>\n";
478 if (empty($logs['logs'])) {
479 echo "</table>\n";
480 return;
483 $row = 1;
484 foreach ($logs['logs'] as $log) {
486 $log->info = $log->coursename;
487 $row = ($row + 1) % 2;
489 if (isset($ldcache[$log->module][$log->action])) {
490 $ld = $ldcache[$log->module][$log->action];
491 } else {
492 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
493 $ldcache[$log->module][$log->action] = $ld;
495 if (0 && $ld && !empty($log->info)) {
496 // ugly hack to make sure fullname is shown correctly
497 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
498 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
499 } else {
500 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
504 //Filter log->info
505 $log->info = format_string($log->info);
507 echo '<tr class="r'.$row.'">';
508 if ($course->id == SITEID) {
509 $courseshortname = format_string($courses[$log->course], true, array('context' => get_context_instance(CONTEXT_COURSE, SITEID)));
510 echo "<td class=\"r$row c0\" >\n";
511 echo " <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
512 echo "</td>\n";
514 echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
515 ' '.userdate($log->time, $strftimedatetime)."</td>\n";
516 echo "<td class=\"r$row c2\" >\n";
517 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
518 echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
519 echo "</td>\n";
520 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $course->id)));
521 echo "<td class=\"r$row c3\" >\n";
522 echo " <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
523 echo "</td>\n";
524 echo "<td class=\"r$row c4\">\n";
525 echo $log->action .': '.$log->module;
526 echo "</td>\n";;
527 echo "<td class=\"r$row c5\">{$log->info}</td>\n";
528 echo "</tr>\n";
530 echo "</table>\n";
532 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
536 function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
537 $modid, $modaction, $groupid) {
538 global $DB, $CFG;
540 $text = get_string('course')."\t".get_string('time')."\t".get_string('ip_address')."\t".
541 get_string('fullnameuser')."\t".get_string('action')."\t".get_string('info');
543 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
544 $modname, $modid, $modaction, $groupid)) {
545 return false;
548 $courses = array();
550 if ($course->id == SITEID) {
551 $courses[0] = '';
552 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
553 foreach ($ccc as $cc) {
554 $courses[$cc->id] = $cc->shortname;
557 } else {
558 $courses[$course->id] = $course->shortname;
561 $count=0;
562 $ldcache = array();
563 $tt = getdate(time());
564 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
566 $strftimedatetime = get_string("strftimedatetime");
568 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
569 $filename .= '.txt';
570 header("Content-Type: application/download\n");
571 header("Content-Disposition: attachment; filename=\"$filename\"");
572 header("Expires: 0");
573 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
574 header("Pragma: public");
576 echo get_string('savedat').userdate(time(), $strftimedatetime)."\n";
577 echo $text."\n";
579 if (empty($logs['logs'])) {
580 return true;
583 foreach ($logs['logs'] as $log) {
584 if (isset($ldcache[$log->module][$log->action])) {
585 $ld = $ldcache[$log->module][$log->action];
586 } else {
587 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
588 $ldcache[$log->module][$log->action] = $ld;
590 if ($ld && is_numeric($log->info)) {
591 // ugly hack to make sure fullname is shown correctly
592 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
593 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
594 } else {
595 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
599 //Filter log->info
600 $log->info = format_string($log->info);
601 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
603 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
604 $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
605 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
606 $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action, $log->info);
607 $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
608 $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
609 $text = implode("\t", $row);
610 echo $text." \n";
612 return true;
616 function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
617 $modid, $modaction, $groupid) {
619 global $CFG, $DB;
621 require_once("$CFG->libdir/excellib.class.php");
623 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
624 $modname, $modid, $modaction, $groupid)) {
625 return false;
628 $courses = array();
630 if ($course->id == SITEID) {
631 $courses[0] = '';
632 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
633 foreach ($ccc as $cc) {
634 $courses[$cc->id] = $cc->shortname;
637 } else {
638 $courses[$course->id] = $course->shortname;
641 $count=0;
642 $ldcache = array();
643 $tt = getdate(time());
644 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
646 $strftimedatetime = get_string("strftimedatetime");
648 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
649 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
650 $filename .= '.xls';
652 $workbook = new MoodleExcelWorkbook('-');
653 $workbook->send($filename);
655 $worksheet = array();
656 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
657 get_string('fullnameuser'), get_string('action'), get_string('info'));
659 // Creating worksheets
660 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
661 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
662 $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
663 $worksheet[$wsnumber]->set_column(1, 1, 30);
664 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
665 userdate(time(), $strftimedatetime));
666 $col = 0;
667 foreach ($headers as $item) {
668 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
669 $col++;
673 if (empty($logs['logs'])) {
674 $workbook->close();
675 return true;
678 $formatDate =& $workbook->add_format();
679 $formatDate->set_num_format(get_string('log_excel_date_format'));
681 $row = FIRSTUSEDEXCELROW;
682 $wsnumber = 1;
683 $myxls =& $worksheet[$wsnumber];
684 foreach ($logs['logs'] as $log) {
685 if (isset($ldcache[$log->module][$log->action])) {
686 $ld = $ldcache[$log->module][$log->action];
687 } else {
688 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
689 $ldcache[$log->module][$log->action] = $ld;
691 if ($ld && is_numeric($log->info)) {
692 // ugly hack to make sure fullname is shown correctly
693 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
694 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
695 } else {
696 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
700 // Filter log->info
701 $log->info = format_string($log->info);
702 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
704 if ($nroPages>1) {
705 if ($row > EXCELROWS) {
706 $wsnumber++;
707 $myxls =& $worksheet[$wsnumber];
708 $row = FIRSTUSEDEXCELROW;
712 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
714 $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
715 $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
716 $myxls->write($row, 2, $log->ip, '');
717 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
718 $myxls->write($row, 3, $fullname, '');
719 $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
720 $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
721 $myxls->write($row, 5, $log->info, '');
723 $row++;
726 $workbook->close();
727 return true;
730 function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
731 $modid, $modaction, $groupid) {
733 global $CFG, $DB;
735 require_once("$CFG->libdir/odslib.class.php");
737 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
738 $modname, $modid, $modaction, $groupid)) {
739 return false;
742 $courses = array();
744 if ($course->id == SITEID) {
745 $courses[0] = '';
746 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
747 foreach ($ccc as $cc) {
748 $courses[$cc->id] = $cc->shortname;
751 } else {
752 $courses[$course->id] = $course->shortname;
755 $count=0;
756 $ldcache = array();
757 $tt = getdate(time());
758 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
760 $strftimedatetime = get_string("strftimedatetime");
762 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
763 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
764 $filename .= '.ods';
766 $workbook = new MoodleODSWorkbook('-');
767 $workbook->send($filename);
769 $worksheet = array();
770 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
771 get_string('fullnameuser'), get_string('action'), get_string('info'));
773 // Creating worksheets
774 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
775 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
776 $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
777 $worksheet[$wsnumber]->set_column(1, 1, 30);
778 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
779 userdate(time(), $strftimedatetime));
780 $col = 0;
781 foreach ($headers as $item) {
782 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
783 $col++;
787 if (empty($logs['logs'])) {
788 $workbook->close();
789 return true;
792 $formatDate =& $workbook->add_format();
793 $formatDate->set_num_format(get_string('log_excel_date_format'));
795 $row = FIRSTUSEDEXCELROW;
796 $wsnumber = 1;
797 $myxls =& $worksheet[$wsnumber];
798 foreach ($logs['logs'] as $log) {
799 if (isset($ldcache[$log->module][$log->action])) {
800 $ld = $ldcache[$log->module][$log->action];
801 } else {
802 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
803 $ldcache[$log->module][$log->action] = $ld;
805 if ($ld && is_numeric($log->info)) {
806 // ugly hack to make sure fullname is shown correctly
807 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
808 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
809 } else {
810 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
814 // Filter log->info
815 $log->info = format_string($log->info);
816 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
818 if ($nroPages>1) {
819 if ($row > EXCELROWS) {
820 $wsnumber++;
821 $myxls =& $worksheet[$wsnumber];
822 $row = FIRSTUSEDEXCELROW;
826 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
828 $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
829 $myxls->write_date($row, 1, $log->time);
830 $myxls->write_string($row, 2, $log->ip);
831 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
832 $myxls->write_string($row, 3, $fullname);
833 $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
834 $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
835 $myxls->write_string($row, 5, $log->info);
837 $row++;
840 $workbook->close();
841 return true;
845 function print_overview($courses, array $remote_courses=array()) {
846 global $CFG, $USER, $DB, $OUTPUT;
848 $htmlarray = array();
849 if ($modules = $DB->get_records('modules')) {
850 foreach ($modules as $mod) {
851 if (file_exists(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php')) {
852 include_once(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php');
853 $fname = $mod->name.'_print_overview';
854 if (function_exists($fname)) {
855 $fname($courses,$htmlarray);
860 foreach ($courses as $course) {
861 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
862 echo $OUTPUT->box_start('coursebox');
863 // decode &amp;'s. format_string above will have encoded them and html_writer will encode again when it processed the title
864 // attribute leading to double encoding.
865 $attributes = array('title' => str_replace('&amp;', '&', $fullname));
866 if (empty($course->visible)) {
867 $attributes['class'] = 'dimmed';
869 echo $OUTPUT->heading(html_writer::link(
870 new moodle_url('/course/view.php', array('id' => $course->id)), $fullname, $attributes), 3);
871 if (array_key_exists($course->id,$htmlarray)) {
872 foreach ($htmlarray[$course->id] as $modname => $html) {
873 echo $html;
876 echo $OUTPUT->box_end();
879 if (!empty($remote_courses)) {
880 echo $OUTPUT->heading(get_string('remotecourses', 'mnet'));
882 foreach ($remote_courses as $course) {
883 echo $OUTPUT->box_start('coursebox');
884 $attributes = array('title' => s($course->fullname));
885 echo $OUTPUT->heading(html_writer::link(
886 new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
887 format_string($course->shortname),
888 $attributes) . ' (' . format_string($course->hostname) . ')', 3);
889 echo $OUTPUT->box_end();
895 * This function trawls through the logs looking for
896 * anything new since the user's last login
898 function print_recent_activity($course) {
899 // $course is an object
900 global $CFG, $USER, $SESSION, $DB, $OUTPUT;
902 $context = get_context_instance(CONTEXT_COURSE, $course->id);
904 $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
906 $timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
908 if (!isguestuser()) {
909 if (!empty($USER->lastcourseaccess[$course->id])) {
910 if ($USER->lastcourseaccess[$course->id] > $timestart) {
911 $timestart = $USER->lastcourseaccess[$course->id];
916 echo '<div class="activitydate">';
917 echo get_string('activitysince', '', userdate($timestart));
918 echo '</div>';
919 echo '<div class="activityhead">';
921 echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
923 echo "</div>\n";
925 $content = false;
927 /// Firstly, have there been any new enrolments?
929 $users = get_recent_enrolments($course->id, $timestart);
931 //Accessibility: new users now appear in an <OL> list.
932 if ($users) {
933 echo '<div class="newusers">';
934 echo $OUTPUT->heading(get_string("newusers").':', 3);
935 $content = true;
936 echo "<ol class=\"list\">\n";
937 foreach ($users as $user) {
938 $fullname = fullname($user, $viewfullnames);
939 echo '<li class="name"><a href="'."$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a></li>\n";
941 echo "</ol>\n</div>\n";
944 /// Next, have there been any modifications to the course structure?
946 $modinfo = get_fast_modinfo($course);
948 $changelist = array();
950 $logs = $DB->get_records_select('log', "time > ? AND course = ? AND
951 module = 'course' AND
952 (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
953 array($timestart, $course->id), "id ASC");
955 if ($logs) {
956 $actions = array('add mod', 'update mod', 'delete mod');
957 $newgones = array(); // added and later deleted items
958 foreach ($logs as $key => $log) {
959 if (!in_array($log->action, $actions)) {
960 continue;
962 $info = explode(' ', $log->info);
964 // note: in most cases I replaced hardcoding of label with use of
965 // $cm->has_view() but it was not possible to do this here because
966 // we don't necessarily have the $cm for it
967 if ($info[0] == 'label') { // Labels are ignored in recent activity
968 continue;
971 if (count($info) != 2) {
972 debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
973 continue;
976 $modname = $info[0];
977 $instanceid = $info[1];
979 if ($log->action == 'delete mod') {
980 // unfortunately we do not know if the mod was visible
981 if (!array_key_exists($log->info, $newgones)) {
982 $strdeleted = get_string('deletedactivity', 'moodle', get_string('modulename', $modname));
983 $changelist[$log->info] = array ('operation' => 'delete', 'text' => $strdeleted);
985 } else {
986 if (!isset($modinfo->instances[$modname][$instanceid])) {
987 if ($log->action == 'add mod') {
988 // do not display added and later deleted activities
989 $newgones[$log->info] = true;
991 continue;
993 $cm = $modinfo->instances[$modname][$instanceid];
994 if (!$cm->uservisible) {
995 continue;
998 if ($log->action == 'add mod') {
999 $stradded = get_string('added', 'moodle', get_string('modulename', $modname));
1000 $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>");
1002 } else if ($log->action == 'update mod' and empty($changelist[$log->info])) {
1003 $strupdated = get_string('updated', 'moodle', get_string('modulename', $modname));
1004 $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>");
1010 if (!empty($changelist)) {
1011 echo $OUTPUT->heading(get_string("courseupdates").':', 3);
1012 $content = true;
1013 foreach ($changelist as $changeinfo => $change) {
1014 echo '<p class="activity">'.$change['text'].'</p>';
1018 /// Now display new things from each module
1020 $usedmodules = array();
1021 foreach($modinfo->cms as $cm) {
1022 if (isset($usedmodules[$cm->modname])) {
1023 continue;
1025 if (!$cm->uservisible) {
1026 continue;
1028 $usedmodules[$cm->modname] = $cm->modname;
1031 foreach ($usedmodules as $modname) { // Each module gets it's own logs and prints them
1032 if (file_exists($CFG->dirroot.'/mod/'.$modname.'/lib.php')) {
1033 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
1034 $print_recent_activity = $modname.'_print_recent_activity';
1035 if (function_exists($print_recent_activity)) {
1036 // NOTE: original $isteacher (second parameter below) was replaced with $viewfullnames!
1037 $content = $print_recent_activity($course, $viewfullnames, $timestart) || $content;
1039 } else {
1040 debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
1044 if (! $content) {
1045 echo '<p class="message">'.get_string('nothingnew').'</p>';
1050 * For a given course, returns an array of course activity objects
1051 * Each item in the array contains he following properties:
1053 function get_array_of_activities($courseid) {
1054 // cm - course module id
1055 // mod - name of the module (eg forum)
1056 // section - the number of the section (eg week or topic)
1057 // name - the name of the instance
1058 // visible - is the instance visible or not
1059 // groupingid - grouping id
1060 // groupmembersonly - is this instance visible to group members only
1061 // extra - contains extra string to include in any link
1062 global $CFG, $DB;
1063 if(!empty($CFG->enableavailability)) {
1064 require_once($CFG->libdir.'/conditionlib.php');
1067 $course = $DB->get_record('course', array('id'=>$courseid));
1069 if (empty($course)) {
1070 throw new moodle_exception('courseidnotfound');
1073 $mod = array();
1075 $rawmods = get_course_mods($courseid);
1076 if (empty($rawmods)) {
1077 return $mod; // always return array
1080 if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
1081 foreach ($sections as $section) {
1082 if (!empty($section->sequence)) {
1083 $sequence = explode(",", $section->sequence);
1084 foreach ($sequence as $seq) {
1085 if (empty($rawmods[$seq])) {
1086 continue;
1088 $mod[$seq] = new stdClass();
1089 $mod[$seq]->id = $rawmods[$seq]->instance;
1090 $mod[$seq]->cm = $rawmods[$seq]->id;
1091 $mod[$seq]->mod = $rawmods[$seq]->modname;
1093 // Oh dear. Inconsistent names left here for backward compatibility.
1094 $mod[$seq]->section = $section->section;
1095 $mod[$seq]->sectionid = $rawmods[$seq]->section;
1097 $mod[$seq]->module = $rawmods[$seq]->module;
1098 $mod[$seq]->added = $rawmods[$seq]->added;
1099 $mod[$seq]->score = $rawmods[$seq]->score;
1100 $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
1101 $mod[$seq]->visible = $rawmods[$seq]->visible;
1102 $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
1103 $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
1104 $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
1105 $mod[$seq]->groupmembersonly = $rawmods[$seq]->groupmembersonly;
1106 $mod[$seq]->indent = $rawmods[$seq]->indent;
1107 $mod[$seq]->completion = $rawmods[$seq]->completion;
1108 $mod[$seq]->extra = "";
1109 $mod[$seq]->completiongradeitemnumber =
1110 $rawmods[$seq]->completiongradeitemnumber;
1111 $mod[$seq]->completionview = $rawmods[$seq]->completionview;
1112 $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
1113 $mod[$seq]->availablefrom = $rawmods[$seq]->availablefrom;
1114 $mod[$seq]->availableuntil = $rawmods[$seq]->availableuntil;
1115 $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
1116 $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
1117 if (!empty($CFG->enableavailability)) {
1118 condition_info::fill_availability_conditions($rawmods[$seq]);
1119 $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
1120 $mod[$seq]->conditionsgrade = $rawmods[$seq]->conditionsgrade;
1123 $modname = $mod[$seq]->mod;
1124 $functionname = $modname."_get_coursemodule_info";
1126 if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
1127 continue;
1130 include_once("$CFG->dirroot/mod/$modname/lib.php");
1132 if ($hasfunction = function_exists($functionname)) {
1133 if ($info = $functionname($rawmods[$seq])) {
1134 if (!empty($info->icon)) {
1135 $mod[$seq]->icon = $info->icon;
1137 if (!empty($info->iconcomponent)) {
1138 $mod[$seq]->iconcomponent = $info->iconcomponent;
1140 if (!empty($info->name)) {
1141 $mod[$seq]->name = $info->name;
1143 if ($info instanceof cached_cm_info) {
1144 // When using cached_cm_info you can include three new fields
1145 // that aren't available for legacy code
1146 if (!empty($info->content)) {
1147 $mod[$seq]->content = $info->content;
1149 if (!empty($info->extraclasses)) {
1150 $mod[$seq]->extraclasses = $info->extraclasses;
1152 if (!empty($info->iconurl)) {
1153 $mod[$seq]->iconurl = $info->iconurl;
1155 if (!empty($info->onclick)) {
1156 $mod[$seq]->onclick = $info->onclick;
1158 if (!empty($info->customdata)) {
1159 $mod[$seq]->customdata = $info->customdata;
1161 } else {
1162 // When using a stdclass, the (horrible) deprecated ->extra field
1163 // is available for BC
1164 if (!empty($info->extra)) {
1165 $mod[$seq]->extra = $info->extra;
1170 // When there is no modname_get_coursemodule_info function,
1171 // but showdescriptions is enabled, then we use the 'intro'
1172 // and 'introformat' fields in the module table
1173 if (!$hasfunction && $rawmods[$seq]->showdescription) {
1174 if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
1175 array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
1176 // Set content from intro and introformat. Filters are disabled
1177 // because we filter it with format_text at display time
1178 $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
1179 $modvalues, $rawmods[$seq]->id, false);
1181 // To save making another query just below, put name in here
1182 $mod[$seq]->name = $modvalues->name;
1185 if (!isset($mod[$seq]->name)) {
1186 $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1189 // Minimise the database size by unsetting default options when they are
1190 // 'empty'. This list corresponds to code in the cm_info constructor.
1191 foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
1192 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
1193 'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom',
1194 'availableuntil', 'conditionscompletion', 'conditionsgrade',
1195 'completionview', 'completionexpected', 'score', 'showdescription')
1196 as $property) {
1197 if (property_exists($mod[$seq], $property) &&
1198 empty($mod[$seq]->{$property})) {
1199 unset($mod[$seq]->{$property});
1202 // Special case: this value is usually set to null, but may be 0
1203 if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
1204 is_null($mod[$seq]->completiongradeitemnumber)) {
1205 unset($mod[$seq]->completiongradeitemnumber);
1211 return $mod;
1216 * Returns a number of useful structures for course displays
1218 function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modnamesused) {
1219 global $CFG, $DB, $COURSE;
1221 $mods = array(); // course modules indexed by id
1222 $modnames = array(); // all course module names (except resource!)
1223 $modnamesplural= array(); // all course module names (plural form)
1224 $modnamesused = array(); // course module names used
1226 if ($allmods = $DB->get_records("modules")) {
1227 foreach ($allmods as $mod) {
1228 if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
1229 continue;
1231 if ($mod->visible) {
1232 $modnames[$mod->name] = get_string("modulename", "$mod->name");
1233 $modnamesplural[$mod->name] = get_string("modulenameplural", "$mod->name");
1236 collatorlib::asort($modnames);
1237 } else {
1238 print_error("nomodules", 'debug');
1241 $course = ($courseid==$COURSE->id) ? $COURSE : $DB->get_record('course',array('id'=>$courseid));
1242 $modinfo = get_fast_modinfo($course);
1244 if ($rawmods=$modinfo->cms) {
1245 foreach($rawmods as $mod) { // Index the mods
1246 if (empty($modnames[$mod->modname])) {
1247 continue;
1249 $mods[$mod->id] = $mod;
1250 $mods[$mod->id]->modfullname = $modnames[$mod->modname];
1251 if (!$mod->visible and !has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_COURSE, $courseid))) {
1252 continue;
1254 // Check groupings
1255 if (!groups_course_module_visible($mod)) {
1256 continue;
1258 $modnamesused[$mod->modname] = $modnames[$mod->modname];
1260 if ($modnamesused) {
1261 collatorlib::asort($modnamesused);
1267 * Returns an array of sections for the requested course id
1269 * This function stores the sections against the course id within a staticvar encase
1270 * of subsequent requests. This is used all over + in some standard libs and course
1271 * format callbacks so subsequent requests are a reality.
1273 * Note: Since Moodle 2.3, it is more efficient to get this data by calling
1274 * get_fast_modinfo, then using $modinfo->get_section_info or get_section_info_all.
1276 * @staticvar array $coursesections
1277 * @param int $courseid
1278 * @return array Array of sections
1280 function get_all_sections($courseid) {
1281 global $DB;
1282 static $coursesections = array();
1283 if (!array_key_exists($courseid, $coursesections)) {
1284 $coursesections[$courseid] = $DB->get_records("course_sections", array("course"=>"$courseid"), "section",
1285 'section, id, course, name, summary, summaryformat, sequence, visible, ' .
1286 'availablefrom, availableuntil, showavailability, groupingid');
1288 return $coursesections[$courseid];
1292 * Set highlighted section. Only one section can be highlighted at the time.
1294 * @param int $courseid course id
1295 * @param int $marker highlight section with this number, 0 means remove higlightin
1296 * @return void
1298 function course_set_marker($courseid, $marker) {
1299 global $DB;
1300 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1304 * For a given course section, marks it visible or hidden,
1305 * and does the same for every activity in that section
1307 * @param int $courseid course id
1308 * @param int $sectionnumber The section number to adjust
1309 * @param int $visibility The new visibility
1310 * @return array A list of resources which were hidden in the section
1312 function set_section_visible($courseid, $sectionnumber, $visibility) {
1313 global $DB;
1315 $resourcestotoggle = array();
1316 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1317 $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1318 if (!empty($section->sequence)) {
1319 $modules = explode(",", $section->sequence);
1320 foreach ($modules as $moduleid) {
1321 set_coursemodule_visible($moduleid, $visibility, true);
1324 rebuild_course_cache($courseid);
1326 // Determine which modules are visible for AJAX update
1327 if (!empty($modules)) {
1328 list($insql, $params) = $DB->get_in_or_equal($modules);
1329 $select = 'id ' . $insql . ' AND visible = ?';
1330 array_push($params, $visibility);
1331 if (!$visibility) {
1332 $select .= ' AND visibleold = 1';
1334 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1337 return $resourcestotoggle;
1341 * Obtains shared data that is used in print_section when displaying a
1342 * course-module entry.
1344 * Calls format_text or format_string as appropriate, and obtains the correct icon.
1346 * This data is also used in other areas of the code.
1347 * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
1348 * @param object $course Moodle course object
1349 * @return array An array with the following values in this order:
1350 * $content (optional extra content for after link),
1351 * $instancename (text of link)
1353 function get_print_section_cm_text(cm_info $cm, $course) {
1354 global $OUTPUT;
1356 // Get content from modinfo if specified. Content displays either
1357 // in addition to the standard link (below), or replaces it if
1358 // the link is turned off by setting ->url to null.
1359 if (($content = $cm->get_content()) !== '') {
1360 // Improve filter performance by preloading filter setttings for all
1361 // activities on the course (this does nothing if called multiple
1362 // times)
1363 filter_preload_activities($cm->get_modinfo());
1365 // Get module context
1366 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1367 $labelformatoptions = new stdClass();
1368 $labelformatoptions->noclean = true;
1369 $labelformatoptions->overflowdiv = true;
1370 $labelformatoptions->context = $modulecontext;
1371 $content = format_text($content, FORMAT_HTML, $labelformatoptions);
1372 } else {
1373 $content = '';
1376 // Get course context
1377 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1378 $stringoptions = new stdClass;
1379 $stringoptions->context = $coursecontext;
1380 $instancename = format_string($cm->name, true, $stringoptions);
1381 return array($content, $instancename);
1385 * Prints a section full of activity modules
1387 * @param stdClass $course The course
1388 * @param stdClass $section The section
1389 * @param array $mods The modules in the section
1390 * @param array $modnamesused An array containing the list of modules and their names
1391 * @param bool $absolute All links are absolute
1392 * @param string $width Width of the container
1393 * @param bool $hidecompletion Hide completion status
1394 * @param int $sectionreturn The section to return to
1395 * @return void
1397 function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn=null) {
1398 global $CFG, $USER, $DB, $PAGE, $OUTPUT;
1400 static $initialised;
1402 static $groupbuttons;
1403 static $groupbuttonslink;
1404 static $isediting;
1405 static $ismoving;
1406 static $strmovehere;
1407 static $strmovefull;
1408 static $strunreadpostsone;
1409 static $modulenames;
1411 if (!isset($initialised)) {
1412 $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
1413 $groupbuttonslink = (!$course->groupmodeforce);
1414 $isediting = $PAGE->user_is_editing();
1415 $ismoving = $isediting && ismoving($course->id);
1416 if ($ismoving) {
1417 $strmovehere = get_string("movehere");
1418 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
1420 $modulenames = array();
1421 $initialised = true;
1424 $modinfo = get_fast_modinfo($course);
1425 $completioninfo = new completion_info($course);
1427 //Accessibility: replace table with list <ul>, but don't output empty list.
1428 if (!empty($section->sequence)) {
1430 // Fix bug #5027, don't want style=\"width:$width\".
1431 echo "<ul class=\"section img-text\">\n";
1432 $sectionmods = explode(",", $section->sequence);
1434 foreach ($sectionmods as $modnumber) {
1435 if (empty($mods[$modnumber])) {
1436 continue;
1440 * @var cm_info
1442 $mod = $mods[$modnumber];
1444 if ($ismoving and $mod->id == $USER->activitycopy) {
1445 // do not display moving mod
1446 continue;
1449 if (isset($modinfo->cms[$modnumber])) {
1450 // We can continue (because it will not be displayed at all)
1451 // if:
1452 // 1) The activity is not visible to users
1453 // and
1454 // 2a) The 'showavailability' option is not set (if that is set,
1455 // we need to display the activity so we can show
1456 // availability info)
1457 // or
1458 // 2b) The 'availableinfo' is empty, i.e. the activity was
1459 // hidden in a way that leaves no info, such as using the
1460 // eye icon.
1461 if (!$modinfo->cms[$modnumber]->uservisible &&
1462 (empty($modinfo->cms[$modnumber]->showavailability) ||
1463 empty($modinfo->cms[$modnumber]->availableinfo))) {
1464 // visibility shortcut
1465 continue;
1467 } else {
1468 if (!file_exists("$CFG->dirroot/mod/$mod->modname/lib.php")) {
1469 // module not installed
1470 continue;
1472 if (!coursemodule_visible_for_user($mod) &&
1473 empty($mod->showavailability)) {
1474 // full visibility check
1475 continue;
1479 if (!isset($modulenames[$mod->modname])) {
1480 $modulenames[$mod->modname] = get_string('modulename', $mod->modname);
1482 $modulename = $modulenames[$mod->modname];
1484 // In some cases the activity is visible to user, but it is
1485 // dimmed. This is done if viewhiddenactivities is true and if:
1486 // 1. the activity is not visible, or
1487 // 2. the activity has dates set which do not include current, or
1488 // 3. the activity has any other conditions set (regardless of whether
1489 // current user meets them)
1490 $modcontext = context_module::instance($mod->id);
1491 $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
1492 $accessiblebutdim = false;
1493 $conditionalhidden = false;
1494 if ($canviewhidden) {
1495 $accessiblebutdim = !$mod->visible;
1496 if (!empty($CFG->enableavailability)) {
1497 $conditionalhidden = $mod->availablefrom > time() ||
1498 ($mod->availableuntil && $mod->availableuntil < time()) ||
1499 count($mod->conditionsgrade) > 0 ||
1500 count($mod->conditionscompletion) > 0;
1502 $accessiblebutdim = $conditionalhidden || $accessiblebutdim;
1505 $liclasses = array();
1506 $liclasses[] = 'activity';
1507 $liclasses[] = $mod->modname;
1508 $liclasses[] = 'modtype_'.$mod->modname;
1509 $extraclasses = $mod->get_extra_classes();
1510 if ($extraclasses) {
1511 $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
1513 echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
1514 if ($ismoving) {
1515 echo '<a title="'.$strmovefull.'"'.
1516 ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
1517 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1518 ' alt="'.$strmovehere.'" /></a><br />
1522 $classes = array('mod-indent');
1523 if (!empty($mod->indent)) {
1524 $classes[] = 'mod-indent-'.$mod->indent;
1525 if ($mod->indent > 15) {
1526 $classes[] = 'mod-indent-huge';
1529 echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
1531 // Get data about this course-module
1532 list($content, $instancename) =
1533 get_print_section_cm_text($modinfo->cms[$modnumber], $course);
1535 //Accessibility: for files get description via icon, this is very ugly hack!
1536 $altname = '';
1537 $altname = $mod->modfullname;
1538 // Avoid unnecessary duplication: if e.g. a forum name already
1539 // includes the word forum (or Forum, etc) then it is unhelpful
1540 // to include that in the accessible description that is added.
1541 if (false !== strpos(textlib::strtolower($instancename),
1542 textlib::strtolower($altname))) {
1543 $altname = '';
1545 // File type after name, for alphabetic lists (screen reader).
1546 if ($altname) {
1547 $altname = get_accesshide(' '.$altname);
1550 // We may be displaying this just in order to show information
1551 // about visibility, without the actual link
1552 $contentpart = '';
1553 if ($mod->uservisible) {
1554 // Nope - in this case the link is fully working for user
1555 $linkclasses = '';
1556 $textclasses = '';
1557 if ($accessiblebutdim) {
1558 $linkclasses .= ' dimmed';
1559 $textclasses .= ' dimmed_text';
1560 if ($conditionalhidden) {
1561 $linkclasses .= ' conditionalhidden';
1562 $textclasses .= ' conditionalhidden';
1564 $accesstext = '<span class="accesshide">'.
1565 get_string('hiddenfromstudents').': </span>';
1566 } else {
1567 $accesstext = '';
1569 if ($linkclasses) {
1570 $linkcss = 'class="' . trim($linkclasses) . '" ';
1571 } else {
1572 $linkcss = '';
1574 if ($textclasses) {
1575 $textcss = 'class="' . trim($textclasses) . '" ';
1576 } else {
1577 $textcss = '';
1580 // Get on-click attribute value if specified
1581 $onclick = $mod->get_on_click();
1582 if ($onclick) {
1583 $onclick = ' onclick="' . $onclick . '"';
1586 if ($url = $mod->get_url()) {
1587 // Display link itself
1588 echo '<a ' . $linkcss . $mod->extra . $onclick .
1589 ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
1590 '" class="activityicon" alt="' . $modulename . '" /> ' .
1591 $accesstext . '<span class="instancename">' .
1592 $instancename . $altname . '</span></a>';
1594 // If specified, display extra content after link
1595 if ($content) {
1596 $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) .
1597 '">' . $content . '</div>';
1599 } else {
1600 // No link, so display only content
1601 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1602 $accesstext . $content . '</div>';
1605 if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
1606 $groupings = groups_get_all_groupings($course->id);
1607 echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
1609 } else {
1610 $textclasses = $extraclasses;
1611 $textclasses .= ' dimmed_text';
1612 if ($textclasses) {
1613 $textcss = 'class="' . trim($textclasses) . '" ';
1614 } else {
1615 $textcss = '';
1617 $accesstext = '<span class="accesshide">' .
1618 get_string('notavailableyet', 'condition') .
1619 ': </span>';
1621 if ($url = $mod->get_url()) {
1622 // Display greyed-out text of link
1623 echo '<div ' . $textcss . $mod->extra .
1624 ' >' . '<img src="' . $mod->get_icon_url() .
1625 '" class="activityicon" alt="" /> <span>'. $instancename . $altname .
1626 '</span></div>';
1628 // Do not display content after link when it is greyed out like this.
1629 } else {
1630 // No link, so display only content (also greyed)
1631 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1632 $accesstext . $content . '</div>';
1636 // Module can put text after the link (e.g. forum unread)
1637 echo $mod->get_after_link();
1639 // If there is content but NO link (eg label), then display the
1640 // content here (BEFORE any icons). In this case cons must be
1641 // displayed after the content so that it makes more sense visually
1642 // and for accessibility reasons, e.g. if you have a one-line label
1643 // it should work similarly (at least in terms of ordering) to an
1644 // activity.
1645 if (empty($url)) {
1646 echo $contentpart;
1649 if ($isediting) {
1650 if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
1651 if (! $mod->groupmodelink = $groupbuttonslink) {
1652 $mod->groupmode = $course->groupmode;
1655 } else {
1656 $mod->groupmode = false;
1658 echo '&nbsp;&nbsp;';
1659 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
1660 echo $mod->get_after_edit_icons();
1663 // Completion
1664 $completion = $hidecompletion
1665 ? COMPLETION_TRACKING_NONE
1666 : $completioninfo->is_enabled($mod);
1667 if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
1668 !isguestuser() && $mod->uservisible) {
1669 $completiondata = $completioninfo->get_data($mod,true);
1670 $completionicon = '';
1671 if ($isediting) {
1672 switch ($completion) {
1673 case COMPLETION_TRACKING_MANUAL :
1674 $completionicon = 'manual-enabled'; break;
1675 case COMPLETION_TRACKING_AUTOMATIC :
1676 $completionicon = 'auto-enabled'; break;
1677 default: // wtf
1679 } else if ($completion==COMPLETION_TRACKING_MANUAL) {
1680 switch($completiondata->completionstate) {
1681 case COMPLETION_INCOMPLETE:
1682 $completionicon = 'manual-n'; break;
1683 case COMPLETION_COMPLETE:
1684 $completionicon = 'manual-y'; break;
1686 } else { // Automatic
1687 switch($completiondata->completionstate) {
1688 case COMPLETION_INCOMPLETE:
1689 $completionicon = 'auto-n'; break;
1690 case COMPLETION_COMPLETE:
1691 $completionicon = 'auto-y'; break;
1692 case COMPLETION_COMPLETE_PASS:
1693 $completionicon = 'auto-pass'; break;
1694 case COMPLETION_COMPLETE_FAIL:
1695 $completionicon = 'auto-fail'; break;
1698 if ($completionicon) {
1699 $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
1700 $formattedname = format_string($mod->name, true, array('context' => $modcontext));
1701 $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
1702 if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
1703 $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
1704 $newstate =
1705 $completiondata->completionstate==COMPLETION_COMPLETE
1706 ? COMPLETION_INCOMPLETE
1707 : COMPLETION_COMPLETE;
1708 // In manual mode the icon is a toggle form...
1710 // If this completion state is used by the
1711 // conditional activities system, we need to turn
1712 // off the JS.
1713 if (!empty($CFG->enableavailability) &&
1714 condition_info::completion_value_used_as_condition($course, $mod)) {
1715 $extraclass = ' preventjs';
1716 } else {
1717 $extraclass = '';
1719 echo html_writer::start_tag('form', array(
1720 'class' => 'togglecompletion' . $extraclass,
1721 'method' => 'post',
1722 'action' => $CFG->wwwroot . '/course/togglecompletion.php'));
1723 echo html_writer::start_tag('div');
1724 echo html_writer::empty_tag('input', array(
1725 'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
1726 echo html_writer::empty_tag('input', array(
1727 'type' => 'hidden', 'name' => 'modulename',
1728 'value' => $mod->name));
1729 echo html_writer::empty_tag('input', array(
1730 'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
1731 echo html_writer::empty_tag('input', array(
1732 'type' => 'hidden', 'name' => 'completionstate',
1733 'value' => $newstate));
1734 echo html_writer::empty_tag('input', array(
1735 'type' => 'image', 'src' => $imgsrc, 'alt' => $imgalt, 'title' => $imgtitle,
1736 'aria-live' => 'polite'));
1737 echo html_writer::end_tag('div');
1738 echo html_writer::end_tag('form');
1739 } else {
1740 // In auto mode, or when editing, the icon is just an image.
1741 echo html_writer::tag('span', html_writer::empty_tag('img', array(
1742 'src' => $imgsrc, 'alt' => $imgalt, 'title' => $imgalt)),
1743 array('class' => 'autocompletion'));
1748 // If there is content AND a link, then display the content here
1749 // (AFTER any icons). Otherwise it was displayed before
1750 if (!empty($url)) {
1751 echo $contentpart;
1754 // Show availability information (for someone who isn't allowed to
1755 // see the activity itself, or for staff)
1756 if (!$mod->uservisible) {
1757 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
1758 } else if ($canviewhidden && !empty($CFG->enableavailability)) {
1759 // Don't add availability information if user is not editing and activity is hidden.
1760 if ($mod->visible || $PAGE->user_is_editing()) {
1761 $hidinfoclass = '';
1762 if (!$mod->visible) {
1763 $hidinfoclass = 'hide';
1765 $ci = new condition_info($mod);
1766 $fullinfo = $ci->get_full_information();
1767 if($fullinfo) {
1768 echo '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
1769 ? 'userrestriction_visible'
1770 : 'userrestriction_hidden','condition',
1771 $fullinfo).'</div>';
1776 echo html_writer::end_tag('div');
1777 echo html_writer::end_tag('li')."\n";
1780 } elseif ($ismoving) {
1781 echo "<ul class=\"section\">\n";
1784 if ($ismoving) {
1785 echo '<li><a title="'.$strmovefull.'"'.
1786 ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
1787 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1788 ' alt="'.$strmovehere.'" /></a></li>
1791 if (!empty($section->sequence) || $ismoving) {
1792 echo "</ul><!--class='section'-->\n\n";
1797 * Prints the menus to add activities and resources.
1799 * @param stdClass $course The course
1800 * @param int $section relative section number (field course_sections.section)
1801 * @param array $modnames An array containing the list of modules and their names
1802 * @param bool $vertical Vertical orientation
1803 * @param bool $return Return the menus or send them to output
1804 * @param int $sectionreturn The section to link back to
1805 * @return void|string depending on $return
1807 function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false, $sectionreturn=null) {
1808 global $CFG, $OUTPUT;
1810 // check to see if user can add menus
1811 if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id))) {
1812 return false;
1815 // Retrieve all modules with associated metadata
1816 $modules = get_module_metadata($course, $modnames, $sectionreturn);
1818 // We'll sort resources and activities into two lists
1819 $resources = array();
1820 $activities = array();
1822 // We need to add the section section to the link for each module
1823 $sectionlink = '&section=' . $section . '&sr=' . $sectionreturn;
1825 foreach ($modules as $module) {
1826 if (isset($module->types)) {
1827 // This module has a subtype
1828 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1829 $subtypes = array();
1830 foreach ($module->types as $subtype) {
1831 $subtypes[$subtype->link . $sectionlink] = $subtype->title;
1834 // Sort module subtypes into the list
1835 if (!empty($module->title)) {
1836 // This grouping has a name
1837 if ($module->archetype == MOD_CLASS_RESOURCE) {
1838 $resources[] = array($module->title=>$subtypes);
1839 } else {
1840 $activities[] = array($module->title=>$subtypes);
1842 } else {
1843 // This grouping does not have a name
1844 if ($module->archetype == MOD_CLASS_RESOURCE) {
1845 $resources = array_merge($resources, $subtypes);
1846 } else {
1847 $activities = array_merge($activities, $subtypes);
1850 } else {
1851 // This module has no subtypes
1852 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
1853 $resources[$module->link . $sectionlink] = $module->title;
1854 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
1855 // System modules cannot be added by user, do not add to dropdown
1856 } else {
1857 $activities[$module->link . $sectionlink] = $module->title;
1862 $straddactivity = get_string('addactivity');
1863 $straddresource = get_string('addresource');
1865 $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
1867 if (!$vertical) {
1868 $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
1871 if (!empty($resources)) {
1872 $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
1873 $select->set_help_icon('resources');
1874 $output .= $OUTPUT->render($select);
1877 if (!empty($activities)) {
1878 $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
1879 $select->set_help_icon('activities');
1880 $output .= $OUTPUT->render($select);
1883 if (!$vertical) {
1884 $output .= html_writer::end_tag('div');
1887 $output .= html_writer::end_tag('div');
1889 if (course_ajax_enabled($course)) {
1890 $straddeither = get_string('addresourceoractivity');
1891 // The module chooser link
1892 $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
1893 $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
1894 $icon = $OUTPUT->pix_icon('t/add', '');
1895 $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
1896 $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
1897 $modchooser.= html_writer::end_tag('div');
1898 $modchooser.= html_writer::end_tag('div');
1900 // Wrap the normal output in a noscript div
1901 $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
1902 if ($usemodchooser) {
1903 $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
1904 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
1905 } else {
1906 // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
1907 $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
1908 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
1910 $output = $modchooser . $output;
1913 if ($return) {
1914 return $output;
1915 } else {
1916 echo $output;
1921 * Retrieve all metadata for the requested modules
1923 * @param object $course The Course
1924 * @param array $modnames An array containing the list of modules and their
1925 * names
1926 * @param int $sectionreturn The section to return to
1927 * @return array A list of stdClass objects containing metadata about each
1928 * module
1930 function get_module_metadata($course, $modnames, $sectionreturn = null) {
1931 global $CFG, $OUTPUT;
1933 // get_module_metadata will be called once per section on the page and courses may show
1934 // different modules to one another
1935 static $modlist = array();
1936 if (!isset($modlist[$course->id])) {
1937 $modlist[$course->id] = array();
1940 $return = array();
1941 $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&sr='.$sectionreturn.'&add=';
1942 foreach($modnames as $modname => $modnamestr) {
1943 if (!course_allowed_module($course, $modname)) {
1944 continue;
1946 if (isset($modlist[$course->id][$modname])) {
1947 // This module is already cached
1948 $return[$modname] = $modlist[$course->id][$modname];
1949 continue;
1952 // Include the module lib
1953 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1954 if (!file_exists($libfile)) {
1955 continue;
1957 include_once($libfile);
1959 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1960 $gettypesfunc = $modname.'_get_types';
1961 if (function_exists($gettypesfunc)) {
1962 $types = $gettypesfunc();
1963 if (is_array($types) && count($types) > 0) {
1964 $group = new stdClass();
1965 $group->name = $modname;
1966 $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1967 foreach($types as $type) {
1968 if ($type->typestr === '--') {
1969 continue;
1971 if (strpos($type->typestr, '--') === 0) {
1972 $group->title = str_replace('--', '', $type->typestr);
1973 continue;
1975 // Set the Sub Type metadata
1976 $subtype = new stdClass();
1977 $subtype->title = $type->typestr;
1978 $subtype->type = str_replace('&amp;', '&', $type->type);
1979 $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1980 $subtype->archetype = $type->modclass;
1982 // The group archetype should match the subtype archetypes and all subtypes
1983 // should have the same archetype
1984 $group->archetype = $subtype->archetype;
1986 if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1987 $subtype->help = get_string('help' . $subtype->name, $modname);
1989 $subtype->link = $urlbase . $subtype->type;
1990 $group->types[] = $subtype;
1992 $modlist[$course->id][$modname] = $group;
1994 } else {
1995 $module = new stdClass();
1996 $module->title = get_string('modulename', $modname);
1997 $module->name = $modname;
1998 $module->link = $urlbase . $modname;
1999 $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
2000 $sm = get_string_manager();
2001 if ($sm->string_exists('modulename_help', $modname)) {
2002 $module->help = get_string('modulename_help', $modname);
2003 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs
2004 $link = get_string('modulename_link', $modname);
2005 $linktext = get_string('morehelp');
2006 $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
2009 $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2010 $modlist[$course->id][$modname] = $module;
2012 if (isset($modlist[$course->id][$modname])) {
2013 $return[$modname] = $modlist[$course->id][$modname];
2014 } else {
2015 debugging("Invalid module metadata configuration for {$modname}");
2019 return $return;
2023 * Return the course category context for the category with id $categoryid, except
2024 * that if $categoryid is 0, return the system context.
2026 * @param integer $categoryid a category id or 0.
2027 * @return object the corresponding context
2029 function get_category_or_system_context($categoryid) {
2030 if ($categoryid) {
2031 return get_context_instance(CONTEXT_COURSECAT, $categoryid);
2032 } else {
2033 return get_context_instance(CONTEXT_SYSTEM);
2038 * Gets the child categories of a given courses category. Uses a static cache
2039 * to make repeat calls efficient.
2041 * @param int $parentid the id of a course category.
2042 * @return array all the child course categories.
2044 function get_child_categories($parentid) {
2045 static $allcategories = null;
2047 // only fill in this variable the first time
2048 if (null == $allcategories) {
2049 $allcategories = array();
2051 $categories = get_categories();
2052 foreach ($categories as $category) {
2053 if (empty($allcategories[$category->parent])) {
2054 $allcategories[$category->parent] = array();
2056 $allcategories[$category->parent][] = $category;
2060 if (empty($allcategories[$parentid])) {
2061 return array();
2062 } else {
2063 return $allcategories[$parentid];
2068 * This function recursively travels the categories, building up a nice list
2069 * for display. It also makes an array that list all the parents for each
2070 * category.
2072 * For example, if you have a tree of categories like:
2073 * Miscellaneous (id = 1)
2074 * Subcategory (id = 2)
2075 * Sub-subcategory (id = 4)
2076 * Other category (id = 3)
2077 * Then after calling this function you will have
2078 * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
2079 * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
2080 * 3 => 'Other category');
2081 * $parents = array(2 => array(1), 4 => array(1, 2));
2083 * If you specify $requiredcapability, then only categories where the current
2084 * user has that capability will be added to $list, although all categories
2085 * will still be added to $parents, and if you only have $requiredcapability
2086 * in a child category, not the parent, then the child catgegory will still be
2087 * included.
2089 * If you specify the option $excluded, then that category, and all its children,
2090 * are omitted from the tree. This is useful when you are doing something like
2091 * moving categories, where you do not want to allow people to move a category
2092 * to be the child of itself.
2094 * @param array $list For output, accumulates an array categoryid => full category path name
2095 * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
2096 * @param string/array $requiredcapability if given, only categories where the current
2097 * user has this capability will be added to $list. Can also be an array of capabilities,
2098 * in which case they are all required.
2099 * @param integer $excludeid Omit this category and its children from the lists built.
2100 * @param object $category Build the tree starting at this category - otherwise starts at the top level.
2101 * @param string $path For internal use, as part of recursive calls.
2103 function make_categories_list(&$list, &$parents, $requiredcapability = '',
2104 $excludeid = 0, $category = NULL, $path = "") {
2106 // initialize the arrays if needed
2107 if (!is_array($list)) {
2108 $list = array();
2110 if (!is_array($parents)) {
2111 $parents = array();
2114 if (empty($category)) {
2115 // Start at the top level.
2116 $category = new stdClass;
2117 $category->id = 0;
2118 } else {
2119 // This is the excluded category, don't include it.
2120 if ($excludeid > 0 && $excludeid == $category->id) {
2121 return;
2124 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2125 $categoryname = format_string($category->name, true, array('context' => $context));
2127 // Update $path.
2128 if ($path) {
2129 $path = $path.' / '.$categoryname;
2130 } else {
2131 $path = $categoryname;
2134 // Add this category to $list, if the permissions check out.
2135 if (empty($requiredcapability)) {
2136 $list[$category->id] = $path;
2138 } else {
2139 $requiredcapability = (array)$requiredcapability;
2140 if (has_all_capabilities($requiredcapability, $context)) {
2141 $list[$category->id] = $path;
2146 // Add all the children recursively, while updating the parents array.
2147 if ($categories = get_child_categories($category->id)) {
2148 foreach ($categories as $cat) {
2149 if (!empty($category->id)) {
2150 if (isset($parents[$category->id])) {
2151 $parents[$cat->id] = $parents[$category->id];
2153 $parents[$cat->id][] = $category->id;
2155 make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
2161 * This function generates a structured array of courses and categories.
2163 * The depth of categories is limited by $CFG->maxcategorydepth however there
2164 * is no limit on the number of courses!
2166 * Suitable for use with the course renderers course_category_tree method:
2167 * $renderer = $PAGE->get_renderer('core','course');
2168 * echo $renderer->course_category_tree(get_course_category_tree());
2170 * @global moodle_database $DB
2171 * @param int $id
2172 * @param int $depth
2174 function get_course_category_tree($id = 0, $depth = 0) {
2175 global $DB, $CFG;
2176 $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM));
2177 $categories = get_child_categories($id);
2178 $categoryids = array();
2179 foreach ($categories as $key => &$category) {
2180 if (!$category->visible && !$viewhiddencats) {
2181 unset($categories[$key]);
2182 continue;
2184 $categoryids[$category->id] = $category;
2185 if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
2186 list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
2187 foreach ($subcategories as $subid=>$subcat) {
2188 $categoryids[$subid] = $subcat;
2190 $category->courses = array();
2194 if ($depth > 0) {
2195 // This is a recursive call so return the required array
2196 return array($categories, $categoryids);
2199 if (empty($categoryids)) {
2200 // No categories available (probably all hidden).
2201 return array();
2204 // The depth is 0 this function has just been called so we can finish it off
2206 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
2207 list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
2208 $sql = "SELECT
2209 c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
2210 $ccselect
2211 FROM {course} c
2212 $ccjoin
2213 WHERE c.category $catsql ORDER BY c.sortorder ASC";
2214 if ($courses = $DB->get_records_sql($sql, $catparams)) {
2215 // loop throught them
2216 foreach ($courses as $course) {
2217 if ($course->id == SITEID) {
2218 continue;
2220 context_instance_preload($course);
2221 if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
2222 $categoryids[$course->category]->courses[$course->id] = $course;
2226 return $categories;
2230 * Recursive function to print out all the categories in a nice format
2231 * with or without courses included
2233 function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
2234 global $CFG;
2236 // maxcategorydepth == 0 meant no limit
2237 if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
2238 return;
2241 if (!$displaylist) {
2242 make_categories_list($displaylist, $parentslist);
2245 if ($category) {
2246 if ($category->visible or has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM))) {
2247 print_category_info($category, $depth, $showcourses);
2248 } else {
2249 return; // Don't bother printing children of invisible categories
2252 } else {
2253 $category = new stdClass();
2254 $category->id = "0";
2257 if ($categories = get_child_categories($category->id)) { // Print all the children recursively
2258 $countcats = count($categories);
2259 $count = 0;
2260 $first = true;
2261 $last = false;
2262 foreach ($categories as $cat) {
2263 $count++;
2264 if ($count == $countcats) {
2265 $last = true;
2267 $up = $first ? false : true;
2268 $down = $last ? false : true;
2269 $first = false;
2271 print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
2277 * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
2279 function make_categories_options() {
2280 make_categories_list($cats,$parents);
2281 foreach ($cats as $key => $value) {
2282 if (array_key_exists($key,$parents)) {
2283 if ($indent = count($parents[$key])) {
2284 for ($i = 0; $i < $indent; $i++) {
2285 $cats[$key] = '&nbsp;'.$cats[$key];
2290 return $cats;
2294 * Prints the category info in indented fashion
2295 * This function is only used by print_whole_category_list() above
2297 function print_category_info($category, $depth=0, $showcourses = false) {
2298 global $CFG, $DB, $OUTPUT;
2300 $strsummary = get_string('summary');
2302 $catlinkcss = null;
2303 if (!$category->visible) {
2304 $catlinkcss = array('class'=>'dimmed');
2306 static $coursecount = null;
2307 if (null === $coursecount) {
2308 // only need to check this once
2309 $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
2312 if ($showcourses and $coursecount) {
2313 $catimage = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
2314 } else {
2315 $catimage = "&nbsp;";
2318 $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
2319 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2320 $fullname = format_string($category->name, true, array('context' => $context));
2322 if ($showcourses and $coursecount) {
2323 echo '<div class="categorylist clearfix">';
2324 $cat = '';
2325 $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
2326 $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2327 $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
2329 $html = '';
2330 if ($depth > 0) {
2331 for ($i=0; $i< $depth; $i++) {
2332 $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
2333 $cat = '';
2335 } else {
2336 $html = $cat;
2338 echo html_writer::tag('div', $html, array('class'=>'category'));
2339 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2341 // does the depth exceed maxcategorydepth
2342 // maxcategorydepth == 0 or unset meant no limit
2343 $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
2344 if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
2345 foreach ($courses as $course) {
2346 $linkcss = null;
2347 if (!$course->visible) {
2348 $linkcss = array('class'=>'dimmed');
2351 $coursename = get_course_display_name_for_list($course);
2352 $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
2354 // print enrol info
2355 $courseicon = '';
2356 if ($icons = enrol_get_course_info_icons($course)) {
2357 foreach ($icons as $pix_icon) {
2358 $courseicon = $OUTPUT->render($pix_icon).' ';
2362 $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
2364 if ($course->summary) {
2365 $link = new moodle_url('/course/info.php?id='.$course->id);
2366 $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
2367 new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
2368 array('title'=>$strsummary));
2370 $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
2373 $html = '';
2374 for ($i=0; $i <= $depth; $i++) {
2375 $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
2376 $coursecontent = '';
2378 echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
2381 echo '</div>';
2382 } else {
2383 echo '<div class="categorylist">';
2384 $html = '';
2385 $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2386 if (count($courses) > 0) {
2387 $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
2390 if ($depth > 0) {
2391 for ($i=0; $i< $depth; $i++) {
2392 $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
2393 $cat = '';
2395 } else {
2396 $html = $cat;
2399 echo html_writer::tag('div', $html, array('class'=>'category'));
2400 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2401 echo '</div>';
2406 * Print the buttons relating to course requests.
2408 * @param object $systemcontext the system context.
2410 function print_course_request_buttons($systemcontext) {
2411 global $CFG, $DB, $OUTPUT;
2412 if (empty($CFG->enablecourserequests)) {
2413 return;
2415 if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
2416 /// Print a button to request a new course
2417 echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
2419 /// Print a button to manage pending requests
2420 if (has_capability('moodle/site:approvecourse', $systemcontext)) {
2421 $disabled = !$DB->record_exists('course_request', array());
2422 echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
2427 * Does the user have permission to edit things in this category?
2429 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2430 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
2432 function can_edit_in_category($categoryid = 0) {
2433 $context = get_category_or_system_context($categoryid);
2434 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
2438 * Prints the turn editing on/off button on course/index.php or course/category.php.
2440 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2441 * @return string HTML of the editing button, or empty string, if this user is not allowed
2442 * to see it.
2444 function update_category_button($categoryid = 0) {
2445 global $CFG, $PAGE, $OUTPUT;
2447 // Check permissions.
2448 if (!can_edit_in_category($categoryid)) {
2449 return '';
2452 // Work out the appropriate action.
2453 if ($PAGE->user_is_editing()) {
2454 $label = get_string('turneditingoff');
2455 $edit = 'off';
2456 } else {
2457 $label = get_string('turneditingon');
2458 $edit = 'on';
2461 // Generate the button HTML.
2462 $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
2463 if ($categoryid) {
2464 $options['id'] = $categoryid;
2465 $page = 'category.php';
2466 } else {
2467 $page = 'index.php';
2469 return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
2473 * Print courses in category. If category is 0 then all courses are printed.
2474 * @param int|stdClass $category category object or id.
2475 * @return bool true if courses found and printed, else false.
2477 function print_courses($category) {
2478 global $CFG, $OUTPUT;
2480 if (!is_object($category) && $category==0) {
2481 $categories = get_child_categories(0); // Parent = 0 ie top-level categories only
2482 if (is_array($categories) && count($categories) == 1) {
2483 $category = array_shift($categories);
2484 $courses = get_courses_wmanagers($category->id,
2485 'c.sortorder ASC',
2486 array('summary','summaryformat'));
2487 } else {
2488 $courses = get_courses_wmanagers('all',
2489 'c.sortorder ASC',
2490 array('summary','summaryformat'));
2492 unset($categories);
2493 } else {
2494 $courses = get_courses_wmanagers($category->id,
2495 'c.sortorder ASC',
2496 array('summary','summaryformat'));
2499 if ($courses) {
2500 echo html_writer::start_tag('ul', array('class'=>'unlist'));
2501 foreach ($courses as $course) {
2502 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2503 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2504 echo html_writer::start_tag('li');
2505 print_course($course);
2506 echo html_writer::end_tag('li');
2509 echo html_writer::end_tag('ul');
2510 } else {
2511 echo $OUTPUT->heading(get_string("nocoursesyet"));
2512 $context = get_context_instance(CONTEXT_SYSTEM);
2513 if (has_capability('moodle/course:create', $context)) {
2514 $options = array();
2515 if (!empty($category->id)) {
2516 $options['category'] = $category->id;
2517 } else {
2518 $options['category'] = $CFG->defaultrequestcategory;
2520 echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
2521 echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
2522 echo html_writer::end_tag('div');
2523 return false;
2526 return true;
2530 * Print a description of a course, suitable for browsing in a list.
2532 * @param object $course the course object.
2533 * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
2535 function print_course($course, $highlightterms = '') {
2536 global $CFG, $USER, $DB, $OUTPUT;
2538 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2540 // Rewrite file URLs so that they are correct
2541 $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
2543 echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
2544 echo html_writer::start_tag('div', array('class'=>'info'));
2545 echo html_writer::start_tag('h3', array('class'=>'name'));
2547 $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
2549 $coursename = get_course_display_name_for_list($course);
2550 $linktext = highlight($highlightterms, format_string($coursename));
2551 $linkparams = array('title'=>get_string('entercourse'));
2552 if (empty($course->visible)) {
2553 $linkparams['class'] = 'dimmed';
2555 echo html_writer::link($linkhref, $linktext, $linkparams);
2556 echo html_writer::end_tag('h3');
2558 /// first find all roles that are supposed to be displayed
2559 if (!empty($CFG->coursecontact)) {
2560 $managerroles = explode(',', $CFG->coursecontact);
2561 $namesarray = array();
2562 $rusers = array();
2564 if (!isset($course->managers)) {
2565 $rusers = get_role_users($managerroles, $context, true,
2566 'ra.id AS raid, u.id, u.username, u.firstname, u.lastname,
2567 r.name AS rolename, r.sortorder, r.id AS roleid',
2568 'r.sortorder ASC, u.lastname ASC');
2569 } else {
2570 // use the managers array if we have it for perf reasosn
2571 // populate the datastructure like output of get_role_users();
2572 foreach ($course->managers as $manager) {
2573 $u = new stdClass();
2574 $u = $manager->user;
2575 $u->roleid = $manager->roleid;
2576 $u->rolename = $manager->rolename;
2578 $rusers[] = $u;
2582 /// Rename some of the role names if needed
2583 if (isset($context)) {
2584 $aliasnames = $DB->get_records('role_names', array('contextid'=>$context->id), '', 'roleid,contextid,name');
2587 $namesarray = array();
2588 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
2589 foreach ($rusers as $ra) {
2590 if (isset($namesarray[$ra->id])) {
2591 // only display a user once with the higest sortorder role
2592 continue;
2595 if (isset($aliasnames[$ra->roleid])) {
2596 $ra->rolename = $aliasnames[$ra->roleid]->name;
2599 $fullname = fullname($ra, $canviewfullnames);
2600 $namesarray[$ra->id] = format_string($ra->rolename).': '.
2601 html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
2604 if (!empty($namesarray)) {
2605 echo html_writer::start_tag('ul', array('class'=>'teachers'));
2606 foreach ($namesarray as $name) {
2607 echo html_writer::tag('li', $name);
2609 echo html_writer::end_tag('ul');
2612 echo html_writer::end_tag('div'); // End of info div
2614 echo html_writer::start_tag('div', array('class'=>'summary'));
2615 $options = new stdClass();
2616 $options->noclean = true;
2617 $options->para = false;
2618 $options->overflowdiv = true;
2619 if (!isset($course->summaryformat)) {
2620 $course->summaryformat = FORMAT_MOODLE;
2622 echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options, $course->id));
2623 if ($icons = enrol_get_course_info_icons($course)) {
2624 echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
2625 foreach ($icons as $icon) {
2626 $icon->attributes["alt"] .= ": ". format_string($coursename, true, array('context'=>$context));
2627 echo $OUTPUT->render($icon);
2629 echo html_writer::end_tag('div'); // End of enrolmenticons div
2631 echo html_writer::end_tag('div'); // End of summary div
2632 echo html_writer::end_tag('div'); // End of coursebox div
2636 * Prints custom user information on the home page.
2637 * Over time this can include all sorts of information
2639 function print_my_moodle() {
2640 global $USER, $CFG, $DB, $OUTPUT;
2642 if (!isloggedin() or isguestuser()) {
2643 print_error('nopermissions', '', '', 'See My Moodle');
2646 $courses = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
2647 $rhosts = array();
2648 $rcourses = array();
2649 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
2650 $rcourses = get_my_remotecourses($USER->id);
2651 $rhosts = get_my_remotehosts();
2654 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
2656 if (!empty($courses)) {
2657 echo '<ul class="unlist">';
2658 foreach ($courses as $course) {
2659 if ($course->id == SITEID) {
2660 continue;
2662 echo '<li>';
2663 print_course($course);
2664 echo "</li>\n";
2666 echo "</ul>\n";
2669 // MNET
2670 if (!empty($rcourses)) {
2671 // at the IDP, we know of all the remote courses
2672 foreach ($rcourses as $course) {
2673 print_remote_course($course, "100%");
2675 } elseif (!empty($rhosts)) {
2676 // non-IDP, we know of all the remote servers, but not courses
2677 foreach ($rhosts as $host) {
2678 print_remote_host($host, "100%");
2681 unset($course);
2682 unset($host);
2684 if ($DB->count_records("course") > (count($courses) + 1) ) { // Some courses not being displayed
2685 echo "<table width=\"100%\"><tr><td align=\"center\">";
2686 print_course_search("", false, "short");
2687 echo "</td><td align=\"center\">";
2688 echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
2689 echo "</td></tr></table>\n";
2692 } else {
2693 if ($DB->count_records("course_categories") > 1) {
2694 echo $OUTPUT->box_start("categorybox");
2695 print_whole_category_list();
2696 echo $OUTPUT->box_end();
2697 } else {
2698 print_courses(0);
2704 function print_course_search($value="", $return=false, $format="plain") {
2705 global $CFG;
2706 static $count = 0;
2708 $count++;
2710 $id = 'coursesearch';
2712 if ($count > 1) {
2713 $id .= $count;
2716 $strsearchcourses= get_string("searchcourses");
2718 if ($format == 'plain') {
2719 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2720 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2721 $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
2722 $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
2723 $output .= '<input type="submit" value="'.get_string('go').'" />';
2724 $output .= '</fieldset></form>';
2725 } else if ($format == 'short') {
2726 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2727 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2728 $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
2729 $output .= '<input type="text" id="shortsearchbox" size="12" name="search" value="'.s($value).'" />';
2730 $output .= '<input type="submit" value="'.get_string('go').'" />';
2731 $output .= '</fieldset></form>';
2732 } else if ($format == 'navbar') {
2733 $output = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2734 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2735 $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
2736 $output .= '<input type="text" id="navsearchbox" size="20" name="search" value="'.s($value).'" />';
2737 $output .= '<input type="submit" value="'.get_string('go').'" />';
2738 $output .= '</fieldset></form>';
2741 if ($return) {
2742 return $output;
2744 echo $output;
2747 function print_remote_course($course, $width="100%") {
2748 global $CFG, $USER;
2750 $linkcss = '';
2752 $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}";
2754 echo '<div class="coursebox remotecoursebox clearfix">';
2755 echo '<div class="info">';
2756 echo '<div class="name"><a title="'.get_string('entercourse').'"'.
2757 $linkcss.' href="'.$url.'">'
2758 . format_string($course->fullname) .'</a><br />'
2759 . format_string($course->hostname) . ' : '
2760 . format_string($course->cat_name) . ' : '
2761 . format_string($course->shortname). '</div>';
2762 echo '</div><div class="summary">';
2763 $options = new stdClass();
2764 $options->noclean = true;
2765 $options->para = false;
2766 $options->overflowdiv = true;
2767 echo format_text($course->summary, $course->summaryformat, $options);
2768 echo '</div>';
2769 echo '</div>';
2772 function print_remote_host($host, $width="100%") {
2773 global $OUTPUT;
2775 $linkcss = '';
2777 echo '<div class="coursebox clearfix">';
2778 echo '<div class="info">';
2779 echo '<div class="name">';
2780 echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
2781 echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
2782 . s($host['name']).'</a> - ';
2783 echo $host['count'] . ' ' . get_string('courses');
2784 echo '</div>';
2785 echo '</div>';
2786 echo '</div>';
2790 /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
2792 function add_course_module($mod) {
2793 global $DB;
2795 $mod->added = time();
2796 unset($mod->id);
2798 return $DB->insert_record("course_modules", $mod);
2802 * Returns course section - creates new if does not exist yet.
2803 * @param int $relative section number
2804 * @param int $courseid
2805 * @return object $course_section object
2807 function get_course_section($section, $courseid) {
2808 global $DB;
2810 if ($cw = $DB->get_record("course_sections", array("section"=>$section, "course"=>$courseid))) {
2811 return $cw;
2813 $cw = new stdClass();
2814 $cw->course = $courseid;
2815 $cw->section = $section;
2816 $cw->summary = "";
2817 $cw->summaryformat = FORMAT_HTML;
2818 $cw->sequence = "";
2819 $id = $DB->insert_record("course_sections", $cw);
2820 rebuild_course_cache($courseid, true);
2821 return $DB->get_record("course_sections", array("id"=>$id));
2825 * Given a full mod object with section and course already defined, adds this module to that section.
2827 * @param object $mod
2828 * @param int $beforemod An existing ID which we will insert the new module before
2829 * @return int The course_sections ID where the mod is inserted
2831 function add_mod_to_section($mod, $beforemod=NULL) {
2832 global $DB;
2834 if ($section = $DB->get_record("course_sections", array("course"=>$mod->course, "section"=>$mod->section))) {
2836 $section->sequence = trim($section->sequence);
2838 if (empty($section->sequence)) {
2839 $newsequence = "$mod->coursemodule";
2841 } else if ($beforemod) {
2842 $modarray = explode(",", $section->sequence);
2844 if ($key = array_keys($modarray, $beforemod->id)) {
2845 $insertarray = array($mod->id, $beforemod->id);
2846 array_splice($modarray, $key[0], 1, $insertarray);
2847 $newsequence = implode(",", $modarray);
2849 } else { // Just tack it on the end anyway
2850 $newsequence = "$section->sequence,$mod->coursemodule";
2853 } else {
2854 $newsequence = "$section->sequence,$mod->coursemodule";
2857 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2858 $DB->set_field("course_modules", "section", $section->id, array("id" => $mod->coursemodule));
2859 return $section->id; // Return course_sections ID that was used.
2861 } else { // Insert a new record
2862 $section = new stdClass();
2863 $section->course = $mod->course;
2864 $section->section = $mod->section;
2865 $section->summary = "";
2866 $section->summaryformat = FORMAT_HTML;
2867 $section->sequence = $mod->coursemodule;
2868 $section->id = $DB->insert_record("course_sections", $section);
2869 $DB->set_field("course_modules", "section", $section->id, array("id" => $mod->coursemodule));
2870 return $section->id;
2874 function set_coursemodule_groupmode($id, $groupmode) {
2875 global $DB;
2876 return $DB->set_field("course_modules", "groupmode", $groupmode, array("id"=>$id));
2879 function set_coursemodule_idnumber($id, $idnumber) {
2880 global $DB;
2881 return $DB->set_field("course_modules", "idnumber", $idnumber, array("id"=>$id));
2885 * $prevstateoverrides = true will set the visibility of the course module
2886 * to what is defined in visibleold. This enables us to remember the current
2887 * visibility when making a whole section hidden, so that when we toggle
2888 * that section back to visible, we are able to return the visibility of
2889 * the course module back to what it was originally.
2891 function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
2892 global $DB, $CFG;
2893 require_once($CFG->libdir.'/gradelib.php');
2895 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2896 return false;
2899 // Create events and propagate visibility to associated grade items if the value has changed.
2900 // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
2901 if ($cm->visible == $visible) {
2902 return true;
2905 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
2906 return false;
2908 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2909 foreach($events as $event) {
2910 if ($visible) {
2911 show_event($event);
2912 } else {
2913 hide_event($event);
2918 // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
2919 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
2920 if ($grade_items) {
2921 foreach ($grade_items as $grade_item) {
2922 $grade_item->set_hidden(!$visible);
2926 $cminfo = new stdClass();
2927 $cminfo->id = $id;
2928 $cminfo->visible = $visible;
2930 if ($prevstateoverrides) {
2931 // If we making whole section visiblility change..
2932 if ($visible == 0) {
2933 // Retain previous visibility state.
2934 $cminfo->visibleold = $cm->visible;
2935 } else {
2936 // Restore previous visibility state.
2937 $cminfo->visible = $cm->visibleold;
2939 } else {
2940 $cminfo->visibleold = $visible;
2942 return $DB->update_record('course_modules', $cminfo);
2946 * Delete a course module and any associated data at the course level (events)
2947 * Until 1.5 this function simply marked a deleted flag ... now it
2948 * deletes it completely.
2951 function delete_course_module($id) {
2952 global $CFG, $DB;
2953 require_once($CFG->libdir.'/gradelib.php');
2954 require_once($CFG->dirroot.'/blog/lib.php');
2956 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2957 return true;
2959 $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
2960 //delete events from calendar
2961 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2962 foreach($events as $event) {
2963 delete_event($event->id);
2966 //delete grade items, outcome items and grades attached to modules
2967 if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
2968 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
2969 foreach ($grade_items as $grade_item) {
2970 $grade_item->delete('moddelete');
2973 // Delete completion and availability data; it is better to do this even if the
2974 // features are not turned on, in case they were turned on previously (these will be
2975 // very quick on an empty table)
2976 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
2977 $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
2978 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
2979 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
2981 delete_context(CONTEXT_MODULE, $cm->id);
2982 return $DB->delete_records('course_modules', array('id'=>$cm->id));
2985 function delete_mod_from_section($mod, $section) {
2986 global $DB;
2988 if ($section = $DB->get_record("course_sections", array("id"=>$section)) ) {
2990 $modarray = explode(",", $section->sequence);
2992 if ($key = array_keys ($modarray, $mod)) {
2993 array_splice($modarray, $key[0], 1);
2994 $newsequence = implode(",", $modarray);
2995 return $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2996 } else {
2997 return false;
3001 return false;
3005 * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
3007 * @param object $course course object
3008 * @param int $section Section number (not id!!!)
3009 * @param int $move (-1 or 1)
3010 * @return boolean true if section moved successfully
3011 * @todo MDL-33379 remove this function in 2.5
3013 function move_section($course, $section, $move) {
3014 debugging('This function will be removed before 2.5 is released please use move_section_to', DEBUG_DEVELOPER);
3016 /// Moves a whole course section up and down within the course
3017 global $USER, $DB;
3019 if (!$move) {
3020 return true;
3023 $sectiondest = $section + $move;
3025 if ($sectiondest > $course->numsections or $sectiondest < 1) {
3026 return false;
3029 $retval = move_section_to($course, $section, $sectiondest);
3030 // If section moved, then rebuild course cache.
3031 if ($retval) {
3032 rebuild_course_cache($course->id, true);
3034 return $retval;
3038 * Moves a section within a course, from a position to another.
3039 * Be very careful: $section and $destination refer to section number,
3040 * not id!.
3042 * @param object $course
3043 * @param int $section Section number (not id!!!)
3044 * @param int $destination
3045 * @return boolean Result
3047 function move_section_to($course, $section, $destination) {
3048 /// Moves a whole course section up and down within the course
3049 global $USER, $DB;
3051 if (!$destination && $destination != 0) {
3052 return true;
3055 if (($destination > $course->numsections) || ($destination < 1)) {
3056 return false;
3059 // Get all sections for this course and re-order them (2 of them should now share the same section number)
3060 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
3061 'section ASC, id ASC', 'id, section')) {
3062 return false;
3065 $movedsections = reorder_sections($sections, $section, $destination);
3067 // Update all sections. Do this in 2 steps to avoid breaking database
3068 // uniqueness constraint
3069 $transaction = $DB->start_delegated_transaction();
3070 foreach ($movedsections as $id => $position) {
3071 if ($sections[$id] !== $position) {
3072 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
3075 foreach ($movedsections as $id => $position) {
3076 if ($sections[$id] !== $position) {
3077 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
3081 // If we move the highlighted section itself, then just highlight the destination.
3082 // Adjust the higlighted section location if we move something over it either direction.
3083 if ($section == $course->marker) {
3084 course_set_marker($course->id, $destination);
3085 } elseif ($section > $course->marker && $course->marker >= $destination) {
3086 course_set_marker($course->id, $course->marker+1);
3087 } elseif ($section < $course->marker && $course->marker <= $destination) {
3088 course_set_marker($course->id, $course->marker-1);
3091 $transaction->allow_commit();
3092 return true;
3096 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
3097 * an original position number and a target position number, rebuilds the array so that the
3098 * move is made without any duplication of section positions.
3099 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
3100 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
3102 * @param array $sections
3103 * @param int $origin_position
3104 * @param int $target_position
3105 * @return array
3107 function reorder_sections($sections, $origin_position, $target_position) {
3108 if (!is_array($sections)) {
3109 return false;
3112 // We can't move section position 0
3113 if ($origin_position < 1) {
3114 echo "We can't move section position 0";
3115 return false;
3118 // Locate origin section in sections array
3119 if (!$origin_key = array_search($origin_position, $sections)) {
3120 echo "searched position not in sections array";
3121 return false; // searched position not in sections array
3124 // Extract origin section
3125 $origin_section = $sections[$origin_key];
3126 unset($sections[$origin_key]);
3128 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
3129 $found = false;
3130 $append_array = array();
3131 foreach ($sections as $id => $position) {
3132 if ($found) {
3133 $append_array[$id] = $position;
3134 unset($sections[$id]);
3136 if ($position == $target_position) {
3137 if ($target_position < $origin_position) {
3138 $append_array[$id] = $position;
3139 unset($sections[$id]);
3141 $found = true;
3145 // Append moved section
3146 $sections[$origin_key] = $origin_section;
3148 // Append rest of array (if applicable)
3149 if (!empty($append_array)) {
3150 foreach ($append_array as $id => $position) {
3151 $sections[$id] = $position;
3155 // Renumber positions
3156 $position = 0;
3157 foreach ($sections as $id => $p) {
3158 $sections[$id] = $position;
3159 $position++;
3162 return $sections;
3167 * Move the module object $mod to the specified $section
3168 * If $beforemod exists then that is the module
3169 * before which $modid should be inserted
3170 * All parameters are objects
3172 function moveto_module($mod, $section, $beforemod=NULL) {
3173 global $DB, $OUTPUT;
3175 /// Remove original module from original section
3176 if (! delete_mod_from_section($mod->id, $mod->section)) {
3177 echo $OUTPUT->notification("Could not delete module from existing section");
3180 /// Update module itself if necessary
3182 // If moving to a hidden section then hide module.
3183 if ($mod->section != $section->id) {
3184 if (!$section->visible && $mod->visible) {
3185 // Set this in the object because it is sent as a response to ajax calls.
3186 set_coursemodule_visible($mod->id, 0, true);
3187 $mod->visible = 0;
3189 if ($section->visible && !$mod->visible) {
3190 set_coursemodule_visible($mod->id, 1, true);
3191 // Set this in the object because it is sent as a response to ajax calls.
3192 $mod->visible = $mod->visibleold;
3196 /// Add the module into the new section
3198 $mod->course = $section->course;
3199 $mod->section = $section->section; // need relative reference
3200 $mod->coursemodule = $mod->id;
3202 if (! add_mod_to_section($mod, $beforemod)) {
3203 return false;
3206 return true;
3210 * Produces the editing buttons for a module
3212 * @global core_renderer $OUTPUT
3213 * @staticvar type $str
3214 * @param stdClass $mod The module to produce editing buttons for
3215 * @param bool $absolute_ignored ignored - all links are absolute
3216 * @param bool $moveselect If true a move seleciton process is used (default true)
3217 * @param int $indent The current indenting
3218 * @param int $section The section to link back to
3219 * @return string XHTML for the editing buttons
3221 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=null) {
3222 global $CFG, $OUTPUT, $COURSE;
3224 static $str;
3226 $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
3227 $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
3229 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
3230 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
3232 // no permission to edit anything
3233 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
3234 return false;
3237 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
3239 if (!isset($str)) {
3240 $str = new stdClass;
3241 $str->assign = get_string("assignroles", 'role');
3242 $str->delete = get_string("delete");
3243 $str->move = get_string("move");
3244 $str->moveup = get_string("moveup");
3245 $str->movedown = get_string("movedown");
3246 $str->moveright = get_string("moveright");
3247 $str->moveleft = get_string("moveleft");
3248 $str->update = get_string("update");
3249 $str->duplicate = get_string("duplicate");
3250 $str->hide = get_string("hide");
3251 $str->show = get_string("show");
3252 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
3253 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
3254 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
3255 $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
3256 $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
3257 $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
3258 $str->edittitle = get_string('edittitle', 'moodle');
3261 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
3263 if ($section !== null) {
3264 $baseurl->param('sr', $section);
3266 $actions = array();
3268 // AJAX edit title
3269 if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
3270 $actions[] = new action_link(
3271 new moodle_url($baseurl, array('update' => $mod->id)),
3272 new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
3273 null,
3274 array('class' => 'editing_title', 'title' => $str->edittitle)
3278 // leftright
3279 if ($hasmanageactivities) {
3280 if (right_to_left()) { // Exchange arrows on RTL
3281 $rightarrow = 't/left';
3282 $leftarrow = 't/right';
3283 } else {
3284 $rightarrow = 't/right';
3285 $leftarrow = 't/left';
3288 if ($indent > 0) {
3289 $actions[] = new action_link(
3290 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
3291 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3292 null,
3293 array('class' => 'editing_moveleft', 'title' => $str->moveleft)
3296 if ($indent >= 0) {
3297 $actions[] = new action_link(
3298 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
3299 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3300 null,
3301 array('class' => 'editing_moveright', 'title' => $str->moveright)
3306 // move
3307 if ($hasmanageactivities) {
3308 if ($moveselect) {
3309 $actions[] = new action_link(
3310 new moodle_url($baseurl, array('copy' => $mod->id)),
3311 new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3312 null,
3313 array('class' => 'editing_move', 'title' => $str->move)
3315 } else {
3316 $actions[] = new action_link(
3317 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
3318 new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3319 null,
3320 array('class' => 'editing_moveup', 'title' => $str->moveup)
3322 $actions[] = new action_link(
3323 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
3324 new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3325 null,
3326 array('class' => 'editing_movedown', 'title' => $str->movedown)
3331 // Update
3332 if ($hasmanageactivities) {
3333 $actions[] = new action_link(
3334 new moodle_url($baseurl, array('update' => $mod->id)),
3335 new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3336 null,
3337 array('class' => 'editing_update', 'title' => $str->update)
3341 // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
3342 if (has_all_capabilities($dupecaps, $coursecontext) && plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
3343 $actions[] = new action_link(
3344 new moodle_url($baseurl, array('duplicate' => $mod->id)),
3345 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3346 null,
3347 array('class' => 'editing_duplicate', 'title' => $str->duplicate)
3351 // Delete
3352 if ($hasmanageactivities) {
3353 $actions[] = new action_link(
3354 new moodle_url($baseurl, array('delete' => $mod->id)),
3355 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3356 null,
3357 array('class' => 'editing_delete', 'title' => $str->delete)
3361 // hideshow
3362 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
3363 if ($mod->visible) {
3364 $actions[] = new action_link(
3365 new moodle_url($baseurl, array('hide' => $mod->id)),
3366 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3367 null,
3368 array('class' => 'editing_hide', 'title' => $str->hide)
3370 } else {
3371 $actions[] = new action_link(
3372 new moodle_url($baseurl, array('show' => $mod->id)),
3373 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3374 null,
3375 array('class' => 'editing_show', 'title' => $str->show)
3380 // groupmode
3381 if ($hasmanageactivities and $mod->groupmode !== false) {
3382 if ($mod->groupmode == SEPARATEGROUPS) {
3383 $groupmode = 0;
3384 $grouptitle = $str->groupsseparate;
3385 $forcedgrouptitle = $str->forcedgroupsseparate;
3386 $groupclass = 'editing_groupsseparate';
3387 $groupimage = 't/groups';
3388 } else if ($mod->groupmode == VISIBLEGROUPS) {
3389 $groupmode = 1;
3390 $grouptitle = $str->groupsvisible;
3391 $forcedgrouptitle = $str->forcedgroupsvisible;
3392 $groupclass = 'editing_groupsvisible';
3393 $groupimage = 't/groupv';
3394 } else {
3395 $groupmode = 2;
3396 $grouptitle = $str->groupsnone;
3397 $forcedgrouptitle = $str->forcedgroupsnone;
3398 $groupclass = 'editing_groupsnone';
3399 $groupimage = 't/groupn';
3401 if ($mod->groupmodelink) {
3402 $actions[] = new action_link(
3403 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
3404 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3405 null,
3406 array('class' => $groupclass, 'title' => $grouptitle)
3408 } else {
3409 $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
3413 // Assign
3414 if (has_capability('moodle/role:assign', $modcontext)){
3415 $actions[] = new action_link(
3416 new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
3417 new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
3418 null,
3419 array('class' => 'editing_assign', 'title' => $str->assign)
3423 $output = html_writer::start_tag('span', array('class' => 'commands'));
3424 foreach ($actions as $action) {
3425 if ($action instanceof renderable) {
3426 $output .= $OUTPUT->render($action);
3427 } else {
3428 $output .= $action;
3431 $output .= html_writer::end_tag('span');
3432 return $output;
3436 * given a course object with shortname & fullname, this function will
3437 * truncate the the number of chars allowed and add ... if it was too long
3439 function course_format_name ($course,$max=100) {
3441 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3442 $shortname = format_string($course->shortname, true, array('context' => $context));
3443 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
3444 $str = $shortname.': '. $fullname;
3445 if (textlib::strlen($str) <= $max) {
3446 return $str;
3448 else {
3449 return textlib::substr($str,0,$max-3).'...';
3454 * Is the user allowed to add this type of module to this course?
3455 * @param object $course the course settings. Only $course->id is used.
3456 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
3457 * @return bool whether the current user is allowed to add this type of module to this course.
3459 function course_allowed_module($course, $modname) {
3460 global $DB;
3462 if (is_numeric($modname)) {
3463 throw new coding_exception('Function course_allowed_module no longer
3464 supports numeric module ids. Please update your code to pass the module name.');
3467 $capability = 'mod/' . $modname . ':addinstance';
3468 if (!get_capability_info($capability)) {
3469 // Debug warning that the capability does not exist, but no more than once per page.
3470 static $warned = array();
3471 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
3472 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
3473 debugging('The module ' . $modname . ' does not define the standard capability ' .
3474 $capability , DEBUG_DEVELOPER);
3475 $warned[$modname] = 1;
3478 // If the capability does not exist, the module can always be added.
3479 return true;
3482 $coursecontext = context_course::instance($course->id);
3483 return has_capability($capability, $coursecontext);
3487 * Recursively delete category including all subcategories and courses.
3488 * @param stdClass $category
3489 * @param boolean $showfeedback display some notices
3490 * @return array return deleted courses
3492 function category_delete_full($category, $showfeedback=true) {
3493 global $CFG, $DB;
3494 require_once($CFG->libdir.'/gradelib.php');
3495 require_once($CFG->libdir.'/questionlib.php');
3496 require_once($CFG->dirroot.'/cohort/lib.php');
3498 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3499 foreach ($children as $childcat) {
3500 category_delete_full($childcat, $showfeedback);
3504 $deletedcourses = array();
3505 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
3506 foreach ($courses as $course) {
3507 if (!delete_course($course, false)) {
3508 throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
3510 $deletedcourses[] = $course;
3514 // move or delete cohorts in this context
3515 cohort_delete_category($category);
3517 // now delete anything that may depend on course category context
3518 grade_course_category_delete($category->id, 0, $showfeedback);
3519 if (!question_delete_course_category($category, 0, $showfeedback)) {
3520 throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
3523 // finally delete the category and it's context
3524 $DB->delete_records('course_categories', array('id'=>$category->id));
3525 delete_context(CONTEXT_COURSECAT, $category->id);
3526 add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
3528 events_trigger('course_category_deleted', $category);
3530 return $deletedcourses;
3534 * Delete category, but move contents to another category.
3535 * @param object $ccategory
3536 * @param int $newparentid category id
3537 * @return bool status
3539 function category_delete_move($category, $newparentid, $showfeedback=true) {
3540 global $CFG, $DB, $OUTPUT;
3541 require_once($CFG->libdir.'/gradelib.php');
3542 require_once($CFG->libdir.'/questionlib.php');
3543 require_once($CFG->dirroot.'/cohort/lib.php');
3545 if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
3546 return false;
3549 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3550 foreach ($children as $childcat) {
3551 move_category($childcat, $newparentcat);
3555 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
3556 if (!move_courses(array_keys($courses), $newparentid)) {
3557 if ($showfeedback) {
3558 echo $OUTPUT->notification("Error moving courses");
3560 return false;
3562 if ($showfeedback) {
3563 echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
3567 // move or delete cohorts in this context
3568 cohort_delete_category($category);
3570 // now delete anything that may depend on course category context
3571 grade_course_category_delete($category->id, $newparentid, $showfeedback);
3572 if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
3573 if ($showfeedback) {
3574 echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
3576 return false;
3579 // finally delete the category and it's context
3580 $DB->delete_records('course_categories', array('id'=>$category->id));
3581 delete_context(CONTEXT_COURSECAT, $category->id);
3582 add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
3584 events_trigger('course_category_deleted', $category);
3586 if ($showfeedback) {
3587 echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
3589 return true;
3593 * Efficiently moves many courses around while maintaining
3594 * sortorder in order.
3596 * @param array $courseids is an array of course ids
3597 * @param int $categoryid
3598 * @return bool success
3600 function move_courses($courseids, $categoryid) {
3601 global $CFG, $DB, $OUTPUT;
3603 if (empty($courseids)) {
3604 // nothing to do
3605 return;
3608 if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
3609 return false;
3612 $courseids = array_reverse($courseids);
3613 $newparent = get_context_instance(CONTEXT_COURSECAT, $category->id);
3614 $i = 1;
3616 foreach ($courseids as $courseid) {
3617 if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
3618 $course = new stdClass();
3619 $course->id = $courseid;
3620 $course->category = $category->id;
3621 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
3622 if ($category->visible == 0) {
3623 // hide the course when moving into hidden category,
3624 // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
3625 $course->visible = 0;
3628 $DB->update_record('course', $course);
3629 add_to_log($course->id, "course", "move", "edit.php?id=$course->id", $course->id);
3631 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3632 context_moved($context, $newparent);
3635 fix_course_sortorder();
3637 return true;
3641 * Hide course category and child course and subcategories
3642 * @param stdClass $category
3643 * @return void
3645 function course_category_hide($category) {
3646 global $DB;
3648 $category->visible = 0;
3649 $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
3650 $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
3651 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($category->id)); // store visible flag so that we can return to it if we immediately unhide
3652 $DB->set_field('course', 'visible', 0, array('category' => $category->id));
3653 // get all child categories and hide too
3654 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3655 foreach ($subcats as $cat) {
3656 $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
3657 $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
3658 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
3659 $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
3662 add_to_log(SITEID, "category", "hide", "editcategory.php?id=$category->id", $category->id);
3666 * Show course category and child course and subcategories
3667 * @param stdClass $category
3668 * @return void
3670 function course_category_show($category) {
3671 global $DB;
3673 $category->visible = 1;
3674 $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
3675 $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
3676 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
3677 // get all child categories and unhide too
3678 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3679 foreach ($subcats as $cat) {
3680 if ($cat->visibleold) {
3681 $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
3683 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
3686 add_to_log(SITEID, "category", "show", "editcategory.php?id=$category->id", $category->id);
3690 * Efficiently moves a category - NOTE that this can have
3691 * a huge impact access-control-wise...
3693 function move_category($category, $newparentcat) {
3694 global $CFG, $DB;
3696 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
3698 $hidecat = false;
3699 if (empty($newparentcat->id)) {
3700 $DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id));
3702 $newparent = get_context_instance(CONTEXT_SYSTEM);
3704 } else {
3705 $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id));
3706 $newparent = get_context_instance(CONTEXT_COURSECAT, $newparentcat->id);
3708 if (!$newparentcat->visible and $category->visible) {
3709 // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
3710 $hidecat = true;
3714 context_moved($context, $newparent);
3716 // now make it last in new category
3717 $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
3719 // Log action.
3720 add_to_log(SITEID, "category", "move", "editcategory.php?id=$category->id", $category->id);
3722 // and fix the sortorders
3723 fix_course_sortorder();
3725 if ($hidecat) {
3726 course_category_hide($category);
3731 * Returns the display name of the given section that the course prefers.
3733 * This function utilizes a callback that can be implemented within the course
3734 * formats lib.php file to customize the display name that is used to reference
3735 * the section.
3737 * By default (if callback is not defined) the method
3738 * {@see get_numeric_section_name} is called instead.
3740 * @param stdClass $course The course to get the section name for
3741 * @param stdClass $section Section object from database
3742 * @return Display name that the course format prefers, e.g. "Week 2"
3744 * @see get_generic_section_name
3746 function get_section_name(stdClass $course, stdClass $section) {
3747 global $CFG;
3749 /// Inelegant hack for bug 3408
3750 if ($course->format == 'site') {
3751 return get_string('site');
3754 // Use course formatter callback if it exists
3755 $namingfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
3756 $namingfunction = 'callback_'.$course->format.'_get_section_name';
3757 if (!function_exists($namingfunction) && file_exists($namingfile)) {
3758 require_once $namingfile;
3760 if (function_exists($namingfunction)) {
3761 return $namingfunction($course, $section);
3764 // else, default behavior:
3765 return get_generic_section_name($course->format, $section);
3769 * Gets the generic section name for a courses section.
3771 * @param string $format Course format ID e.g. 'weeks' $course->format
3772 * @param stdClass $section Section object from database
3773 * @return Display name that the course format prefers, e.g. "Week 2"
3775 function get_generic_section_name($format, stdClass $section) {
3776 return get_string('sectionname', "format_$format") . ' ' . $section->section;
3780 function course_format_uses_sections($format) {
3781 global $CFG;
3783 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3784 $featurefunction = 'callback_'.$format.'_uses_sections';
3785 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3786 require_once $featurefile;
3788 if (function_exists($featurefunction)) {
3789 return $featurefunction();
3792 return false;
3796 * Returns the information about the ajax support in the given source format
3798 * The returned object's property (boolean)capable indicates that
3799 * the course format supports Moodle course ajax features.
3800 * The property (array)testedbrowsers can be used as a parameter for {@see ajaxenabled()}.
3802 * @param string $format
3803 * @return stdClass
3805 function course_format_ajax_support($format) {
3806 global $CFG;
3808 // set up default values
3809 $ajaxsupport = new stdClass();
3810 $ajaxsupport->capable = false;
3811 $ajaxsupport->testedbrowsers = array();
3813 // get the information from the course format library
3814 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3815 $featurefunction = 'callback_'.$format.'_ajax_support';
3816 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3817 require_once $featurefile;
3819 if (function_exists($featurefunction)) {
3820 $formatsupport = $featurefunction();
3821 if (isset($formatsupport->capable)) {
3822 $ajaxsupport->capable = $formatsupport->capable;
3824 if (is_array($formatsupport->testedbrowsers)) {
3825 $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers;
3829 return $ajaxsupport;
3833 * Can the current user delete this course?
3834 * Course creators have exception,
3835 * 1 day after the creation they can sill delete the course.
3836 * @param int $courseid
3837 * @return boolean
3839 function can_delete_course($courseid) {
3840 global $USER, $DB;
3842 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3844 if (has_capability('moodle/course:delete', $context)) {
3845 return true;
3848 // hack: now try to find out if creator created this course recently (1 day)
3849 if (!has_capability('moodle/course:create', $context)) {
3850 return false;
3853 $since = time() - 60*60*24;
3855 $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
3856 $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
3858 return $DB->record_exists_select('log', $select, $params);
3862 * Save the Your name for 'Some role' strings.
3864 * @param integer $courseid the id of this course.
3865 * @param array $data the data that came from the course settings form.
3867 function save_local_role_names($courseid, $data) {
3868 global $DB;
3869 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3871 foreach ($data as $fieldname => $value) {
3872 if (strpos($fieldname, 'role_') !== 0) {
3873 continue;
3875 list($ignored, $roleid) = explode('_', $fieldname);
3877 // make up our mind whether we want to delete, update or insert
3878 if (!$value) {
3879 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
3881 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
3882 $rolename->name = $value;
3883 $DB->update_record('role_names', $rolename);
3885 } else {
3886 $rolename = new stdClass;
3887 $rolename->contextid = $context->id;
3888 $rolename->roleid = $roleid;
3889 $rolename->name = $value;
3890 $DB->insert_record('role_names', $rolename);
3896 * Create a course and either return a $course object
3898 * Please note this functions does not verify any access control,
3899 * the calling code is responsible for all validation (usually it is the form definition).
3901 * @param array $editoroptions course description editor options
3902 * @param object $data - all the data needed for an entry in the 'course' table
3903 * @return object new course instance
3905 function create_course($data, $editoroptions = NULL) {
3906 global $CFG, $DB;
3908 //check the categoryid - must be given for all new courses
3909 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
3911 //check if the shortname already exist
3912 if (!empty($data->shortname)) {
3913 if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
3914 throw new moodle_exception('shortnametaken');
3918 //check if the id number already exist
3919 if (!empty($data->idnumber)) {
3920 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
3921 throw new moodle_exception('idnumbertaken');
3925 $data->timecreated = time();
3926 $data->timemodified = $data->timecreated;
3928 // place at beginning of any category
3929 $data->sortorder = 0;
3931 if ($editoroptions) {
3932 // summary text is updated later, we need context to store the files first
3933 $data->summary = '';
3934 $data->summary_format = FORMAT_HTML;
3937 if (!isset($data->visible)) {
3938 // data not from form, add missing visibility info
3939 $data->visible = $category->visible;
3941 $data->visibleold = $data->visible;
3943 $newcourseid = $DB->insert_record('course', $data);
3944 $context = get_context_instance(CONTEXT_COURSE, $newcourseid, MUST_EXIST);
3946 if ($editoroptions) {
3947 // Save the files used in the summary editor and store
3948 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3949 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
3950 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
3953 $course = $DB->get_record('course', array('id'=>$newcourseid));
3955 // Setup the blocks
3956 blocks_add_default_course_blocks($course);
3958 $section = new stdClass();
3959 $section->course = $course->id; // Create a default section.
3960 $section->section = 0;
3961 $section->summaryformat = FORMAT_HTML;
3962 $DB->insert_record('course_sections', $section);
3964 fix_course_sortorder();
3966 // new context created - better mark it as dirty
3967 mark_context_dirty($context->path);
3969 // Save any custom role names.
3970 save_local_role_names($course->id, (array)$data);
3972 // set up enrolments
3973 enrol_course_updated(true, $course, $data);
3975 add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
3977 // Trigger events
3978 events_trigger('course_created', $course);
3980 return $course;
3984 * Create a new course category and marks the context as dirty
3986 * This function does not set the sortorder for the new category and
3987 * @see{fix_course_sortorder} should be called after creating a new course
3988 * category
3990 * Please note that this function does not verify access control.
3992 * @param object $category All of the data required for an entry in the course_categories table
3993 * @return object new course category
3995 function create_course_category($category) {
3996 global $DB;
3998 $category->timemodified = time();
3999 $category->id = $DB->insert_record('course_categories', $category);
4000 $category = $DB->get_record('course_categories', array('id' => $category->id));
4002 // We should mark the context as dirty
4003 $category->context = context_coursecat::instance($category->id);
4004 $category->context->mark_dirty();
4006 return $category;
4010 * Update a course.
4012 * Please note this functions does not verify any access control,
4013 * the calling code is responsible for all validation (usually it is the form definition).
4015 * @param object $data - all the data needed for an entry in the 'course' table
4016 * @param array $editoroptions course description editor options
4017 * @return void
4019 function update_course($data, $editoroptions = NULL) {
4020 global $CFG, $DB;
4022 $data->timemodified = time();
4024 $oldcourse = $DB->get_record('course', array('id'=>$data->id), '*', MUST_EXIST);
4025 $context = get_context_instance(CONTEXT_COURSE, $oldcourse->id);
4027 if ($editoroptions) {
4028 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
4031 if (!isset($data->category) or empty($data->category)) {
4032 // prevent nulls and 0 in category field
4033 unset($data->category);
4035 $movecat = (isset($data->category) and $oldcourse->category != $data->category);
4037 if (!isset($data->visible)) {
4038 // data not from form, add missing visibility info
4039 $data->visible = $oldcourse->visible;
4042 if ($data->visible != $oldcourse->visible) {
4043 // reset the visibleold flag when manually hiding/unhiding course
4044 $data->visibleold = $data->visible;
4045 } else {
4046 if ($movecat) {
4047 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
4048 if (empty($newcategory->visible)) {
4049 // make sure when moving into hidden category the course is hidden automatically
4050 $data->visible = 0;
4055 // Update with the new data
4056 $DB->update_record('course', $data);
4058 $course = $DB->get_record('course', array('id'=>$data->id));
4060 if ($movecat) {
4061 $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category);
4062 context_moved($context, $newparent);
4065 fix_course_sortorder();
4067 // Test for and remove blocks which aren't appropriate anymore
4068 blocks_remove_inappropriate($course);
4070 // Save any custom role names.
4071 save_local_role_names($course->id, $data);
4073 // update enrol settings
4074 enrol_course_updated(false, $course, $data);
4076 add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
4078 // Trigger events
4079 events_trigger('course_updated', $course);
4083 * Average number of participants
4084 * @return integer
4086 function average_number_of_participants() {
4087 global $DB, $SITE;
4089 //count total of enrolments for visible course (except front page)
4090 $sql = 'SELECT COUNT(*) FROM (
4091 SELECT DISTINCT ue.userid, e.courseid
4092 FROM {user_enrolments} ue, {enrol} e, {course} c
4093 WHERE ue.enrolid = e.id
4094 AND e.courseid <> :siteid
4095 AND c.id = e.courseid
4096 AND c.visible = 1) total';
4097 $params = array('siteid' => $SITE->id);
4098 $enrolmenttotal = $DB->count_records_sql($sql, $params);
4101 //count total of visible courses (minus front page)
4102 $coursetotal = $DB->count_records('course', array('visible' => 1));
4103 $coursetotal = $coursetotal - 1 ;
4105 //average of enrolment
4106 if (empty($coursetotal)) {
4107 $participantaverage = 0;
4108 } else {
4109 $participantaverage = $enrolmenttotal / $coursetotal;
4112 return $participantaverage;
4116 * Average number of course modules
4117 * @return integer
4119 function average_number_of_courses_modules() {
4120 global $DB, $SITE;
4122 //count total of visible course module (except front page)
4123 $sql = 'SELECT COUNT(*) FROM (
4124 SELECT cm.course, cm.module
4125 FROM {course} c, {course_modules} cm
4126 WHERE c.id = cm.course
4127 AND c.id <> :siteid
4128 AND cm.visible = 1
4129 AND c.visible = 1) total';
4130 $params = array('siteid' => $SITE->id);
4131 $moduletotal = $DB->count_records_sql($sql, $params);
4134 //count total of visible courses (minus front page)
4135 $coursetotal = $DB->count_records('course', array('visible' => 1));
4136 $coursetotal = $coursetotal - 1 ;
4138 //average of course module
4139 if (empty($coursetotal)) {
4140 $coursemoduleaverage = 0;
4141 } else {
4142 $coursemoduleaverage = $moduletotal / $coursetotal;
4145 return $coursemoduleaverage;
4149 * This class pertains to course requests and contains methods associated with
4150 * create, approving, and removing course requests.
4152 * Please note we do not allow embedded images here because there is no context
4153 * to store them with proper access control.
4155 * @copyright 2009 Sam Hemelryk
4156 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4157 * @since Moodle 2.0
4159 * @property-read int $id
4160 * @property-read string $fullname
4161 * @property-read string $shortname
4162 * @property-read string $summary
4163 * @property-read int $summaryformat
4164 * @property-read int $summarytrust
4165 * @property-read string $reason
4166 * @property-read int $requester
4168 class course_request {
4171 * This is the stdClass that stores the properties for the course request
4172 * and is externally accessed through the __get magic method
4173 * @var stdClass
4175 protected $properties;
4178 * An array of options for the summary editor used by course request forms.
4179 * This is initially set by {@link summary_editor_options()}
4180 * @var array
4181 * @static
4183 protected static $summaryeditoroptions;
4186 * Static function to prepare the summary editor for working with a course
4187 * request.
4189 * @static
4190 * @param null|stdClass $data Optional, an object containing the default values
4191 * for the form, these may be modified when preparing the
4192 * editor so this should be called before creating the form
4193 * @return stdClass An object that can be used to set the default values for
4194 * an mforms form
4196 public static function prepare($data=null) {
4197 if ($data === null) {
4198 $data = new stdClass;
4200 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
4201 return $data;
4205 * Static function to create a new course request when passed an array of properties
4206 * for it.
4208 * This function also handles saving any files that may have been used in the editor
4210 * @static
4211 * @param stdClass $data
4212 * @return course_request The newly created course request
4214 public static function create($data) {
4215 global $USER, $DB, $CFG;
4216 $data->requester = $USER->id;
4218 // Summary is a required field so copy the text over
4219 $data->summary = $data->summary_editor['text'];
4220 $data->summaryformat = $data->summary_editor['format'];
4222 $data->id = $DB->insert_record('course_request', $data);
4224 // Create a new course_request object and return it
4225 $request = new course_request($data);
4227 // Notify the admin if required.
4228 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
4230 $a = new stdClass;
4231 $a->link = "$CFG->wwwroot/course/pending.php";
4232 $a->user = fullname($USER);
4233 $subject = get_string('courserequest');
4234 $message = get_string('courserequestnotifyemail', 'admin', $a);
4235 foreach ($users as $user) {
4236 $request->notify($user, $USER, 'courserequested', $subject, $message);
4240 return $request;
4244 * Returns an array of options to use with a summary editor
4246 * @uses course_request::$summaryeditoroptions
4247 * @return array An array of options to use with the editor
4249 public static function summary_editor_options() {
4250 global $CFG;
4251 if (self::$summaryeditoroptions === null) {
4252 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
4254 return self::$summaryeditoroptions;
4258 * Loads the properties for this course request object. Id is required and if
4259 * only id is provided then we load the rest of the properties from the database
4261 * @param stdClass|int $properties Either an object containing properties
4262 * or the course_request id to load
4264 public function __construct($properties) {
4265 global $DB;
4266 if (empty($properties->id)) {
4267 if (empty($properties)) {
4268 throw new coding_exception('You must provide a course request id when creating a course_request object');
4270 $id = $properties;
4271 $properties = new stdClass;
4272 $properties->id = (int)$id;
4273 unset($id);
4275 if (empty($properties->requester)) {
4276 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
4277 print_error('unknowncourserequest');
4279 } else {
4280 $this->properties = $properties;
4282 $this->properties->collision = null;
4286 * Returns the requested property
4288 * @param string $key
4289 * @return mixed
4291 public function __get($key) {
4292 return $this->properties->$key;
4296 * Override this to ensure empty($request->blah) calls return a reliable answer...
4298 * This is required because we define the __get method
4300 * @param mixed $key
4301 * @return bool True is it not empty, false otherwise
4303 public function __isset($key) {
4304 return (!empty($this->properties->$key));
4308 * Returns the user who requested this course
4310 * Uses a static var to cache the results and cut down the number of db queries
4312 * @staticvar array $requesters An array of cached users
4313 * @return stdClass The user who requested the course
4315 public function get_requester() {
4316 global $DB;
4317 static $requesters= array();
4318 if (!array_key_exists($this->properties->requester, $requesters)) {
4319 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
4321 return $requesters[$this->properties->requester];
4325 * Checks that the shortname used by the course does not conflict with any other
4326 * courses that exist
4328 * @param string|null $shortnamemark The string to append to the requests shortname
4329 * should a conflict be found
4330 * @return bool true is there is a conflict, false otherwise
4332 public function check_shortname_collision($shortnamemark = '[*]') {
4333 global $DB;
4335 if ($this->properties->collision !== null) {
4336 return $this->properties->collision;
4339 if (empty($this->properties->shortname)) {
4340 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
4341 $this->properties->collision = false;
4342 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
4343 if (!empty($shortnamemark)) {
4344 $this->properties->shortname .= ' '.$shortnamemark;
4346 $this->properties->collision = true;
4347 } else {
4348 $this->properties->collision = false;
4350 return $this->properties->collision;
4354 * This function approves the request turning it into a course
4356 * This function converts the course request into a course, at the same time
4357 * transferring any files used in the summary to the new course and then removing
4358 * the course request and the files associated with it.
4360 * @return int The id of the course that was created from this request
4362 public function approve() {
4363 global $CFG, $DB, $USER;
4365 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
4367 $category = get_course_category($CFG->defaultrequestcategory);
4368 $courseconfig = get_config('moodlecourse');
4370 // Transfer appropriate settings
4371 $data = clone($this->properties);
4372 unset($data->id);
4373 unset($data->reason);
4374 unset($data->requester);
4376 // Set category
4377 $data->category = $category->id;
4378 $data->sortorder = $category->sortorder; // place as the first in category
4380 // Set misc settings
4381 $data->requested = 1;
4383 // Apply course default settings
4384 $data->format = $courseconfig->format;
4385 $data->numsections = $courseconfig->numsections;
4386 $data->hiddensections = $courseconfig->hiddensections;
4387 $data->newsitems = $courseconfig->newsitems;
4388 $data->showgrades = $courseconfig->showgrades;
4389 $data->showreports = $courseconfig->showreports;
4390 $data->maxbytes = $courseconfig->maxbytes;
4391 $data->groupmode = $courseconfig->groupmode;
4392 $data->groupmodeforce = $courseconfig->groupmodeforce;
4393 $data->visible = $courseconfig->visible;
4394 $data->visibleold = $data->visible;
4395 $data->lang = $courseconfig->lang;
4397 $course = create_course($data);
4398 $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
4400 // add enrol instances
4401 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
4402 if ($manual = enrol_get_plugin('manual')) {
4403 $manual->add_default_instance($course);
4407 // enrol the requester as teacher if necessary
4408 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
4409 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
4412 $this->delete();
4414 $a = new stdClass();
4415 $a->name = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
4416 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
4417 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
4419 return $course->id;
4423 * Reject a course request
4425 * This function rejects a course request, emailing the requesting user the
4426 * provided notice and then removing the request from the database
4428 * @param string $notice The message to display to the user
4430 public function reject($notice) {
4431 global $USER, $DB;
4432 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
4433 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
4434 $this->delete();
4438 * Deletes the course request and any associated files
4440 public function delete() {
4441 global $DB;
4442 $DB->delete_records('course_request', array('id' => $this->properties->id));
4446 * Send a message from one user to another using events_trigger
4448 * @param object $touser
4449 * @param object $fromuser
4450 * @param string $name
4451 * @param string $subject
4452 * @param string $message
4454 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
4455 $eventdata = new stdClass();
4456 $eventdata->component = 'moodle';
4457 $eventdata->name = $name;
4458 $eventdata->userfrom = $fromuser;
4459 $eventdata->userto = $touser;
4460 $eventdata->subject = $subject;
4461 $eventdata->fullmessage = $message;
4462 $eventdata->fullmessageformat = FORMAT_PLAIN;
4463 $eventdata->fullmessagehtml = '';
4464 $eventdata->smallmessage = '';
4465 $eventdata->notification = 1;
4466 message_send($eventdata);
4471 * Return a list of page types
4472 * @param string $pagetype current page type
4473 * @param stdClass $parentcontext Block's parent context
4474 * @param stdClass $currentcontext Current context of block
4476 function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
4477 // $currentcontext could be null, get_context_info_array() will throw an error if this is the case.
4478 if (isset($currentcontext)) {
4479 // if above course context ,display all course fomats
4480 list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
4481 if ($course->id == SITEID) {
4482 return array('*'=>get_string('page-x', 'pagetype'));
4485 return array('*'=>get_string('page-x', 'pagetype'),
4486 'course-*'=>get_string('page-course-x', 'pagetype'),
4487 'course-view-*'=>get_string('page-course-view-x', 'pagetype')
4492 * Determine whether course ajax should be enabled for the specified course
4494 * @param stdClass $course The course to test against
4495 * @return boolean Whether course ajax is enabled or note
4497 function course_ajax_enabled($course) {
4498 global $CFG, $PAGE, $SITE;
4500 // Ajax must be enabled globally
4501 if (!$CFG->enableajax) {
4502 return false;
4505 // The user must be editing for AJAX to be included
4506 if (!$PAGE->user_is_editing()) {
4507 return false;
4510 // Check that the theme suports
4511 if (!$PAGE->theme->enablecourseajax) {
4512 return false;
4515 // Check that the course format supports ajax functionality
4516 // The site 'format' doesn't have information on course format support
4517 if ($SITE->id !== $course->id) {
4518 $courseformatajaxsupport = course_format_ajax_support($course->format);
4519 if (!$courseformatajaxsupport->capable) {
4520 return false;
4524 // All conditions have been met so course ajax should be enabled
4525 return true;
4529 * Include the relevant javascript and language strings for the resource
4530 * toolbox YUI module
4532 * @param integer $id The ID of the course being applied to
4533 * @param array $usedmodules An array containing the names of the modules in use on the page
4534 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
4535 * @param stdClass $config An object containing configuration parameters for ajax modules including:
4536 * * resourceurl The URL to post changes to for resource changes
4537 * * sectionurl The URL to post changes to for section changes
4538 * * pageparams Additional parameters to pass through in the post
4539 * @return bool
4541 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
4542 global $PAGE, $SITE;
4544 // Ensure that ajax should be included
4545 if (!course_ajax_enabled($course)) {
4546 return false;
4549 if (!$config) {
4550 $config = new stdClass();
4553 // The URL to use for resource changes
4554 if (!isset($config->resourceurl)) {
4555 $config->resourceurl = '/course/rest.php';
4558 // The URL to use for section changes
4559 if (!isset($config->sectionurl)) {
4560 $config->sectionurl = '/course/rest.php';
4563 // Any additional parameters which need to be included on page submission
4564 if (!isset($config->pageparams)) {
4565 $config->pageparams = array();
4568 // Include toolboxes
4569 $PAGE->requires->yui_module('moodle-course-toolboxes',
4570 'M.course.init_resource_toolbox',
4571 array(array(
4572 'courseid' => $course->id,
4573 'ajaxurl' => $config->resourceurl,
4574 'config' => $config,
4577 $PAGE->requires->yui_module('moodle-course-toolboxes',
4578 'M.course.init_section_toolbox',
4579 array(array(
4580 'courseid' => $course->id,
4581 'format' => $course->format,
4582 'ajaxurl' => $config->sectionurl,
4583 'config' => $config,
4587 // Include course dragdrop
4588 if ($course->id != $SITE->id) {
4589 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
4590 array(array(
4591 'courseid' => $course->id,
4592 'ajaxurl' => $config->sectionurl,
4593 'config' => $config,
4594 )), null, true);
4596 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
4597 array(array(
4598 'courseid' => $course->id,
4599 'ajaxurl' => $config->resourceurl,
4600 'config' => $config,
4601 )), null, true);
4604 // Include blocks dragdrop
4605 $params = array(
4606 'courseid' => $course->id,
4607 'pagetype' => $PAGE->pagetype,
4608 'pagelayout' => $PAGE->pagelayout,
4609 'subpage' => $PAGE->subpage,
4610 'regions' => $PAGE->blocks->get_regions(),
4612 $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
4614 // Require various strings for the command toolbox
4615 $PAGE->requires->strings_for_js(array(
4616 'moveleft',
4617 'deletechecktype',
4618 'deletechecktypename',
4619 'edittitle',
4620 'edittitleinstructions',
4621 'show',
4622 'hide',
4623 'groupsnone',
4624 'groupsvisible',
4625 'groupsseparate',
4626 'clicktochangeinbrackets',
4627 'markthistopic',
4628 'markedthistopic',
4629 'move',
4630 'movesection',
4631 ), 'moodle');
4633 // Include format-specific strings
4634 if ($course->id != $SITE->id) {
4635 $PAGE->requires->strings_for_js(array(
4636 'showfromothers',
4637 'hidefromothers',
4638 ), 'format_' . $course->format);
4641 // For confirming resource deletion we need the name of the module in question
4642 foreach ($usedmodules as $module => $modname) {
4643 $PAGE->requires->string_for_js('pluginname', $module);
4646 // Load drag and drop upload AJAX.
4647 dndupload_add_to_course($course, $enabledmodules);
4649 // Add the module chooser
4650 $PAGE->requires->yui_module('moodle-course-modchooser',
4651 'M.course.init_chooser',
4652 array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
4654 $PAGE->requires->strings_for_js(array(
4655 'addresourceoractivity',
4656 'modchooserenable',
4657 'modchooserdisable',
4658 ), 'moodle');
4660 return true;
4664 * The URL to use for the specified course (with section)
4666 * @param stdClass $course The course to get the section name for
4667 * @param int $sectionno The section number to return a link to
4668 * if omitted the course view page is returned
4669 * @param array $options options for view URL. At the moment core uses:
4670 * 'navigation' (bool) if true and section has no separate page, the function returns null
4671 * 'sr' (int) used by multipage formats to specify to which section to return
4672 * @return moodle_url The url of course
4674 function course_get_url($course, $sectionno = null, $options = array()) {
4675 if ($course->id == SITEID) {
4676 return new moodle_url('/');
4678 $url = new moodle_url('/course/view.php', array('id' => $course->id));
4680 $sr = null;
4681 if (array_key_exists('sr', $options)) {
4682 $sr = $options['sr'];
4684 if ($sectionno !== null) {
4685 if ($sr !== null) {
4686 if ($sr) {
4687 $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE;
4688 $sectionno = $sr;
4689 } else {
4690 $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE;
4692 } else {
4693 $usercoursedisplay = $course->coursedisplay;
4695 if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
4696 $url->param('section', $sectionno);
4697 } else {
4698 if (!empty($options['navigation'])) {
4699 return null;
4701 $url->set_anchor('section-'.$sectionno);
4705 return $url;