MDL-33074 Course drag and drop upload - fixed incorrect group mode button
[moodle.git] / course / lib.php
blob8e0737376072ae63457e78f693b298d22fa80ffc
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * Library of useful functions
21 * @copyright 1999 Martin Dougiamas http://dougiamas.com
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @package core
24 * @subpackage course
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir.'/completionlib.php');
30 require_once($CFG->libdir.'/filelib.php');
31 require_once($CFG->dirroot.'/course/dnduploadlib.php');
33 define('COURSE_MAX_LOGS_PER_PAGE', 1000); // records
34 define('COURSE_MAX_RECENT_PERIOD', 172800); // Two days, in seconds
35 define('COURSE_MAX_SUMMARIES_PER_PAGE', 10); // courses
36 define('COURSE_MAX_COURSES_PER_DROPDOWN',1000); // max courses in log dropdown before switching to optional
37 define('COURSE_MAX_USERS_PER_DROPDOWN',1000); // max users in log dropdown before switching to optional
38 define('FRONTPAGENEWS', '0');
39 define('FRONTPAGECOURSELIST', '1');
40 define('FRONTPAGECATEGORYNAMES', '2');
41 define('FRONTPAGETOPICONLY', '3');
42 define('FRONTPAGECATEGORYCOMBO', '4');
43 define('FRONTPAGECOURSELIMIT', 200); // maximum number of courses displayed on the frontpage
44 define('EXCELROWS', 65535);
45 define('FIRSTUSEDEXCELROW', 3);
47 define('MOD_CLASS_ACTIVITY', 0);
48 define('MOD_CLASS_RESOURCE', 1);
50 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
51 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
53 function make_log_url($module, $url) {
54 switch ($module) {
55 case 'course':
56 if (strpos($url, 'report/') === 0) {
57 // there is only one report type, course reports are deprecated
58 $url = "/$url";
59 break;
61 case 'file':
62 case 'login':
63 case 'lib':
64 case 'admin':
65 case 'calendar':
66 case 'mnet course':
67 if (strpos($url, '../') === 0) {
68 $url = ltrim($url, '.');
69 } else {
70 $url = "/course/$url";
72 break;
73 case 'user':
74 case 'blog':
75 $url = "/$module/$url";
76 break;
77 case 'upload':
78 $url = $url;
79 break;
80 case 'coursetags':
81 $url = '/'.$url;
82 break;
83 case 'library':
84 case '':
85 $url = '/';
86 break;
87 case 'message':
88 $url = "/message/$url";
89 break;
90 case 'notes':
91 $url = "/notes/$url";
92 break;
93 case 'tag':
94 $url = "/tag/$url";
95 break;
96 case 'role':
97 $url = '/'.$url;
98 break;
99 default:
100 $url = "/mod/$module/$url";
101 break;
104 //now let's sanitise urls - there might be some ugly nasties:-(
105 $parts = explode('?', $url);
106 $script = array_shift($parts);
107 if (strpos($script, 'http') === 0) {
108 $script = clean_param($script, PARAM_URL);
109 } else {
110 $script = clean_param($script, PARAM_PATH);
113 $query = '';
114 if ($parts) {
115 $query = implode('', $parts);
116 $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
117 $parts = explode('&', $query);
118 $eq = urlencode('=');
119 foreach ($parts as $key=>$part) {
120 $part = urlencode(urldecode($part));
121 $part = str_replace($eq, '=', $part);
122 $parts[$key] = $part;
124 $query = '?'.implode('&amp;', $parts);
127 return $script.$query;
131 function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
132 $modname="", $modid=0, $modaction="", $groupid=0) {
133 global $CFG, $DB;
135 // It is assumed that $date is the GMT time of midnight for that day,
136 // and so the next 86400 seconds worth of logs are printed.
138 /// Setup for group handling.
140 // TODO: I don't understand group/context/etc. enough to be able to do
141 // something interesting with it here
142 // What is the context of a remote course?
144 /// If the group mode is separate, and this user does not have editing privileges,
145 /// then only the user's group can be viewed.
146 //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
147 // $groupid = get_current_group($course->id);
149 /// If this course doesn't have groups, no groupid can be specified.
150 //else if (!$course->groupmode) {
151 // $groupid = 0;
154 $groupid = 0;
156 $joins = array();
157 $where = '';
159 $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
160 FROM {mnet_log} l
161 LEFT JOIN {user} u ON l.userid = u.id
162 WHERE ";
163 $params = array();
165 $where .= "l.hostid = :hostid";
166 $params['hostid'] = $hostid;
168 // TODO: Is 1 really a magic number referring to the sitename?
169 if ($course != SITEID || $modid != 0) {
170 $where .= " AND l.course=:courseid";
171 $params['courseid'] = $course;
174 if ($modname) {
175 $where .= " AND l.module = :modname";
176 $params['modname'] = $modname;
179 if ('site_errors' === $modid) {
180 $where .= " AND ( l.action='error' OR l.action='infected' )";
181 } else if ($modid) {
182 //TODO: This assumes that modids are the same across sites... probably
183 //not true
184 $where .= " AND l.cmid = :modid";
185 $params['modid'] = $modid;
188 if ($modaction) {
189 $firstletter = substr($modaction, 0, 1);
190 if ($firstletter == '-') {
191 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
192 $params['modaction'] = '%'.substr($modaction, 1).'%';
193 } else {
194 $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
195 $params['modaction'] = '%'.$modaction.'%';
199 if ($user) {
200 $where .= " AND l.userid = :user";
201 $params['user'] = $user;
204 if ($date) {
205 $enddate = $date + 86400;
206 $where .= " AND l.time > :date AND l.time < :enddate";
207 $params['date'] = $date;
208 $params['enddate'] = $enddate;
211 $result = array();
212 $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
213 if(!empty($result['totalcount'])) {
214 $where .= " ORDER BY $order";
215 $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
216 } else {
217 $result['logs'] = array();
219 return $result;
222 function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
223 $modname="", $modid=0, $modaction="", $groupid=0) {
224 global $DB, $SESSION, $USER;
225 // It is assumed that $date is the GMT time of midnight for that day,
226 // and so the next 86400 seconds worth of logs are printed.
228 /// Setup for group handling.
230 /// If the group mode is separate, and this user does not have editing privileges,
231 /// then only the user's group can be viewed.
232 if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
233 if (isset($SESSION->currentgroup[$course->id])) {
234 $groupid = $SESSION->currentgroup[$course->id];
235 } else {
236 $groupid = groups_get_all_groups($course->id, $USER->id);
237 if (is_array($groupid)) {
238 $groupid = array_shift(array_keys($groupid));
239 $SESSION->currentgroup[$course->id] = $groupid;
240 } else {
241 $groupid = 0;
245 /// If this course doesn't have groups, no groupid can be specified.
246 else if (!$course->groupmode) {
247 $groupid = 0;
250 $joins = array();
251 $params = array();
253 if ($course->id != SITEID || $modid != 0) {
254 $joins[] = "l.course = :courseid";
255 $params['courseid'] = $course->id;
258 if ($modname) {
259 $joins[] = "l.module = :modname";
260 $params['modname'] = $modname;
263 if ('site_errors' === $modid) {
264 $joins[] = "( l.action='error' OR l.action='infected' )";
265 } else if ($modid) {
266 $joins[] = "l.cmid = :modid";
267 $params['modid'] = $modid;
270 if ($modaction) {
271 $firstletter = substr($modaction, 0, 1);
272 if ($firstletter == '-') {
273 $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
274 $params['modaction'] = '%'.substr($modaction, 1).'%';
275 } else {
276 $joins[] = $DB->sql_like('l.action', ':modaction', false);
277 $params['modaction'] = '%'.$modaction.'%';
282 /// Getting all members of a group.
283 if ($groupid and !$user) {
284 if ($gusers = groups_get_members($groupid)) {
285 $gusers = array_keys($gusers);
286 $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
287 } else {
288 $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
291 else if ($user) {
292 $joins[] = "l.userid = :userid";
293 $params['userid'] = $user;
296 if ($date) {
297 $enddate = $date + 86400;
298 $joins[] = "l.time > :date AND l.time < :enddate";
299 $params['date'] = $date;
300 $params['enddate'] = $enddate;
303 $selector = implode(' AND ', $joins);
305 $totalcount = 0; // Initialise
306 $result = array();
307 $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
308 $result['totalcount'] = $totalcount;
309 return $result;
313 function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
314 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
316 global $CFG, $DB, $OUTPUT;
318 if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
319 $modname, $modid, $modaction, $groupid)) {
320 echo $OUTPUT->notification("No logs found!");
321 echo $OUTPUT->footer();
322 exit;
325 $courses = array();
327 if ($course->id == SITEID) {
328 $courses[0] = '';
329 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
330 foreach ($ccc as $cc) {
331 $courses[$cc->id] = $cc->shortname;
334 } else {
335 $courses[$course->id] = $course->shortname;
338 $totalcount = $logs['totalcount'];
339 $count=0;
340 $ldcache = array();
341 $tt = getdate(time());
342 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
344 $strftimedatetime = get_string("strftimedatetime");
346 echo "<div class=\"info\">\n";
347 print_string("displayingrecords", "", $totalcount);
348 echo "</div>\n";
350 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
352 $table = new html_table();
353 $table->classes = array('logtable','generalbox');
354 $table->align = array('right', 'left', 'left');
355 $table->head = array(
356 get_string('time'),
357 get_string('ip_address'),
358 get_string('fullnameuser'),
359 get_string('action'),
360 get_string('info')
362 $table->data = array();
364 if ($course->id == SITEID) {
365 array_unshift($table->align, 'left');
366 array_unshift($table->head, get_string('course'));
369 // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
370 if (empty($logs['logs'])) {
371 $logs['logs'] = array();
374 foreach ($logs['logs'] as $log) {
376 if (isset($ldcache[$log->module][$log->action])) {
377 $ld = $ldcache[$log->module][$log->action];
378 } else {
379 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
380 $ldcache[$log->module][$log->action] = $ld;
382 if ($ld && is_numeric($log->info)) {
383 // ugly hack to make sure fullname is shown correctly
384 if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
385 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
386 } else {
387 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
391 //Filter log->info
392 $log->info = format_string($log->info);
394 // If $log->url has been trimmed short by the db size restriction
395 // code in add_to_log, keep a note so we don't add a link to a broken url
396 $brokenurl=(textlib::strlen($log->url)==100 && textlib::substr($log->url,97)=='...');
398 $row = array();
399 if ($course->id == SITEID) {
400 if (empty($log->course)) {
401 $row[] = get_string('site');
402 } else {
403 $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
407 $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
409 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
410 $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
412 $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $course->id))));
414 $displayaction="$log->module $log->action";
415 if ($brokenurl) {
416 $row[] = $displayaction;
417 } else {
418 $link = make_log_url($log->module,$log->url);
419 $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
421 $row[] = $log->info;
422 $table->data[] = $row;
425 echo html_writer::table($table);
426 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
430 function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
431 $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
433 global $CFG, $DB, $OUTPUT;
435 if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
436 $modname, $modid, $modaction, $groupid)) {
437 echo $OUTPUT->notification("No logs found!");
438 echo $OUTPUT->footer();
439 exit;
442 if ($course->id == SITEID) {
443 $courses[0] = '';
444 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
445 foreach ($ccc as $cc) {
446 $courses[$cc->id] = $cc->shortname;
451 $totalcount = $logs['totalcount'];
452 $count=0;
453 $ldcache = array();
454 $tt = getdate(time());
455 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
457 $strftimedatetime = get_string("strftimedatetime");
459 echo "<div class=\"info\">\n";
460 print_string("displayingrecords", "", $totalcount);
461 echo "</div>\n";
463 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
465 echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
466 echo "<tr>";
467 if ($course->id == SITEID) {
468 echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
470 echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
471 echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
472 echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
473 echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
474 echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
475 echo "</tr>\n";
477 if (empty($logs['logs'])) {
478 echo "</table>\n";
479 return;
482 $row = 1;
483 foreach ($logs['logs'] as $log) {
485 $log->info = $log->coursename;
486 $row = ($row + 1) % 2;
488 if (isset($ldcache[$log->module][$log->action])) {
489 $ld = $ldcache[$log->module][$log->action];
490 } else {
491 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
492 $ldcache[$log->module][$log->action] = $ld;
494 if (0 && $ld && !empty($log->info)) {
495 // ugly hack to make sure fullname is shown correctly
496 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
497 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
498 } else {
499 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
503 //Filter log->info
504 $log->info = format_string($log->info);
506 echo '<tr class="r'.$row.'">';
507 if ($course->id == SITEID) {
508 $courseshortname = format_string($courses[$log->course], true, array('context' => get_context_instance(CONTEXT_COURSE, SITEID)));
509 echo "<td class=\"r$row c0\" >\n";
510 echo " <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
511 echo "</td>\n";
513 echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
514 ' '.userdate($log->time, $strftimedatetime)."</td>\n";
515 echo "<td class=\"r$row c2\" >\n";
516 $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
517 echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
518 echo "</td>\n";
519 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $course->id)));
520 echo "<td class=\"r$row c3\" >\n";
521 echo " <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
522 echo "</td>\n";
523 echo "<td class=\"r$row c4\">\n";
524 echo $log->action .': '.$log->module;
525 echo "</td>\n";;
526 echo "<td class=\"r$row c5\">{$log->info}</td>\n";
527 echo "</tr>\n";
529 echo "</table>\n";
531 echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
535 function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
536 $modid, $modaction, $groupid) {
537 global $DB;
539 $text = get_string('course')."\t".get_string('time')."\t".get_string('ip_address')."\t".
540 get_string('fullnameuser')."\t".get_string('action')."\t".get_string('info');
542 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
543 $modname, $modid, $modaction, $groupid)) {
544 return false;
547 $courses = array();
549 if ($course->id == SITEID) {
550 $courses[0] = '';
551 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
552 foreach ($ccc as $cc) {
553 $courses[$cc->id] = $cc->shortname;
556 } else {
557 $courses[$course->id] = $course->shortname;
560 $count=0;
561 $ldcache = array();
562 $tt = getdate(time());
563 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
565 $strftimedatetime = get_string("strftimedatetime");
567 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
568 $filename .= '.txt';
569 header("Content-Type: application/download\n");
570 header("Content-Disposition: attachment; filename=\"$filename\"");
571 header("Expires: 0");
572 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
573 header("Pragma: public");
575 echo get_string('savedat').userdate(time(), $strftimedatetime)."\n";
576 echo $text."\n";
578 if (empty($logs['logs'])) {
579 return true;
582 foreach ($logs['logs'] as $log) {
583 if (isset($ldcache[$log->module][$log->action])) {
584 $ld = $ldcache[$log->module][$log->action];
585 } else {
586 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
587 $ldcache[$log->module][$log->action] = $ld;
589 if ($ld && !empty($log->info)) {
590 // ugly hack to make sure fullname is shown correctly
591 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
592 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
593 } else {
594 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
598 //Filter log->info
599 $log->info = format_string($log->info);
600 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
602 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
603 $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
604 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
605 $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action, $log->info);
606 $text = implode("\t", $row);
607 echo $text." \n";
609 return true;
613 function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
614 $modid, $modaction, $groupid) {
616 global $CFG, $DB;
618 require_once("$CFG->libdir/excellib.class.php");
620 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
621 $modname, $modid, $modaction, $groupid)) {
622 return false;
625 $courses = array();
627 if ($course->id == SITEID) {
628 $courses[0] = '';
629 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
630 foreach ($ccc as $cc) {
631 $courses[$cc->id] = $cc->shortname;
634 } else {
635 $courses[$course->id] = $course->shortname;
638 $count=0;
639 $ldcache = array();
640 $tt = getdate(time());
641 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
643 $strftimedatetime = get_string("strftimedatetime");
645 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
646 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
647 $filename .= '.xls';
649 $workbook = new MoodleExcelWorkbook('-');
650 $workbook->send($filename);
652 $worksheet = array();
653 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
654 get_string('fullnameuser'), get_string('action'), get_string('info'));
656 // Creating worksheets
657 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
658 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
659 $worksheet[$wsnumber] =& $workbook->add_worksheet($sheettitle);
660 $worksheet[$wsnumber]->set_column(1, 1, 30);
661 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
662 userdate(time(), $strftimedatetime));
663 $col = 0;
664 foreach ($headers as $item) {
665 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
666 $col++;
670 if (empty($logs['logs'])) {
671 $workbook->close();
672 return true;
675 $formatDate =& $workbook->add_format();
676 $formatDate->set_num_format(get_string('log_excel_date_format'));
678 $row = FIRSTUSEDEXCELROW;
679 $wsnumber = 1;
680 $myxls =& $worksheet[$wsnumber];
681 foreach ($logs['logs'] as $log) {
682 if (isset($ldcache[$log->module][$log->action])) {
683 $ld = $ldcache[$log->module][$log->action];
684 } else {
685 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
686 $ldcache[$log->module][$log->action] = $ld;
688 if ($ld && !empty($log->info)) {
689 // ugly hack to make sure fullname is shown correctly
690 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
691 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
692 } else {
693 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
697 // Filter log->info
698 $log->info = format_string($log->info);
699 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
701 if ($nroPages>1) {
702 if ($row > EXCELROWS) {
703 $wsnumber++;
704 $myxls =& $worksheet[$wsnumber];
705 $row = FIRSTUSEDEXCELROW;
709 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
711 $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
712 $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
713 $myxls->write($row, 2, $log->ip, '');
714 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
715 $myxls->write($row, 3, $fullname, '');
716 $myxls->write($row, 4, $log->module.' '.$log->action, '');
717 $myxls->write($row, 5, $log->info, '');
719 $row++;
722 $workbook->close();
723 return true;
726 function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
727 $modid, $modaction, $groupid) {
729 global $CFG, $DB;
731 require_once("$CFG->libdir/odslib.class.php");
733 if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
734 $modname, $modid, $modaction, $groupid)) {
735 return false;
738 $courses = array();
740 if ($course->id == SITEID) {
741 $courses[0] = '';
742 if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
743 foreach ($ccc as $cc) {
744 $courses[$cc->id] = $cc->shortname;
747 } else {
748 $courses[$course->id] = $course->shortname;
751 $count=0;
752 $ldcache = array();
753 $tt = getdate(time());
754 $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
756 $strftimedatetime = get_string("strftimedatetime");
758 $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
759 $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
760 $filename .= '.ods';
762 $workbook = new MoodleODSWorkbook('-');
763 $workbook->send($filename);
765 $worksheet = array();
766 $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
767 get_string('fullnameuser'), get_string('action'), get_string('info'));
769 // Creating worksheets
770 for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
771 $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
772 $worksheet[$wsnumber] =& $workbook->add_worksheet($sheettitle);
773 $worksheet[$wsnumber]->set_column(1, 1, 30);
774 $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
775 userdate(time(), $strftimedatetime));
776 $col = 0;
777 foreach ($headers as $item) {
778 $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
779 $col++;
783 if (empty($logs['logs'])) {
784 $workbook->close();
785 return true;
788 $formatDate =& $workbook->add_format();
789 $formatDate->set_num_format(get_string('log_excel_date_format'));
791 $row = FIRSTUSEDEXCELROW;
792 $wsnumber = 1;
793 $myxls =& $worksheet[$wsnumber];
794 foreach ($logs['logs'] as $log) {
795 if (isset($ldcache[$log->module][$log->action])) {
796 $ld = $ldcache[$log->module][$log->action];
797 } else {
798 $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
799 $ldcache[$log->module][$log->action] = $ld;
801 if ($ld && !empty($log->info)) {
802 // ugly hack to make sure fullname is shown correctly
803 if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
804 $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
805 } else {
806 $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
810 // Filter log->info
811 $log->info = format_string($log->info);
812 $log->info = strip_tags(urldecode($log->info)); // Some XSS protection
814 if ($nroPages>1) {
815 if ($row > EXCELROWS) {
816 $wsnumber++;
817 $myxls =& $worksheet[$wsnumber];
818 $row = FIRSTUSEDEXCELROW;
822 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
824 $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
825 $myxls->write_date($row, 1, $log->time);
826 $myxls->write_string($row, 2, $log->ip);
827 $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
828 $myxls->write_string($row, 3, $fullname);
829 $myxls->write_string($row, 4, $log->module.' '.$log->action);
830 $myxls->write_string($row, 5, $log->info);
832 $row++;
835 $workbook->close();
836 return true;
840 function print_overview($courses, array $remote_courses=array()) {
841 global $CFG, $USER, $DB, $OUTPUT;
843 $htmlarray = array();
844 if ($modules = $DB->get_records('modules')) {
845 foreach ($modules as $mod) {
846 if (file_exists(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php')) {
847 include_once(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php');
848 $fname = $mod->name.'_print_overview';
849 if (function_exists($fname)) {
850 $fname($courses,$htmlarray);
855 foreach ($courses as $course) {
856 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
857 echo $OUTPUT->box_start('coursebox');
858 $attributes = array('title' => s($fullname));
859 if (empty($course->visible)) {
860 $attributes['class'] = 'dimmed';
862 echo $OUTPUT->heading(html_writer::link(
863 new moodle_url('/course/view.php', array('id' => $course->id)), $fullname, $attributes), 3);
864 if (array_key_exists($course->id,$htmlarray)) {
865 foreach ($htmlarray[$course->id] as $modname => $html) {
866 echo $html;
869 echo $OUTPUT->box_end();
872 if (!empty($remote_courses)) {
873 echo $OUTPUT->heading(get_string('remotecourses', 'mnet'));
875 foreach ($remote_courses as $course) {
876 echo $OUTPUT->box_start('coursebox');
877 $attributes = array('title' => s($course->fullname));
878 echo $OUTPUT->heading(html_writer::link(
879 new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
880 format_string($course->shortname),
881 $attributes) . ' (' . format_string($course->hostname) . ')', 3);
882 echo $OUTPUT->box_end();
888 * This function trawls through the logs looking for
889 * anything new since the user's last login
891 function print_recent_activity($course) {
892 // $course is an object
893 global $CFG, $USER, $SESSION, $DB, $OUTPUT;
895 $context = get_context_instance(CONTEXT_COURSE, $course->id);
897 $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
899 $timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
901 if (!isguestuser()) {
902 if (!empty($USER->lastcourseaccess[$course->id])) {
903 if ($USER->lastcourseaccess[$course->id] > $timestart) {
904 $timestart = $USER->lastcourseaccess[$course->id];
909 echo '<div class="activitydate">';
910 echo get_string('activitysince', '', userdate($timestart));
911 echo '</div>';
912 echo '<div class="activityhead">';
914 echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
916 echo "</div>\n";
918 $content = false;
920 /// Firstly, have there been any new enrolments?
922 $users = get_recent_enrolments($course->id, $timestart);
924 //Accessibility: new users now appear in an <OL> list.
925 if ($users) {
926 echo '<div class="newusers">';
927 echo $OUTPUT->heading(get_string("newusers").':', 3);
928 $content = true;
929 echo "<ol class=\"list\">\n";
930 foreach ($users as $user) {
931 $fullname = fullname($user, $viewfullnames);
932 echo '<li class="name"><a href="'."$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a></li>\n";
934 echo "</ol>\n</div>\n";
937 /// Next, have there been any modifications to the course structure?
939 $modinfo = get_fast_modinfo($course);
941 $changelist = array();
943 $logs = $DB->get_records_select('log', "time > ? AND course = ? AND
944 module = 'course' AND
945 (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
946 array($timestart, $course->id), "id ASC");
948 if ($logs) {
949 $actions = array('add mod', 'update mod', 'delete mod');
950 $newgones = array(); // added and later deleted items
951 foreach ($logs as $key => $log) {
952 if (!in_array($log->action, $actions)) {
953 continue;
955 $info = explode(' ', $log->info);
957 // note: in most cases I replaced hardcoding of label with use of
958 // $cm->has_view() but it was not possible to do this here because
959 // we don't necessarily have the $cm for it
960 if ($info[0] == 'label') { // Labels are ignored in recent activity
961 continue;
964 if (count($info) != 2) {
965 debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
966 continue;
969 $modname = $info[0];
970 $instanceid = $info[1];
972 if ($log->action == 'delete mod') {
973 // unfortunately we do not know if the mod was visible
974 if (!array_key_exists($log->info, $newgones)) {
975 $strdeleted = get_string('deletedactivity', 'moodle', get_string('modulename', $modname));
976 $changelist[$log->info] = array ('operation' => 'delete', 'text' => $strdeleted);
978 } else {
979 if (!isset($modinfo->instances[$modname][$instanceid])) {
980 if ($log->action == 'add mod') {
981 // do not display added and later deleted activities
982 $newgones[$log->info] = true;
984 continue;
986 $cm = $modinfo->instances[$modname][$instanceid];
987 if (!$cm->uservisible) {
988 continue;
991 if ($log->action == 'add mod') {
992 $stradded = get_string('added', 'moodle', get_string('modulename', $modname));
993 $changelist[$log->info] = array('operation' => 'add', 'text' => "$stradded:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
995 } else if ($log->action == 'update mod' and empty($changelist[$log->info])) {
996 $strupdated = get_string('updated', 'moodle', get_string('modulename', $modname));
997 $changelist[$log->info] = array('operation' => 'update', 'text' => "$strupdated:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
1003 if (!empty($changelist)) {
1004 echo $OUTPUT->heading(get_string("courseupdates").':', 3);
1005 $content = true;
1006 foreach ($changelist as $changeinfo => $change) {
1007 echo '<p class="activity">'.$change['text'].'</p>';
1011 /// Now display new things from each module
1013 $usedmodules = array();
1014 foreach($modinfo->cms as $cm) {
1015 if (isset($usedmodules[$cm->modname])) {
1016 continue;
1018 if (!$cm->uservisible) {
1019 continue;
1021 $usedmodules[$cm->modname] = $cm->modname;
1024 foreach ($usedmodules as $modname) { // Each module gets it's own logs and prints them
1025 if (file_exists($CFG->dirroot.'/mod/'.$modname.'/lib.php')) {
1026 include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
1027 $print_recent_activity = $modname.'_print_recent_activity';
1028 if (function_exists($print_recent_activity)) {
1029 // NOTE: original $isteacher (second parameter below) was replaced with $viewfullnames!
1030 $content = $print_recent_activity($course, $viewfullnames, $timestart) || $content;
1032 } else {
1033 debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
1037 if (! $content) {
1038 echo '<p class="message">'.get_string('nothingnew').'</p>';
1043 * For a given course, returns an array of course activity objects
1044 * Each item in the array contains he following properties:
1046 function get_array_of_activities($courseid) {
1047 // cm - course module id
1048 // mod - name of the module (eg forum)
1049 // section - the number of the section (eg week or topic)
1050 // name - the name of the instance
1051 // visible - is the instance visible or not
1052 // groupingid - grouping id
1053 // groupmembersonly - is this instance visible to group members only
1054 // extra - contains extra string to include in any link
1055 global $CFG, $DB;
1056 if(!empty($CFG->enableavailability)) {
1057 require_once($CFG->libdir.'/conditionlib.php');
1060 $course = $DB->get_record('course', array('id'=>$courseid));
1062 if (empty($course)) {
1063 throw new moodle_exception('courseidnotfound');
1066 $mod = array();
1068 $rawmods = get_course_mods($courseid);
1069 if (empty($rawmods)) {
1070 return $mod; // always return array
1073 if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
1074 foreach ($sections as $section) {
1075 if (!empty($section->sequence)) {
1076 $sequence = explode(",", $section->sequence);
1077 foreach ($sequence as $seq) {
1078 if (empty($rawmods[$seq])) {
1079 continue;
1081 $mod[$seq] = new stdClass();
1082 $mod[$seq]->id = $rawmods[$seq]->instance;
1083 $mod[$seq]->cm = $rawmods[$seq]->id;
1084 $mod[$seq]->mod = $rawmods[$seq]->modname;
1086 // Oh dear. Inconsistent names left here for backward compatibility.
1087 $mod[$seq]->section = $section->section;
1088 $mod[$seq]->sectionid = $rawmods[$seq]->section;
1090 $mod[$seq]->module = $rawmods[$seq]->module;
1091 $mod[$seq]->added = $rawmods[$seq]->added;
1092 $mod[$seq]->score = $rawmods[$seq]->score;
1093 $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
1094 $mod[$seq]->visible = $rawmods[$seq]->visible;
1095 $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
1096 $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
1097 $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
1098 $mod[$seq]->groupmembersonly = $rawmods[$seq]->groupmembersonly;
1099 $mod[$seq]->indent = $rawmods[$seq]->indent;
1100 $mod[$seq]->completion = $rawmods[$seq]->completion;
1101 $mod[$seq]->extra = "";
1102 $mod[$seq]->completiongradeitemnumber =
1103 $rawmods[$seq]->completiongradeitemnumber;
1104 $mod[$seq]->completionview = $rawmods[$seq]->completionview;
1105 $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
1106 $mod[$seq]->availablefrom = $rawmods[$seq]->availablefrom;
1107 $mod[$seq]->availableuntil = $rawmods[$seq]->availableuntil;
1108 $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
1109 $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
1110 if (!empty($CFG->enableavailability)) {
1111 condition_info::fill_availability_conditions($rawmods[$seq]);
1112 $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
1113 $mod[$seq]->conditionsgrade = $rawmods[$seq]->conditionsgrade;
1116 $modname = $mod[$seq]->mod;
1117 $functionname = $modname."_get_coursemodule_info";
1119 if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
1120 continue;
1123 include_once("$CFG->dirroot/mod/$modname/lib.php");
1125 if ($hasfunction = function_exists($functionname)) {
1126 if ($info = $functionname($rawmods[$seq])) {
1127 if (!empty($info->icon)) {
1128 $mod[$seq]->icon = $info->icon;
1130 if (!empty($info->iconcomponent)) {
1131 $mod[$seq]->iconcomponent = $info->iconcomponent;
1133 if (!empty($info->name)) {
1134 $mod[$seq]->name = $info->name;
1136 if ($info instanceof cached_cm_info) {
1137 // When using cached_cm_info you can include three new fields
1138 // that aren't available for legacy code
1139 if (!empty($info->content)) {
1140 $mod[$seq]->content = $info->content;
1142 if (!empty($info->extraclasses)) {
1143 $mod[$seq]->extraclasses = $info->extraclasses;
1145 if (!empty($info->iconurl)) {
1146 $mod[$seq]->iconurl = $info->iconurl;
1148 if (!empty($info->onclick)) {
1149 $mod[$seq]->onclick = $info->onclick;
1151 if (!empty($info->customdata)) {
1152 $mod[$seq]->customdata = $info->customdata;
1154 } else {
1155 // When using a stdclass, the (horrible) deprecated ->extra field
1156 // is available for BC
1157 if (!empty($info->extra)) {
1158 $mod[$seq]->extra = $info->extra;
1163 // When there is no modname_get_coursemodule_info function,
1164 // but showdescriptions is enabled, then we use the 'intro'
1165 // and 'introformat' fields in the module table
1166 if (!$hasfunction && $rawmods[$seq]->showdescription) {
1167 if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
1168 array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
1169 // Set content from intro and introformat. Filters are disabled
1170 // because we filter it with format_text at display time
1171 $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
1172 $modvalues, $rawmods[$seq]->id, false);
1174 // To save making another query just below, put name in here
1175 $mod[$seq]->name = $modvalues->name;
1178 if (!isset($mod[$seq]->name)) {
1179 $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1182 // Minimise the database size by unsetting default options when they are
1183 // 'empty'. This list corresponds to code in the cm_info constructor.
1184 foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
1185 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
1186 'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom',
1187 'availableuntil', 'conditionscompletion', 'conditionsgrade',
1188 'completionview', 'completionexpected', 'score', 'showdescription')
1189 as $property) {
1190 if (property_exists($mod[$seq], $property) &&
1191 empty($mod[$seq]->{$property})) {
1192 unset($mod[$seq]->{$property});
1195 // Special case: this value is usually set to null, but may be 0
1196 if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
1197 is_null($mod[$seq]->completiongradeitemnumber)) {
1198 unset($mod[$seq]->completiongradeitemnumber);
1204 return $mod;
1209 * Returns a number of useful structures for course displays
1211 function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modnamesused) {
1212 global $CFG, $DB, $COURSE;
1214 $mods = array(); // course modules indexed by id
1215 $modnames = array(); // all course module names (except resource!)
1216 $modnamesplural= array(); // all course module names (plural form)
1217 $modnamesused = array(); // course module names used
1219 if ($allmods = $DB->get_records("modules")) {
1220 foreach ($allmods as $mod) {
1221 if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
1222 continue;
1224 if ($mod->visible) {
1225 $modnames[$mod->name] = get_string("modulename", "$mod->name");
1226 $modnamesplural[$mod->name] = get_string("modulenameplural", "$mod->name");
1229 collatorlib::asort($modnames);
1230 } else {
1231 print_error("nomodules", 'debug');
1234 $course = ($courseid==$COURSE->id) ? $COURSE : $DB->get_record('course',array('id'=>$courseid));
1235 $modinfo = get_fast_modinfo($course);
1237 if ($rawmods=$modinfo->cms) {
1238 foreach($rawmods as $mod) { // Index the mods
1239 if (empty($modnames[$mod->modname])) {
1240 continue;
1242 $mods[$mod->id] = $mod;
1243 $mods[$mod->id]->modfullname = $modnames[$mod->modname];
1244 if (!$mod->visible and !has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_COURSE, $courseid))) {
1245 continue;
1247 // Check groupings
1248 if (!groups_course_module_visible($mod)) {
1249 continue;
1251 $modnamesused[$mod->modname] = $modnames[$mod->modname];
1253 if ($modnamesused) {
1254 collatorlib::asort($modnamesused);
1260 * Returns an array of sections for the requested course id
1262 * This function stores the sections against the course id within a staticvar encase
1263 * of subsequent requests. This is used all over + in some standard libs and course
1264 * format callbacks so subsequent requests are a reality.
1266 * Note: Since Moodle 2.3, it is more efficient to get this data by calling
1267 * get_fast_modinfo, then using $modinfo->get_section_info or get_section_info_all.
1269 * @staticvar array $coursesections
1270 * @param int $courseid
1271 * @return array Array of sections
1273 function get_all_sections($courseid) {
1274 global $DB;
1275 static $coursesections = array();
1276 if (!array_key_exists($courseid, $coursesections)) {
1277 $coursesections[$courseid] = $DB->get_records("course_sections", array("course"=>"$courseid"), "section",
1278 'section, id, course, name, summary, summaryformat, sequence, visible, ' .
1279 'availablefrom, availableuntil, showavailability, groupingid');
1281 return $coursesections[$courseid];
1285 * Set highlighted section. Only one section can be highlighted at the time.
1287 * @param int $courseid course id
1288 * @param int $marker highlight section with this number, 0 means remove higlightin
1289 * @return void
1291 function course_set_marker($courseid, $marker) {
1292 global $DB;
1293 $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1297 * For a given course section, marks it visible or hidden,
1298 * and does the same for every activity in that section
1300 * @param int $courseid course id
1301 * @param int $sectionnumber The section number to adjust
1302 * @param int $visibility The new visibility
1303 * @return array A list of resources which were hidden in the section
1305 function set_section_visible($courseid, $sectionnumber, $visibility) {
1306 global $DB;
1308 $resourcestotoggle = array();
1309 if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1310 $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1311 if (!empty($section->sequence)) {
1312 $modules = explode(",", $section->sequence);
1313 foreach ($modules as $moduleid) {
1314 set_coursemodule_visible($moduleid, $visibility, true);
1317 rebuild_course_cache($courseid);
1319 // Determine which modules are visible for AJAX update
1320 if (!empty($modules)) {
1321 list($insql, $params) = $DB->get_in_or_equal($modules);
1322 $select = 'id ' . $insql . ' AND visible = ?';
1323 array_push($params, $visibility);
1324 if (!$visibility) {
1325 $select .= ' AND visibleold = 1';
1327 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1330 return $resourcestotoggle;
1334 * Obtains shared data that is used in print_section when displaying a
1335 * course-module entry.
1337 * Calls format_text or format_string as appropriate, and obtains the correct icon.
1339 * This data is also used in other areas of the code.
1340 * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
1341 * @param object $course Moodle course object
1342 * @return array An array with the following values in this order:
1343 * $content (optional extra content for after link),
1344 * $instancename (text of link)
1346 function get_print_section_cm_text(cm_info $cm, $course) {
1347 global $OUTPUT;
1349 // Get content from modinfo if specified. Content displays either
1350 // in addition to the standard link (below), or replaces it if
1351 // the link is turned off by setting ->url to null.
1352 if (($content = $cm->get_content()) !== '') {
1353 // Improve filter performance by preloading filter setttings for all
1354 // activities on the course (this does nothing if called multiple
1355 // times)
1356 filter_preload_activities($cm->get_modinfo());
1358 // Get module context
1359 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1360 $labelformatoptions = new stdClass();
1361 $labelformatoptions->noclean = true;
1362 $labelformatoptions->overflowdiv = true;
1363 $labelformatoptions->context = $modulecontext;
1364 $content = format_text($content, FORMAT_HTML, $labelformatoptions);
1365 } else {
1366 $content = '';
1369 // Get course context
1370 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1371 $stringoptions = new stdClass;
1372 $stringoptions->context = $coursecontext;
1373 $instancename = format_string($cm->name, true, $stringoptions);
1374 return array($content, $instancename);
1378 * Prints a section full of activity modules
1380 function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn = false) {
1381 global $CFG, $USER, $DB, $PAGE, $OUTPUT;
1383 static $initialised;
1385 static $groupbuttons;
1386 static $groupbuttonslink;
1387 static $isediting;
1388 static $ismoving;
1389 static $strmovehere;
1390 static $strmovefull;
1391 static $strunreadpostsone;
1392 static $modulenames;
1394 if (!isset($initialised)) {
1395 $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
1396 $groupbuttonslink = (!$course->groupmodeforce);
1397 $isediting = $PAGE->user_is_editing();
1398 $ismoving = $isediting && ismoving($course->id);
1399 if ($ismoving) {
1400 $strmovehere = get_string("movehere");
1401 $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
1403 $modulenames = array();
1404 $initialised = true;
1407 $modinfo = get_fast_modinfo($course);
1408 $completioninfo = new completion_info($course);
1410 //Accessibility: replace table with list <ul>, but don't output empty list.
1411 if (!empty($section->sequence)) {
1413 // Fix bug #5027, don't want style=\"width:$width\".
1414 echo "<ul class=\"section img-text\">\n";
1415 $sectionmods = explode(",", $section->sequence);
1417 foreach ($sectionmods as $modnumber) {
1418 if (empty($mods[$modnumber])) {
1419 continue;
1423 * @var cm_info
1425 $mod = $mods[$modnumber];
1427 if ($ismoving and $mod->id == $USER->activitycopy) {
1428 // do not display moving mod
1429 continue;
1432 if (isset($modinfo->cms[$modnumber])) {
1433 // We can continue (because it will not be displayed at all)
1434 // if:
1435 // 1) The activity is not visible to users
1436 // and
1437 // 2a) The 'showavailability' option is not set (if that is set,
1438 // we need to display the activity so we can show
1439 // availability info)
1440 // or
1441 // 2b) The 'availableinfo' is empty, i.e. the activity was
1442 // hidden in a way that leaves no info, such as using the
1443 // eye icon.
1444 if (!$modinfo->cms[$modnumber]->uservisible &&
1445 (empty($modinfo->cms[$modnumber]->showavailability) ||
1446 empty($modinfo->cms[$modnumber]->availableinfo))) {
1447 // visibility shortcut
1448 continue;
1450 } else {
1451 if (!file_exists("$CFG->dirroot/mod/$mod->modname/lib.php")) {
1452 // module not installed
1453 continue;
1455 if (!coursemodule_visible_for_user($mod) &&
1456 empty($mod->showavailability)) {
1457 // full visibility check
1458 continue;
1462 if (!isset($modulenames[$mod->modname])) {
1463 $modulenames[$mod->modname] = get_string('modulename', $mod->modname);
1465 $modulename = $modulenames[$mod->modname];
1467 // In some cases the activity is visible to user, but it is
1468 // dimmed. This is done if viewhiddenactivities is true and if:
1469 // 1. the activity is not visible, or
1470 // 2. the activity has dates set which do not include current, or
1471 // 3. the activity has any other conditions set (regardless of whether
1472 // current user meets them)
1473 $canviewhidden = has_capability(
1474 'moodle/course:viewhiddenactivities',
1475 get_context_instance(CONTEXT_MODULE, $mod->id));
1476 $accessiblebutdim = false;
1477 if ($canviewhidden) {
1478 $accessiblebutdim = !$mod->visible;
1479 if (!empty($CFG->enableavailability)) {
1480 $accessiblebutdim = $accessiblebutdim ||
1481 $mod->availablefrom > time() ||
1482 ($mod->availableuntil && $mod->availableuntil < time()) ||
1483 count($mod->conditionsgrade) > 0 ||
1484 count($mod->conditionscompletion) > 0;
1488 $liclasses = array();
1489 $liclasses[] = 'activity';
1490 $liclasses[] = $mod->modname;
1491 $liclasses[] = 'modtype_'.$mod->modname;
1492 $extraclasses = $mod->get_extra_classes();
1493 if ($extraclasses) {
1494 $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
1496 echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
1497 if ($ismoving) {
1498 echo '<a title="'.$strmovefull.'"'.
1499 ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
1500 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1501 ' alt="'.$strmovehere.'" /></a><br />
1505 $classes = array('mod-indent');
1506 if (!empty($mod->indent)) {
1507 $classes[] = 'mod-indent-'.$mod->indent;
1508 if ($mod->indent > 15) {
1509 $classes[] = 'mod-indent-huge';
1512 echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
1514 // Get data about this course-module
1515 list($content, $instancename) =
1516 get_print_section_cm_text($modinfo->cms[$modnumber], $course);
1518 //Accessibility: for files get description via icon, this is very ugly hack!
1519 $altname = '';
1520 $altname = $mod->modfullname;
1521 // Avoid unnecessary duplication: if e.g. a forum name already
1522 // includes the word forum (or Forum, etc) then it is unhelpful
1523 // to include that in the accessible description that is added.
1524 if (false !== strpos(textlib::strtolower($instancename),
1525 textlib::strtolower($altname))) {
1526 $altname = '';
1528 // File type after name, for alphabetic lists (screen reader).
1529 if ($altname) {
1530 $altname = get_accesshide(' '.$altname);
1533 // We may be displaying this just in order to show information
1534 // about visibility, without the actual link
1535 $contentpart = '';
1536 if ($mod->uservisible) {
1537 // Nope - in this case the link is fully working for user
1538 $linkclasses = '';
1539 $textclasses = '';
1540 if ($accessiblebutdim) {
1541 $linkclasses .= ' dimmed';
1542 $textclasses .= ' dimmed_text';
1543 $accesstext = '<span class="accesshide">'.
1544 get_string('hiddenfromstudents').': </span>';
1545 } else {
1546 $accesstext = '';
1548 if ($linkclasses) {
1549 $linkcss = 'class="' . trim($linkclasses) . '" ';
1550 } else {
1551 $linkcss = '';
1553 if ($textclasses) {
1554 $textcss = 'class="' . trim($textclasses) . '" ';
1555 } else {
1556 $textcss = '';
1559 // Get on-click attribute value if specified
1560 $onclick = $mod->get_on_click();
1561 if ($onclick) {
1562 $onclick = ' onclick="' . $onclick . '"';
1565 if ($url = $mod->get_url()) {
1566 // Display link itself
1567 echo '<a ' . $linkcss . $mod->extra . $onclick .
1568 ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
1569 '" class="activityicon" alt="' .
1570 $modulename . '" /> ' .
1571 $accesstext . '<span class="instancename">' .
1572 $instancename . $altname . '</span></a>';
1574 // If specified, display extra content after link
1575 if ($content) {
1576 $contentpart = '<div class="' . trim('contentafterlink' . $textclasses) .
1577 '">' . $content . '</div>';
1579 } else {
1580 // No link, so display only content
1581 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1582 $accesstext . $content . '</div>';
1585 if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
1586 $groupings = groups_get_all_groupings($course->id);
1587 echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
1589 } else {
1590 $textclasses = $extraclasses;
1591 $textclasses .= ' dimmed_text';
1592 if ($textclasses) {
1593 $textcss = 'class="' . trim($textclasses) . '" ';
1594 } else {
1595 $textcss = '';
1597 $accesstext = '<span class="accesshide">' .
1598 get_string('notavailableyet', 'condition') .
1599 ': </span>';
1601 if ($url = $mod->get_url()) {
1602 // Display greyed-out text of link
1603 echo '<div ' . $textcss . $mod->extra .
1604 ' >' . '<img src="' . $mod->get_icon_url() .
1605 '" class="activityicon" alt="' .
1606 $modulename .
1607 '" /> <span>'. $instancename . $altname .
1608 '</span></div>';
1610 // Do not display content after link when it is greyed out like this.
1611 } else {
1612 // No link, so display only content (also greyed)
1613 $contentpart = '<div ' . $textcss . $mod->extra . '>' .
1614 $accesstext . $content . '</div>';
1618 // Module can put text after the link (e.g. forum unread)
1619 echo $mod->get_after_link();
1621 // If there is content but NO link (eg label), then display the
1622 // content here (BEFORE any icons). In this case cons must be
1623 // displayed after the content so that it makes more sense visually
1624 // and for accessibility reasons, e.g. if you have a one-line label
1625 // it should work similarly (at least in terms of ordering) to an
1626 // activity.
1627 if (empty($url)) {
1628 echo $contentpart;
1631 if ($isediting) {
1632 if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
1633 if (! $mod->groupmodelink = $groupbuttonslink) {
1634 $mod->groupmode = $course->groupmode;
1637 } else {
1638 $mod->groupmode = false;
1640 echo '&nbsp;&nbsp;';
1642 if ($sectionreturn) {
1643 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
1644 } else {
1645 echo make_editing_buttons($mod, $absolute, true, $mod->indent, 0);
1647 echo $mod->get_after_edit_icons();
1650 // Completion
1651 $completion = $hidecompletion
1652 ? COMPLETION_TRACKING_NONE
1653 : $completioninfo->is_enabled($mod);
1654 if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
1655 !isguestuser() && $mod->uservisible) {
1656 $completiondata = $completioninfo->get_data($mod,true);
1657 $completionicon = '';
1658 if ($isediting) {
1659 switch ($completion) {
1660 case COMPLETION_TRACKING_MANUAL :
1661 $completionicon = 'manual-enabled'; break;
1662 case COMPLETION_TRACKING_AUTOMATIC :
1663 $completionicon = 'auto-enabled'; break;
1664 default: // wtf
1666 } else if ($completion==COMPLETION_TRACKING_MANUAL) {
1667 switch($completiondata->completionstate) {
1668 case COMPLETION_INCOMPLETE:
1669 $completionicon = 'manual-n'; break;
1670 case COMPLETION_COMPLETE:
1671 $completionicon = 'manual-y'; break;
1673 } else { // Automatic
1674 switch($completiondata->completionstate) {
1675 case COMPLETION_INCOMPLETE:
1676 $completionicon = 'auto-n'; break;
1677 case COMPLETION_COMPLETE:
1678 $completionicon = 'auto-y'; break;
1679 case COMPLETION_COMPLETE_PASS:
1680 $completionicon = 'auto-pass'; break;
1681 case COMPLETION_COMPLETE_FAIL:
1682 $completionicon = 'auto-fail'; break;
1685 if ($completionicon) {
1686 $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
1687 $imgalt = s(get_string('completion-alt-'.$completionicon, 'completion', $mod->name));
1688 if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
1689 $imgtitle = s(get_string('completion-title-'.$completionicon, 'completion', $mod->name));
1690 $newstate =
1691 $completiondata->completionstate==COMPLETION_COMPLETE
1692 ? COMPLETION_INCOMPLETE
1693 : COMPLETION_COMPLETE;
1694 // In manual mode the icon is a toggle form...
1696 // If this completion state is used by the
1697 // conditional activities system, we need to turn
1698 // off the JS.
1699 if (!empty($CFG->enableavailability) &&
1700 condition_info::completion_value_used_as_condition($course, $mod)) {
1701 $extraclass = ' preventjs';
1702 } else {
1703 $extraclass = '';
1705 echo "
1706 <form class='togglecompletion$extraclass' method='post' action='".$CFG->wwwroot."/course/togglecompletion.php'><div>
1707 <input type='hidden' name='id' value='{$mod->id}' />
1708 <input type='hidden' name='modulename' value='".s($mod->name)."' />
1709 <input type='hidden' name='sesskey' value='".sesskey()."' />
1710 <input type='hidden' name='completionstate' value='$newstate' />
1711 <input type='image' src='$imgsrc' alt='$imgalt' title='$imgtitle' />
1712 </div></form>";
1713 } else {
1714 // In auto mode, or when editing, the icon is just an image
1715 echo "<span class='autocompletion'>";
1716 echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
1721 // If there is content AND a link, then display the content here
1722 // (AFTER any icons). Otherwise it was displayed before
1723 if (!empty($url)) {
1724 echo $contentpart;
1727 // Show availability information (for someone who isn't allowed to
1728 // see the activity itself, or for staff)
1729 if (!$mod->uservisible) {
1730 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
1731 } else if ($canviewhidden && !empty($CFG->enableavailability)) {
1732 $ci = new condition_info($mod);
1733 $fullinfo = $ci->get_full_information();
1734 if($fullinfo) {
1735 echo '<div class="availabilityinfo">'.get_string($mod->showavailability
1736 ? 'userrestriction_visible'
1737 : 'userrestriction_hidden','condition',
1738 $fullinfo).'</div>';
1742 echo html_writer::end_tag('div');
1743 echo html_writer::end_tag('li')."\n";
1746 } elseif ($ismoving) {
1747 echo "<ul class=\"section\">\n";
1750 if ($ismoving) {
1751 echo '<li><a title="'.$strmovefull.'"'.
1752 ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
1753 '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
1754 ' alt="'.$strmovehere.'" /></a></li>
1757 if (!empty($section->sequence) || $ismoving) {
1758 echo "</ul><!--class='section'-->\n\n";
1763 * Prints the menus to add activities and resources.
1765 function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false) {
1766 global $CFG, $OUTPUT;
1768 // check to see if user can add menus
1769 if (!has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $course->id))) {
1770 return false;
1773 // Retrieve all modules with associated metadata
1774 $modules = get_module_metadata($course, $modnames);
1776 // We'll sort resources and activities into two lists
1777 $resources = array();
1778 $activities = array();
1780 // We need to add the section section to the link for each module
1781 $sectionlink = '&section=' . $section;
1783 foreach ($modules as $module) {
1784 if (isset($module->types)) {
1785 // This module has a subtype
1786 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1787 $subtypes = array();
1788 foreach ($module->types as $subtype) {
1789 $subtypes[$subtype->link . $sectionlink] = $subtype->title;
1792 // Sort module subtypes into the list
1793 if (!empty($module->title)) {
1794 // This grouping has a name
1795 if ($module->archetype == MOD_CLASS_RESOURCE) {
1796 $resources[] = array($module->title=>$subtypes);
1797 } else {
1798 $activities[] = array($module->title=>$subtypes);
1800 } else {
1801 // This grouping does not have a name
1802 if ($module->archetype == MOD_CLASS_RESOURCE) {
1803 $resources = array_merge($resources, $subtypes);
1804 } else {
1805 $activities = array_merge($activities, $subtypes);
1808 } else {
1809 // This module has no subtypes
1810 if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
1811 $resources[$module->link . $sectionlink] = $module->title;
1812 } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
1813 // System modules cannot be added by user, do not add to dropdown
1814 } else {
1815 $activities[$module->link . $sectionlink] = $module->title;
1820 $straddactivity = get_string('addactivity');
1821 $straddresource = get_string('addresource');
1823 $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
1825 if (!$vertical) {
1826 $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
1829 if (!empty($resources)) {
1830 $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
1831 $select->set_help_icon('resources');
1832 $output .= $OUTPUT->render($select);
1835 if (!empty($activities)) {
1836 $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
1837 $select->set_help_icon('activities');
1838 $output .= $OUTPUT->render($select);
1841 if (!$vertical) {
1842 $output .= html_writer::end_tag('div');
1845 $output .= html_writer::end_tag('div');
1847 if (course_ajax_enabled($course)) {
1848 $straddeither = get_string('addresourceoractivity');
1849 // The module chooser link
1850 $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
1851 $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
1852 $icon = $OUTPUT->pix_icon('t/add', $straddeither);
1853 $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
1854 $modchooser.= html_writer::link('#', $icon.$span, array('class' => 'section-modchooser-link'));
1855 $modchooser.= html_writer::end_tag('div');
1856 $modchooser.= html_writer::end_tag('div');
1858 // Wrap the normal output in a noscript div
1859 $usemodchooser = get_user_preferences('usemodchooser', 1);
1860 if ($usemodchooser) {
1861 $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
1862 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
1863 } else {
1864 $output = html_writer::tag('div', $output, array('class' => 'visibleifjs addresourcedropdown'));
1865 $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hiddenifjs addresourcemodchooser'));
1867 $output = $modchooser . $output;
1870 if ($return) {
1871 return $output;
1872 } else {
1873 echo $output;
1878 * Retrieve all metadata for the requested modules
1880 * @param object $course The Course
1881 * @param array $modnames An array containing the list of modules and their
1882 * names
1883 * @return array A list of stdClass objects containing metadata about each
1884 * module
1886 function get_module_metadata($course, $modnames) {
1887 global $CFG, $OUTPUT;
1889 // get_module_metadata will be called once per section on the page and courses may show
1890 // different modules to one another
1891 static $modlist = array();
1892 if (!isset($modlist[$course->id])) {
1893 $modlist[$course->id] = array();
1896 $return = array();
1897 $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&add=';
1898 foreach($modnames as $modname => $modnamestr) {
1899 if (!course_allowed_module($course, $modname)) {
1900 continue;
1902 if (isset($modlist[$modname])) {
1903 // This module is already cached
1904 $return[$modname] = $modlist[$course->id][$modname];
1905 continue;
1908 // Include the module lib
1909 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1910 if (!file_exists($libfile)) {
1911 continue;
1913 include_once($libfile);
1915 // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1916 $gettypesfunc = $modname.'_get_types';
1917 if (function_exists($gettypesfunc)) {
1918 if ($types = $gettypesfunc()) {
1919 $group = new stdClass();
1920 $group->name = $modname;
1921 $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1922 foreach($types as $type) {
1923 if ($type->typestr === '--') {
1924 continue;
1926 if (strpos($type->typestr, '--') === 0) {
1927 $group->title = str_replace('--', '', $type->typestr);
1928 continue;
1930 // Set the Sub Type metadata
1931 $subtype = new stdClass();
1932 $subtype->title = $type->typestr;
1933 $subtype->type = str_replace('&amp;', '&', $type->type);
1934 $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1935 $subtype->archetype = $type->modclass;
1937 // The group archetype should match the subtype archetypes and all subtypes
1938 // should have the same archetype
1939 $group->archetype = $subtype->archetype;
1941 if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1942 $subtype->help = get_string('help' . $subtype->name, $modname);
1944 $subtype->link = $urlbase . $subtype->type;
1945 $group->types[] = $subtype;
1947 $modlist[$course->id][$modname] = $group;
1949 } else {
1950 $module = new stdClass();
1951 $module->title = get_string('modulename', $modname);
1952 $module->name = $modname;
1953 $module->link = $urlbase . $modname;
1954 $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1955 $sm = get_string_manager();
1956 if ($sm->string_exists('modulename_help', $modname)) {
1957 $module->help = get_string('modulename_help', $modname);
1958 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs
1959 $link = get_string('modulename_link', $modname);
1960 $linktext = get_string('morehelp');
1961 $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
1964 $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1965 $modlist[$course->id][$modname] = $module;
1967 $return[$modname] = $modlist[$course->id][$modname];
1970 return $return;
1974 * Return the course category context for the category with id $categoryid, except
1975 * that if $categoryid is 0, return the system context.
1977 * @param integer $categoryid a category id or 0.
1978 * @return object the corresponding context
1980 function get_category_or_system_context($categoryid) {
1981 if ($categoryid) {
1982 return get_context_instance(CONTEXT_COURSECAT, $categoryid);
1983 } else {
1984 return get_context_instance(CONTEXT_SYSTEM);
1989 * Gets the child categories of a given courses category. Uses a static cache
1990 * to make repeat calls efficient.
1992 * @param int $parentid the id of a course category.
1993 * @return array all the child course categories.
1995 function get_child_categories($parentid) {
1996 static $allcategories = null;
1998 // only fill in this variable the first time
1999 if (null == $allcategories) {
2000 $allcategories = array();
2002 $categories = get_categories();
2003 foreach ($categories as $category) {
2004 if (empty($allcategories[$category->parent])) {
2005 $allcategories[$category->parent] = array();
2007 $allcategories[$category->parent][] = $category;
2011 if (empty($allcategories[$parentid])) {
2012 return array();
2013 } else {
2014 return $allcategories[$parentid];
2019 * This function recursively travels the categories, building up a nice list
2020 * for display. It also makes an array that list all the parents for each
2021 * category.
2023 * For example, if you have a tree of categories like:
2024 * Miscellaneous (id = 1)
2025 * Subcategory (id = 2)
2026 * Sub-subcategory (id = 4)
2027 * Other category (id = 3)
2028 * Then after calling this function you will have
2029 * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
2030 * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
2031 * 3 => 'Other category');
2032 * $parents = array(2 => array(1), 4 => array(1, 2));
2034 * If you specify $requiredcapability, then only categories where the current
2035 * user has that capability will be added to $list, although all categories
2036 * will still be added to $parents, and if you only have $requiredcapability
2037 * in a child category, not the parent, then the child catgegory will still be
2038 * included.
2040 * If you specify the option $excluded, then that category, and all its children,
2041 * are omitted from the tree. This is useful when you are doing something like
2042 * moving categories, where you do not want to allow people to move a category
2043 * to be the child of itself.
2045 * @param array $list For output, accumulates an array categoryid => full category path name
2046 * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
2047 * @param string/array $requiredcapability if given, only categories where the current
2048 * user has this capability will be added to $list. Can also be an array of capabilities,
2049 * in which case they are all required.
2050 * @param integer $excludeid Omit this category and its children from the lists built.
2051 * @param object $category Build the tree starting at this category - otherwise starts at the top level.
2052 * @param string $path For internal use, as part of recursive calls.
2054 function make_categories_list(&$list, &$parents, $requiredcapability = '',
2055 $excludeid = 0, $category = NULL, $path = "") {
2057 // initialize the arrays if needed
2058 if (!is_array($list)) {
2059 $list = array();
2061 if (!is_array($parents)) {
2062 $parents = array();
2065 if (empty($category)) {
2066 // Start at the top level.
2067 $category = new stdClass;
2068 $category->id = 0;
2069 } else {
2070 // This is the excluded category, don't include it.
2071 if ($excludeid > 0 && $excludeid == $category->id) {
2072 return;
2075 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2076 $categoryname = format_string($category->name, true, array('context' => $context));
2078 // Update $path.
2079 if ($path) {
2080 $path = $path.' / '.$categoryname;
2081 } else {
2082 $path = $categoryname;
2085 // Add this category to $list, if the permissions check out.
2086 if (empty($requiredcapability)) {
2087 $list[$category->id] = $path;
2089 } else {
2090 $requiredcapability = (array)$requiredcapability;
2091 if (has_all_capabilities($requiredcapability, $context)) {
2092 $list[$category->id] = $path;
2097 // Add all the children recursively, while updating the parents array.
2098 if ($categories = get_child_categories($category->id)) {
2099 foreach ($categories as $cat) {
2100 if (!empty($category->id)) {
2101 if (isset($parents[$category->id])) {
2102 $parents[$cat->id] = $parents[$category->id];
2104 $parents[$cat->id][] = $category->id;
2106 make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
2112 * This function generates a structured array of courses and categories.
2114 * The depth of categories is limited by $CFG->maxcategorydepth however there
2115 * is no limit on the number of courses!
2117 * Suitable for use with the course renderers course_category_tree method:
2118 * $renderer = $PAGE->get_renderer('core','course');
2119 * echo $renderer->course_category_tree(get_course_category_tree());
2121 * @global moodle_database $DB
2122 * @param int $id
2123 * @param int $depth
2125 function get_course_category_tree($id = 0, $depth = 0) {
2126 global $DB, $CFG;
2127 $viewhiddencats = has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM));
2128 $categories = get_child_categories($id);
2129 $categoryids = array();
2130 foreach ($categories as $key => &$category) {
2131 if (!$category->visible && !$viewhiddencats) {
2132 unset($categories[$key]);
2133 continue;
2135 $categoryids[$category->id] = $category;
2136 if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
2137 list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
2138 foreach ($subcategories as $subid=>$subcat) {
2139 $categoryids[$subid] = $subcat;
2141 $category->courses = array();
2145 if ($depth > 0) {
2146 // This is a recursive call so return the required array
2147 return array($categories, $categoryids);
2150 // The depth is 0 this function has just been called so we can finish it off
2152 list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
2153 list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
2154 $sql = "SELECT
2155 c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
2156 $ccselect
2157 FROM {course} c
2158 $ccjoin
2159 WHERE c.category $catsql ORDER BY c.sortorder ASC";
2160 if ($courses = $DB->get_records_sql($sql, $catparams)) {
2161 // loop throught them
2162 foreach ($courses as $course) {
2163 if ($course->id == SITEID) {
2164 continue;
2166 context_instance_preload($course);
2167 if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))) {
2168 $categoryids[$course->category]->courses[$course->id] = $course;
2172 return $categories;
2176 * Recursive function to print out all the categories in a nice format
2177 * with or without courses included
2179 function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
2180 global $CFG;
2182 // maxcategorydepth == 0 meant no limit
2183 if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
2184 return;
2187 if (!$displaylist) {
2188 make_categories_list($displaylist, $parentslist);
2191 if ($category) {
2192 if ($category->visible or has_capability('moodle/category:viewhiddencategories', get_context_instance(CONTEXT_SYSTEM))) {
2193 print_category_info($category, $depth, $showcourses);
2194 } else {
2195 return; // Don't bother printing children of invisible categories
2198 } else {
2199 $category = new stdClass();
2200 $category->id = "0";
2203 if ($categories = get_child_categories($category->id)) { // Print all the children recursively
2204 $countcats = count($categories);
2205 $count = 0;
2206 $first = true;
2207 $last = false;
2208 foreach ($categories as $cat) {
2209 $count++;
2210 if ($count == $countcats) {
2211 $last = true;
2213 $up = $first ? false : true;
2214 $down = $last ? false : true;
2215 $first = false;
2217 print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
2223 * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
2225 function make_categories_options() {
2226 make_categories_list($cats,$parents);
2227 foreach ($cats as $key => $value) {
2228 if (array_key_exists($key,$parents)) {
2229 if ($indent = count($parents[$key])) {
2230 for ($i = 0; $i < $indent; $i++) {
2231 $cats[$key] = '&nbsp;'.$cats[$key];
2236 return $cats;
2240 * Gets the name of a course to be displayed when showing a list of courses.
2241 * By default this is just $course->fullname but user can configure it. The
2242 * result of this function should be passed through print_string.
2243 * @param object $course Moodle course object
2244 * @return string Display name of course (either fullname or short + fullname)
2246 function get_course_display_name_for_list($course) {
2247 global $CFG;
2248 if (!empty($CFG->courselistshortnames)) {
2249 return $course->shortname . ' ' .$course->fullname;
2250 } else {
2251 return $course->fullname;
2256 * Prints the category info in indented fashion
2257 * This function is only used by print_whole_category_list() above
2259 function print_category_info($category, $depth=0, $showcourses = false) {
2260 global $CFG, $DB, $OUTPUT;
2262 $strsummary = get_string('summary');
2264 $catlinkcss = null;
2265 if (!$category->visible) {
2266 $catlinkcss = array('class'=>'dimmed');
2268 static $coursecount = null;
2269 if (null === $coursecount) {
2270 // only need to check this once
2271 $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
2274 if ($showcourses and $coursecount) {
2275 $catimage = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
2276 } else {
2277 $catimage = "&nbsp;";
2280 $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
2281 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
2282 $fullname = format_string($category->name, true, array('context' => $context));
2284 if ($showcourses and $coursecount) {
2285 echo '<div class="categorylist clearfix">';
2286 $cat = '';
2287 $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
2288 $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2289 $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
2291 $html = '';
2292 if ($depth > 0) {
2293 for ($i=0; $i< $depth; $i++) {
2294 $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
2295 $cat = '';
2297 } else {
2298 $html = $cat;
2300 echo html_writer::tag('div', $html, array('class'=>'category'));
2301 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2303 // does the depth exceed maxcategorydepth
2304 // maxcategorydepth == 0 or unset meant no limit
2305 $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
2306 if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
2307 foreach ($courses as $course) {
2308 $linkcss = null;
2309 if (!$course->visible) {
2310 $linkcss = array('class'=>'dimmed');
2313 $coursename = get_course_display_name_for_list($course);
2314 $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
2316 // print enrol info
2317 $courseicon = '';
2318 if ($icons = enrol_get_course_info_icons($course)) {
2319 foreach ($icons as $pix_icon) {
2320 $courseicon = $OUTPUT->render($pix_icon).' ';
2324 $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
2326 if ($course->summary) {
2327 $link = new moodle_url('/course/info.php?id='.$course->id);
2328 $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
2329 new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
2330 array('title'=>$strsummary));
2332 $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
2335 $html = '';
2336 for ($i=0; $i <= $depth; $i++) {
2337 $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
2338 $coursecontent = '';
2340 echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
2343 echo '</div>';
2344 } else {
2345 echo '<div class="categorylist">';
2346 $html = '';
2347 $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
2348 if (count($courses) > 0) {
2349 $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
2352 if ($depth > 0) {
2353 for ($i=0; $i< $depth; $i++) {
2354 $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
2355 $cat = '';
2357 } else {
2358 $html = $cat;
2361 echo html_writer::tag('div', $html, array('class'=>'category'));
2362 echo html_writer::tag('div', '', array('class'=>'clearfloat'));
2363 echo '</div>';
2368 * Print the buttons relating to course requests.
2370 * @param object $systemcontext the system context.
2372 function print_course_request_buttons($systemcontext) {
2373 global $CFG, $DB, $OUTPUT;
2374 if (empty($CFG->enablecourserequests)) {
2375 return;
2377 if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
2378 /// Print a button to request a new course
2379 echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
2381 /// Print a button to manage pending requests
2382 if (has_capability('moodle/site:approvecourse', $systemcontext)) {
2383 $disabled = !$DB->record_exists('course_request', array());
2384 echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
2389 * Does the user have permission to edit things in this category?
2391 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2392 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
2394 function can_edit_in_category($categoryid = 0) {
2395 $context = get_category_or_system_context($categoryid);
2396 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
2400 * Prints the turn editing on/off button on course/index.php or course/category.php.
2402 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
2403 * @return string HTML of the editing button, or empty string, if this user is not allowed
2404 * to see it.
2406 function update_category_button($categoryid = 0) {
2407 global $CFG, $PAGE, $OUTPUT;
2409 // Check permissions.
2410 if (!can_edit_in_category($categoryid)) {
2411 return '';
2414 // Work out the appropriate action.
2415 if ($PAGE->user_is_editing()) {
2416 $label = get_string('turneditingoff');
2417 $edit = 'off';
2418 } else {
2419 $label = get_string('turneditingon');
2420 $edit = 'on';
2423 // Generate the button HTML.
2424 $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
2425 if ($categoryid) {
2426 $options['id'] = $categoryid;
2427 $page = 'category.php';
2428 } else {
2429 $page = 'index.php';
2431 return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
2435 * Category is 0 (for all courses) or an object
2437 function print_courses($category) {
2438 global $CFG, $OUTPUT;
2440 if (!is_object($category) && $category==0) {
2441 $categories = get_child_categories(0); // Parent = 0 ie top-level categories only
2442 if (is_array($categories) && count($categories) == 1) {
2443 $category = array_shift($categories);
2444 $courses = get_courses_wmanagers($category->id,
2445 'c.sortorder ASC',
2446 array('summary','summaryformat'));
2447 } else {
2448 $courses = get_courses_wmanagers('all',
2449 'c.sortorder ASC',
2450 array('summary','summaryformat'));
2452 unset($categories);
2453 } else {
2454 $courses = get_courses_wmanagers($category->id,
2455 'c.sortorder ASC',
2456 array('summary','summaryformat'));
2459 if ($courses) {
2460 echo html_writer::start_tag('ul', array('class'=>'unlist'));
2461 foreach ($courses as $course) {
2462 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
2463 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
2464 echo html_writer::start_tag('li');
2465 print_course($course);
2466 echo html_writer::end_tag('li');
2469 echo html_writer::end_tag('ul');
2470 } else {
2471 echo $OUTPUT->heading(get_string("nocoursesyet"));
2472 $context = get_context_instance(CONTEXT_SYSTEM);
2473 if (has_capability('moodle/course:create', $context)) {
2474 $options = array();
2475 if (!empty($category->id)) {
2476 $options['category'] = $category->id;
2477 } else {
2478 $options['category'] = $CFG->defaultrequestcategory;
2480 echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
2481 echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
2482 echo html_writer::end_tag('div');
2488 * Print a description of a course, suitable for browsing in a list.
2490 * @param object $course the course object.
2491 * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
2493 function print_course($course, $highlightterms = '') {
2494 global $CFG, $USER, $DB, $OUTPUT;
2496 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2498 // Rewrite file URLs so that they are correct
2499 $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
2501 echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
2502 echo html_writer::start_tag('div', array('class'=>'info'));
2503 echo html_writer::start_tag('h3', array('class'=>'name'));
2505 $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
2507 $coursename = get_course_display_name_for_list($course);
2508 $linktext = highlight($highlightterms, format_string($coursename));
2509 $linkparams = array('title'=>get_string('entercourse'));
2510 if (empty($course->visible)) {
2511 $linkparams['class'] = 'dimmed';
2513 echo html_writer::link($linkhref, $linktext, $linkparams);
2514 echo html_writer::end_tag('h3');
2516 /// first find all roles that are supposed to be displayed
2517 if (!empty($CFG->coursecontact)) {
2518 $managerroles = explode(',', $CFG->coursecontact);
2519 $namesarray = array();
2520 $rusers = array();
2522 if (!isset($course->managers)) {
2523 $rusers = get_role_users($managerroles, $context, true,
2524 'ra.id AS raid, u.id, u.username, u.firstname, u.lastname,
2525 r.name AS rolename, r.sortorder, r.id AS roleid',
2526 'r.sortorder ASC, u.lastname ASC');
2527 } else {
2528 // use the managers array if we have it for perf reasosn
2529 // populate the datastructure like output of get_role_users();
2530 foreach ($course->managers as $manager) {
2531 $u = new stdClass();
2532 $u = $manager->user;
2533 $u->roleid = $manager->roleid;
2534 $u->rolename = $manager->rolename;
2536 $rusers[] = $u;
2540 /// Rename some of the role names if needed
2541 if (isset($context)) {
2542 $aliasnames = $DB->get_records('role_names', array('contextid'=>$context->id), '', 'roleid,contextid,name');
2545 $namesarray = array();
2546 $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
2547 foreach ($rusers as $ra) {
2548 if (isset($namesarray[$ra->id])) {
2549 // only display a user once with the higest sortorder role
2550 continue;
2553 if (isset($aliasnames[$ra->roleid])) {
2554 $ra->rolename = $aliasnames[$ra->roleid]->name;
2557 $fullname = fullname($ra, $canviewfullnames);
2558 $namesarray[$ra->id] = format_string($ra->rolename).': '.
2559 html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
2562 if (!empty($namesarray)) {
2563 echo html_writer::start_tag('ul', array('class'=>'teachers'));
2564 foreach ($namesarray as $name) {
2565 echo html_writer::tag('li', $name);
2567 echo html_writer::end_tag('ul');
2570 echo html_writer::end_tag('div'); // End of info div
2572 echo html_writer::start_tag('div', array('class'=>'summary'));
2573 $options = new stdClass();
2574 $options->noclean = true;
2575 $options->para = false;
2576 $options->overflowdiv = true;
2577 if (!isset($course->summaryformat)) {
2578 $course->summaryformat = FORMAT_MOODLE;
2580 echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options, $course->id));
2581 if ($icons = enrol_get_course_info_icons($course)) {
2582 echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
2583 foreach ($icons as $icon) {
2584 echo $OUTPUT->render($icon);
2586 echo html_writer::end_tag('div'); // End of enrolmenticons div
2588 echo html_writer::end_tag('div'); // End of summary div
2589 echo html_writer::end_tag('div'); // End of coursebox div
2593 * Prints custom user information on the home page.
2594 * Over time this can include all sorts of information
2596 function print_my_moodle() {
2597 global $USER, $CFG, $DB, $OUTPUT;
2599 if (!isloggedin() or isguestuser()) {
2600 print_error('nopermissions', '', '', 'See My Moodle');
2603 $courses = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
2604 $rhosts = array();
2605 $rcourses = array();
2606 if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
2607 $rcourses = get_my_remotecourses($USER->id);
2608 $rhosts = get_my_remotehosts();
2611 if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
2613 if (!empty($courses)) {
2614 echo '<ul class="unlist">';
2615 foreach ($courses as $course) {
2616 if ($course->id == SITEID) {
2617 continue;
2619 echo '<li>';
2620 print_course($course);
2621 echo "</li>\n";
2623 echo "</ul>\n";
2626 // MNET
2627 if (!empty($rcourses)) {
2628 // at the IDP, we know of all the remote courses
2629 foreach ($rcourses as $course) {
2630 print_remote_course($course, "100%");
2632 } elseif (!empty($rhosts)) {
2633 // non-IDP, we know of all the remote servers, but not courses
2634 foreach ($rhosts as $host) {
2635 print_remote_host($host, "100%");
2638 unset($course);
2639 unset($host);
2641 if ($DB->count_records("course") > (count($courses) + 1) ) { // Some courses not being displayed
2642 echo "<table width=\"100%\"><tr><td align=\"center\">";
2643 print_course_search("", false, "short");
2644 echo "</td><td align=\"center\">";
2645 echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
2646 echo "</td></tr></table>\n";
2649 } else {
2650 if ($DB->count_records("course_categories") > 1) {
2651 echo $OUTPUT->box_start("categorybox");
2652 print_whole_category_list();
2653 echo $OUTPUT->box_end();
2654 } else {
2655 print_courses(0);
2661 function print_course_search($value="", $return=false, $format="plain") {
2662 global $CFG;
2663 static $count = 0;
2665 $count++;
2667 $id = 'coursesearch';
2669 if ($count > 1) {
2670 $id .= $count;
2673 $strsearchcourses= get_string("searchcourses");
2675 if ($format == 'plain') {
2676 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2677 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2678 $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
2679 $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
2680 $output .= '<input type="submit" value="'.get_string('go').'" />';
2681 $output .= '</fieldset></form>';
2682 } else if ($format == 'short') {
2683 $output = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2684 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2685 $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
2686 $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2687 $output .= '<input type="submit" value="'.get_string('go').'" />';
2688 $output .= '</fieldset></form>';
2689 } else if ($format == 'navbar') {
2690 $output = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
2691 $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
2692 $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
2693 $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
2694 $output .= '<input type="submit" value="'.get_string('go').'" />';
2695 $output .= '</fieldset></form>';
2698 if ($return) {
2699 return $output;
2701 echo $output;
2704 function print_remote_course($course, $width="100%") {
2705 global $CFG, $USER;
2707 $linkcss = '';
2709 $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}";
2711 echo '<div class="coursebox remotecoursebox clearfix">';
2712 echo '<div class="info">';
2713 echo '<div class="name"><a title="'.get_string('entercourse').'"'.
2714 $linkcss.' href="'.$url.'">'
2715 . format_string($course->fullname) .'</a><br />'
2716 . format_string($course->hostname) . ' : '
2717 . format_string($course->cat_name) . ' : '
2718 . format_string($course->shortname). '</div>';
2719 echo '</div><div class="summary">';
2720 $options = new stdClass();
2721 $options->noclean = true;
2722 $options->para = false;
2723 $options->overflowdiv = true;
2724 echo format_text($course->summary, $course->summaryformat, $options);
2725 echo '</div>';
2726 echo '</div>';
2729 function print_remote_host($host, $width="100%") {
2730 global $OUTPUT;
2732 $linkcss = '';
2734 echo '<div class="coursebox clearfix">';
2735 echo '<div class="info">';
2736 echo '<div class="name">';
2737 echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
2738 echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
2739 . s($host['name']).'</a> - ';
2740 echo $host['count'] . ' ' . get_string('courses');
2741 echo '</div>';
2742 echo '</div>';
2743 echo '</div>';
2747 /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
2749 function add_course_module($mod) {
2750 global $DB;
2752 $mod->added = time();
2753 unset($mod->id);
2755 return $DB->insert_record("course_modules", $mod);
2759 * Returns course section - creates new if does not exist yet.
2760 * @param int $relative section number
2761 * @param int $courseid
2762 * @return object $course_section object
2764 function get_course_section($section, $courseid) {
2765 global $DB;
2767 if ($cw = $DB->get_record("course_sections", array("section"=>$section, "course"=>$courseid))) {
2768 return $cw;
2770 $cw = new stdClass();
2771 $cw->course = $courseid;
2772 $cw->section = $section;
2773 $cw->summary = "";
2774 $cw->summaryformat = FORMAT_HTML;
2775 $cw->sequence = "";
2776 $id = $DB->insert_record("course_sections", $cw);
2777 rebuild_course_cache($courseid, true);
2778 return $DB->get_record("course_sections", array("id"=>$id));
2782 * Given a full mod object with section and course already defined, adds this module to that section.
2784 * @param object $mod
2785 * @param int $beforemod An existing ID which we will insert the new module before
2786 * @return int The course_sections ID where the mod is inserted
2788 function add_mod_to_section($mod, $beforemod=NULL) {
2789 global $DB;
2791 if ($section = $DB->get_record("course_sections", array("course"=>$mod->course, "section"=>$mod->section))) {
2793 $section->sequence = trim($section->sequence);
2795 if (empty($section->sequence)) {
2796 $newsequence = "$mod->coursemodule";
2798 } else if ($beforemod) {
2799 $modarray = explode(",", $section->sequence);
2801 if ($key = array_keys($modarray, $beforemod->id)) {
2802 $insertarray = array($mod->id, $beforemod->id);
2803 array_splice($modarray, $key[0], 1, $insertarray);
2804 $newsequence = implode(",", $modarray);
2806 } else { // Just tack it on the end anyway
2807 $newsequence = "$section->sequence,$mod->coursemodule";
2810 } else {
2811 $newsequence = "$section->sequence,$mod->coursemodule";
2814 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2815 return $section->id; // Return course_sections ID that was used.
2817 } else { // Insert a new record
2818 $section = new stdClass();
2819 $section->course = $mod->course;
2820 $section->section = $mod->section;
2821 $section->summary = "";
2822 $section->summaryformat = FORMAT_HTML;
2823 $section->sequence = $mod->coursemodule;
2824 return $DB->insert_record("course_sections", $section);
2828 function set_coursemodule_groupmode($id, $groupmode) {
2829 global $DB;
2830 return $DB->set_field("course_modules", "groupmode", $groupmode, array("id"=>$id));
2833 function set_coursemodule_idnumber($id, $idnumber) {
2834 global $DB;
2835 return $DB->set_field("course_modules", "idnumber", $idnumber, array("id"=>$id));
2839 * $prevstateoverrides = true will set the visibility of the course module
2840 * to what is defined in visibleold. This enables us to remember the current
2841 * visibility when making a whole section hidden, so that when we toggle
2842 * that section back to visible, we are able to return the visibility of
2843 * the course module back to what it was originally.
2845 function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
2846 global $DB, $CFG;
2847 require_once($CFG->libdir.'/gradelib.php');
2849 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2850 return false;
2852 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
2853 return false;
2855 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2856 foreach($events as $event) {
2857 if ($visible) {
2858 show_event($event);
2859 } else {
2860 hide_event($event);
2865 // hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there
2866 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
2867 if ($grade_items) {
2868 foreach ($grade_items as $grade_item) {
2869 $grade_item->set_hidden(!$visible);
2873 if ($prevstateoverrides) {
2874 if ($visible == '0') {
2875 // Remember the current visible state so we can toggle this back.
2876 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id'=>$id));
2877 } else {
2878 // Get the previous saved visible states.
2879 return $DB->set_field('course_modules', 'visible', $cm->visibleold, array('id'=>$id));
2882 return $DB->set_field("course_modules", "visible", $visible, array("id"=>$id));
2886 * Delete a course module and any associated data at the course level (events)
2887 * Until 1.5 this function simply marked a deleted flag ... now it
2888 * deletes it completely.
2891 function delete_course_module($id) {
2892 global $CFG, $DB;
2893 require_once($CFG->libdir.'/gradelib.php');
2894 require_once($CFG->dirroot.'/blog/lib.php');
2896 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
2897 return true;
2899 $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
2900 //delete events from calendar
2901 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
2902 foreach($events as $event) {
2903 delete_event($event->id);
2906 //delete grade items, outcome items and grades attached to modules
2907 if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
2908 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
2909 foreach ($grade_items as $grade_item) {
2910 $grade_item->delete('moddelete');
2913 // Delete completion and availability data; it is better to do this even if the
2914 // features are not turned on, in case they were turned on previously (these will be
2915 // very quick on an empty table)
2916 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
2917 $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
2918 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
2919 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
2921 delete_context(CONTEXT_MODULE, $cm->id);
2922 return $DB->delete_records('course_modules', array('id'=>$cm->id));
2925 function delete_mod_from_section($mod, $section) {
2926 global $DB;
2928 if ($section = $DB->get_record("course_sections", array("id"=>$section)) ) {
2930 $modarray = explode(",", $section->sequence);
2932 if ($key = array_keys ($modarray, $mod)) {
2933 array_splice($modarray, $key[0], 1);
2934 $newsequence = implode(",", $modarray);
2935 return $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
2936 } else {
2937 return false;
2941 return false;
2945 * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
2947 * @param object $course course object
2948 * @param int $section Section number (not id!!!)
2949 * @param int $move (-1 or 1)
2950 * @return boolean true if section moved successfully
2952 function move_section($course, $section, $move) {
2953 /// Moves a whole course section up and down within the course
2954 global $USER, $DB;
2956 if (!$move) {
2957 return true;
2960 $sectiondest = $section + $move;
2962 if ($sectiondest > $course->numsections or $sectiondest < 1) {
2963 return false;
2966 if (!$sectionrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$section))) {
2967 return false;
2970 if (!$sectiondestrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$sectiondest))) {
2971 return false;
2974 // Three-step change ensures that the section always remains unique (there is
2975 // a unique index now)
2976 $DB->set_field("course_sections", "section", -$sectiondest, array("id"=>$sectionrecord->id));
2977 $DB->set_field("course_sections", "section", $section, array("id"=>$sectiondestrecord->id));
2978 $DB->set_field("course_sections", "section", $sectiondest, array("id"=>$sectionrecord->id));
2980 // Update highlighting if the move affects highlighted section
2981 if ($course->marker == $section) {
2982 course_set_marker($course->id, $sectiondest);
2983 } elseif ($course->marker == $sectiondest) {
2984 course_set_marker($course->id, $section);
2988 // Fix order if needed. The database prevents duplicate sections, but it is
2989 // possible there could be a gap in the numbering.
2990 $sections = $DB->get_records('course_sections', array('course'=>$course->id), 'section ASC');
2991 $n = 0;
2992 foreach ($sections as $section) {
2993 if ($section->section != $n) {
2994 $DB->set_field('course_sections', 'section', $n, array('id'=>$section->id));
2996 $n++;
2998 return true;
3002 * Moves a section within a course, from a position to another.
3003 * Be very careful: $section and $destination refer to section number,
3004 * not id!.
3006 * @param object $course
3007 * @param int $section Section number (not id!!!)
3008 * @param int $destination
3009 * @return boolean Result
3011 function move_section_to($course, $section, $destination) {
3012 /// Moves a whole course section up and down within the course
3013 global $USER, $DB;
3015 if (!$destination && $destination != 0) {
3016 return true;
3019 if ($destination > $course->numsections) {
3020 return false;
3023 // Get all sections for this course and re-order them (2 of them should now share the same section number)
3024 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
3025 'section ASC, id ASC', 'id, section')) {
3026 return false;
3029 $movedsections = reorder_sections($sections, $section, $destination);
3031 // Update all sections. Do this in 2 steps to avoid breaking database
3032 // uniqueness constraint
3033 $transaction = $DB->start_delegated_transaction();
3034 foreach ($movedsections as $id => $position) {
3035 if ($sections[$id] !== $position) {
3036 $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
3039 foreach ($movedsections as $id => $position) {
3040 if ($sections[$id] !== $position) {
3041 $DB->set_field('course_sections', 'section', $position, array('id' => $id));
3045 // Adjust destination to reflect the actual section
3046 $moveup = false;
3047 if ($section > $destination) {
3048 $destination++;
3049 $moveup = true;
3052 // If we move the highlighted section itself, then just highlight the destination.
3053 // Adjust the higlighted section location if we move something over it either direction.
3054 if ($section == $course->marker) {
3055 course_set_marker($course->id, $destination);
3056 } elseif ($moveup && $section > $course->marker && $course->marker >= $destination) {
3057 course_set_marker($course->id, $course->marker+1);
3058 } elseif (!$moveup && $section < $course->marker && $course->marker <= $destination) {
3059 course_set_marker($course->id, $course->marker-1);
3062 $transaction->allow_commit();
3063 return true;
3067 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
3068 * an original position number and a target position number, rebuilds the array so that the
3069 * move is made without any duplication of section positions.
3070 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
3071 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
3073 * @param array $sections
3074 * @param int $origin_position
3075 * @param int $target_position
3076 * @return array
3078 function reorder_sections($sections, $origin_position, $target_position) {
3079 if (!is_array($sections)) {
3080 return false;
3083 // We can't move section position 0
3084 if ($origin_position < 1) {
3085 echo "We can't move section position 0";
3086 return false;
3089 // Locate origin section in sections array
3090 if (!$origin_key = array_search($origin_position, $sections)) {
3091 echo "searched position not in sections array";
3092 return false; // searched position not in sections array
3095 // Extract origin section
3096 $origin_section = $sections[$origin_key];
3097 unset($sections[$origin_key]);
3099 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
3100 $found = false;
3101 $append_array = array();
3102 foreach ($sections as $id => $position) {
3103 if ($found) {
3104 $append_array[$id] = $position;
3105 unset($sections[$id]);
3107 if ($position == $target_position) {
3108 $found = true;
3112 // Append moved section
3113 $sections[$origin_key] = $origin_section;
3115 // Append rest of array (if applicable)
3116 if (!empty($append_array)) {
3117 foreach ($append_array as $id => $position) {
3118 $sections[$id] = $position;
3122 // Renumber positions
3123 $position = 0;
3124 foreach ($sections as $id => $p) {
3125 $sections[$id] = $position;
3126 $position++;
3129 return $sections;
3134 * Move the module object $mod to the specified $section
3135 * If $beforemod exists then that is the module
3136 * before which $modid should be inserted
3137 * All parameters are objects
3139 function moveto_module($mod, $section, $beforemod=NULL) {
3140 global $DB, $OUTPUT;
3142 /// Remove original module from original section
3143 if (! delete_mod_from_section($mod->id, $mod->section)) {
3144 echo $OUTPUT->notification("Could not delete module from existing section");
3147 /// Update module itself if necessary
3149 if ($mod->section != $section->id) {
3150 $mod->section = $section->id;
3151 $DB->update_record("course_modules", $mod);
3152 // if moving to a hidden section then hide module
3153 if (!$section->visible) {
3154 set_coursemodule_visible($mod->id, 0);
3158 /// Add the module into the new section
3160 $mod->course = $section->course;
3161 $mod->section = $section->section; // need relative reference
3162 $mod->coursemodule = $mod->id;
3164 if (! add_mod_to_section($mod, $beforemod)) {
3165 return false;
3168 return true;
3172 * Produces the editing buttons for a module
3174 * @global core_renderer $OUTPUT
3175 * @staticvar type $str
3176 * @param stdClass $mod The module to produce editing buttons for
3177 * @param bool $absolute_ignored ignored - all links are absolute
3178 * @param bool $moveselect If true a move seleciton process is used (default true)
3179 * @param int $indent The current indenting
3180 * @param int $section The section to link back to
3181 * @return string XHTML for the editing buttons
3183 function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) {
3184 global $CFG, $OUTPUT, $COURSE;
3186 static $str;
3188 $coursecontext = get_context_instance(CONTEXT_COURSE, $mod->course);
3189 $modcontext = get_context_instance(CONTEXT_MODULE, $mod->id);
3191 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
3192 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
3194 // no permission to edit anything
3195 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
3196 return false;
3199 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
3201 if (!isset($str)) {
3202 $str = new stdClass;
3203 $str->assign = get_string("assignroles", 'role');
3204 $str->delete = get_string("delete");
3205 $str->move = get_string("move");
3206 $str->moveup = get_string("moveup");
3207 $str->movedown = get_string("movedown");
3208 $str->moveright = get_string("moveright");
3209 $str->moveleft = get_string("moveleft");
3210 $str->update = get_string("update");
3211 $str->duplicate = get_string("duplicate");
3212 $str->hide = get_string("hide");
3213 $str->show = get_string("show");
3214 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
3215 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
3216 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
3217 $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
3218 $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
3219 $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
3220 $str->edittitle = get_string('edittitle', 'moodle');
3223 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
3225 if ($section >= 0) {
3226 $baseurl->param('sr', $section);
3228 $actions = array();
3230 // AJAX edit title
3231 if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
3232 $actions[] = new action_link(
3233 new moodle_url($baseurl, array('update' => $mod->id)),
3234 new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')),
3235 null,
3236 array('class' => 'editing_title', 'title' => $str->edittitle)
3240 // leftright
3241 if ($hasmanageactivities) {
3242 if (right_to_left()) { // Exchange arrows on RTL
3243 $rightarrow = 't/left';
3244 $leftarrow = 't/right';
3245 } else {
3246 $rightarrow = 't/right';
3247 $leftarrow = 't/left';
3250 if ($indent > 0) {
3251 $actions[] = new action_link(
3252 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
3253 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall')),
3254 null,
3255 array('class' => 'editing_moveleft', 'title' => $str->moveleft)
3258 if ($indent >= 0) {
3259 $actions[] = new action_link(
3260 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
3261 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall')),
3262 null,
3263 array('class' => 'editing_moveright', 'title' => $str->moveright)
3268 // move
3269 if ($hasmanageactivities) {
3270 if ($moveselect) {
3271 $actions[] = new action_link(
3272 new moodle_url($baseurl, array('copy' => $mod->id)),
3273 new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall')),
3274 null,
3275 array('class' => 'editing_move', 'title' => $str->move)
3277 } else {
3278 $actions[] = new action_link(
3279 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
3280 new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall')),
3281 null,
3282 array('class' => 'editing_moveup', 'title' => $str->moveup)
3284 $actions[] = new action_link(
3285 new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
3286 new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall')),
3287 null,
3288 array('class' => 'editing_movedown', 'title' => $str->movedown)
3293 // Update
3294 if ($hasmanageactivities) {
3295 $actions[] = new action_link(
3296 new moodle_url($baseurl, array('update' => $mod->id)),
3297 new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall')),
3298 null,
3299 array('class' => 'editing_update', 'title' => $str->update)
3303 // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
3304 if (has_all_capabilities($dupecaps, $coursecontext)) {
3305 $actions[] = new action_link(
3306 new moodle_url($baseurl, array('duplicate' => $mod->id)),
3307 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
3308 null,
3309 array('class' => 'editing_duplicate', 'title' => $str->duplicate)
3313 // Delete
3314 if ($hasmanageactivities) {
3315 $actions[] = new action_link(
3316 new moodle_url($baseurl, array('delete' => $mod->id)),
3317 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall')),
3318 null,
3319 array('class' => 'editing_delete', 'title' => $str->delete)
3323 // hideshow
3324 if (has_capability('moodle/course:activityvisibility', $modcontext)) {
3325 if ($mod->visible) {
3326 $actions[] = new action_link(
3327 new moodle_url($baseurl, array('hide' => $mod->id)),
3328 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall')),
3329 null,
3330 array('class' => 'editing_hide', 'title' => $str->hide)
3332 } else {
3333 $actions[] = new action_link(
3334 new moodle_url($baseurl, array('show' => $mod->id)),
3335 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall')),
3336 null,
3337 array('class' => 'editing_show', 'title' => $str->show)
3342 // groupmode
3343 if ($hasmanageactivities and $mod->groupmode !== false) {
3344 if ($mod->groupmode == SEPARATEGROUPS) {
3345 $groupmode = 0;
3346 $grouptitle = $str->groupsseparate;
3347 $forcedgrouptitle = $str->forcedgroupsseparate;
3348 $groupclass = 'editing_groupsseparate';
3349 $groupimage = 't/groups';
3350 } else if ($mod->groupmode == VISIBLEGROUPS) {
3351 $groupmode = 1;
3352 $grouptitle = $str->groupsvisible;
3353 $forcedgrouptitle = $str->forcedgroupsvisible;
3354 $groupclass = 'editing_groupsvisible';
3355 $groupimage = 't/groupv';
3356 } else {
3357 $groupmode = 2;
3358 $grouptitle = $str->groupsnone;
3359 $forcedgrouptitle = $str->forcedgroupsnone;
3360 $groupclass = 'editing_groupsnone';
3361 $groupimage = 't/groupn';
3363 if ($mod->groupmodelink) {
3364 $actions[] = new action_link(
3365 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
3366 new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
3367 null,
3368 array('class' => $groupclass, 'title' => $grouptitle)
3370 } else {
3371 $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
3375 // Assign
3376 if (has_capability('moodle/role:assign', $modcontext)){
3377 $actions[] = new action_link(
3378 new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
3379 new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall')),
3380 null,
3381 array('class' => 'editing_assign', 'title' => $str->assign)
3385 $output = html_writer::start_tag('span', array('class' => 'commands'));
3386 foreach ($actions as $action) {
3387 if ($action instanceof renderable) {
3388 $output .= $OUTPUT->render($action);
3389 } else {
3390 $output .= $action;
3393 $output .= html_writer::end_tag('span');
3394 return $output;
3398 * given a course object with shortname & fullname, this function will
3399 * truncate the the number of chars allowed and add ... if it was too long
3401 function course_format_name ($course,$max=100) {
3403 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3404 $shortname = format_string($course->shortname, true, array('context' => $context));
3405 $fullname = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
3406 $str = $shortname.': '. $fullname;
3407 if (textlib::strlen($str) <= $max) {
3408 return $str;
3410 else {
3411 return textlib::substr($str,0,$max-3).'...';
3416 * Is the user allowed to add this type of module to this course?
3417 * @param object $course the course settings. Only $course->id is used.
3418 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
3419 * @return bool whether the current user is allowed to add this type of module to this course.
3421 function course_allowed_module($course, $modname) {
3422 global $DB;
3424 if (is_numeric($modname)) {
3425 throw new coding_exception('Function course_allowed_module no longer
3426 supports numeric module ids. Please update your code to pass the module name.');
3429 $capability = 'mod/' . $modname . ':addinstance';
3430 if (!get_capability_info($capability)) {
3431 // Debug warning that the capability does not exist, but no more than once per page.
3432 static $warned = array();
3433 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
3434 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
3435 debugging('The module ' . $modname . ' does not define the standard capability ' .
3436 $capability , DEBUG_DEVELOPER);
3437 $warned[$modname] = 1;
3440 // If the capability does not exist, the module can always be added.
3441 return true;
3444 $coursecontext = context_course::instance($course->id);
3445 return has_capability($capability, $coursecontext);
3449 * Recursively delete category including all subcategories and courses.
3450 * @param stdClass $category
3451 * @param boolean $showfeedback display some notices
3452 * @return array return deleted courses
3454 function category_delete_full($category, $showfeedback=true) {
3455 global $CFG, $DB;
3456 require_once($CFG->libdir.'/gradelib.php');
3457 require_once($CFG->libdir.'/questionlib.php');
3458 require_once($CFG->dirroot.'/cohort/lib.php');
3460 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3461 foreach ($children as $childcat) {
3462 category_delete_full($childcat, $showfeedback);
3466 $deletedcourses = array();
3467 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
3468 foreach ($courses as $course) {
3469 if (!delete_course($course, false)) {
3470 throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
3472 $deletedcourses[] = $course;
3476 // move or delete cohorts in this context
3477 cohort_delete_category($category);
3479 // now delete anything that may depend on course category context
3480 grade_course_category_delete($category->id, 0, $showfeedback);
3481 if (!question_delete_course_category($category, 0, $showfeedback)) {
3482 throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
3485 // finally delete the category and it's context
3486 $DB->delete_records('course_categories', array('id'=>$category->id));
3487 delete_context(CONTEXT_COURSECAT, $category->id);
3489 events_trigger('course_category_deleted', $category);
3491 return $deletedcourses;
3495 * Delete category, but move contents to another category.
3496 * @param object $ccategory
3497 * @param int $newparentid category id
3498 * @return bool status
3500 function category_delete_move($category, $newparentid, $showfeedback=true) {
3501 global $CFG, $DB, $OUTPUT;
3502 require_once($CFG->libdir.'/gradelib.php');
3503 require_once($CFG->libdir.'/questionlib.php');
3504 require_once($CFG->dirroot.'/cohort/lib.php');
3506 if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
3507 return false;
3510 if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
3511 foreach ($children as $childcat) {
3512 move_category($childcat, $newparentcat);
3516 if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
3517 if (!move_courses(array_keys($courses), $newparentid)) {
3518 if ($showfeedback) {
3519 echo $OUTPUT->notification("Error moving courses");
3521 return false;
3523 if ($showfeedback) {
3524 echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
3528 // move or delete cohorts in this context
3529 cohort_delete_category($category);
3531 // now delete anything that may depend on course category context
3532 grade_course_category_delete($category->id, $newparentid, $showfeedback);
3533 if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
3534 if ($showfeedback) {
3535 echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
3537 return false;
3540 // finally delete the category and it's context
3541 $DB->delete_records('course_categories', array('id'=>$category->id));
3542 delete_context(CONTEXT_COURSECAT, $category->id);
3544 events_trigger('course_category_deleted', $category);
3546 if ($showfeedback) {
3547 echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
3549 return true;
3553 * Efficiently moves many courses around while maintaining
3554 * sortorder in order.
3556 * @param array $courseids is an array of course ids
3557 * @param int $categoryid
3558 * @return bool success
3560 function move_courses($courseids, $categoryid) {
3561 global $CFG, $DB, $OUTPUT;
3563 if (empty($courseids)) {
3564 // nothing to do
3565 return;
3568 if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
3569 return false;
3572 $courseids = array_reverse($courseids);
3573 $newparent = get_context_instance(CONTEXT_COURSECAT, $category->id);
3574 $i = 1;
3576 foreach ($courseids as $courseid) {
3577 if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
3578 $course = new stdClass();
3579 $course->id = $courseid;
3580 $course->category = $category->id;
3581 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
3582 if ($category->visible == 0) {
3583 // hide the course when moving into hidden category,
3584 // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
3585 $course->visible = 0;
3588 $DB->update_record('course', $course);
3590 $context = get_context_instance(CONTEXT_COURSE, $course->id);
3591 context_moved($context, $newparent);
3594 fix_course_sortorder();
3596 return true;
3600 * Hide course category and child course and subcategories
3601 * @param stdClass $category
3602 * @return void
3604 function course_category_hide($category) {
3605 global $DB;
3607 $category->visible = 0;
3608 $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
3609 $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
3610 $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
3611 $DB->set_field('course', 'visible', 0, array('category' => $category->id));
3612 // get all child categories and hide too
3613 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3614 foreach ($subcats as $cat) {
3615 $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
3616 $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
3617 $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
3618 $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
3624 * Show course category and child course and subcategories
3625 * @param stdClass $category
3626 * @return void
3628 function course_category_show($category) {
3629 global $DB;
3631 $category->visible = 1;
3632 $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
3633 $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
3634 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
3635 // get all child categories and unhide too
3636 if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
3637 foreach ($subcats as $cat) {
3638 if ($cat->visibleold) {
3639 $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
3641 $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
3647 * Efficiently moves a category - NOTE that this can have
3648 * a huge impact access-control-wise...
3650 function move_category($category, $newparentcat) {
3651 global $CFG, $DB;
3653 $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
3655 $hidecat = false;
3656 if (empty($newparentcat->id)) {
3657 $DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id));
3659 $newparent = get_context_instance(CONTEXT_SYSTEM);
3661 } else {
3662 $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id));
3663 $newparent = get_context_instance(CONTEXT_COURSECAT, $newparentcat->id);
3665 if (!$newparentcat->visible and $category->visible) {
3666 // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
3667 $hidecat = true;
3671 context_moved($context, $newparent);
3673 // now make it last in new category
3674 $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
3676 // and fix the sortorders
3677 fix_course_sortorder();
3679 if ($hidecat) {
3680 course_category_hide($category);
3685 * Returns the display name of the given section that the course prefers.
3687 * This function utilizes a callback that can be implemented within the course
3688 * formats lib.php file to customize the display name that is used to reference
3689 * the section.
3691 * By default (if callback is not defined) the method
3692 * {@see get_numeric_section_name} is called instead.
3694 * @param stdClass $course The course to get the section name for
3695 * @param stdClass $section Section object from database
3696 * @return Display name that the course format prefers, e.g. "Week 2"
3698 * @see get_generic_section_name
3700 function get_section_name(stdClass $course, stdClass $section) {
3701 global $CFG;
3703 /// Inelegant hack for bug 3408
3704 if ($course->format == 'site') {
3705 return get_string('site');
3708 // Use course formatter callback if it exists
3709 $namingfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
3710 $namingfunction = 'callback_'.$course->format.'_get_section_name';
3711 if (!function_exists($namingfunction) && file_exists($namingfile)) {
3712 require_once $namingfile;
3714 if (function_exists($namingfunction)) {
3715 return $namingfunction($course, $section);
3718 // else, default behavior:
3719 return get_generic_section_name($course->format, $section);
3723 * Gets the generic section name for a courses section.
3725 * @param string $format Course format ID e.g. 'weeks' $course->format
3726 * @param stdClass $section Section object from database
3727 * @return Display name that the course format prefers, e.g. "Week 2"
3729 function get_generic_section_name($format, stdClass $section) {
3730 return get_string('sectionname', "format_$format") . ' ' . $section->section;
3734 function course_format_uses_sections($format) {
3735 global $CFG;
3737 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3738 $featurefunction = 'callback_'.$format.'_uses_sections';
3739 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3740 require_once $featurefile;
3742 if (function_exists($featurefunction)) {
3743 return $featurefunction();
3746 return false;
3750 * Returns the information about the ajax support in the given source format
3752 * The returned object's property (boolean)capable indicates that
3753 * the course format supports Moodle course ajax features.
3754 * The property (array)testedbrowsers can be used as a parameter for {@see ajaxenabled()}.
3756 * @param string $format
3757 * @return stdClass
3759 function course_format_ajax_support($format) {
3760 global $CFG;
3762 // set up default values
3763 $ajaxsupport = new stdClass();
3764 $ajaxsupport->capable = false;
3765 $ajaxsupport->testedbrowsers = array();
3767 // get the information from the course format library
3768 $featurefile = $CFG->dirroot.'/course/format/'.$format.'/lib.php';
3769 $featurefunction = 'callback_'.$format.'_ajax_support';
3770 if (!function_exists($featurefunction) && file_exists($featurefile)) {
3771 require_once $featurefile;
3773 if (function_exists($featurefunction)) {
3774 $formatsupport = $featurefunction();
3775 if (isset($formatsupport->capable)) {
3776 $ajaxsupport->capable = $formatsupport->capable;
3778 if (is_array($formatsupport->testedbrowsers)) {
3779 $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers;
3783 return $ajaxsupport;
3787 * Can the current user delete this course?
3788 * Course creators have exception,
3789 * 1 day after the creation they can sill delete the course.
3790 * @param int $courseid
3791 * @return boolean
3793 function can_delete_course($courseid) {
3794 global $USER, $DB;
3796 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3798 if (has_capability('moodle/course:delete', $context)) {
3799 return true;
3802 // hack: now try to find out if creator created this course recently (1 day)
3803 if (!has_capability('moodle/course:create', $context)) {
3804 return false;
3807 $since = time() - 60*60*24;
3809 $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
3810 $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
3812 return $DB->record_exists_select('log', $select, $params);
3816 * Save the Your name for 'Some role' strings.
3818 * @param integer $courseid the id of this course.
3819 * @param array $data the data that came from the course settings form.
3821 function save_local_role_names($courseid, $data) {
3822 global $DB;
3823 $context = get_context_instance(CONTEXT_COURSE, $courseid);
3825 foreach ($data as $fieldname => $value) {
3826 if (strpos($fieldname, 'role_') !== 0) {
3827 continue;
3829 list($ignored, $roleid) = explode('_', $fieldname);
3831 // make up our mind whether we want to delete, update or insert
3832 if (!$value) {
3833 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
3835 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
3836 $rolename->name = $value;
3837 $DB->update_record('role_names', $rolename);
3839 } else {
3840 $rolename = new stdClass;
3841 $rolename->contextid = $context->id;
3842 $rolename->roleid = $roleid;
3843 $rolename->name = $value;
3844 $DB->insert_record('role_names', $rolename);
3850 * Create a course and either return a $course object
3852 * Please note this functions does not verify any access control,
3853 * the calling code is responsible for all validation (usually it is the form definition).
3855 * @param array $editoroptions course description editor options
3856 * @param object $data - all the data needed for an entry in the 'course' table
3857 * @return object new course instance
3859 function create_course($data, $editoroptions = NULL) {
3860 global $CFG, $DB;
3862 //check the categoryid - must be given for all new courses
3863 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
3865 //check if the shortname already exist
3866 if (!empty($data->shortname)) {
3867 if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
3868 throw new moodle_exception('shortnametaken');
3872 //check if the id number already exist
3873 if (!empty($data->idnumber)) {
3874 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
3875 throw new moodle_exception('idnumbertaken');
3879 $data->timecreated = time();
3880 $data->timemodified = $data->timecreated;
3882 // place at beginning of any category
3883 $data->sortorder = 0;
3885 if ($editoroptions) {
3886 // summary text is updated later, we need context to store the files first
3887 $data->summary = '';
3888 $data->summary_format = FORMAT_HTML;
3891 if (!isset($data->visible)) {
3892 // data not from form, add missing visibility info
3893 $data->visible = $category->visible;
3895 $data->visibleold = $data->visible;
3897 $newcourseid = $DB->insert_record('course', $data);
3898 $context = get_context_instance(CONTEXT_COURSE, $newcourseid, MUST_EXIST);
3900 if ($editoroptions) {
3901 // Save the files used in the summary editor and store
3902 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3903 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
3904 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
3907 $course = $DB->get_record('course', array('id'=>$newcourseid));
3909 // Setup the blocks
3910 blocks_add_default_course_blocks($course);
3912 $section = new stdClass();
3913 $section->course = $course->id; // Create a default section.
3914 $section->section = 0;
3915 $section->summaryformat = FORMAT_HTML;
3916 $DB->insert_record('course_sections', $section);
3918 fix_course_sortorder();
3920 // new context created - better mark it as dirty
3921 mark_context_dirty($context->path);
3923 // Save any custom role names.
3924 save_local_role_names($course->id, (array)$data);
3926 // set up enrolments
3927 enrol_course_updated(true, $course, $data);
3929 add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
3931 // Trigger events
3932 events_trigger('course_created', $course);
3934 return $course;
3938 * Create a new course category and marks the context as dirty
3940 * This function does not set the sortorder for the new category and
3941 * @see{fix_course_sortorder} should be called after creating a new course
3942 * category
3944 * Please note that this function does not verify access control.
3946 * @param object $category All of the data required for an entry in the course_categories table
3947 * @return object new course category
3949 function create_course_category($category) {
3950 global $DB;
3952 $category->timemodified = time();
3953 $category->id = $DB->insert_record('course_categories', $category);
3954 $category = $DB->get_record('course_categories', array('id' => $category->id));
3956 // We should mark the context as dirty
3957 $category->context = context_coursecat::instance($category->id);
3958 $category->context->mark_dirty();
3960 return $category;
3964 * Update a course.
3966 * Please note this functions does not verify any access control,
3967 * the calling code is responsible for all validation (usually it is the form definition).
3969 * @param object $data - all the data needed for an entry in the 'course' table
3970 * @param array $editoroptions course description editor options
3971 * @return void
3973 function update_course($data, $editoroptions = NULL) {
3974 global $CFG, $DB;
3976 $data->timemodified = time();
3978 $oldcourse = $DB->get_record('course', array('id'=>$data->id), '*', MUST_EXIST);
3979 $context = get_context_instance(CONTEXT_COURSE, $oldcourse->id);
3981 if ($editoroptions) {
3982 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
3985 if (!isset($data->category) or empty($data->category)) {
3986 // prevent nulls and 0 in category field
3987 unset($data->category);
3989 $movecat = (isset($data->category) and $oldcourse->category != $data->category);
3991 if (!isset($data->visible)) {
3992 // data not from form, add missing visibility info
3993 $data->visible = $oldcourse->visible;
3996 if ($data->visible != $oldcourse->visible) {
3997 // reset the visibleold flag when manually hiding/unhiding course
3998 $data->visibleold = $data->visible;
3999 } else {
4000 if ($movecat) {
4001 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
4002 if (empty($newcategory->visible)) {
4003 // make sure when moving into hidden category the course is hidden automatically
4004 $data->visible = 0;
4009 // Update with the new data
4010 $DB->update_record('course', $data);
4012 $course = $DB->get_record('course', array('id'=>$data->id));
4014 if ($movecat) {
4015 $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category);
4016 context_moved($context, $newparent);
4019 fix_course_sortorder();
4021 // Test for and remove blocks which aren't appropriate anymore
4022 blocks_remove_inappropriate($course);
4024 // Save any custom role names.
4025 save_local_role_names($course->id, $data);
4027 // update enrol settings
4028 enrol_course_updated(false, $course, $data);
4030 add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
4032 // Trigger events
4033 events_trigger('course_updated', $course);
4037 * Average number of participants
4038 * @return integer
4040 function average_number_of_participants() {
4041 global $DB, $SITE;
4043 //count total of enrolments for visible course (except front page)
4044 $sql = 'SELECT COUNT(*) FROM (
4045 SELECT DISTINCT ue.userid, e.courseid
4046 FROM {user_enrolments} ue, {enrol} e, {course} c
4047 WHERE ue.enrolid = e.id
4048 AND e.courseid <> :siteid
4049 AND c.id = e.courseid
4050 AND c.visible = 1) as total';
4051 $params = array('siteid' => $SITE->id);
4052 $enrolmenttotal = $DB->count_records_sql($sql, $params);
4055 //count total of visible courses (minus front page)
4056 $coursetotal = $DB->count_records('course', array('visible' => 1));
4057 $coursetotal = $coursetotal - 1 ;
4059 //average of enrolment
4060 if (empty($coursetotal)) {
4061 $participantaverage = 0;
4062 } else {
4063 $participantaverage = $enrolmenttotal / $coursetotal;
4066 return $participantaverage;
4070 * Average number of course modules
4071 * @return integer
4073 function average_number_of_courses_modules() {
4074 global $DB, $SITE;
4076 //count total of visible course module (except front page)
4077 $sql = 'SELECT COUNT(*) FROM (
4078 SELECT cm.course, cm.module
4079 FROM {course} c, {course_modules} cm
4080 WHERE c.id = cm.course
4081 AND c.id <> :siteid
4082 AND cm.visible = 1
4083 AND c.visible = 1) as total';
4084 $params = array('siteid' => $SITE->id);
4085 $moduletotal = $DB->count_records_sql($sql, $params);
4088 //count total of visible courses (minus front page)
4089 $coursetotal = $DB->count_records('course', array('visible' => 1));
4090 $coursetotal = $coursetotal - 1 ;
4092 //average of course module
4093 if (empty($coursetotal)) {
4094 $coursemoduleaverage = 0;
4095 } else {
4096 $coursemoduleaverage = $moduletotal / $coursetotal;
4099 return $coursemoduleaverage;
4103 * This class pertains to course requests and contains methods associated with
4104 * create, approving, and removing course requests.
4106 * Please note we do not allow embedded images here because there is no context
4107 * to store them with proper access control.
4109 * @copyright 2009 Sam Hemelryk
4110 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4111 * @since Moodle 2.0
4113 * @property-read int $id
4114 * @property-read string $fullname
4115 * @property-read string $shortname
4116 * @property-read string $summary
4117 * @property-read int $summaryformat
4118 * @property-read int $summarytrust
4119 * @property-read string $reason
4120 * @property-read int $requester
4122 class course_request {
4125 * This is the stdClass that stores the properties for the course request
4126 * and is externally accessed through the __get magic method
4127 * @var stdClass
4129 protected $properties;
4132 * An array of options for the summary editor used by course request forms.
4133 * This is initially set by {@link summary_editor_options()}
4134 * @var array
4135 * @static
4137 protected static $summaryeditoroptions;
4140 * Static function to prepare the summary editor for working with a course
4141 * request.
4143 * @static
4144 * @param null|stdClass $data Optional, an object containing the default values
4145 * for the form, these may be modified when preparing the
4146 * editor so this should be called before creating the form
4147 * @return stdClass An object that can be used to set the default values for
4148 * an mforms form
4150 public static function prepare($data=null) {
4151 if ($data === null) {
4152 $data = new stdClass;
4154 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
4155 return $data;
4159 * Static function to create a new course request when passed an array of properties
4160 * for it.
4162 * This function also handles saving any files that may have been used in the editor
4164 * @static
4165 * @param stdClass $data
4166 * @return course_request The newly created course request
4168 public static function create($data) {
4169 global $USER, $DB, $CFG;
4170 $data->requester = $USER->id;
4172 // Summary is a required field so copy the text over
4173 $data->summary = $data->summary_editor['text'];
4174 $data->summaryformat = $data->summary_editor['format'];
4176 $data->id = $DB->insert_record('course_request', $data);
4178 // Create a new course_request object and return it
4179 $request = new course_request($data);
4181 // Notify the admin if required.
4182 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
4184 $a = new stdClass;
4185 $a->link = "$CFG->wwwroot/course/pending.php";
4186 $a->user = fullname($USER);
4187 $subject = get_string('courserequest');
4188 $message = get_string('courserequestnotifyemail', 'admin', $a);
4189 foreach ($users as $user) {
4190 $request->notify($user, $USER, 'courserequested', $subject, $message);
4194 return $request;
4198 * Returns an array of options to use with a summary editor
4200 * @uses course_request::$summaryeditoroptions
4201 * @return array An array of options to use with the editor
4203 public static function summary_editor_options() {
4204 global $CFG;
4205 if (self::$summaryeditoroptions === null) {
4206 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
4208 return self::$summaryeditoroptions;
4212 * Loads the properties for this course request object. Id is required and if
4213 * only id is provided then we load the rest of the properties from the database
4215 * @param stdClass|int $properties Either an object containing properties
4216 * or the course_request id to load
4218 public function __construct($properties) {
4219 global $DB;
4220 if (empty($properties->id)) {
4221 if (empty($properties)) {
4222 throw new coding_exception('You must provide a course request id when creating a course_request object');
4224 $id = $properties;
4225 $properties = new stdClass;
4226 $properties->id = (int)$id;
4227 unset($id);
4229 if (empty($properties->requester)) {
4230 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
4231 print_error('unknowncourserequest');
4233 } else {
4234 $this->properties = $properties;
4236 $this->properties->collision = null;
4240 * Returns the requested property
4242 * @param string $key
4243 * @return mixed
4245 public function __get($key) {
4246 return $this->properties->$key;
4250 * Override this to ensure empty($request->blah) calls return a reliable answer...
4252 * This is required because we define the __get method
4254 * @param mixed $key
4255 * @return bool True is it not empty, false otherwise
4257 public function __isset($key) {
4258 return (!empty($this->properties->$key));
4262 * Returns the user who requested this course
4264 * Uses a static var to cache the results and cut down the number of db queries
4266 * @staticvar array $requesters An array of cached users
4267 * @return stdClass The user who requested the course
4269 public function get_requester() {
4270 global $DB;
4271 static $requesters= array();
4272 if (!array_key_exists($this->properties->requester, $requesters)) {
4273 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
4275 return $requesters[$this->properties->requester];
4279 * Checks that the shortname used by the course does not conflict with any other
4280 * courses that exist
4282 * @param string|null $shortnamemark The string to append to the requests shortname
4283 * should a conflict be found
4284 * @return bool true is there is a conflict, false otherwise
4286 public function check_shortname_collision($shortnamemark = '[*]') {
4287 global $DB;
4289 if ($this->properties->collision !== null) {
4290 return $this->properties->collision;
4293 if (empty($this->properties->shortname)) {
4294 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
4295 $this->properties->collision = false;
4296 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
4297 if (!empty($shortnamemark)) {
4298 $this->properties->shortname .= ' '.$shortnamemark;
4300 $this->properties->collision = true;
4301 } else {
4302 $this->properties->collision = false;
4304 return $this->properties->collision;
4308 * This function approves the request turning it into a course
4310 * This function converts the course request into a course, at the same time
4311 * transferring any files used in the summary to the new course and then removing
4312 * the course request and the files associated with it.
4314 * @return int The id of the course that was created from this request
4316 public function approve() {
4317 global $CFG, $DB, $USER;
4319 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
4321 $category = get_course_category($CFG->defaultrequestcategory);
4322 $courseconfig = get_config('moodlecourse');
4324 // Transfer appropriate settings
4325 $data = clone($this->properties);
4326 unset($data->id);
4327 unset($data->reason);
4328 unset($data->requester);
4330 // Set category
4331 $data->category = $category->id;
4332 $data->sortorder = $category->sortorder; // place as the first in category
4334 // Set misc settings
4335 $data->requested = 1;
4337 // Apply course default settings
4338 $data->format = $courseconfig->format;
4339 $data->numsections = $courseconfig->numsections;
4340 $data->hiddensections = $courseconfig->hiddensections;
4341 $data->newsitems = $courseconfig->newsitems;
4342 $data->showgrades = $courseconfig->showgrades;
4343 $data->showreports = $courseconfig->showreports;
4344 $data->maxbytes = $courseconfig->maxbytes;
4345 $data->groupmode = $courseconfig->groupmode;
4346 $data->groupmodeforce = $courseconfig->groupmodeforce;
4347 $data->visible = $courseconfig->visible;
4348 $data->visibleold = $data->visible;
4349 $data->lang = $courseconfig->lang;
4351 $course = create_course($data);
4352 $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
4354 // add enrol instances
4355 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
4356 if ($manual = enrol_get_plugin('manual')) {
4357 $manual->add_default_instance($course);
4361 // enrol the requester as teacher if necessary
4362 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
4363 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
4366 $this->delete();
4368 $a = new stdClass();
4369 $a->name = format_string($course->fullname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
4370 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
4371 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
4373 return $course->id;
4377 * Reject a course request
4379 * This function rejects a course request, emailing the requesting user the
4380 * provided notice and then removing the request from the database
4382 * @param string $notice The message to display to the user
4384 public function reject($notice) {
4385 global $USER, $DB;
4386 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
4387 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
4388 $this->delete();
4392 * Deletes the course request and any associated files
4394 public function delete() {
4395 global $DB;
4396 $DB->delete_records('course_request', array('id' => $this->properties->id));
4400 * Send a message from one user to another using events_trigger
4402 * @param object $touser
4403 * @param object $fromuser
4404 * @param string $name
4405 * @param string $subject
4406 * @param string $message
4408 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
4409 $eventdata = new stdClass();
4410 $eventdata->component = 'moodle';
4411 $eventdata->name = $name;
4412 $eventdata->userfrom = $fromuser;
4413 $eventdata->userto = $touser;
4414 $eventdata->subject = $subject;
4415 $eventdata->fullmessage = $message;
4416 $eventdata->fullmessageformat = FORMAT_PLAIN;
4417 $eventdata->fullmessagehtml = '';
4418 $eventdata->smallmessage = '';
4419 $eventdata->notification = 1;
4420 message_send($eventdata);
4425 * Return a list of page types
4426 * @param string $pagetype current page type
4427 * @param stdClass $parentcontext Block's parent context
4428 * @param stdClass $currentcontext Current context of block
4430 function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
4431 // if above course context ,display all course fomats
4432 list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
4433 if ($course->id == SITEID) {
4434 return array('*'=>get_string('page-x', 'pagetype'));
4435 } else {
4436 return array('*'=>get_string('page-x', 'pagetype'),
4437 'course-*'=>get_string('page-course-x', 'pagetype'),
4438 'course-view-*'=>get_string('page-course-view-x', 'pagetype')
4444 * Determine whether course ajax should be enabled for the specified course
4446 * @param stdClass $course The course to test against
4447 * @return boolean Whether course ajax is enabled or note
4449 function course_ajax_enabled($course) {
4450 global $CFG, $PAGE, $SITE;
4452 // Ajax must be enabled globally
4453 if (!$CFG->enableajax) {
4454 return false;
4457 // The user must be editing for AJAX to be included
4458 if (!$PAGE->user_is_editing()) {
4459 return false;
4462 // Check that the theme suports
4463 if (!$PAGE->theme->enablecourseajax) {
4464 return false;
4467 // Check that the course format supports ajax functionality
4468 // The site 'format' doesn't have information on course format support
4469 if ($SITE->id !== $course->id) {
4470 $courseformatajaxsupport = course_format_ajax_support($course->format);
4471 if (!$courseformatajaxsupport->capable) {
4472 return false;
4476 // All conditions have been met so course ajax should be enabled
4477 return true;
4481 * Include the relevant javascript and language strings for the resource
4482 * toolbox YUI module
4484 * @param integer $id The ID of the course being applied to
4485 * @param array $usedmodules An array containing the names of the modules in use on the page
4486 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
4487 * @param stdClass $config An object containing configuration parameters for ajax modules including:
4488 * * resourceurl The URL to post changes to for resource changes
4489 * * sectionurl The URL to post changes to for section changes
4490 * * pageparams Additional parameters to pass through in the post
4491 * @return bool
4493 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
4494 global $PAGE, $SITE;
4496 // Ensure that ajax should be included
4497 if (!course_ajax_enabled($course)) {
4498 return false;
4501 if (!$config) {
4502 $config = new stdClass();
4505 // The URL to use for resource changes
4506 if (!isset($config->resourceurl)) {
4507 $config->resourceurl = '/course/rest.php';
4510 // The URL to use for section changes
4511 if (!isset($config->sectionurl)) {
4512 $config->sectionurl = '/course/rest.php';
4515 // Any additional parameters which need to be included on page submission
4516 if (!isset($config->pageparams)) {
4517 $config->pageparams = array();
4520 // Include toolboxes
4521 $PAGE->requires->yui_module('moodle-course-toolboxes',
4522 'M.course.init_resource_toolbox',
4523 array(array(
4524 'courseid' => $course->id,
4525 'ajaxurl' => $config->resourceurl,
4526 'config' => $config,
4529 $PAGE->requires->yui_module('moodle-course-toolboxes',
4530 'M.course.init_section_toolbox',
4531 array(array(
4532 'courseid' => $course->id,
4533 'format' => $course->format,
4534 'ajaxurl' => $config->sectionurl,
4535 'config' => $config,
4539 // Include course dragdrop
4540 if ($course->id != $SITE->id) {
4541 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
4542 array(array(
4543 'courseid' => $course->id,
4544 'ajaxurl' => $config->sectionurl,
4545 'config' => $config,
4546 )), null, true);
4548 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
4549 array(array(
4550 'courseid' => $course->id,
4551 'ajaxurl' => $config->resourceurl,
4552 'config' => $config,
4553 )), null, true);
4556 // Include blocks dragdrop
4557 $params = array(
4558 'courseid' => $course->id,
4559 'pagetype' => $PAGE->pagetype,
4560 'pagelayout' => $PAGE->pagelayout,
4561 'regions' => $PAGE->blocks->get_regions(),
4563 $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
4565 // Require various strings for the command toolbox
4566 $PAGE->requires->strings_for_js(array(
4567 'moveleft',
4568 'deletechecktype',
4569 'deletechecktypename',
4570 'edittitle',
4571 'edittitleinstructions',
4572 'show',
4573 'hide',
4574 'groupsnone',
4575 'groupsvisible',
4576 'groupsseparate',
4577 'clicktochangeinbrackets',
4578 'markthistopic',
4579 'markedthistopic',
4580 'move',
4581 'movesection',
4582 ), 'moodle');
4584 // Include format-specific strings
4585 if ($course->id != $SITE->id) {
4586 $PAGE->requires->strings_for_js(array(
4587 'showfromothers',
4588 'hidefromothers',
4589 ), 'format_' . $course->format);
4592 // For confirming resource deletion we need the name of the module in question
4593 foreach ($usedmodules as $module => $modname) {
4594 $PAGE->requires->string_for_js('pluginname', $module);
4597 // Load drag and drop upload AJAX.
4598 dndupload_add_to_course($course, $enabledmodules);
4600 // Add the module chooser
4601 $PAGE->requires->yui_module('moodle-course-modchooser',
4602 'M.course.init_chooser',
4603 array(array('courseid' => $course->id))
4605 $PAGE->requires->strings_for_js(array(
4606 'addresourceoractivity',
4607 'modchooserenable',
4608 'modchooserdisable',
4609 ), 'moodle');
4611 return true;
4615 * The URL to use for the specified course (with section)
4617 * @param stdClass $course The course to get the section name for
4618 * @param int $sectionno The section number to return a link to
4619 * @return moodle_url The url of course
4621 function course_get_url($course, $sectionno = null) {
4622 $url = new moodle_url('/course/view.php', array('id' => $course->id));
4624 if (!is_null($sectionno)) {
4625 if ($course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
4626 $url->param('section', $sectionno);
4627 } else {
4628 $url->set_anchor('section-'.$sectionno);
4632 return $url;