source: moodle/trunk/fuentes/course/lib.php @ 1331

Last change on this file since 1331 was 1331, checked in by jrpelegrina, 3 years ago

Updated to moodle 3.0.3

File size: 142.7 KB
Line 
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Library of useful functions
19 *
20 * @copyright 1999 Martin Dougiamas  http://dougiamas.com
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 * @package core_course
23 */
24
25defined('MOODLE_INTERNAL') || die;
26
27require_once($CFG->libdir.'/completionlib.php');
28require_once($CFG->libdir.'/filelib.php');
29require_once($CFG->dirroot.'/course/format/lib.php');
30
31define('COURSE_MAX_LOGS_PER_PAGE', 1000);       // Records.
32define('COURSE_MAX_RECENT_PERIOD', 172800);     // Two days, in seconds.
33
34/**
35 * Number of courses to display when summaries are included.
36 * @var int
37 * @deprecated since 2.4, use $CFG->courseswithsummarieslimit instead.
38 */
39define('COURSE_MAX_SUMMARIES_PER_PAGE', 10);
40
41// Max courses in log dropdown before switching to optional.
42define('COURSE_MAX_COURSES_PER_DROPDOWN', 1000);
43// Max users in log dropdown before switching to optional.
44define('COURSE_MAX_USERS_PER_DROPDOWN', 1000);
45define('FRONTPAGENEWS', '0');
46define('FRONTPAGECATEGORYNAMES', '2');
47define('FRONTPAGECATEGORYCOMBO', '4');
48define('FRONTPAGEENROLLEDCOURSELIST', '5');
49define('FRONTPAGEALLCOURSELIST', '6');
50define('FRONTPAGECOURSESEARCH', '7');
51// Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage.
52define('EXCELROWS', 65535);
53define('FIRSTUSEDEXCELROW', 3);
54
55define('MOD_CLASS_ACTIVITY', 0);
56define('MOD_CLASS_RESOURCE', 1);
57
58function make_log_url($module, $url) {
59    switch ($module) {
60        case 'course':
61            if (strpos($url, 'report/') === 0) {
62                // there is only one report type, course reports are deprecated
63                $url = "/$url";
64                break;
65            }
66        case 'file':
67        case 'login':
68        case 'lib':
69        case 'admin':
70        case 'category':
71        case 'mnet course':
72            if (strpos($url, '../') === 0) {
73                $url = ltrim($url, '.');
74            } else {
75                $url = "/course/$url";
76            }
77            break;
78        case 'calendar':
79            $url = "/calendar/$url";
80            break;
81        case 'user':
82        case 'blog':
83            $url = "/$module/$url";
84            break;
85        case 'upload':
86            $url = $url;
87            break;
88        case 'coursetags':
89            $url = '/'.$url;
90            break;
91        case 'library':
92        case '':
93            $url = '/';
94            break;
95        case 'message':
96            $url = "/message/$url";
97            break;
98        case 'notes':
99            $url = "/notes/$url";
100            break;
101        case 'tag':
102            $url = "/tag/$url";
103            break;
104        case 'role':
105            $url = '/'.$url;
106            break;
107        case 'grade':
108            $url = "/grade/$url";
109            break;
110        default:
111            $url = "/mod/$module/$url";
112            break;
113    }
114
115    //now let's sanitise urls - there might be some ugly nasties:-(
116    $parts = explode('?', $url);
117    $script = array_shift($parts);
118    if (strpos($script, 'http') === 0) {
119        $script = clean_param($script, PARAM_URL);
120    } else {
121        $script = clean_param($script, PARAM_PATH);
122    }
123
124    $query = '';
125    if ($parts) {
126        $query = implode('', $parts);
127        $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
128        $parts = explode('&', $query);
129        $eq = urlencode('=');
130        foreach ($parts as $key=>$part) {
131            $part = urlencode(urldecode($part));
132            $part = str_replace($eq, '=', $part);
133            $parts[$key] = $part;
134        }
135        $query = '?'.implode('&amp;', $parts);
136    }
137
138    return $script.$query;
139}
140
141
142function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
143                   $modname="", $modid=0, $modaction="", $groupid=0) {
144    global $CFG, $DB;
145
146    // It is assumed that $date is the GMT time of midnight for that day,
147    // and so the next 86400 seconds worth of logs are printed.
148
149    /// Setup for group handling.
150
151    // TODO: I don't understand group/context/etc. enough to be able to do
152    // something interesting with it here
153    // What is the context of a remote course?
154
155    /// If the group mode is separate, and this user does not have editing privileges,
156    /// then only the user's group can be viewed.
157    //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
158    //    $groupid = get_current_group($course->id);
159    //}
160    /// If this course doesn't have groups, no groupid can be specified.
161    //else if (!$course->groupmode) {
162    //    $groupid = 0;
163    //}
164
165    $groupid = 0;
166
167    $joins = array();
168    $where = '';
169
170    $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
171              FROM {mnet_log} l
172               LEFT JOIN {user} u ON l.userid = u.id
173              WHERE ";
174    $params = array();
175
176    $where .= "l.hostid = :hostid";
177    $params['hostid'] = $hostid;
178
179    // TODO: Is 1 really a magic number referring to the sitename?
180    if ($course != SITEID || $modid != 0) {
181        $where .= " AND l.course=:courseid";
182        $params['courseid'] = $course;
183    }
184
185    if ($modname) {
186        $where .= " AND l.module = :modname";
187        $params['modname'] = $modname;
188    }
189
190    if ('site_errors' === $modid) {
191        $where .= " AND ( l.action='error' OR l.action='infected' )";
192    } else if ($modid) {
193        //TODO: This assumes that modids are the same across sites... probably
194        //not true
195        $where .= " AND l.cmid = :modid";
196        $params['modid'] = $modid;
197    }
198
199    if ($modaction) {
200        $firstletter = substr($modaction, 0, 1);
201        if ($firstletter == '-') {
202            $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
203            $params['modaction'] = '%'.substr($modaction, 1).'%';
204        } else {
205            $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
206            $params['modaction'] = '%'.$modaction.'%';
207        }
208    }
209
210    if ($user) {
211        $where .= " AND l.userid = :user";
212        $params['user'] = $user;
213    }
214
215    if ($date) {
216        $enddate = $date + 86400;
217        $where .= " AND l.time > :date AND l.time < :enddate";
218        $params['date'] = $date;
219        $params['enddate'] = $enddate;
220    }
221
222    $result = array();
223    $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
224    if(!empty($result['totalcount'])) {
225        $where .= " ORDER BY $order";
226        $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
227    } else {
228        $result['logs'] = array();
229    }
230    return $result;
231}
232
233function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
234                   $modname="", $modid=0, $modaction="", $groupid=0) {
235    global $DB, $SESSION, $USER;
236    // It is assumed that $date is the GMT time of midnight for that day,
237    // and so the next 86400 seconds worth of logs are printed.
238
239    /// Setup for group handling.
240
241    /// If the group mode is separate, and this user does not have editing privileges,
242    /// then only the user's group can be viewed.
243    if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
244        if (isset($SESSION->currentgroup[$course->id])) {
245            $groupid =  $SESSION->currentgroup[$course->id];
246        } else {
247            $groupid = groups_get_all_groups($course->id, $USER->id);
248            if (is_array($groupid)) {
249                $groupid = array_shift(array_keys($groupid));
250                $SESSION->currentgroup[$course->id] = $groupid;
251            } else {
252                $groupid = 0;
253            }
254        }
255    }
256    /// If this course doesn't have groups, no groupid can be specified.
257    else if (!$course->groupmode) {
258        $groupid = 0;
259    }
260
261    $joins = array();
262    $params = array();
263
264    if ($course->id != SITEID || $modid != 0) {
265        $joins[] = "l.course = :courseid";
266        $params['courseid'] = $course->id;
267    }
268
269    if ($modname) {
270        $joins[] = "l.module = :modname";
271        $params['modname'] = $modname;
272    }
273
274    if ('site_errors' === $modid) {
275        $joins[] = "( l.action='error' OR l.action='infected' )";
276    } else if ($modid) {
277        $joins[] = "l.cmid = :modid";
278        $params['modid'] = $modid;
279    }
280
281    if ($modaction) {
282        $firstletter = substr($modaction, 0, 1);
283        if ($firstletter == '-') {
284            $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
285            $params['modaction'] = '%'.substr($modaction, 1).'%';
286        } else {
287            $joins[] = $DB->sql_like('l.action', ':modaction', false);
288            $params['modaction'] = '%'.$modaction.'%';
289        }
290    }
291
292
293    /// Getting all members of a group.
294    if ($groupid and !$user) {
295        if ($gusers = groups_get_members($groupid)) {
296            $gusers = array_keys($gusers);
297            $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
298        } else {
299            $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
300        }
301    }
302    else if ($user) {
303        $joins[] = "l.userid = :userid";
304        $params['userid'] = $user;
305    }
306
307    if ($date) {
308        $enddate = $date + 86400;
309        $joins[] = "l.time > :date AND l.time < :enddate";
310        $params['date'] = $date;
311        $params['enddate'] = $enddate;
312    }
313
314    $selector = implode(' AND ', $joins);
315
316    $totalcount = 0;  // Initialise
317    $result = array();
318    $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
319    $result['totalcount'] = $totalcount;
320    return $result;
321}
322
323
324function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
325                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
326
327    global $CFG, $DB, $OUTPUT;
328
329    if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
330                       $modname, $modid, $modaction, $groupid)) {
331        echo $OUTPUT->notification("No logs found!");
332        echo $OUTPUT->footer();
333        exit;
334    }
335
336    $courses = array();
337
338    if ($course->id == SITEID) {
339        $courses[0] = '';
340        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
341            foreach ($ccc as $cc) {
342                $courses[$cc->id] = $cc->shortname;
343            }
344        }
345    } else {
346        $courses[$course->id] = $course->shortname;
347    }
348
349    $totalcount = $logs['totalcount'];
350    $count=0;
351    $ldcache = array();
352    $tt = getdate(time());
353    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
354
355    $strftimedatetime = get_string("strftimedatetime");
356
357    echo "<div class=\"info\">\n";
358    print_string("displayingrecords", "", $totalcount);
359    echo "</div>\n";
360
361    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
362
363    $table = new html_table();
364    $table->classes = array('logtable','generaltable');
365    $table->align = array('right', 'left', 'left');
366    $table->head = array(
367        get_string('time'),
368        get_string('ip_address'),
369        get_string('fullnameuser'),
370        get_string('action'),
371        get_string('info')
372    );
373    $table->data = array();
374
375    if ($course->id == SITEID) {
376        array_unshift($table->align, 'left');
377        array_unshift($table->head, get_string('course'));
378    }
379
380    // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
381    if (empty($logs['logs'])) {
382        $logs['logs'] = array();
383    }
384
385    foreach ($logs['logs'] as $log) {
386
387        if (isset($ldcache[$log->module][$log->action])) {
388            $ld = $ldcache[$log->module][$log->action];
389        } else {
390            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
391            $ldcache[$log->module][$log->action] = $ld;
392        }
393        if ($ld && is_numeric($log->info)) {
394            // ugly hack to make sure fullname is shown correctly
395            if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
396                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
397            } else {
398                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
399            }
400        }
401
402        //Filter log->info
403        $log->info = format_string($log->info);
404
405        // If $log->url has been trimmed short by the db size restriction
406        // code in add_to_log, keep a note so we don't add a link to a broken url
407        $brokenurl=(core_text::strlen($log->url)==100 && core_text::substr($log->url,97)=='...');
408
409        $row = array();
410        if ($course->id == SITEID) {
411            if (empty($log->course)) {
412                $row[] = get_string('site');
413            } else {
414                $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
415            }
416        }
417
418        $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
419
420        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
421        $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
422
423        $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id))));
424
425        $displayaction="$log->module $log->action";
426        if ($brokenurl) {
427            $row[] = $displayaction;
428        } else {
429            $link = make_log_url($log->module,$log->url);
430            $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
431        }
432        $row[] = $log->info;
433        $table->data[] = $row;
434    }
435
436    echo html_writer::table($table);
437    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
438}
439
440
441function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
442                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
443
444    global $CFG, $DB, $OUTPUT;
445
446    if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
447                       $modname, $modid, $modaction, $groupid)) {
448        echo $OUTPUT->notification("No logs found!");
449        echo $OUTPUT->footer();
450        exit;
451    }
452
453    if ($course->id == SITEID) {
454        $courses[0] = '';
455        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
456            foreach ($ccc as $cc) {
457                $courses[$cc->id] = $cc->shortname;
458            }
459        }
460    }
461
462    $totalcount = $logs['totalcount'];
463    $count=0;
464    $ldcache = array();
465    $tt = getdate(time());
466    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
467
468    $strftimedatetime = get_string("strftimedatetime");
469
470    echo "<div class=\"info\">\n";
471    print_string("displayingrecords", "", $totalcount);
472    echo "</div>\n";
473
474    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
475
476    echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
477    echo "<tr>";
478    if ($course->id == SITEID) {
479        echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
480    }
481    echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
482    echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
483    echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
484    echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
485    echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
486    echo "</tr>\n";
487
488    if (empty($logs['logs'])) {
489        echo "</table>\n";
490        return;
491    }
492
493    $row = 1;
494    foreach ($logs['logs'] as $log) {
495
496        $log->info = $log->coursename;
497        $row = ($row + 1) % 2;
498
499        if (isset($ldcache[$log->module][$log->action])) {
500            $ld = $ldcache[$log->module][$log->action];
501        } else {
502            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
503            $ldcache[$log->module][$log->action] = $ld;
504        }
505        if (0 && $ld && !empty($log->info)) {
506            // ugly hack to make sure fullname is shown correctly
507            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
508                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
509            } else {
510                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
511            }
512        }
513
514        //Filter log->info
515        $log->info = format_string($log->info);
516
517        echo '<tr class="r'.$row.'">';
518        if ($course->id == SITEID) {
519            $courseshortname = format_string($courses[$log->course], true, array('context' => context_course::instance(SITEID)));
520            echo "<td class=\"r$row c0\" >\n";
521            echo "    <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
522            echo "</td>\n";
523        }
524        echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
525             ' '.userdate($log->time, $strftimedatetime)."</td>\n";
526        echo "<td class=\"r$row c2\" >\n";
527        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
528        echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
529        echo "</td>\n";
530        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id)));
531        echo "<td class=\"r$row c3\" >\n";
532        echo "    <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
533        echo "</td>\n";
534        echo "<td class=\"r$row c4\">\n";
535        echo $log->action .': '.$log->module;
536        echo "</td>\n";
537        echo "<td class=\"r$row c5\">{$log->info}</td>\n";
538        echo "</tr>\n";
539    }
540    echo "</table>\n";
541
542    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
543}
544
545
546function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
547                        $modid, $modaction, $groupid) {
548    global $DB, $CFG;
549
550    require_once($CFG->libdir . '/csvlib.class.php');
551
552    $csvexporter = new csv_export_writer('tab');
553
554    $header = array();
555    $header[] = get_string('course');
556    $header[] = get_string('time');
557    $header[] = get_string('ip_address');
558    $header[] = get_string('fullnameuser');
559    $header[] = get_string('action');
560    $header[] = get_string('info');
561
562    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
563                       $modname, $modid, $modaction, $groupid)) {
564        return false;
565    }
566
567    $courses = array();
568
569    if ($course->id == SITEID) {
570        $courses[0] = '';
571        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
572            foreach ($ccc as $cc) {
573                $courses[$cc->id] = $cc->shortname;
574            }
575        }
576    } else {
577        $courses[$course->id] = $course->shortname;
578    }
579
580    $count=0;
581    $ldcache = array();
582    $tt = getdate(time());
583    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
584
585    $strftimedatetime = get_string("strftimedatetime");
586
587    $csvexporter->set_filename('logs', '.txt');
588    $title = array(get_string('savedat').userdate(time(), $strftimedatetime));
589    $csvexporter->add_data($title);
590    $csvexporter->add_data($header);
591
592    if (empty($logs['logs'])) {
593        return true;
594    }
595
596    foreach ($logs['logs'] as $log) {
597        if (isset($ldcache[$log->module][$log->action])) {
598            $ld = $ldcache[$log->module][$log->action];
599        } else {
600            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
601            $ldcache[$log->module][$log->action] = $ld;
602        }
603        if ($ld && is_numeric($log->info)) {
604            // ugly hack to make sure fullname is shown correctly
605            if (($ld->mtable == 'user') and ($ld->field ==  $DB->sql_concat('firstname', "' '" , 'lastname'))) {
606                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
607            } else {
608                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
609            }
610        }
611
612        //Filter log->info
613        $log->info = format_string($log->info);
614        $log->info = strip_tags(urldecode($log->info));    // Some XSS protection
615
616        $coursecontext = context_course::instance($course->id);
617        $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
618        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
619        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
620        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
621        $csvexporter->add_data($row);
622    }
623    $csvexporter->download_file();
624    return true;
625}
626
627
628function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
629                        $modid, $modaction, $groupid) {
630
631    global $CFG, $DB;
632
633    require_once("$CFG->libdir/excellib.class.php");
634
635    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
636                       $modname, $modid, $modaction, $groupid)) {
637        return false;
638    }
639
640    $courses = array();
641
642    if ($course->id == SITEID) {
643        $courses[0] = '';
644        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
645            foreach ($ccc as $cc) {
646                $courses[$cc->id] = $cc->shortname;
647            }
648        }
649    } else {
650        $courses[$course->id] = $course->shortname;
651    }
652
653    $count=0;
654    $ldcache = array();
655    $tt = getdate(time());
656    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
657
658    $strftimedatetime = get_string("strftimedatetime");
659
660    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
661    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
662    $filename .= '.xls';
663
664    $workbook = new MoodleExcelWorkbook('-');
665    $workbook->send($filename);
666
667    $worksheet = array();
668    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
669                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
670
671    // Creating worksheets
672    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
673        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
674        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
675        $worksheet[$wsnumber]->set_column(1, 1, 30);
676        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
677                                    userdate(time(), $strftimedatetime));
678        $col = 0;
679        foreach ($headers as $item) {
680            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
681            $col++;
682        }
683    }
684
685    if (empty($logs['logs'])) {
686        $workbook->close();
687        return true;
688    }
689
690    $formatDate =& $workbook->add_format();
691    $formatDate->set_num_format(get_string('log_excel_date_format'));
692
693    $row = FIRSTUSEDEXCELROW;
694    $wsnumber = 1;
695    $myxls =& $worksheet[$wsnumber];
696    foreach ($logs['logs'] as $log) {
697        if (isset($ldcache[$log->module][$log->action])) {
698            $ld = $ldcache[$log->module][$log->action];
699        } else {
700            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
701            $ldcache[$log->module][$log->action] = $ld;
702        }
703        if ($ld && is_numeric($log->info)) {
704            // ugly hack to make sure fullname is shown correctly
705            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
706                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
707            } else {
708                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
709            }
710        }
711
712        // Filter log->info
713        $log->info = format_string($log->info);
714        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
715
716        if ($nroPages>1) {
717            if ($row > EXCELROWS) {
718                $wsnumber++;
719                $myxls =& $worksheet[$wsnumber];
720                $row = FIRSTUSEDEXCELROW;
721            }
722        }
723
724        $coursecontext = context_course::instance($course->id);
725
726        $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
727        $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
728        $myxls->write($row, 2, $log->ip, '');
729        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
730        $myxls->write($row, 3, $fullname, '');
731        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
732        $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
733        $myxls->write($row, 5, $log->info, '');
734
735        $row++;
736    }
737
738    $workbook->close();
739    return true;
740}
741
742function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
743                        $modid, $modaction, $groupid) {
744
745    global $CFG, $DB;
746
747    require_once("$CFG->libdir/odslib.class.php");
748
749    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
750                       $modname, $modid, $modaction, $groupid)) {
751        return false;
752    }
753
754    $courses = array();
755
756    if ($course->id == SITEID) {
757        $courses[0] = '';
758        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
759            foreach ($ccc as $cc) {
760                $courses[$cc->id] = $cc->shortname;
761            }
762        }
763    } else {
764        $courses[$course->id] = $course->shortname;
765    }
766
767    $count=0;
768    $ldcache = array();
769    $tt = getdate(time());
770    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
771
772    $strftimedatetime = get_string("strftimedatetime");
773
774    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
775    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
776    $filename .= '.ods';
777
778    $workbook = new MoodleODSWorkbook('-');
779    $workbook->send($filename);
780
781    $worksheet = array();
782    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
783                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
784
785    // Creating worksheets
786    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
787        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
788        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
789        $worksheet[$wsnumber]->set_column(1, 1, 30);
790        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
791                                    userdate(time(), $strftimedatetime));
792        $col = 0;
793        foreach ($headers as $item) {
794            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
795            $col++;
796        }
797    }
798
799    if (empty($logs['logs'])) {
800        $workbook->close();
801        return true;
802    }
803
804    $formatDate =& $workbook->add_format();
805    $formatDate->set_num_format(get_string('log_excel_date_format'));
806
807    $row = FIRSTUSEDEXCELROW;
808    $wsnumber = 1;
809    $myxls =& $worksheet[$wsnumber];
810    foreach ($logs['logs'] as $log) {
811        if (isset($ldcache[$log->module][$log->action])) {
812            $ld = $ldcache[$log->module][$log->action];
813        } else {
814            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
815            $ldcache[$log->module][$log->action] = $ld;
816        }
817        if ($ld && is_numeric($log->info)) {
818            // ugly hack to make sure fullname is shown correctly
819            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
820                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
821            } else {
822                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
823            }
824        }
825
826        // Filter log->info
827        $log->info = format_string($log->info);
828        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
829
830        if ($nroPages>1) {
831            if ($row > EXCELROWS) {
832                $wsnumber++;
833                $myxls =& $worksheet[$wsnumber];
834                $row = FIRSTUSEDEXCELROW;
835            }
836        }
837
838        $coursecontext = context_course::instance($course->id);
839
840        $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
841        $myxls->write_date($row, 1, $log->time);
842        $myxls->write_string($row, 2, $log->ip);
843        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
844        $myxls->write_string($row, 3, $fullname);
845        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
846        $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
847        $myxls->write_string($row, 5, $log->info);
848
849        $row++;
850    }
851
852    $workbook->close();
853    return true;
854}
855
856/**
857 * Checks the integrity of the course data.
858 *
859 * In summary - compares course_sections.sequence and course_modules.section.
860 *
861 * More detailed, checks that:
862 * - course_sections.sequence contains each module id not more than once in the course
863 * - for each moduleid from course_sections.sequence the field course_modules.section
864 *   refers to the same section id (this means course_sections.sequence is more
865 *   important if they are different)
866 * - ($fullcheck only) each module in the course is present in one of
867 *   course_sections.sequence
868 * - ($fullcheck only) removes non-existing course modules from section sequences
869 *
870 * If there are any mismatches, the changes are made and records are updated in DB.
871 *
872 * Course cache is NOT rebuilt if there are any errors!
873 *
874 * This function is used each time when course cache is being rebuilt with $fullcheck = false
875 * and in CLI script admin/cli/fix_course_sequence.php with $fullcheck = true
876 *
877 * @param int $courseid id of the course
878 * @param array $rawmods result of funciton {@link get_course_mods()} - containst
879 *     the list of enabled course modules in the course. Retrieved from DB if not specified.
880 *     Argument ignored in cashe of $fullcheck, the list is retrieved form DB anyway.
881 * @param array $sections records from course_sections table for this course.
882 *     Retrieved from DB if not specified
883 * @param bool $fullcheck Will add orphaned modules to their sections and remove non-existing
884 *     course modules from sequences. Only to be used in site maintenance mode when we are
885 *     sure that another user is not in the middle of the process of moving/removing a module.
886 * @param bool $checkonly Only performs the check without updating DB, outputs all errors as debug messages.
887 * @return array array of messages with found problems. Empty output means everything is ok
888 */
889function course_integrity_check($courseid, $rawmods = null, $sections = null, $fullcheck = false, $checkonly = false) {
890    global $DB;
891    $messages = array();
892    if ($sections === null) {
893        $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section', 'id,section,sequence');
894    }
895    if ($fullcheck) {
896        // Retrieve all records from course_modules regardless of module type visibility.
897        $rawmods = $DB->get_records('course_modules', array('course' => $courseid), 'id', 'id,section');
898    }
899    if ($rawmods === null) {
900        $rawmods = get_course_mods($courseid);
901    }
902    if (!$fullcheck && (empty($sections) || empty($rawmods))) {
903        // If either of the arrays is empty, no modules are displayed anyway.
904        return true;
905    }
906    $debuggingprefix = 'Failed integrity check for course ['.$courseid.']. ';
907
908    // First make sure that each module id appears in section sequences only once.
909    // If it appears in several section sequences the last section wins.
910    // If it appears twice in one section sequence, the first occurence wins.
911    $modsection = array();
912    foreach ($sections as $sectionid => $section) {
913        $sections[$sectionid]->newsequence = $section->sequence;
914        if (!empty($section->sequence)) {
915            $sequence = explode(",", $section->sequence);
916            $sequenceunique = array_unique($sequence);
917            if (count($sequenceunique) != count($sequence)) {
918                // Some course module id appears in this section sequence more than once.
919                ksort($sequenceunique); // Preserve initial order of modules.
920                $sequence = array_values($sequenceunique);
921                $sections[$sectionid]->newsequence = join(',', $sequence);
922                $messages[] = $debuggingprefix.'Sequence for course section ['.
923                        $sectionid.'] is "'.$sections[$sectionid]->sequence.'", must be "'.$sections[$sectionid]->newsequence.'"';
924            }
925            foreach ($sequence as $cmid) {
926                if (array_key_exists($cmid, $modsection) && isset($rawmods[$cmid])) {
927                    // Some course module id appears to be in more than one section's sequences.
928                    $wrongsectionid = $modsection[$cmid];
929                    $sections[$wrongsectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$wrongsectionid]->newsequence. ','), ',');
930                    $messages[] = $debuggingprefix.'Course module ['.$cmid.'] must be removed from sequence of section ['.
931                            $wrongsectionid.'] because it is also present in sequence of section ['.$sectionid.']';
932                }
933                $modsection[$cmid] = $sectionid;
934            }
935        }
936    }
937
938    // Add orphaned modules to their sections if they exist or to section 0 otherwise.
939    if ($fullcheck) {
940        foreach ($rawmods as $cmid => $mod) {
941            if (!isset($modsection[$cmid])) {
942                // This is a module that is not mentioned in course_section.sequence at all.
943                // Add it to the section $mod->section or to the last available section.
944                if ($mod->section && isset($sections[$mod->section])) {
945                    $modsection[$cmid] = $mod->section;
946                } else {
947                    $firstsection = reset($sections);
948                    $modsection[$cmid] = $firstsection->id;
949                }
950                $sections[$modsection[$cmid]]->newsequence = trim($sections[$modsection[$cmid]]->newsequence.','.$cmid, ',');
951                $messages[] = $debuggingprefix.'Course module ['.$cmid.'] is missing from sequence of section ['.
952                        $modsection[$cmid].']';
953            }
954        }
955        foreach ($modsection as $cmid => $sectionid) {
956            if (!isset($rawmods[$cmid])) {
957                // Section $sectionid refers to module id that does not exist.
958                $sections[$sectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$sectionid]->newsequence.','), ',');
959                $messages[] = $debuggingprefix.'Course module ['.$cmid.
960                        '] does not exist but is present in the sequence of section ['.$sectionid.']';
961            }
962        }
963    }
964
965    // Update changed sections.
966    if (!$checkonly && !empty($messages)) {
967        foreach ($sections as $sectionid => $section) {
968            if ($section->newsequence !== $section->sequence) {
969                $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $section->newsequence));
970            }
971        }
972    }
973
974    // Now make sure that all modules point to the correct sections.
975    foreach ($rawmods as $cmid => $mod) {
976        if (isset($modsection[$cmid]) && $modsection[$cmid] != $mod->section) {
977            if (!$checkonly) {
978                $DB->update_record('course_modules', array('id' => $cmid, 'section' => $modsection[$cmid]));
979            }
980            $messages[] = $debuggingprefix.'Course module ['.$cmid.
981                    '] points to section ['.$mod->section.'] instead of ['.$modsection[$cmid].']';
982        }
983    }
984
985    return $messages;
986}
987
988/**
989 * For a given course, returns an array of course activity objects
990 * Each item in the array contains he following properties:
991 */
992function get_array_of_activities($courseid) {
993//  cm - course module id
994//  mod - name of the module (eg forum)
995//  section - the number of the section (eg week or topic)
996//  name - the name of the instance
997//  visible - is the instance visible or not
998//  groupingid - grouping id
999//  extra - contains extra string to include in any link
1000    global $CFG, $DB;
1001
1002    $course = $DB->get_record('course', array('id'=>$courseid));
1003
1004    if (empty($course)) {
1005        throw new moodle_exception('courseidnotfound');
1006    }
1007
1008    $mod = array();
1009
1010    $rawmods = get_course_mods($courseid);
1011    if (empty($rawmods)) {
1012        return $mod; // always return array
1013    }
1014
1015    if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
1016        // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
1017        if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
1018            debugging(join('<br>', $errormessages));
1019            $rawmods = get_course_mods($courseid);
1020            $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
1021        }
1022        // Build array of activities.
1023       foreach ($sections as $section) {
1024           if (!empty($section->sequence)) {
1025               $sequence = explode(",", $section->sequence);
1026               foreach ($sequence as $seq) {
1027                   if (empty($rawmods[$seq])) {
1028                       continue;
1029                   }
1030                   $mod[$seq] = new stdClass();
1031                   $mod[$seq]->id               = $rawmods[$seq]->instance;
1032                   $mod[$seq]->cm               = $rawmods[$seq]->id;
1033                   $mod[$seq]->mod              = $rawmods[$seq]->modname;
1034
1035                    // Oh dear. Inconsistent names left here for backward compatibility.
1036                   $mod[$seq]->section          = $section->section;
1037                   $mod[$seq]->sectionid        = $rawmods[$seq]->section;
1038
1039                   $mod[$seq]->module           = $rawmods[$seq]->module;
1040                   $mod[$seq]->added            = $rawmods[$seq]->added;
1041                   $mod[$seq]->score            = $rawmods[$seq]->score;
1042                   $mod[$seq]->idnumber         = $rawmods[$seq]->idnumber;
1043                   $mod[$seq]->visible          = $rawmods[$seq]->visible;
1044                   $mod[$seq]->visibleold       = $rawmods[$seq]->visibleold;
1045                   $mod[$seq]->groupmode        = $rawmods[$seq]->groupmode;
1046                   $mod[$seq]->groupingid       = $rawmods[$seq]->groupingid;
1047                   $mod[$seq]->indent           = $rawmods[$seq]->indent;
1048                   $mod[$seq]->completion       = $rawmods[$seq]->completion;
1049                   $mod[$seq]->extra            = "";
1050                   $mod[$seq]->completiongradeitemnumber =
1051                           $rawmods[$seq]->completiongradeitemnumber;
1052                   $mod[$seq]->completionview   = $rawmods[$seq]->completionview;
1053                   $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
1054                   $mod[$seq]->showdescription  = $rawmods[$seq]->showdescription;
1055                   $mod[$seq]->availability = $rawmods[$seq]->availability;
1056
1057                   $modname = $mod[$seq]->mod;
1058                   $functionname = $modname."_get_coursemodule_info";
1059
1060                   if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
1061                       continue;
1062                   }
1063
1064                   include_once("$CFG->dirroot/mod/$modname/lib.php");
1065
1066                   if ($hasfunction = function_exists($functionname)) {
1067                       if ($info = $functionname($rawmods[$seq])) {
1068                           if (!empty($info->icon)) {
1069                               $mod[$seq]->icon = $info->icon;
1070                           }
1071                           if (!empty($info->iconcomponent)) {
1072                               $mod[$seq]->iconcomponent = $info->iconcomponent;
1073                           }
1074                           if (!empty($info->name)) {
1075                               $mod[$seq]->name = $info->name;
1076                           }
1077                           if ($info instanceof cached_cm_info) {
1078                               // When using cached_cm_info you can include three new fields
1079                               // that aren't available for legacy code
1080                               if (!empty($info->content)) {
1081                                   $mod[$seq]->content = $info->content;
1082                               }
1083                               if (!empty($info->extraclasses)) {
1084                                   $mod[$seq]->extraclasses = $info->extraclasses;
1085                               }
1086                               if (!empty($info->iconurl)) {
1087                                   // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
1088                                   $url = new moodle_url($info->iconurl);
1089                                   $mod[$seq]->iconurl = $url->out(false);
1090                               }
1091                               if (!empty($info->onclick)) {
1092                                   $mod[$seq]->onclick = $info->onclick;
1093                               }
1094                               if (!empty($info->customdata)) {
1095                                   $mod[$seq]->customdata = $info->customdata;
1096                               }
1097                           } else {
1098                               // When using a stdclass, the (horrible) deprecated ->extra field
1099                               // is available for BC
1100                               if (!empty($info->extra)) {
1101                                   $mod[$seq]->extra = $info->extra;
1102                               }
1103                           }
1104                       }
1105                   }
1106                   // When there is no modname_get_coursemodule_info function,
1107                   // but showdescriptions is enabled, then we use the 'intro'
1108                   // and 'introformat' fields in the module table
1109                   if (!$hasfunction && $rawmods[$seq]->showdescription) {
1110                       if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
1111                               array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
1112                           // Set content from intro and introformat. Filters are disabled
1113                           // because we  filter it with format_text at display time
1114                           $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
1115                                   $modvalues, $rawmods[$seq]->id, false);
1116
1117                           // To save making another query just below, put name in here
1118                           $mod[$seq]->name = $modvalues->name;
1119                       }
1120                   }
1121                   if (!isset($mod[$seq]->name)) {
1122                       $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1123                   }
1124
1125                    // Minimise the database size by unsetting default options when they are
1126                    // 'empty'. This list corresponds to code in the cm_info constructor.
1127                    foreach (array('idnumber', 'groupmode', 'groupingid',
1128                            'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
1129                            'icon', 'iconcomponent', 'customdata', 'availability', 'completionview',
1130                            'completionexpected', 'score', 'showdescription') as $property) {
1131                       if (property_exists($mod[$seq], $property) &&
1132                               empty($mod[$seq]->{$property})) {
1133                           unset($mod[$seq]->{$property});
1134                       }
1135                   }
1136                   // Special case: this value is usually set to null, but may be 0
1137                   if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
1138                           is_null($mod[$seq]->completiongradeitemnumber)) {
1139                       unset($mod[$seq]->completiongradeitemnumber);
1140                   }
1141               }
1142            }
1143        }
1144    }
1145    return $mod;
1146}
1147
1148/**
1149 * Returns the localised human-readable names of all used modules
1150 *
1151 * @param bool $plural if true returns the plural forms of the names
1152 * @return array where key is the module name (component name without 'mod_') and
1153 *     the value is the human-readable string. Array sorted alphabetically by value
1154 */
1155function get_module_types_names($plural = false) {
1156    static $modnames = null;
1157    global $DB, $CFG;
1158    if ($modnames === null) {
1159        $modnames = array(0 => array(), 1 => array());
1160        if ($allmods = $DB->get_records("modules")) {
1161            foreach ($allmods as $mod) {
1162                if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) {
1163                    $modnames[0][$mod->name] = get_string("modulename", "$mod->name");
1164                    $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name");
1165                }
1166            }
1167            core_collator::asort($modnames[0]);
1168            core_collator::asort($modnames[1]);
1169        }
1170    }
1171    return $modnames[(int)$plural];
1172}
1173
1174/**
1175 * Set highlighted section. Only one section can be highlighted at the time.
1176 *
1177 * @param int $courseid course id
1178 * @param int $marker highlight section with this number, 0 means remove higlightin
1179 * @return void
1180 */
1181function course_set_marker($courseid, $marker) {
1182    global $DB;
1183    $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1184    format_base::reset_course_cache($courseid);
1185}
1186
1187/**
1188 * For a given course section, marks it visible or hidden,
1189 * and does the same for every activity in that section
1190 *
1191 * @param int $courseid course id
1192 * @param int $sectionnumber The section number to adjust
1193 * @param int $visibility The new visibility
1194 * @return array A list of resources which were hidden in the section
1195 */
1196function set_section_visible($courseid, $sectionnumber, $visibility) {
1197    global $DB;
1198
1199    $resourcestotoggle = array();
1200    if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1201        $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1202
1203        $event = \core\event\course_section_updated::create(array(
1204            'context' => context_course::instance($courseid),
1205            'objectid' => $section->id,
1206            'other' => array(
1207                'sectionnum' => $sectionnumber
1208            )
1209        ));
1210        $event->add_record_snapshot('course_sections', $section);
1211        $event->trigger();
1212
1213        if (!empty($section->sequence)) {
1214            $modules = explode(",", $section->sequence);
1215            foreach ($modules as $moduleid) {
1216                if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
1217                    if ($visibility) {
1218                        // As we unhide the section, we use the previously saved visibility stored in visibleold.
1219                        set_coursemodule_visible($moduleid, $cm->visibleold);
1220                    } else {
1221                        // We hide the section, so we hide the module but we store the original state in visibleold.
1222                        set_coursemodule_visible($moduleid, 0);
1223                        $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
1224                    }
1225                    \core\event\course_module_updated::create_from_cm($cm)->trigger();
1226                }
1227            }
1228        }
1229        rebuild_course_cache($courseid, true);
1230
1231        // Determine which modules are visible for AJAX update
1232        if (!empty($modules)) {
1233            list($insql, $params) = $DB->get_in_or_equal($modules);
1234            $select = 'id ' . $insql . ' AND visible = ?';
1235            array_push($params, $visibility);
1236            if (!$visibility) {
1237                $select .= ' AND visibleold = 1';
1238            }
1239            $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1240        }
1241    }
1242    return $resourcestotoggle;
1243}
1244
1245/**
1246 * Retrieve all metadata for the requested modules
1247 *
1248 * @param object $course The Course
1249 * @param array $modnames An array containing the list of modules and their
1250 * names
1251 * @param int $sectionreturn The section to return to
1252 * @return array A list of stdClass objects containing metadata about each
1253 * module
1254 */
1255function get_module_metadata($course, $modnames, $sectionreturn = null) {
1256    global $CFG, $OUTPUT;
1257
1258    // get_module_metadata will be called once per section on the page and courses may show
1259    // different modules to one another
1260    static $modlist = array();
1261    if (!isset($modlist[$course->id])) {
1262        $modlist[$course->id] = array();
1263    }
1264
1265    $return = array();
1266    $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
1267    if ($sectionreturn !== null) {
1268        $urlbase->param('sr', $sectionreturn);
1269    }
1270    foreach($modnames as $modname => $modnamestr) {
1271        if (!course_allowed_module($course, $modname)) {
1272            continue;
1273        }
1274        if (isset($modlist[$course->id][$modname])) {
1275            // This module is already cached
1276            $return[$modname] = $modlist[$course->id][$modname];
1277            continue;
1278        }
1279
1280        // Include the module lib
1281        $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1282        if (!file_exists($libfile)) {
1283            continue;
1284        }
1285        include_once($libfile);
1286
1287        // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1288        $gettypesfunc =  $modname.'_get_types';
1289        $types = MOD_SUBTYPE_NO_CHILDREN;
1290        if (function_exists($gettypesfunc)) {
1291            $types = $gettypesfunc();
1292        }
1293        if ($types !== MOD_SUBTYPE_NO_CHILDREN) {
1294            if (is_array($types) && count($types) > 0) {
1295                $group = new stdClass();
1296                $group->name = $modname;
1297                $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1298                foreach($types as $type) {
1299                    if ($type->typestr === '--') {
1300                        continue;
1301                    }
1302                    if (strpos($type->typestr, '--') === 0) {
1303                        $group->title = str_replace('--', '', $type->typestr);
1304                        continue;
1305                    }
1306                    // Set the Sub Type metadata
1307                    $subtype = new stdClass();
1308                    $subtype->title = $type->typestr;
1309                    $subtype->type = str_replace('&amp;', '&', $type->type);
1310                    $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1311                    $subtype->archetype = $type->modclass;
1312
1313                    // The group archetype should match the subtype archetypes and all subtypes
1314                    // should have the same archetype
1315                    $group->archetype = $subtype->archetype;
1316
1317                    if (!empty($type->help)) {
1318                        $subtype->help = $type->help;
1319                    } else if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1320                        $subtype->help = get_string('help' . $subtype->name, $modname);
1321                    }
1322                    $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $subtype->name));
1323                    $group->types[] = $subtype;
1324                }
1325                $modlist[$course->id][$modname] = $group;
1326            }
1327        } else {
1328            $module = new stdClass();
1329            $module->title = $modnamestr;
1330            $module->name = $modname;
1331            $module->link = new moodle_url($urlbase, array('add' => $modname));
1332            $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1333            $sm = get_string_manager();
1334            if ($sm->string_exists('modulename_help', $modname)) {
1335                $module->help = get_string('modulename_help', $modname);
1336                if ($sm->string_exists('modulename_link', $modname)) {  // Link to further info in Moodle docs
1337                    $link = get_string('modulename_link', $modname);
1338                    $linktext = get_string('morehelp');
1339                    $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
1340                }
1341            }
1342            $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1343            $modlist[$course->id][$modname] = $module;
1344        }
1345        if (isset($modlist[$course->id][$modname])) {
1346            $return[$modname] = $modlist[$course->id][$modname];
1347        } else {
1348            debugging("Invalid module metadata configuration for {$modname}");
1349        }
1350    }
1351
1352    return $return;
1353}
1354
1355/**
1356 * Return the course category context for the category with id $categoryid, except
1357 * that if $categoryid is 0, return the system context.
1358 *
1359 * @param integer $categoryid a category id or 0.
1360 * @return context the corresponding context
1361 */
1362function get_category_or_system_context($categoryid) {
1363    if ($categoryid) {
1364        return context_coursecat::instance($categoryid, IGNORE_MISSING);
1365    } else {
1366        return context_system::instance();
1367    }
1368}
1369
1370/**
1371 * Returns full course categories trees to be used in html_writer::select()
1372 *
1373 * Calls {@link coursecat::make_categories_list()} to build the tree and
1374 * adds whitespace to denote nesting
1375 *
1376 * @return array array mapping coursecat id to the display name
1377 */
1378function make_categories_options() {
1379    global $CFG;
1380    require_once($CFG->libdir. '/coursecatlib.php');
1381    $cats = coursecat::make_categories_list('', 0, ' / ');
1382    foreach ($cats as $key => $value) {
1383        // Prefix the value with the number of spaces equal to category depth (number of separators in the value).
1384        $cats[$key] = str_repeat('&nbsp;', substr_count($value, ' / ')). $value;
1385    }
1386    return $cats;
1387}
1388
1389/**
1390 * Print the buttons relating to course requests.
1391 *
1392 * @param object $context current page context.
1393 */
1394function print_course_request_buttons($context) {
1395    global $CFG, $DB, $OUTPUT;
1396    if (empty($CFG->enablecourserequests)) {
1397        return;
1398    }
1399    if (!has_capability('moodle/course:create', $context) && has_capability('moodle/course:request', $context)) {
1400    /// Print a button to request a new course
1401        echo $OUTPUT->single_button(new moodle_url('/course/request.php'), get_string('requestcourse'), 'get');
1402    }
1403    /// Print a button to manage pending requests
1404    if ($context->contextlevel == CONTEXT_SYSTEM && has_capability('moodle/site:approvecourse', $context)) {
1405        $disabled = !$DB->record_exists('course_request', array());
1406        echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled));
1407    }
1408}
1409
1410/**
1411 * Does the user have permission to edit things in this category?
1412 *
1413 * @param integer $categoryid The id of the category we are showing, or 0 for system context.
1414 * @return boolean has_any_capability(array(...), ...); in the appropriate context.
1415 */
1416function can_edit_in_category($categoryid = 0) {
1417    $context = get_category_or_system_context($categoryid);
1418    return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
1419}
1420
1421/// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
1422
1423function add_course_module($mod) {
1424    global $DB;
1425
1426    $mod->added = time();
1427    unset($mod->id);
1428
1429    $cmid = $DB->insert_record("course_modules", $mod);
1430    rebuild_course_cache($mod->course, true);
1431    return $cmid;
1432}
1433
1434/**
1435 * Creates missing course section(s) and rebuilds course cache
1436 *
1437 * @param int|stdClass $courseorid course id or course object
1438 * @param int|array $sections list of relative section numbers to create
1439 * @return bool if there were any sections created
1440 */
1441function course_create_sections_if_missing($courseorid, $sections) {
1442    global $DB;
1443    if (!is_array($sections)) {
1444        $sections = array($sections);
1445    }
1446    $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all());
1447    if (is_object($courseorid)) {
1448        $courseorid = $courseorid->id;
1449    }
1450    $coursechanged = false;
1451    foreach ($sections as $sectionnum) {
1452        if (!in_array($sectionnum, $existing)) {
1453            $cw = new stdClass();
1454            $cw->course   = $courseorid;
1455            $cw->section  = $sectionnum;
1456            $cw->summary  = '';
1457            $cw->summaryformat = FORMAT_HTML;
1458            $cw->sequence = '';
1459            $id = $DB->insert_record("course_sections", $cw);
1460            $coursechanged = true;
1461        }
1462    }
1463    if ($coursechanged) {
1464        rebuild_course_cache($courseorid, true);
1465    }
1466    return $coursechanged;
1467}
1468
1469/**
1470 * Adds an existing module to the section
1471 *
1472 * Updates both tables {course_sections} and {course_modules}
1473 *
1474 * Note: This function does not use modinfo PROVIDED that the section you are
1475 * adding the module to already exists. If the section does not exist, it will
1476 * build modinfo if necessary and create the section.
1477 *
1478 * @param int|stdClass $courseorid course id or course object
1479 * @param int $cmid id of the module already existing in course_modules table
1480 * @param int $sectionnum relative number of the section (field course_sections.section)
1481 *     If section does not exist it will be created
1482 * @param int|stdClass $beforemod id or object with field id corresponding to the module
1483 *     before which the module needs to be included. Null for inserting in the
1484 *     end of the section
1485 * @return int The course_sections ID where the module is inserted
1486 */
1487function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) {
1488    global $DB, $COURSE;
1489    if (is_object($beforemod)) {
1490        $beforemod = $beforemod->id;
1491    }
1492    if (is_object($courseorid)) {
1493        $courseid = $courseorid->id;
1494    } else {
1495        $courseid = $courseorid;
1496    }
1497    // Do not try to use modinfo here, there is no guarantee it is valid!
1498    $section = $DB->get_record('course_sections',
1499            array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING);
1500    if (!$section) {
1501        // This function call requires modinfo.
1502        course_create_sections_if_missing($courseorid, $sectionnum);
1503        $section = $DB->get_record('course_sections',
1504                array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST);
1505    }
1506
1507    $modarray = explode(",", trim($section->sequence));
1508    if (empty($section->sequence)) {
1509        $newsequence = "$cmid";
1510    } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) {
1511        $insertarray = array($cmid, $beforemod);
1512        array_splice($modarray, $key[0], 1, $insertarray);
1513        $newsequence = implode(",", $modarray);
1514    } else {
1515        $newsequence = "$section->sequence,$cmid";
1516    }
1517    $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id));
1518    $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid));
1519    if (is_object($courseorid)) {
1520        rebuild_course_cache($courseorid->id, true);
1521    } else {
1522        rebuild_course_cache($courseorid, true);
1523    }
1524    return $section->id;     // Return course_sections ID that was used.
1525}
1526
1527/**
1528 * Change the group mode of a course module.
1529 *
1530 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
1531 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
1532 *
1533 * @param int $id course module ID.
1534 * @param int $groupmode the new groupmode value.
1535 * @return bool True if the $groupmode was updated.
1536 */
1537function set_coursemodule_groupmode($id, $groupmode) {
1538    global $DB;
1539    $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST);
1540    if ($cm->groupmode != $groupmode) {
1541        $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id));
1542        rebuild_course_cache($cm->course, true);
1543    }
1544    return ($cm->groupmode != $groupmode);
1545}
1546
1547function set_coursemodule_idnumber($id, $idnumber) {
1548    global $DB;
1549    $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST);
1550    if ($cm->idnumber != $idnumber) {
1551        $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
1552        rebuild_course_cache($cm->course, true);
1553    }
1554    return ($cm->idnumber != $idnumber);
1555}
1556
1557/**
1558 * Set the visibility of a module and inherent properties.
1559 *
1560 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
1561 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
1562 *
1563 * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
1564 * has been moved to {@link set_section_visible()} which was the only place from which
1565 * the parameter was used.
1566 *
1567 * @param int $id of the module
1568 * @param int $visible state of the module
1569 * @return bool false when the module was not found, true otherwise
1570 */
1571function set_coursemodule_visible($id, $visible) {
1572    global $DB, $CFG;
1573    require_once($CFG->libdir.'/gradelib.php');
1574    require_once($CFG->dirroot.'/calendar/lib.php');
1575
1576    // Trigger developer's attention when using the previously removed argument.
1577    if (func_num_args() > 2) {
1578        debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
1579            has been removed.', DEBUG_DEVELOPER);
1580    }
1581
1582    if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
1583        return false;
1584    }
1585
1586    // Create events and propagate visibility to associated grade items if the value has changed.
1587    // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
1588    if ($cm->visible == $visible) {
1589        return true;
1590    }
1591
1592    if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
1593        return false;
1594    }
1595    if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
1596        foreach($events as $event) {
1597            if ($visible) {
1598                $event = new calendar_event($event);
1599                $event->toggle_visibility(true);
1600            } else {
1601                $event = new calendar_event($event);
1602                $event->toggle_visibility(false);
1603            }
1604        }
1605    }
1606
1607    // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
1608    // affect visibleold to allow for an original visibility restore. See set_section_visible().
1609    $cminfo = new stdClass();
1610    $cminfo->id = $id;
1611    $cminfo->visible = $visible;
1612    $cminfo->visibleold = $visible;
1613    $DB->update_record('course_modules', $cminfo);
1614
1615    // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
1616    // Note that this must be done after updating the row in course_modules, in case
1617    // the modules grade_item_update function needs to access $cm->visible.
1618    if (plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
1619            component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
1620        $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
1621        component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
1622    } else {
1623        $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
1624        if ($grade_items) {
1625            foreach ($grade_items as $grade_item) {
1626                $grade_item->set_hidden(!$visible);
1627            }
1628        }
1629    }
1630
1631    rebuild_course_cache($cm->course, true);
1632    return true;
1633}
1634
1635/**
1636 * This function will handle the whole deletion process of a module. This includes calling
1637 * the modules delete_instance function, deleting files, events, grades, conditional data,
1638 * the data in the course_module and course_sections table and adding a module deletion
1639 * event to the DB.
1640 *
1641 * @param int $cmid the course module id
1642 * @since Moodle 2.5
1643 */
1644function course_delete_module($cmid) {
1645    global $CFG, $DB;
1646
1647    require_once($CFG->libdir.'/gradelib.php');
1648    require_once($CFG->libdir.'/questionlib.php');
1649    require_once($CFG->dirroot.'/blog/lib.php');
1650    require_once($CFG->dirroot.'/calendar/lib.php');
1651    require_once($CFG->dirroot.'/tag/lib.php');
1652
1653    // Get the course module.
1654    if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
1655        return true;
1656    }
1657
1658    // Get the module context.
1659    $modcontext = context_module::instance($cm->id);
1660
1661    // Get the course module name.
1662    $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
1663
1664    // Get the file location of the delete_instance function for this module.
1665    $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
1666
1667    // Include the file required to call the delete_instance function for this module.
1668    if (file_exists($modlib)) {
1669        require_once($modlib);
1670    } else {
1671        throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
1672            "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
1673    }
1674
1675    $deleteinstancefunction = $modulename . '_delete_instance';
1676
1677    // Ensure the delete_instance function exists for this module.
1678    if (!function_exists($deleteinstancefunction)) {
1679        throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
1680            "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
1681    }
1682
1683    // Delete activity context questions and question categories.
1684    question_delete_activity($cm);
1685
1686    // Call the delete_instance function, if it returns false throw an exception.
1687    if (!$deleteinstancefunction($cm->instance)) {
1688        throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
1689            "Cannot delete the module $modulename (instance).");
1690    }
1691
1692    // Remove all module files in case modules forget to do that.
1693    $fs = get_file_storage();
1694    $fs->delete_area_files($modcontext->id);
1695
1696    // Delete events from calendar.
1697    if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
1698        foreach($events as $event) {
1699            $calendarevent = calendar_event::load($event->id);
1700            $calendarevent->delete();
1701        }
1702    }
1703
1704    // Delete grade items, outcome items and grades attached to modules.
1705    if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
1706                                                   'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
1707        foreach ($grade_items as $grade_item) {
1708            $grade_item->delete('moddelete');
1709        }
1710    }
1711
1712    // Delete completion and availability data; it is better to do this even if the
1713    // features are not turned on, in case they were turned on previously (these will be
1714    // very quick on an empty table).
1715    $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
1716    $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
1717                                                            'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
1718
1719    // Delete all tag instances associated with the instance of this module.
1720    tag_delete_instances('mod_' . $modulename, $modcontext->id);
1721
1722    // Delete the context.
1723    context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
1724
1725    // Delete the module from the course_modules table.
1726    $DB->delete_records('course_modules', array('id' => $cm->id));
1727
1728    // Delete module from that section.
1729    if (!delete_mod_from_section($cm->id, $cm->section)) {
1730        throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
1731            "Cannot delete the module $modulename (instance) from section.");
1732    }
1733
1734    // Trigger event for course module delete action.
1735    $event = \core\event\course_module_deleted::create(array(
1736        'courseid' => $cm->course,
1737        'context'  => $modcontext,
1738        'objectid' => $cm->id,
1739        'other'    => array(
1740            'modulename' => $modulename,
1741            'instanceid'   => $cm->instance,
1742        )
1743    ));
1744    $event->add_record_snapshot('course_modules', $cm);
1745    $event->trigger();
1746    rebuild_course_cache($cm->course, true);
1747}
1748
1749function delete_mod_from_section($modid, $sectionid) {
1750    global $DB;
1751
1752    if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) {
1753
1754        $modarray = explode(",", $section->sequence);
1755
1756        if ($key = array_keys ($modarray, $modid)) {
1757            array_splice($modarray, $key[0], 1);
1758            $newsequence = implode(",", $modarray);
1759            $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
1760            rebuild_course_cache($section->course, true);
1761            return true;
1762        } else {
1763            return false;
1764        }
1765
1766    }
1767    return false;
1768}
1769
1770/**
1771 * Moves a section within a course, from a position to another.
1772 * Be very careful: $section and $destination refer to section number,
1773 * not id!.
1774 *
1775 * @param object $course
1776 * @param int $section Section number (not id!!!)
1777 * @param int $destination
1778 * @param bool $ignorenumsections
1779 * @return boolean Result
1780 */
1781function move_section_to($course, $section, $destination, $ignorenumsections = false) {
1782/// Moves a whole course section up and down within the course
1783    global $USER, $DB;
1784
1785    if (!$destination && $destination != 0) {
1786        return true;
1787    }
1788
1789    // compartibility with course formats using field 'numsections'
1790    $courseformatoptions = course_get_format($course)->get_format_options();
1791    if ((!$ignorenumsections && array_key_exists('numsections', $courseformatoptions) &&
1792            ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
1793        return false;
1794    }
1795
1796    // Get all sections for this course and re-order them (2 of them should now share the same section number)
1797    if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
1798            'section ASC, id ASC', 'id, section')) {
1799        return false;
1800    }
1801
1802    $movedsections = reorder_sections($sections, $section, $destination);
1803
1804    // Update all sections. Do this in 2 steps to avoid breaking database
1805    // uniqueness constraint
1806    $transaction = $DB->start_delegated_transaction();
1807    foreach ($movedsections as $id => $position) {
1808        if ($sections[$id] !== $position) {
1809            $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
1810        }
1811    }
1812    foreach ($movedsections as $id => $position) {
1813        if ($sections[$id] !== $position) {
1814            $DB->set_field('course_sections', 'section', $position, array('id' => $id));
1815        }
1816    }
1817
1818    // If we move the highlighted section itself, then just highlight the destination.
1819    // Adjust the higlighted section location if we move something over it either direction.
1820    if ($section == $course->marker) {
1821        course_set_marker($course->id, $destination);
1822    } elseif ($section > $course->marker && $course->marker >= $destination) {
1823        course_set_marker($course->id, $course->marker+1);
1824    } elseif ($section < $course->marker && $course->marker <= $destination) {
1825        course_set_marker($course->id, $course->marker-1);
1826    }
1827
1828    $transaction->allow_commit();
1829    rebuild_course_cache($course->id, true);
1830    return true;
1831}
1832
1833/**
1834 * This method will delete a course section and may delete all modules inside it.
1835 *
1836 * No permissions are checked here, use {@link course_can_delete_section()} to
1837 * check if section can actually be deleted.
1838 *
1839 * @param int|stdClass $course
1840 * @param int|stdClass|section_info $section
1841 * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it.
1842 * @return bool whether section was deleted
1843 */
1844function course_delete_section($course, $section, $forcedeleteifnotempty = true) {
1845    return course_get_format($course)->delete_section($section, $forcedeleteifnotempty);
1846}
1847
1848/**
1849 * Checks if the current user can delete a section (if course format allows it and user has proper permissions).
1850 *
1851 * @param int|stdClass $course
1852 * @param int|stdClass|section_info $section
1853 * @return bool
1854 */
1855function course_can_delete_section($course, $section) {
1856    if (is_object($section)) {
1857        $section = $section->section;
1858    }
1859    if (!$section) {
1860        // Not possible to delete 0-section.
1861        return false;
1862    }
1863    // Course format should allow to delete sections.
1864    if (!course_get_format($course)->can_delete_section($section)) {
1865        return false;
1866    }
1867    // Make sure user has capability to update course and move sections.
1868    $context = context_course::instance(is_object($course) ? $course->id : $course);
1869    if (!has_all_capabilities(array('moodle/course:movesections', 'moodle/course:update'), $context)) {
1870        return false;
1871    }
1872    // Make sure user has capability to delete each activity in this section.
1873    $modinfo = get_fast_modinfo($course);
1874    if (!empty($modinfo->sections[$section])) {
1875        foreach ($modinfo->sections[$section] as $cmid) {
1876            if (!has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
1877                return false;
1878            }
1879        }
1880    }
1881    return true;
1882}
1883
1884/**
1885 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
1886 * an original position number and a target position number, rebuilds the array so that the
1887 * move is made without any duplication of section positions.
1888 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
1889 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
1890 *
1891 * @param array $sections
1892 * @param int $origin_position
1893 * @param int $target_position
1894 * @return array
1895 */
1896function reorder_sections($sections, $origin_position, $target_position) {
1897    if (!is_array($sections)) {
1898        return false;
1899    }
1900
1901    // We can't move section position 0
1902    if ($origin_position < 1) {
1903        echo "We can't move section position 0";
1904        return false;
1905    }
1906
1907    // Locate origin section in sections array
1908    if (!$origin_key = array_search($origin_position, $sections)) {
1909        echo "searched position not in sections array";
1910        return false; // searched position not in sections array
1911    }
1912
1913    // Extract origin section
1914    $origin_section = $sections[$origin_key];
1915    unset($sections[$origin_key]);
1916
1917    // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
1918    $found = false;
1919    $append_array = array();
1920    foreach ($sections as $id => $position) {
1921        if ($found) {
1922            $append_array[$id] = $position;
1923            unset($sections[$id]);
1924        }
1925        if ($position == $target_position) {
1926            if ($target_position < $origin_position) {
1927                $append_array[$id] = $position;
1928                unset($sections[$id]);
1929            }
1930            $found = true;
1931        }
1932    }
1933
1934    // Append moved section
1935    $sections[$origin_key] = $origin_section;
1936
1937    // Append rest of array (if applicable)
1938    if (!empty($append_array)) {
1939        foreach ($append_array as $id => $position) {
1940            $sections[$id] = $position;
1941        }
1942    }
1943
1944    // Renumber positions
1945    $position = 0;
1946    foreach ($sections as $id => $p) {
1947        $sections[$id] = $position;
1948        $position++;
1949    }
1950
1951    return $sections;
1952
1953}
1954
1955/**
1956 * Move the module object $mod to the specified $section
1957 * If $beforemod exists then that is the module
1958 * before which $modid should be inserted
1959 *
1960 * @param stdClass|cm_info $mod
1961 * @param stdClass|section_info $section
1962 * @param int|stdClass $beforemod id or object with field id corresponding to the module
1963 *     before which the module needs to be included. Null for inserting in the
1964 *     end of the section
1965 * @return int new value for module visibility (0 or 1)
1966 */
1967function moveto_module($mod, $section, $beforemod=NULL) {
1968    global $OUTPUT, $DB;
1969
1970    // Current module visibility state - return value of this function.
1971    $modvisible = $mod->visible;
1972
1973    // Remove original module from original section.
1974    if (! delete_mod_from_section($mod->id, $mod->section)) {
1975        echo $OUTPUT->notification("Could not delete module from existing section");
1976    }
1977
1978    // If moving to a hidden section then hide module.
1979    if ($mod->section != $section->id) {
1980        if (!$section->visible && $mod->visible) {
1981            // Module was visible but must become hidden after moving to hidden section.
1982            $modvisible = 0;
1983            set_coursemodule_visible($mod->id, 0);
1984            // Set visibleold to 1 so module will be visible when section is made visible.
1985            $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
1986        }
1987        if ($section->visible && !$mod->visible) {
1988            // Hidden module was moved to the visible section, restore the module visibility from visibleold.
1989            set_coursemodule_visible($mod->id, $mod->visibleold);
1990            $modvisible = $mod->visibleold;
1991        }
1992    }
1993
1994    // Add the module into the new section.
1995    course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod);
1996    return $modvisible;
1997}
1998
1999/**
2000 * Returns the list of all editing actions that current user can perform on the module
2001 *
2002 * @param cm_info $mod The module to produce editing buttons for
2003 * @param int $indent The current indenting (default -1 means no move left-right actions)
2004 * @param int $sr The section to link back to (used for creating the links)
2005 * @return array array of action_link or pix_icon objects
2006 */
2007function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
2008    global $COURSE, $SITE;
2009
2010    static $str;
2011
2012    $coursecontext = context_course::instance($mod->course);
2013    $modcontext = context_module::instance($mod->id);
2014
2015    $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
2016    $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
2017
2018    // No permission to edit anything.
2019    if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
2020        return array();
2021    }
2022
2023    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2024
2025    if (!isset($str)) {
2026        $str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
2027            'editsettings', 'duplicate', 'hide', 'show'), 'moodle');
2028        $str->assign         = get_string('assignroles', 'role');
2029        $str->groupsnone     = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
2030        $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
2031        $str->groupsvisible  = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
2032    }
2033
2034    $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2035
2036    if ($sr !== null) {
2037        $baseurl->param('sr', $sr);
2038    }
2039    $actions = array();
2040
2041    // Update.
2042    if ($hasmanageactivities) {
2043        $actions['update'] = new action_menu_link_secondary(
2044            new moodle_url($baseurl, array('update' => $mod->id)),
2045            new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2046            $str->editsettings,
2047            array('class' => 'editing_update', 'data-action' => 'update')
2048        );
2049    }
2050
2051    // Indent.
2052    if ($hasmanageactivities && $indent >= 0) {
2053        $indentlimits = new stdClass();
2054        $indentlimits->min = 0;
2055        $indentlimits->max = 16;
2056        if (right_to_left()) {   // Exchange arrows on RTL
2057            $rightarrow = 't/left';
2058            $leftarrow  = 't/right';
2059        } else {
2060            $rightarrow = 't/right';
2061            $leftarrow  = 't/left';
2062        }
2063
2064        if ($indent >= $indentlimits->max) {
2065            $enabledclass = 'hidden';
2066        } else {
2067            $enabledclass = '';
2068        }
2069        $actions['moveright'] = new action_menu_link_secondary(
2070            new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
2071            new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2072            $str->moveright,
2073            array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true)
2074        );
2075
2076        if ($indent <= $indentlimits->min) {
2077            $enabledclass = 'hidden';
2078        } else {
2079            $enabledclass = '';
2080        }
2081        $actions['moveleft'] = new action_menu_link_secondary(
2082            new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
2083            new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2084            $str->moveleft,
2085            array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true)
2086        );
2087
2088    }
2089
2090    // Hide/Show.
2091    if (has_capability('moodle/course:activityvisibility', $modcontext)) {
2092        if ($mod->visible) {
2093            $actions['hide'] = new action_menu_link_secondary(
2094                new moodle_url($baseurl, array('hide' => $mod->id)),
2095                new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2096                $str->hide,
2097                array('class' => 'editing_hide', 'data-action' => 'hide')
2098            );
2099        } else {
2100            $actions['show'] = new action_menu_link_secondary(
2101                new moodle_url($baseurl, array('show' => $mod->id)),
2102                new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2103                $str->show,
2104                array('class' => 'editing_show', 'data-action' => 'show')
2105            );
2106        }
2107    }
2108
2109    // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
2110    if (has_all_capabilities($dupecaps, $coursecontext) &&
2111            plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
2112        $actions['duplicate'] = new action_menu_link_secondary(
2113            new moodle_url($baseurl, array('duplicate' => $mod->id)),
2114            new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2115            $str->duplicate,
2116            array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr)
2117        );
2118    }
2119
2120    // Groupmode.
2121    if ($hasmanageactivities && !$mod->coursegroupmodeforce) {
2122        if (plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
2123            if ($mod->effectivegroupmode == SEPARATEGROUPS) {
2124                $nextgroupmode = VISIBLEGROUPS;
2125                $grouptitle = $str->groupsseparate;
2126                $actionname = 'groupsseparate';
2127                $groupimage = 'i/groups';
2128            } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
2129                $nextgroupmode = NOGROUPS;
2130                $grouptitle = $str->groupsvisible;
2131                $actionname = 'groupsvisible';
2132                $groupimage = 'i/groupv';
2133            } else {
2134                $nextgroupmode = SEPARATEGROUPS;
2135                $grouptitle = $str->groupsnone;
2136                $actionname = 'groupsnone';
2137                $groupimage = 'i/groupn';
2138            }
2139
2140            $actions[$actionname] = new action_menu_link_primary(
2141                new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
2142                new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')),
2143                $grouptitle,
2144                array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive')
2145            );
2146        } else {
2147            $actions['nogroupsupport'] = new action_menu_filler();
2148        }
2149    }
2150
2151    // Assign.
2152    if (has_capability('moodle/role:assign', $modcontext)){
2153        $actions['assign'] = new action_menu_link_secondary(
2154            new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
2155            new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2156            $str->assign,
2157            array('class' => 'editing_assign', 'data-action' => 'assignroles')
2158        );
2159    }
2160
2161    // Delete.
2162    if ($hasmanageactivities) {
2163        $actions['delete'] = new action_menu_link_secondary(
2164            new moodle_url($baseurl, array('delete' => $mod->id)),
2165            new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2166            $str->delete,
2167            array('class' => 'editing_delete', 'data-action' => 'delete')
2168        );
2169    }
2170
2171    return $actions;
2172}
2173
2174/**
2175 * Returns the rename action.
2176 *
2177 * @param cm_info $mod The module to produce editing buttons for
2178 * @param int $sr The section to link back to (used for creating the links)
2179 * @return The markup for the rename action, or an empty string if not available.
2180 */
2181function course_get_cm_rename_action(cm_info $mod, $sr = null) {
2182    global $COURSE, $OUTPUT;
2183
2184    static $str;
2185    static $baseurl;
2186
2187    $modcontext = context_module::instance($mod->id);
2188    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2189
2190    if (!isset($str)) {
2191        $str = get_strings(array('edittitle'));
2192    }
2193
2194    if (!isset($baseurl)) {
2195        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2196    }
2197
2198    if ($sr !== null) {
2199        $baseurl->param('sr', $sr);
2200    }
2201
2202    // AJAX edit title.
2203    if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
2204                (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
2205        // we will not display link if we are on some other-course page (where we should not see this module anyway)
2206        return html_writer::span(
2207            html_writer::link(
2208                new moodle_url($baseurl, array('update' => $mod->id)),
2209                $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
2210                array(
2211                    'class' => 'editing_title',
2212                    'data-action' => 'edittitle',
2213                    'title' => $str->edittitle,
2214                )
2215            )
2216        );
2217    }
2218    return '';
2219}
2220
2221/**
2222 * Returns the move action.
2223 *
2224 * @param cm_info $mod The module to produce a move button for
2225 * @param int $sr The section to link back to (used for creating the links)
2226 * @return The markup for the move action, or an empty string if not available.
2227 */
2228function course_get_cm_move(cm_info $mod, $sr = null) {
2229    global $OUTPUT;
2230
2231    static $str;
2232    static $baseurl;
2233
2234    $modcontext = context_module::instance($mod->id);
2235    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2236
2237    if (!isset($str)) {
2238        $str = get_strings(array('move'));
2239    }
2240
2241    if (!isset($baseurl)) {
2242        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2243
2244        if ($sr !== null) {
2245            $baseurl->param('sr', $sr);
2246        }
2247    }
2248
2249    if ($hasmanageactivities) {
2250        $pixicon = 'i/dragdrop';
2251
2252        if (!course_ajax_enabled($mod->get_course())) {
2253            // Override for course frontpage until we get drag/drop working there.
2254            $pixicon = 't/move';
2255        }
2256
2257        return html_writer::link(
2258            new moodle_url($baseurl, array('copy' => $mod->id)),
2259            $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2260            array('class' => 'editing_move', 'data-action' => 'move')
2261        );
2262    }
2263    return '';
2264}
2265
2266/**
2267 * given a course object with shortname & fullname, this function will
2268 * truncate the the number of chars allowed and add ... if it was too long
2269 */
2270function course_format_name ($course,$max=100) {
2271
2272    $context = context_course::instance($course->id);
2273    $shortname = format_string($course->shortname, true, array('context' => $context));
2274    $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
2275    $str = $shortname.': '. $fullname;
2276    if (core_text::strlen($str) <= $max) {
2277        return $str;
2278    }
2279    else {
2280        return core_text::substr($str,0,$max-3).'...';
2281    }
2282}
2283
2284/**
2285 * Is the user allowed to add this type of module to this course?
2286 * @param object $course the course settings. Only $course->id is used.
2287 * @param string $modname the module name. E.g. 'forum' or 'quiz'.
2288 * @return bool whether the current user is allowed to add this type of module to this course.
2289 */
2290function course_allowed_module($course, $modname) {
2291    if (is_numeric($modname)) {
2292        throw new coding_exception('Function course_allowed_module no longer
2293                supports numeric module ids. Please update your code to pass the module name.');
2294    }
2295
2296    $capability = 'mod/' . $modname . ':addinstance';
2297    if (!get_capability_info($capability)) {
2298        // Debug warning that the capability does not exist, but no more than once per page.
2299        static $warned = array();
2300        $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2301        if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
2302            debugging('The module ' . $modname . ' does not define the standard capability ' .
2303                    $capability , DEBUG_DEVELOPER);
2304            $warned[$modname] = 1;
2305        }
2306
2307        // If the capability does not exist, the module can always be added.
2308        return true;
2309    }
2310
2311    $coursecontext = context_course::instance($course->id);
2312    return has_capability($capability, $coursecontext);
2313}
2314
2315/**
2316 * Efficiently moves many courses around while maintaining
2317 * sortorder in order.
2318 *
2319 * @param array $courseids is an array of course ids
2320 * @param int $categoryid
2321 * @return bool success
2322 */
2323function move_courses($courseids, $categoryid) {
2324    global $DB;
2325
2326    if (empty($courseids)) {
2327        // Nothing to do.
2328        return false;
2329    }
2330
2331    if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
2332        return false;
2333    }
2334
2335    $courseids = array_reverse($courseids);
2336    $newparent = context_coursecat::instance($category->id);
2337    $i = 1;
2338
2339    list($where, $params) = $DB->get_in_or_equal($courseids);
2340    $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname');
2341    foreach ($dbcourses as $dbcourse) {
2342        $course = new stdClass();
2343        $course->id = $dbcourse->id;
2344        $course->category  = $category->id;
2345        $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
2346        if ($category->visible == 0) {
2347            // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
2348            // to previous state if somebody unhides the category.
2349            $course->visible = 0;
2350        }
2351
2352        $DB->update_record('course', $course);
2353
2354        // Update context, so it can be passed to event.
2355        $context = context_course::instance($course->id);
2356        $context->update_moved($newparent);
2357
2358        // Trigger a course updated event.
2359        $event = \core\event\course_updated::create(array(
2360            'objectid' => $course->id,
2361            'context' => context_course::instance($course->id),
2362            'other' => array('shortname' => $dbcourse->shortname,
2363                             'fullname' => $dbcourse->fullname)
2364        ));
2365        $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
2366        $event->trigger();
2367    }
2368    fix_course_sortorder();
2369    cache_helper::purge_by_event('changesincourse');
2370
2371    return true;
2372}
2373
2374/**
2375 * Returns the display name of the given section that the course prefers
2376 *
2377 * Implementation of this function is provided by course format
2378 * @see format_base::get_section_name()
2379 *
2380 * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
2381 * @param int|stdClass $section Section object from database or just field course_sections.section
2382 * @return string Display name that the course format prefers, e.g. "Week 2"
2383 */
2384function get_section_name($courseorid, $section) {
2385    return course_get_format($courseorid)->get_section_name($section);
2386}
2387
2388/**
2389 * Tells if current course format uses sections
2390 *
2391 * @param string $format Course format ID e.g. 'weeks' $course->format
2392 * @return bool
2393 */
2394function course_format_uses_sections($format) {
2395    $course = new stdClass();
2396    $course->format = $format;
2397    return course_get_format($course)->uses_sections();
2398}
2399
2400/**
2401 * Returns the information about the ajax support in the given source format
2402 *
2403 * The returned object's property (boolean)capable indicates that
2404 * the course format supports Moodle course ajax features.
2405 *
2406 * @param string $format
2407 * @return stdClass
2408 */
2409function course_format_ajax_support($format) {
2410    $course = new stdClass();
2411    $course->format = $format;
2412    return course_get_format($course)->supports_ajax();
2413}
2414
2415/**
2416 * Can the current user delete this course?
2417 * Course creators have exception,
2418 * 1 day after the creation they can sill delete the course.
2419 * @param int $courseid
2420 * @return boolean
2421 */
2422function can_delete_course($courseid) {
2423    global $USER;
2424
2425    $context = context_course::instance($courseid);
2426
2427    if (has_capability('moodle/course:delete', $context)) {
2428        return true;
2429    }
2430
2431    // hack: now try to find out if creator created this course recently (1 day)
2432    if (!has_capability('moodle/course:create', $context)) {
2433        return false;
2434    }
2435
2436    $since = time() - 60*60*24;
2437    $course = get_course($courseid);
2438
2439    if ($course->timecreated < $since) {
2440        return false; // Return if the course was not created in last 24 hours.
2441    }
2442
2443    $logmanger = get_log_manager();
2444    $readers = $logmanger->get_readers('\core\log\sql_reader');
2445    $reader = reset($readers);
2446
2447    if (empty($reader)) {
2448        return false; // No log reader found.
2449    }
2450
2451    // A proper reader.
2452    $select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since";
2453    $params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created');
2454
2455    return (bool)$reader->get_events_select_count($select, $params);
2456}
2457
2458/**
2459 * Save the Your name for 'Some role' strings.
2460 *
2461 * @param integer $courseid the id of this course.
2462 * @param array $data the data that came from the course settings form.
2463 */
2464function save_local_role_names($courseid, $data) {
2465    global $DB;
2466    $context = context_course::instance($courseid);
2467
2468    foreach ($data as $fieldname => $value) {
2469        if (strpos($fieldname, 'role_') !== 0) {
2470            continue;
2471        }
2472        list($ignored, $roleid) = explode('_', $fieldname);
2473
2474        // make up our mind whether we want to delete, update or insert
2475        if (!$value) {
2476            $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
2477
2478        } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
2479            $rolename->name = $value;
2480            $DB->update_record('role_names', $rolename);
2481
2482        } else {
2483            $rolename = new stdClass;
2484            $rolename->contextid = $context->id;
2485            $rolename->roleid = $roleid;
2486            $rolename->name = $value;
2487            $DB->insert_record('role_names', $rolename);
2488        }
2489        // This will ensure the course contacts cache is purged..
2490        coursecat::role_assignment_changed($roleid, $context);
2491    }
2492}
2493
2494/**
2495 * Returns options to use in course overviewfiles filemanager
2496 *
2497 * @param null|stdClass|course_in_list|int $course either object that has 'id' property or just the course id;
2498 *     may be empty if course does not exist yet (course create form)
2499 * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc.
2500 *     or null if overviewfiles are disabled
2501 */
2502function course_overviewfiles_options($course) {
2503    global $CFG;
2504    if (empty($CFG->courseoverviewfileslimit)) {
2505        return null;
2506    }
2507    $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY);
2508    if (in_array('*', $accepted_types) || empty($accepted_types)) {
2509        $accepted_types = '*';
2510    } else {
2511        // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered.
2512        // Make sure extensions are prefixed with dot unless they are valid typegroups
2513        foreach ($accepted_types as $i => $type) {
2514            if (substr($type, 0, 1) !== '.') {
2515                require_once($CFG->libdir. '/filelib.php');
2516                if (!count(file_get_typegroup('extension', $type))) {
2517                    // It does not start with dot and is not a valid typegroup, this is most likely extension.
2518                    $accepted_types[$i] = '.'. $type;
2519                    $corrected = true;
2520                }
2521            }
2522        }
2523        if (!empty($corrected)) {
2524            set_config('courseoverviewfilesext', join(',', $accepted_types));
2525        }
2526    }
2527    $options = array(
2528        'maxfiles' => $CFG->courseoverviewfileslimit,
2529        'maxbytes' => $CFG->maxbytes,
2530        'subdirs' => 0,
2531        'accepted_types' => $accepted_types
2532    );
2533    if (!empty($course->id)) {
2534        $options['context'] = context_course::instance($course->id);
2535    } else if (is_int($course) && $course > 0) {
2536        $options['context'] = context_course::instance($course);
2537    }
2538    return $options;
2539}
2540
2541/**
2542 * Create a course and either return a $course object
2543 *
2544 * Please note this functions does not verify any access control,
2545 * the calling code is responsible for all validation (usually it is the form definition).
2546 *
2547 * @param array $editoroptions course description editor options
2548 * @param object $data  - all the data needed for an entry in the 'course' table
2549 * @return object new course instance
2550 */
2551function create_course($data, $editoroptions = NULL) {
2552    global $DB, $CFG;
2553    require_once($CFG->dirroot.'/tag/lib.php');
2554
2555    //check the categoryid - must be given for all new courses
2556    $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
2557
2558    // Check if the shortname already exists.
2559    if (!empty($data->shortname)) {
2560        if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
2561            throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2562        }
2563    }
2564
2565    // Check if the idnumber already exists.
2566    if (!empty($data->idnumber)) {
2567        if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
2568            throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2569        }
2570    }
2571
2572    // Check if timecreated is given.
2573    $data->timecreated  = !empty($data->timecreated) ? $data->timecreated : time();
2574    $data->timemodified = $data->timecreated;
2575
2576    // place at beginning of any category
2577    $data->sortorder = 0;
2578
2579    if ($editoroptions) {
2580        // summary text is updated later, we need context to store the files first
2581        $data->summary = '';
2582        $data->summary_format = FORMAT_HTML;
2583    }
2584
2585    if (!isset($data->visible)) {
2586        // data not from form, add missing visibility info
2587        $data->visible = $category->visible;
2588    }
2589    $data->visibleold = $data->visible;
2590
2591    $newcourseid = $DB->insert_record('course', $data);
2592    $context = context_course::instance($newcourseid, MUST_EXIST);
2593
2594    if ($editoroptions) {
2595        // Save the files used in the summary editor and store
2596        $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2597        $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
2598        $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
2599    }
2600    if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) {
2601        // Save the course overviewfiles
2602        $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2603    }
2604
2605    // update course format options
2606    course_get_format($newcourseid)->update_course_format_options($data);
2607
2608    $course = course_get_format($newcourseid)->get_course();
2609
2610    fix_course_sortorder();
2611    // purge appropriate caches in case fix_course_sortorder() did not change anything
2612    cache_helper::purge_by_event('changesincourse');
2613
2614    // new context created - better mark it as dirty
2615    $context->mark_dirty();
2616
2617    // Trigger a course created event.
2618    $event = \core\event\course_created::create(array(
2619        'objectid' => $course->id,
2620        'context' => context_course::instance($course->id),
2621        'other' => array('shortname' => $course->shortname,
2622            'fullname' => $course->fullname)
2623    ));
2624    $event->trigger();
2625
2626    // Setup the blocks
2627    blocks_add_default_course_blocks($course);
2628
2629    // Create a default section.
2630    course_create_sections_if_missing($course, 0);
2631
2632    // Save any custom role names.
2633    save_local_role_names($course->id, (array)$data);
2634
2635    // set up enrolments
2636    enrol_course_updated(true, $course, $data);
2637
2638    // Update course tags.
2639    if ($CFG->usetags && isset($data->tags)) {
2640        tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
2641    }
2642
2643    return $course;
2644}
2645
2646/**
2647 * Update a course.
2648 *
2649 * Please note this functions does not verify any access control,
2650 * the calling code is responsible for all validation (usually it is the form definition).
2651 *
2652 * @param object $data  - all the data needed for an entry in the 'course' table
2653 * @param array $editoroptions course description editor options
2654 * @return void
2655 */
2656function update_course($data, $editoroptions = NULL) {
2657    global $DB, $CFG;
2658    require_once($CFG->dirroot.'/tag/lib.php');
2659
2660    $data->timemodified = time();
2661
2662    $oldcourse = course_get_format($data->id)->get_course();
2663    $context   = context_course::instance($oldcourse->id);
2664
2665    if ($editoroptions) {
2666        $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2667    }
2668    if ($overviewfilesoptions = course_overviewfiles_options($data->id)) {
2669        $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2670    }
2671
2672    // Check we don't have a duplicate shortname.
2673    if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
2674        if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) {
2675            throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2676        }
2677    }
2678
2679    // Check we don't have a duplicate idnumber.
2680    if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
2681        if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) {
2682            throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2683        }
2684    }
2685
2686    if (!isset($data->category) or empty($data->category)) {
2687        // prevent nulls and 0 in category field
2688        unset($data->category);
2689    }
2690    $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category);
2691
2692    if (!isset($data->visible)) {
2693        // data not from form, add missing visibility info
2694        $data->visible = $oldcourse->visible;
2695    }
2696
2697    if ($data->visible != $oldcourse->visible) {
2698        // reset the visibleold flag when manually hiding/unhiding course
2699        $data->visibleold = $data->visible;
2700        $changesincoursecat = true;
2701    } else {
2702        if ($movecat) {
2703            $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
2704            if (empty($newcategory->visible)) {
2705                // make sure when moving into hidden category the course is hidden automatically
2706                $data->visible = 0;
2707            }
2708        }
2709    }
2710
2711    // Update with the new data
2712    $DB->update_record('course', $data);
2713    // make sure the modinfo cache is reset
2714    rebuild_course_cache($data->id);
2715
2716    // update course format options with full course data
2717    course_get_format($data->id)->update_course_format_options($data, $oldcourse);
2718
2719    $course = $DB->get_record('course', array('id'=>$data->id));
2720
2721    if ($movecat) {
2722        $newparent = context_coursecat::instance($course->category);
2723        $context->update_moved($newparent);
2724    }
2725    $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder));
2726    if ($fixcoursesortorder) {
2727        fix_course_sortorder();
2728    }
2729
2730    // purge appropriate caches in case fix_course_sortorder() did not change anything
2731    cache_helper::purge_by_event('changesincourse');
2732    if ($changesincoursecat) {
2733        cache_helper::purge_by_event('changesincoursecat');
2734    }
2735
2736    // Test for and remove blocks which aren't appropriate anymore
2737    blocks_remove_inappropriate($course);
2738
2739    // Save any custom role names.
2740    save_local_role_names($course->id, $data);
2741
2742    // update enrol settings
2743    enrol_course_updated(false, $course, $data);
2744
2745    // Update course tags.
2746    if ($CFG->usetags && isset($data->tags)) {
2747        tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
2748    }
2749
2750    // Trigger a course updated event.
2751    $event = \core\event\course_updated::create(array(
2752        'objectid' => $course->id,
2753        'context' => context_course::instance($course->id),
2754        'other' => array('shortname' => $course->shortname,
2755                         'fullname' => $course->fullname)
2756    ));
2757
2758    $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
2759    $event->trigger();
2760
2761    if ($oldcourse->format !== $course->format) {
2762        // Remove all options stored for the previous format
2763        // We assume that new course format migrated everything it needed watching trigger
2764        // 'course_updated' and in method format_XXX::update_course_format_options()
2765        $DB->delete_records('course_format_options',
2766                array('courseid' => $course->id, 'format' => $oldcourse->format));
2767    }
2768}
2769
2770/**
2771 * Average number of participants
2772 * @return integer
2773 */
2774function average_number_of_participants() {
2775    global $DB, $SITE;
2776
2777    //count total of enrolments for visible course (except front page)
2778    $sql = 'SELECT COUNT(*) FROM (
2779        SELECT DISTINCT ue.userid, e.courseid
2780        FROM {user_enrolments} ue, {enrol} e, {course} c
2781        WHERE ue.enrolid = e.id
2782            AND e.courseid <> :siteid
2783            AND c.id = e.courseid
2784            AND c.visible = 1) total';
2785    $params = array('siteid' => $SITE->id);
2786    $enrolmenttotal = $DB->count_records_sql($sql, $params);
2787
2788
2789    //count total of visible courses (minus front page)
2790    $coursetotal = $DB->count_records('course', array('visible' => 1));
2791    $coursetotal = $coursetotal - 1 ;
2792
2793    //average of enrolment
2794    if (empty($coursetotal)) {
2795        $participantaverage = 0;
2796    } else {
2797        $participantaverage = $enrolmenttotal / $coursetotal;
2798    }
2799
2800    return $participantaverage;
2801}
2802
2803/**
2804 * Average number of course modules
2805 * @return integer
2806 */
2807function average_number_of_courses_modules() {
2808    global $DB, $SITE;
2809
2810    //count total of visible course module (except front page)
2811    $sql = 'SELECT COUNT(*) FROM (
2812        SELECT cm.course, cm.module
2813        FROM {course} c, {course_modules} cm
2814        WHERE c.id = cm.course
2815            AND c.id <> :siteid
2816            AND cm.visible = 1
2817            AND c.visible = 1) total';
2818    $params = array('siteid' => $SITE->id);
2819    $moduletotal = $DB->count_records_sql($sql, $params);
2820
2821
2822    //count total of visible courses (minus front page)
2823    $coursetotal = $DB->count_records('course', array('visible' => 1));
2824    $coursetotal = $coursetotal - 1 ;
2825
2826    //average of course module
2827    if (empty($coursetotal)) {
2828        $coursemoduleaverage = 0;
2829    } else {
2830        $coursemoduleaverage = $moduletotal / $coursetotal;
2831    }
2832
2833    return $coursemoduleaverage;
2834}
2835
2836/**
2837 * This class pertains to course requests and contains methods associated with
2838 * create, approving, and removing course requests.
2839 *
2840 * Please note we do not allow embedded images here because there is no context
2841 * to store them with proper access control.
2842 *
2843 * @copyright 2009 Sam Hemelryk
2844 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2845 * @since Moodle 2.0
2846 *
2847 * @property-read int $id
2848 * @property-read string $fullname
2849 * @property-read string $shortname
2850 * @property-read string $summary
2851 * @property-read int $summaryformat
2852 * @property-read int $summarytrust
2853 * @property-read string $reason
2854 * @property-read int $requester
2855 */
2856class course_request {
2857
2858    /**
2859     * This is the stdClass that stores the properties for the course request
2860     * and is externally accessed through the __get magic method
2861     * @var stdClass
2862     */
2863    protected $properties;
2864
2865    /**
2866     * An array of options for the summary editor used by course request forms.
2867     * This is initially set by {@link summary_editor_options()}
2868     * @var array
2869     * @static
2870     */
2871    protected static $summaryeditoroptions;
2872
2873    /**
2874     * Static function to prepare the summary editor for working with a course
2875     * request.
2876     *
2877     * @static
2878     * @param null|stdClass $data Optional, an object containing the default values
2879     *                       for the form, these may be modified when preparing the
2880     *                       editor so this should be called before creating the form
2881     * @return stdClass An object that can be used to set the default values for
2882     *                   an mforms form
2883     */
2884    public static function prepare($data=null) {
2885        if ($data === null) {
2886            $data = new stdClass;
2887        }
2888        $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
2889        return $data;
2890    }
2891
2892    /**
2893     * Static function to create a new course request when passed an array of properties
2894     * for it.
2895     *
2896     * This function also handles saving any files that may have been used in the editor
2897     *
2898     * @static
2899     * @param stdClass $data
2900     * @return course_request The newly created course request
2901     */
2902    public static function create($data) {
2903        global $USER, $DB, $CFG;
2904        $data->requester = $USER->id;
2905
2906        // Setting the default category if none set.
2907        if (empty($data->category) || empty($CFG->requestcategoryselection)) {
2908            $data->category = $CFG->defaultrequestcategory;
2909        }
2910
2911        // Summary is a required field so copy the text over
2912        $data->summary       = $data->summary_editor['text'];
2913        $data->summaryformat = $data->summary_editor['format'];
2914
2915        $data->id = $DB->insert_record('course_request', $data);
2916
2917        // Create a new course_request object and return it
2918        $request = new course_request($data);
2919
2920        // Notify the admin if required.
2921        if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
2922
2923            $a = new stdClass;
2924            $a->link = "$CFG->wwwroot/course/pending.php";
2925            $a->user = fullname($USER);
2926            $subject = get_string('courserequest');
2927            $message = get_string('courserequestnotifyemail', 'admin', $a);
2928            foreach ($users as $user) {
2929                $request->notify($user, $USER, 'courserequested', $subject, $message);
2930            }
2931        }
2932
2933        return $request;
2934    }
2935
2936    /**
2937     * Returns an array of options to use with a summary editor
2938     *
2939     * @uses course_request::$summaryeditoroptions
2940     * @return array An array of options to use with the editor
2941     */
2942    public static function summary_editor_options() {
2943        global $CFG;
2944        if (self::$summaryeditoroptions === null) {
2945            self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
2946        }
2947        return self::$summaryeditoroptions;
2948    }
2949
2950    /**
2951     * Loads the properties for this course request object. Id is required and if
2952     * only id is provided then we load the rest of the properties from the database
2953     *
2954     * @param stdClass|int $properties Either an object containing properties
2955     *                      or the course_request id to load
2956     */
2957    public function __construct($properties) {
2958        global $DB;
2959        if (empty($properties->id)) {
2960            if (empty($properties)) {
2961                throw new coding_exception('You must provide a course request id when creating a course_request object');
2962            }
2963            $id = $properties;
2964            $properties = new stdClass;
2965            $properties->id = (int)$id;
2966            unset($id);
2967        }
2968        if (empty($properties->requester)) {
2969            if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
2970                print_error('unknowncourserequest');
2971            }
2972        } else {
2973            $this->properties = $properties;
2974        }
2975        $this->properties->collision = null;
2976    }
2977
2978    /**
2979     * Returns the requested property
2980     *
2981     * @param string $key
2982     * @return mixed
2983     */
2984    public function __get($key) {
2985        return $this->properties->$key;
2986    }
2987
2988    /**
2989     * Override this to ensure empty($request->blah) calls return a reliable answer...
2990     *
2991     * This is required because we define the __get method
2992     *
2993     * @param mixed $key
2994     * @return bool True is it not empty, false otherwise
2995     */
2996    public function __isset($key) {
2997        return (!empty($this->properties->$key));
2998    }
2999
3000    /**
3001     * Returns the user who requested this course
3002     *
3003     * Uses a static var to cache the results and cut down the number of db queries
3004     *
3005     * @staticvar array $requesters An array of cached users
3006     * @return stdClass The user who requested the course
3007     */
3008    public function get_requester() {
3009        global $DB;
3010        static $requesters= array();
3011        if (!array_key_exists($this->properties->requester, $requesters)) {
3012            $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
3013        }
3014        return $requesters[$this->properties->requester];
3015    }
3016
3017    /**
3018     * Checks that the shortname used by the course does not conflict with any other
3019     * courses that exist
3020     *
3021     * @param string|null $shortnamemark The string to append to the requests shortname
3022     *                     should a conflict be found
3023     * @return bool true is there is a conflict, false otherwise
3024     */
3025    public function check_shortname_collision($shortnamemark = '[*]') {
3026        global $DB;
3027
3028        if ($this->properties->collision !== null) {
3029            return $this->properties->collision;
3030        }
3031
3032        if (empty($this->properties->shortname)) {
3033            debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
3034            $this->properties->collision = false;
3035        } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
3036            if (!empty($shortnamemark)) {
3037                $this->properties->shortname .= ' '.$shortnamemark;
3038            }
3039            $this->properties->collision = true;
3040        } else {
3041            $this->properties->collision = false;
3042        }
3043        return $this->properties->collision;
3044    }
3045
3046    /**
3047     * Returns the category where this course request should be created
3048     *
3049     * Note that we don't check here that user has a capability to view
3050     * hidden categories if he has capabilities 'moodle/site:approvecourse' and
3051     * 'moodle/course:changecategory'
3052     *
3053     * @return coursecat
3054     */
3055    public function get_category() {
3056        global $CFG;
3057        require_once($CFG->libdir.'/coursecatlib.php');
3058        // If the category is not set, if the current user does not have the rights to change the category, or if the
3059        // category does not exist, we set the default category to the course to be approved.
3060        // The system level is used because the capability moodle/site:approvecourse is based on a system level.
3061        if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
3062                (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) {
3063            $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true);
3064        }
3065        if (!$category) {
3066            $category = coursecat::get_default();
3067        }
3068        return $category;
3069    }
3070
3071    /**
3072     * This function approves the request turning it into a course
3073     *
3074     * This function converts the course request into a course, at the same time
3075     * transferring any files used in the summary to the new course and then removing
3076     * the course request and the files associated with it.
3077     *
3078     * @return int The id of the course that was created from this request
3079     */
3080    public function approve() {
3081        global $CFG, $DB, $USER;
3082
3083        $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
3084
3085        $courseconfig = get_config('moodlecourse');
3086
3087        // Transfer appropriate settings
3088        $data = clone($this->properties);
3089        unset($data->id);
3090        unset($data->reason);
3091        unset($data->requester);
3092
3093        // Set category
3094        $category = $this->get_category();
3095        $data->category = $category->id;
3096        // Set misc settings
3097        $data->requested = 1;
3098
3099        // Apply course default settings
3100        $data->format             = $courseconfig->format;
3101        $data->newsitems          = $courseconfig->newsitems;
3102        $data->showgrades         = $courseconfig->showgrades;
3103        $data->showreports        = $courseconfig->showreports;
3104        $data->maxbytes           = $courseconfig->maxbytes;
3105        $data->groupmode          = $courseconfig->groupmode;
3106        $data->groupmodeforce     = $courseconfig->groupmodeforce;
3107        $data->visible            = $courseconfig->visible;
3108        $data->visibleold         = $data->visible;
3109        $data->lang               = $courseconfig->lang;
3110
3111        $course = create_course($data);
3112        $context = context_course::instance($course->id, MUST_EXIST);
3113
3114        // add enrol instances
3115        if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
3116            if ($manual = enrol_get_plugin('manual')) {
3117                $manual->add_default_instance($course);
3118            }
3119        }
3120
3121        // enrol the requester as teacher if necessary
3122        if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
3123            enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
3124        }
3125
3126        $this->delete();
3127
3128        $a = new stdClass();
3129        $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
3130        $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
3131        $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
3132
3133        return $course->id;
3134    }
3135
3136    /**
3137     * Reject a course request
3138     *
3139     * This function rejects a course request, emailing the requesting user the
3140     * provided notice and then removing the request from the database
3141     *
3142     * @param string $notice The message to display to the user
3143     */
3144    public function reject($notice) {
3145        global $USER, $DB;
3146        $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
3147        $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
3148        $this->delete();
3149    }
3150
3151    /**
3152     * Deletes the course request and any associated files
3153     */
3154    public function delete() {
3155        global $DB;
3156        $DB->delete_records('course_request', array('id' => $this->properties->id));
3157    }
3158
3159    /**
3160     * Send a message from one user to another using events_trigger
3161     *
3162     * @param object $touser
3163     * @param object $fromuser
3164     * @param string $name
3165     * @param string $subject
3166     * @param string $message
3167     */
3168    protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
3169        $eventdata = new stdClass();
3170        $eventdata->component         = 'moodle';
3171        $eventdata->name              = $name;
3172        $eventdata->userfrom          = $fromuser;
3173        $eventdata->userto            = $touser;
3174        $eventdata->subject           = $subject;
3175        $eventdata->fullmessage       = $message;
3176        $eventdata->fullmessageformat = FORMAT_PLAIN;
3177        $eventdata->fullmessagehtml   = '';
3178        $eventdata->smallmessage      = '';
3179        $eventdata->notification      = 1;
3180        message_send($eventdata);
3181    }
3182}
3183
3184/**
3185 * Return a list of page types
3186 * @param string $pagetype current page type
3187 * @param context $parentcontext Block's parent context
3188 * @param context $currentcontext Current context of block
3189 * @return array array of page types
3190 */
3191function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
3192    if ($pagetype === 'course-index' || $pagetype === 'course-index-category') {
3193        // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page
3194        $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3195            'course-index-*' => get_string('page-course-index-x', 'pagetype'),
3196        );
3197    } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) {
3198        // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.)
3199        $pagetypes = array('*' => get_string('page-x', 'pagetype'));
3200    } else {
3201        // Otherwise consider it a page inside a course even if $currentcontext is null
3202        $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3203            'course-*' => get_string('page-course-x', 'pagetype'),
3204            'course-view-*' => get_string('page-course-view-x', 'pagetype')
3205        );
3206    }
3207    return $pagetypes;
3208}
3209
3210/**
3211 * Determine whether course ajax should be enabled for the specified course
3212 *
3213 * @param stdClass $course The course to test against
3214 * @return boolean Whether course ajax is enabled or note
3215 */
3216function course_ajax_enabled($course) {
3217    global $CFG, $PAGE, $SITE;
3218
3219    // The user must be editing for AJAX to be included
3220    if (!$PAGE->user_is_editing()) {
3221        return false;
3222    }
3223
3224    // Check that the theme suports
3225    if (!$PAGE->theme->enablecourseajax) {
3226        return false;
3227    }
3228
3229    // Check that the course format supports ajax functionality
3230    // The site 'format' doesn't have information on course format support
3231    if ($SITE->id !== $course->id) {
3232        $courseformatajaxsupport = course_format_ajax_support($course->format);
3233        if (!$courseformatajaxsupport->capable) {
3234            return false;
3235        }
3236    }
3237
3238    // All conditions have been met so course ajax should be enabled
3239    return true;
3240}
3241
3242/**
3243 * Include the relevant javascript and language strings for the resource
3244 * toolbox YUI module
3245 *
3246 * @param integer $id The ID of the course being applied to
3247 * @param array $usedmodules An array containing the names of the modules in use on the page
3248 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
3249 * @param stdClass $config An object containing configuration parameters for ajax modules including:
3250 *          * resourceurl   The URL to post changes to for resource changes
3251 *          * sectionurl    The URL to post changes to for section changes
3252 *          * pageparams    Additional parameters to pass through in the post
3253 * @return bool
3254 */
3255function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
3256    global $CFG, $PAGE, $SITE;
3257
3258    // Ensure that ajax should be included
3259    if (!course_ajax_enabled($course)) {
3260        return false;
3261    }
3262
3263    if (!$config) {
3264        $config = new stdClass();
3265    }
3266
3267    // The URL to use for resource changes
3268    if (!isset($config->resourceurl)) {
3269        $config->resourceurl = '/course/rest.php';
3270    }
3271
3272    // The URL to use for section changes
3273    if (!isset($config->sectionurl)) {
3274        $config->sectionurl = '/course/rest.php';
3275    }
3276
3277    // Any additional parameters which need to be included on page submission
3278    if (!isset($config->pageparams)) {
3279        $config->pageparams = array();
3280    }
3281
3282    // Include toolboxes
3283    $PAGE->requires->yui_module('moodle-course-toolboxes',
3284            'M.course.init_resource_toolbox',
3285            array(array(
3286                'courseid' => $course->id,
3287                'ajaxurl' => $config->resourceurl,
3288                'config' => $config,
3289            ))
3290    );
3291    $PAGE->requires->yui_module('moodle-course-toolboxes',
3292            'M.course.init_section_toolbox',
3293            array(array(
3294                'courseid' => $course->id,
3295                'format' => $course->format,
3296                'ajaxurl' => $config->sectionurl,
3297                'config' => $config,
3298            ))
3299    );
3300
3301    // Include course dragdrop
3302    if (course_format_uses_sections($course->format)) {
3303        $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
3304            array(array(
3305                'courseid' => $course->id,
3306                'ajaxurl' => $config->sectionurl,
3307                'config' => $config,
3308            )), null, true);
3309
3310        $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
3311            array(array(
3312                'courseid' => $course->id,
3313                'ajaxurl' => $config->resourceurl,
3314                'config' => $config,
3315            )), null, true);
3316    }
3317
3318    // Require various strings for the command toolbox
3319    $PAGE->requires->strings_for_js(array(
3320            'moveleft',
3321            'deletechecktype',
3322            'deletechecktypename',
3323            'edittitle',
3324            'edittitleinstructions',
3325            'show',
3326            'hide',
3327            'highlight',
3328            'highlightoff',
3329            'groupsnone',
3330            'groupsvisible',
3331            'groupsseparate',
3332            'clicktochangeinbrackets',
3333            'markthistopic',
3334            'markedthistopic',
3335            'movesection',
3336            'movecoursemodule',
3337            'movecoursesection',
3338            'movecontent',
3339            'tocontent',
3340            'emptydragdropregion',
3341            'afterresource',
3342            'aftersection',
3343            'totopofsection',
3344        ), 'moodle');
3345
3346    // Include section-specific strings for formats which support sections.
3347    if (course_format_uses_sections($course->format)) {
3348        $PAGE->requires->strings_for_js(array(
3349                'showfromothers',
3350                'hidefromothers',
3351            ), 'format_' . $course->format);
3352    }
3353
3354    // For confirming resource deletion we need the name of the module in question
3355    foreach ($usedmodules as $module => $modname) {
3356        $PAGE->requires->string_for_js('pluginname', $module);
3357    }
3358
3359    // Load drag and drop upload AJAX.
3360    require_once($CFG->dirroot.'/course/dnduploadlib.php');
3361    dndupload_add_to_course($course, $enabledmodules);
3362
3363    return true;
3364}
3365
3366/**
3367 * Returns the sorted list of available course formats, filtered by enabled if necessary
3368 *
3369 * @param bool $enabledonly return only formats that are enabled
3370 * @return array array of sorted format names
3371 */
3372function get_sorted_course_formats($enabledonly = false) {
3373    global $CFG;
3374    $formats = core_component::get_plugin_list('format');
3375
3376    if (!empty($CFG->format_plugins_sortorder)) {
3377        $order = explode(',', $CFG->format_plugins_sortorder);
3378        $order = array_merge(array_intersect($order, array_keys($formats)),
3379                    array_diff(array_keys($formats), $order));
3380    } else {
3381        $order = array_keys($formats);
3382    }
3383    if (!$enabledonly) {
3384        return $order;
3385    }
3386    $sortedformats = array();
3387    foreach ($order as $formatname) {
3388        if (!get_config('format_'.$formatname, 'disabled')) {
3389            $sortedformats[] = $formatname;
3390        }
3391    }
3392    return $sortedformats;
3393}
3394
3395/**
3396 * The URL to use for the specified course (with section)
3397 *
3398 * @param int|stdClass $courseorid The course to get the section name for (either object or just course id)
3399 * @param int|stdClass $section Section object from database or just field course_sections.section
3400 *     if omitted the course view page is returned
3401 * @param array $options options for view URL. At the moment core uses:
3402 *     'navigation' (bool) if true and section has no separate page, the function returns null
3403 *     'sr' (int) used by multipage formats to specify to which section to return
3404 * @return moodle_url The url of course
3405 */
3406function course_get_url($courseorid, $section = null, $options = array()) {
3407    return course_get_format($courseorid)->get_view_url($section, $options);
3408}
3409
3410/**
3411 * Create a module.
3412 *
3413 * It includes:
3414 *      - capability checks and other checks
3415 *      - create the module from the module info
3416 *
3417 * @param object $module
3418 * @return object the created module info
3419 * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
3420 */
3421function create_module($moduleinfo) {
3422    global $DB, $CFG;
3423
3424    require_once($CFG->dirroot . '/course/modlib.php');
3425
3426    // Check manadatory attributs.
3427    $mandatoryfields = array('modulename', 'course', 'section', 'visible');
3428    if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
3429        $mandatoryfields[] = 'introeditor';
3430    }
3431    foreach($mandatoryfields as $mandatoryfield) {
3432        if (!isset($moduleinfo->{$mandatoryfield})) {
3433            throw new moodle_exception('createmodulemissingattribut', '', '', $mandatoryfield);
3434        }
3435    }
3436
3437    // Some additional checks (capability / existing instances).
3438    $course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST);
3439    list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section);
3440
3441    // Add the module.
3442    $moduleinfo->module = $module->id;
3443    $moduleinfo = add_moduleinfo($moduleinfo, $course, null);
3444
3445    return $moduleinfo;
3446}
3447
3448/**
3449 * Update a module.
3450 *
3451 * It includes:
3452 *      - capability and other checks
3453 *      - update the module
3454 *
3455 * @param object $module
3456 * @return object the updated module info
3457 * @throws moodle_exception if current user is not allowed to update the module
3458 */
3459function update_module($moduleinfo) {
3460    global $DB, $CFG;
3461
3462    require_once($CFG->dirroot . '/course/modlib.php');
3463
3464    // Check the course module exists.
3465    $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
3466
3467    // Check the course exists.
3468    $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
3469
3470    // Some checks (capaibility / existing instances).
3471    list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
3472
3473    // Retrieve few information needed by update_moduleinfo.
3474    $moduleinfo->modulename = $cm->modname;
3475    if (!isset($moduleinfo->scale)) {
3476        $moduleinfo->scale = 0;
3477    }
3478    $moduleinfo->type = 'mod';
3479
3480    // Update the module.
3481    list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null);
3482
3483    return $moduleinfo;
3484}
3485
3486/**
3487 * Duplicate a module on the course for ajax.
3488 *
3489 * @see mod_duplicate_module()
3490 * @param object $course The course
3491 * @param object $cm The course module to duplicate
3492 * @param int $sr The section to link back to (used for creating the links)
3493 * @throws moodle_exception if the plugin doesn't support duplication
3494 * @return Object containing:
3495 * - fullcontent: The HTML markup for the created CM
3496 * - cmid: The CMID of the newly created CM
3497 * - redirect: Whether to trigger a redirect following this change
3498 */
3499function mod_duplicate_activity($course, $cm, $sr = null) {
3500    global $PAGE;
3501
3502    $newcm = duplicate_module($course, $cm);
3503
3504    $resp = new stdClass();
3505    if ($newcm) {
3506        $courserenderer = $PAGE->get_renderer('core', 'course');
3507        $completioninfo = new completion_info($course);
3508        $modulehtml = $courserenderer->course_section_cm($course, $completioninfo,
3509                $newcm, null, array());
3510
3511        $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr);
3512        $resp->cmid = $newcm->id;
3513    } else {
3514        // Trigger a redirect.
3515        $resp->redirect = true;
3516    }
3517    return $resp;
3518}
3519
3520/**
3521 * Api to duplicate a module.
3522 *
3523 * @param object $course course object.
3524 * @param object $cm course module object to be duplicated.
3525 * @since Moodle 2.8
3526 *
3527 * @throws Exception
3528 * @throws coding_exception
3529 * @throws moodle_exception
3530 * @throws restore_controller_exception
3531 *
3532 * @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm.
3533 */
3534function duplicate_module($course, $cm) {
3535    global $CFG, $DB, $USER;
3536    require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
3537    require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
3538    require_once($CFG->libdir . '/filelib.php');
3539
3540    $a          = new stdClass();
3541    $a->modtype = get_string('modulename', $cm->modname);
3542    $a->modname = format_string($cm->name);
3543
3544    if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) {
3545        throw new moodle_exception('duplicatenosupport', 'error', '', $a);
3546    }
3547
3548    // Backup the activity.
3549
3550    $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
3551            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
3552
3553    $backupid       = $bc->get_backupid();
3554    $backupbasepath = $bc->get_plan()->get_basepath();
3555
3556    $bc->execute_plan();
3557
3558    $bc->destroy();
3559
3560    // Restore the backup immediately.
3561
3562    $rc = new restore_controller($backupid, $course->id,
3563            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
3564
3565    $cmcontext = context_module::instance($cm->id);
3566    if (!$rc->execute_precheck()) {
3567        $precheckresults = $rc->get_precheck_results();
3568        if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
3569            if (empty($CFG->keeptempdirectoriesonbackup)) {
3570                fulldelete($backupbasepath);
3571            }
3572        }
3573    }
3574
3575    $rc->execute_plan();
3576
3577    // Now a bit hacky part follows - we try to get the cmid of the newly
3578    // restored copy of the module.
3579    $newcmid = null;
3580    $tasks = $rc->get_plan()->get_tasks();
3581    foreach ($tasks as $task) {
3582        if (is_subclass_of($task, 'restore_activity_task')) {
3583            if ($task->get_old_contextid() == $cmcontext->id) {
3584                $newcmid = $task->get_moduleid();
3585                break;
3586            }
3587        }
3588    }
3589
3590    // If we know the cmid of the new course module, let us move it
3591    // right below the original one. otherwise it will stay at the
3592    // end of the section.
3593    if ($newcmid) {
3594        $info = get_fast_modinfo($course);
3595        $newcm = $info->get_cm($newcmid);
3596        $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
3597        moveto_module($newcm, $section, $cm);
3598        moveto_module($cm, $section, $newcm);
3599
3600        // Trigger course module created event. We can trigger the event only if we know the newcmid.
3601        $event = \core\event\course_module_created::create_from_cm($newcm);
3602        $event->trigger();
3603    }
3604    rebuild_course_cache($cm->course);
3605
3606    $rc->destroy();
3607
3608    if (empty($CFG->keeptempdirectoriesonbackup)) {
3609        fulldelete($backupbasepath);
3610    }
3611
3612    return isset($newcm) ? $newcm : null;
3613}
3614
3615/**
3616 * Compare two objects to find out their correct order based on timestamp (to be used by usort).
3617 * Sorts by descending order of time.
3618 *
3619 * @param stdClass $a First object
3620 * @param stdClass $b Second object
3621 * @return int 0,1,-1 representing the order
3622 */
3623function compare_activities_by_time_desc($a, $b) {
3624    // Make sure the activities actually have a timestamp property.
3625    if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3626        return 0;
3627    }
3628    // We treat instances without timestamp as if they have a timestamp of 0.
3629    if ((!property_exists($a, 'timestamp')) && (property_exists($b,'timestamp'))) {
3630        return 1;
3631    }
3632    if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3633        return -1;
3634    }
3635    if ($a->timestamp == $b->timestamp) {
3636        return 0;
3637    }
3638    return ($a->timestamp > $b->timestamp) ? -1 : 1;
3639}
3640
3641/**
3642 * Compare two objects to find out their correct order based on timestamp (to be used by usort).
3643 * Sorts by ascending order of time.
3644 *
3645 * @param stdClass $a First object
3646 * @param stdClass $b Second object
3647 * @return int 0,1,-1 representing the order
3648 */
3649function compare_activities_by_time_asc($a, $b) {
3650    // Make sure the activities actually have a timestamp property.
3651    if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3652      return 0;
3653    }
3654    // We treat instances without timestamp as if they have a timestamp of 0.
3655    if ((!property_exists($a, 'timestamp')) && (property_exists($b, 'timestamp'))) {
3656        return -1;
3657    }
3658    if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3659        return 1;
3660    }
3661    if ($a->timestamp == $b->timestamp) {
3662        return 0;
3663    }
3664    return ($a->timestamp < $b->timestamp) ? -1 : 1;
3665}
3666
3667/**
3668 * Changes the visibility of a course.
3669 *
3670 * @param int $courseid The course to change.
3671 * @param bool $show True to make it visible, false otherwise.
3672 * @return bool
3673 */
3674function course_change_visibility($courseid, $show = true) {
3675    $course = new stdClass;
3676    $course->id = $courseid;
3677    $course->visible = ($show) ? '1' : '0';
3678    $course->visibleold = $course->visible;
3679    update_course($course);
3680    return true;
3681}
3682
3683/**
3684 * Changes the course sortorder by one, moving it up or down one in respect to sort order.
3685 *
3686 * @param stdClass|course_in_list $course
3687 * @param bool $up If set to true the course will be moved up one. Otherwise down one.
3688 * @return bool
3689 */
3690function course_change_sortorder_by_one($course, $up) {
3691    global $DB;
3692    $params = array($course->sortorder, $course->category);
3693    if ($up) {
3694        $select = 'sortorder < ? AND category = ?';
3695        $sort = 'sortorder DESC';
3696    } else {
3697        $select = 'sortorder > ? AND category = ?';
3698        $sort = 'sortorder ASC';
3699    }
3700    fix_course_sortorder();
3701    $swapcourse = $DB->get_records_select('course', $select, $params, $sort, '*', 0, 1);
3702    if ($swapcourse) {
3703        $swapcourse = reset($swapcourse);
3704        $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $course->id));
3705        $DB->set_field('course', 'sortorder', $course->sortorder, array('id' => $swapcourse->id));
3706        // Finally reorder courses.
3707        fix_course_sortorder();
3708        cache_helper::purge_by_event('changesincourse');
3709        return true;
3710    }
3711    return false;
3712}
3713
3714/**
3715 * Changes the sort order of courses in a category so that the first course appears after the second.
3716 *
3717 * @param int|stdClass $courseorid The course to focus on.
3718 * @param int $moveaftercourseid The course to shifter after or 0 if you want it to be the first course in the category.
3719 * @return bool
3720 */
3721function course_change_sortorder_after_course($courseorid, $moveaftercourseid) {
3722    global $DB;
3723
3724    if (!is_object($courseorid)) {
3725        $course = get_course($courseorid);
3726    } else {
3727        $course = $courseorid;
3728    }
3729
3730    if ((int)$moveaftercourseid === 0) {
3731        // We've moving the course to the start of the queue.
3732        $sql = 'SELECT sortorder
3733                      FROM {course}
3734                     WHERE category = :categoryid
3735                  ORDER BY sortorder';
3736        $params = array(
3737            'categoryid' => $course->category
3738        );
3739        $sortorder = $DB->get_field_sql($sql, $params, IGNORE_MULTIPLE);
3740
3741        $sql = 'UPDATE {course}
3742                   SET sortorder = sortorder + 1
3743                 WHERE category = :categoryid
3744                   AND id <> :id';
3745        $params = array(
3746            'categoryid' => $course->category,
3747            'id' => $course->id,
3748        );
3749        $DB->execute($sql, $params);
3750        $DB->set_field('course', 'sortorder', $sortorder, array('id' => $course->id));
3751    } else if ($course->id === $moveaftercourseid) {
3752        // They're the same - moronic.
3753        debugging("Invalid move after course given.", DEBUG_DEVELOPER);
3754        return false;
3755    } else {
3756        // Moving this course after the given course. It could be before it could be after.
3757        $moveaftercourse = get_course($moveaftercourseid);
3758        if ($course->category !== $moveaftercourse->category) {
3759            debugging("Cannot re-order courses. The given courses do not belong to the same category.", DEBUG_DEVELOPER);
3760            return false;
3761        }
3762        // Increment all courses in the same category that are ordered after the moveafter course.
3763        // This makes a space for the course we're moving.
3764        $sql = 'UPDATE {course}
3765                       SET sortorder = sortorder + 1
3766                     WHERE category = :categoryid
3767                       AND sortorder > :sortorder';
3768        $params = array(
3769            'categoryid' => $moveaftercourse->category,
3770            'sortorder' => $moveaftercourse->sortorder
3771        );
3772        $DB->execute($sql, $params);
3773        $DB->set_field('course', 'sortorder', $moveaftercourse->sortorder + 1, array('id' => $course->id));
3774    }
3775    fix_course_sortorder();
3776    cache_helper::purge_by_event('changesincourse');
3777    return true;
3778}
3779
3780/**
3781 * Trigger course viewed event. This API function is used when course view actions happens,
3782 * usually in course/view.php but also in external functions.
3783 *
3784 * @param stdClass  $context course context object
3785 * @param int $sectionnumber section number
3786 * @since Moodle 2.9
3787 */
3788function course_view($context, $sectionnumber = 0) {
3789
3790    $eventdata = array('context' => $context);
3791
3792    if (!empty($sectionnumber)) {
3793        $eventdata['other']['coursesectionnumber'] = $sectionnumber;
3794    }
3795
3796    $event = \core\event\course_viewed::create($eventdata);
3797    $event->trigger();
3798}
Note: See TracBrowser for help on using the repository browser.