[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 25 defined('MOODLE_INTERNAL') || die; 26 27 require_once($CFG->libdir.'/completionlib.php'); 28 require_once($CFG->libdir.'/filelib.php'); 29 require_once($CFG->dirroot.'/course/format/lib.php'); 30 31 define('COURSE_MAX_LOGS_PER_PAGE', 1000); // Records. 32 define('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 */ 39 define('COURSE_MAX_SUMMARIES_PER_PAGE', 10); 40 41 // Max courses in log dropdown before switching to optional. 42 define('COURSE_MAX_COURSES_PER_DROPDOWN', 1000); 43 // Max users in log dropdown before switching to optional. 44 define('COURSE_MAX_USERS_PER_DROPDOWN', 1000); 45 define('FRONTPAGENEWS', '0'); 46 define('FRONTPAGECATEGORYNAMES', '2'); 47 define('FRONTPAGECATEGORYCOMBO', '4'); 48 define('FRONTPAGEENROLLEDCOURSELIST', '5'); 49 define('FRONTPAGEALLCOURSELIST', '6'); 50 define('FRONTPAGECOURSESEARCH', '7'); 51 // Important! Replaced with $CFG->frontpagecourselimit - maximum number of courses displayed on the frontpage. 52 define('EXCELROWS', 65535); 53 define('FIRSTUSEDEXCELROW', 3); 54 55 define('MOD_CLASS_ACTIVITY', 0); 56 define('MOD_CLASS_RESOURCE', 1); 57 58 function 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('&', '&', $query); // both & and & 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('&', $parts); 136 } 137 138 return $script.$query; 139 } 140 141 142 function 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 233 function 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 324 function 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 441 function 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 546 function 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 628 function 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 742 function 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 */ 889 function 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 */ 992 function 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 */ 1155 function 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 */ 1181 function 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 */ 1196 function 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 course_update_section($courseid, $section, array('visible' => $visibility)); 1202 1203 // Determine which modules are visible for AJAX update 1204 $modules = !empty($section->sequence) ? explode(',', $section->sequence) : array(); 1205 if (!empty($modules)) { 1206 list($insql, $params) = $DB->get_in_or_equal($modules); 1207 $select = 'id ' . $insql . ' AND visible = ?'; 1208 array_push($params, $visibility); 1209 if (!$visibility) { 1210 $select .= ' AND visibleold = 1'; 1211 } 1212 $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params); 1213 } 1214 } 1215 return $resourcestotoggle; 1216 } 1217 1218 /** 1219 * Retrieve all metadata for the requested modules 1220 * 1221 * @param object $course The Course 1222 * @param array $modnames An array containing the list of modules and their 1223 * names 1224 * @param int $sectionreturn The section to return to 1225 * @return array A list of stdClass objects containing metadata about each 1226 * module 1227 */ 1228 function get_module_metadata($course, $modnames, $sectionreturn = null) { 1229 global $OUTPUT; 1230 1231 // get_module_metadata will be called once per section on the page and courses may show 1232 // different modules to one another 1233 static $modlist = array(); 1234 if (!isset($modlist[$course->id])) { 1235 $modlist[$course->id] = array(); 1236 } 1237 1238 $return = array(); 1239 $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey())); 1240 if ($sectionreturn !== null) { 1241 $urlbase->param('sr', $sectionreturn); 1242 } 1243 foreach($modnames as $modname => $modnamestr) { 1244 if (!course_allowed_module($course, $modname)) { 1245 continue; 1246 } 1247 if (isset($modlist[$course->id][$modname])) { 1248 // This module is already cached 1249 $return += $modlist[$course->id][$modname]; 1250 continue; 1251 } 1252 $modlist[$course->id][$modname] = array(); 1253 1254 // Create an object for a default representation of this module type in the activity chooser. It will be used 1255 // if module does not implement callback get_shortcuts() and it will also be passed to the callback if it exists. 1256 $defaultmodule = new stdClass(); 1257 $defaultmodule->title = $modnamestr; 1258 $defaultmodule->name = $modname; 1259 $defaultmodule->link = new moodle_url($urlbase, array('add' => $modname)); 1260 $defaultmodule->icon = $OUTPUT->pix_icon('icon', '', $defaultmodule->name, array('class' => 'icon')); 1261 $sm = get_string_manager(); 1262 if ($sm->string_exists('modulename_help', $modname)) { 1263 $defaultmodule->help = get_string('modulename_help', $modname); 1264 if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs. 1265 $link = get_string('modulename_link', $modname); 1266 $linktext = get_string('morehelp'); 1267 $defaultmodule->help .= html_writer::tag('div', 1268 $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink')); 1269 } 1270 } 1271 $defaultmodule->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); 1272 1273 // Legacy support for callback get_types() - do not use any more, use get_shortcuts() instead! 1274 $typescallbackexists = component_callback_exists($modname, 'get_types'); 1275 1276 // Each module can implement callback modulename_get_shortcuts() in its lib.php and return the list 1277 // of elements to be added to activity chooser. 1278 $items = component_callback($modname, 'get_shortcuts', array($defaultmodule), null); 1279 if ($items !== null) { 1280 foreach ($items as $item) { 1281 // Add all items to the return array. All items must have different links, use them as a key in the return array. 1282 if (!isset($item->archetype)) { 1283 $item->archetype = $defaultmodule->archetype; 1284 } 1285 if (!isset($item->icon)) { 1286 $item->icon = $defaultmodule->icon; 1287 } 1288 // If plugin returned the only one item with the same link as default item - cache it as $modname, 1289 // otherwise append the link url to the module name. 1290 $item->name = (count($items) == 1 && 1291 $item->link->out() === $defaultmodule->link->out()) ? $modname : $modname . ':' . $item->link; 1292 1293 // If the module provides the helptext property, append it to the help text to match the look and feel 1294 // of the default course modules. 1295 if (isset($item->help) && isset($item->helplink)) { 1296 $linktext = get_string('morehelp'); 1297 $item->help .= html_writer::tag('div', 1298 $OUTPUT->doc_link($item->helplink, $linktext, true), array('class' => 'helpdoclink')); 1299 } 1300 $modlist[$course->id][$modname][$item->name] = $item; 1301 } 1302 $return += $modlist[$course->id][$modname]; 1303 if ($typescallbackexists) { 1304 debugging('Both callbacks get_shortcuts() and get_types() are found in module ' . $modname . 1305 '. Callback get_types() will be completely ignored', DEBUG_DEVELOPER); 1306 } 1307 // If get_shortcuts() callback is defined, the default module action is not added. 1308 // It is a responsibility of the callback to add it to the return value unless it is not needed. 1309 continue; 1310 } 1311 1312 if ($typescallbackexists) { 1313 debugging('Callback get_types() is found in module ' . $modname . ', this functionality is deprecated, ' . 1314 'please use callback get_shortcuts() instead', DEBUG_DEVELOPER); 1315 } 1316 $types = component_callback($modname, 'get_types', array(), MOD_SUBTYPE_NO_CHILDREN); 1317 if ($types !== MOD_SUBTYPE_NO_CHILDREN) { 1318 // Legacy support for deprecated callback get_types(). To be removed in Moodle 3.5. TODO MDL-53697. 1319 if (is_array($types) && count($types) > 0) { 1320 $grouptitle = $modnamestr; 1321 $icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon')); 1322 foreach($types as $type) { 1323 if ($type->typestr === '--') { 1324 continue; 1325 } 1326 if (strpos($type->typestr, '--') === 0) { 1327 $grouptitle = str_replace('--', '', $type->typestr); 1328 continue; 1329 } 1330 // Set the Sub Type metadata. 1331 $subtype = new stdClass(); 1332 $subtype->title = get_string('activitytypetitle', '', 1333 (object)['activity' => $grouptitle, 'type' => $type->typestr]); 1334 $subtype->type = str_replace('&', '&', $type->type); 1335 $typename = preg_replace('/.*type=/', '', $subtype->type); 1336 $subtype->archetype = $type->modclass; 1337 1338 if (!empty($type->help)) { 1339 $subtype->help = $type->help; 1340 } else if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) { 1341 $subtype->help = get_string('help' . $subtype->name, $modname); 1342 } 1343 $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $typename)); 1344 $subtype->name = $modname . ':' . $subtype->link; 1345 $subtype->icon = $icon; 1346 $modlist[$course->id][$modname][$subtype->name] = $subtype; 1347 } 1348 $return += $modlist[$course->id][$modname]; 1349 } 1350 } else { 1351 // Neither get_shortcuts() nor get_types() callbacks found, use the default item for the activity chooser. 1352 $modlist[$course->id][$modname][$modname] = $defaultmodule; 1353 $return[$modname] = $defaultmodule; 1354 } 1355 } 1356 1357 core_collator::asort_objects_by_property($return, 'title'); 1358 return $return; 1359 } 1360 1361 /** 1362 * Return the course category context for the category with id $categoryid, except 1363 * that if $categoryid is 0, return the system context. 1364 * 1365 * @param integer $categoryid a category id or 0. 1366 * @return context the corresponding context 1367 */ 1368 function get_category_or_system_context($categoryid) { 1369 if ($categoryid) { 1370 return context_coursecat::instance($categoryid, IGNORE_MISSING); 1371 } else { 1372 return context_system::instance(); 1373 } 1374 } 1375 1376 /** 1377 * Returns full course categories trees to be used in html_writer::select() 1378 * 1379 * Calls {@link coursecat::make_categories_list()} to build the tree and 1380 * adds whitespace to denote nesting 1381 * 1382 * @return array array mapping coursecat id to the display name 1383 */ 1384 function make_categories_options() { 1385 global $CFG; 1386 require_once($CFG->libdir. '/coursecatlib.php'); 1387 $cats = coursecat::make_categories_list('', 0, ' / '); 1388 foreach ($cats as $key => $value) { 1389 // Prefix the value with the number of spaces equal to category depth (number of separators in the value). 1390 $cats[$key] = str_repeat(' ', substr_count($value, ' / ')). $value; 1391 } 1392 return $cats; 1393 } 1394 1395 /** 1396 * Print the buttons relating to course requests. 1397 * 1398 * @param object $context current page context. 1399 */ 1400 function print_course_request_buttons($context) { 1401 global $CFG, $DB, $OUTPUT; 1402 if (empty($CFG->enablecourserequests)) { 1403 return; 1404 } 1405 if (!has_capability('moodle/course:create', $context) && has_capability('moodle/course:request', $context)) { 1406 /// Print a button to request a new course 1407 echo $OUTPUT->single_button(new moodle_url('/course/request.php'), get_string('requestcourse'), 'get'); 1408 } 1409 /// Print a button to manage pending requests 1410 if ($context->contextlevel == CONTEXT_SYSTEM && has_capability('moodle/site:approvecourse', $context)) { 1411 $disabled = !$DB->record_exists('course_request', array()); 1412 echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled)); 1413 } 1414 } 1415 1416 /** 1417 * Does the user have permission to edit things in this category? 1418 * 1419 * @param integer $categoryid The id of the category we are showing, or 0 for system context. 1420 * @return boolean has_any_capability(array(...), ...); in the appropriate context. 1421 */ 1422 function can_edit_in_category($categoryid = 0) { 1423 $context = get_category_or_system_context($categoryid); 1424 return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context); 1425 } 1426 1427 /// MODULE FUNCTIONS ///////////////////////////////////////////////////////////////// 1428 1429 function add_course_module($mod) { 1430 global $DB; 1431 1432 $mod->added = time(); 1433 unset($mod->id); 1434 1435 $cmid = $DB->insert_record("course_modules", $mod); 1436 rebuild_course_cache($mod->course, true); 1437 return $cmid; 1438 } 1439 1440 /** 1441 * Creates missing course section(s) and rebuilds course cache 1442 * 1443 * @param int|stdClass $courseorid course id or course object 1444 * @param int|array $sections list of relative section numbers to create 1445 * @return bool if there were any sections created 1446 */ 1447 function course_create_sections_if_missing($courseorid, $sections) { 1448 global $DB; 1449 if (!is_array($sections)) { 1450 $sections = array($sections); 1451 } 1452 $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all()); 1453 if (is_object($courseorid)) { 1454 $courseorid = $courseorid->id; 1455 } 1456 $coursechanged = false; 1457 foreach ($sections as $sectionnum) { 1458 if (!in_array($sectionnum, $existing)) { 1459 $cw = new stdClass(); 1460 $cw->course = $courseorid; 1461 $cw->section = $sectionnum; 1462 $cw->summary = ''; 1463 $cw->summaryformat = FORMAT_HTML; 1464 $cw->sequence = ''; 1465 $id = $DB->insert_record("course_sections", $cw); 1466 $coursechanged = true; 1467 } 1468 } 1469 if ($coursechanged) { 1470 rebuild_course_cache($courseorid, true); 1471 } 1472 return $coursechanged; 1473 } 1474 1475 /** 1476 * Adds an existing module to the section 1477 * 1478 * Updates both tables {course_sections} and {course_modules} 1479 * 1480 * Note: This function does not use modinfo PROVIDED that the section you are 1481 * adding the module to already exists. If the section does not exist, it will 1482 * build modinfo if necessary and create the section. 1483 * 1484 * @param int|stdClass $courseorid course id or course object 1485 * @param int $cmid id of the module already existing in course_modules table 1486 * @param int $sectionnum relative number of the section (field course_sections.section) 1487 * If section does not exist it will be created 1488 * @param int|stdClass $beforemod id or object with field id corresponding to the module 1489 * before which the module needs to be included. Null for inserting in the 1490 * end of the section 1491 * @return int The course_sections ID where the module is inserted 1492 */ 1493 function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) { 1494 global $DB, $COURSE; 1495 if (is_object($beforemod)) { 1496 $beforemod = $beforemod->id; 1497 } 1498 if (is_object($courseorid)) { 1499 $courseid = $courseorid->id; 1500 } else { 1501 $courseid = $courseorid; 1502 } 1503 // Do not try to use modinfo here, there is no guarantee it is valid! 1504 $section = $DB->get_record('course_sections', 1505 array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING); 1506 if (!$section) { 1507 // This function call requires modinfo. 1508 course_create_sections_if_missing($courseorid, $sectionnum); 1509 $section = $DB->get_record('course_sections', 1510 array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST); 1511 } 1512 1513 $modarray = explode(",", trim($section->sequence)); 1514 if (empty($section->sequence)) { 1515 $newsequence = "$cmid"; 1516 } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) { 1517 $insertarray = array($cmid, $beforemod); 1518 array_splice($modarray, $key[0], 1, $insertarray); 1519 $newsequence = implode(",", $modarray); 1520 } else { 1521 $newsequence = "$section->sequence,$cmid"; 1522 } 1523 $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id)); 1524 $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid)); 1525 if (is_object($courseorid)) { 1526 rebuild_course_cache($courseorid->id, true); 1527 } else { 1528 rebuild_course_cache($courseorid, true); 1529 } 1530 return $section->id; // Return course_sections ID that was used. 1531 } 1532 1533 /** 1534 * Change the group mode of a course module. 1535 * 1536 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs 1537 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}. 1538 * 1539 * @param int $id course module ID. 1540 * @param int $groupmode the new groupmode value. 1541 * @return bool True if the $groupmode was updated. 1542 */ 1543 function set_coursemodule_groupmode($id, $groupmode) { 1544 global $DB; 1545 $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST); 1546 if ($cm->groupmode != $groupmode) { 1547 $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id)); 1548 rebuild_course_cache($cm->course, true); 1549 } 1550 return ($cm->groupmode != $groupmode); 1551 } 1552 1553 function set_coursemodule_idnumber($id, $idnumber) { 1554 global $DB; 1555 $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST); 1556 if ($cm->idnumber != $idnumber) { 1557 $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id)); 1558 rebuild_course_cache($cm->course, true); 1559 } 1560 return ($cm->idnumber != $idnumber); 1561 } 1562 1563 /** 1564 * Set the visibility of a module and inherent properties. 1565 * 1566 * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs 1567 * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}. 1568 * 1569 * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered 1570 * has been moved to {@link set_section_visible()} which was the only place from which 1571 * the parameter was used. 1572 * 1573 * @param int $id of the module 1574 * @param int $visible state of the module 1575 * @return bool false when the module was not found, true otherwise 1576 */ 1577 function set_coursemodule_visible($id, $visible) { 1578 global $DB, $CFG; 1579 require_once($CFG->libdir.'/gradelib.php'); 1580 require_once($CFG->dirroot.'/calendar/lib.php'); 1581 1582 // Trigger developer's attention when using the previously removed argument. 1583 if (func_num_args() > 2) { 1584 debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides 1585 has been removed.', DEBUG_DEVELOPER); 1586 } 1587 1588 if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) { 1589 return false; 1590 } 1591 1592 // Create events and propagate visibility to associated grade items if the value has changed. 1593 // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades. 1594 if ($cm->visible == $visible) { 1595 return true; 1596 } 1597 1598 if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) { 1599 return false; 1600 } 1601 if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) { 1602 foreach($events as $event) { 1603 if ($visible) { 1604 $event = new calendar_event($event); 1605 $event->toggle_visibility(true); 1606 } else { 1607 $event = new calendar_event($event); 1608 $event->toggle_visibility(false); 1609 } 1610 } 1611 } 1612 1613 // Updating visible and visibleold to keep them in sync. Only changing a section visibility will 1614 // affect visibleold to allow for an original visibility restore. See set_section_visible(). 1615 $cminfo = new stdClass(); 1616 $cminfo->id = $id; 1617 $cminfo->visible = $visible; 1618 $cminfo->visibleold = $visible; 1619 $DB->update_record('course_modules', $cminfo); 1620 1621 // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there. 1622 // Note that this must be done after updating the row in course_modules, in case 1623 // the modules grade_item_update function needs to access $cm->visible. 1624 if (plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) && 1625 component_callback_exists('mod_' . $modulename, 'grade_item_update')) { 1626 $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST); 1627 component_callback('mod_' . $modulename, 'grade_item_update', array($instance)); 1628 } else { 1629 $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course)); 1630 if ($grade_items) { 1631 foreach ($grade_items as $grade_item) { 1632 $grade_item->set_hidden(!$visible); 1633 } 1634 } 1635 } 1636 1637 rebuild_course_cache($cm->course, true); 1638 return true; 1639 } 1640 1641 /** 1642 * Changes the course module name 1643 * 1644 * @param int $id course module id 1645 * @param string $name new value for a name 1646 * @return bool whether a change was made 1647 */ 1648 function set_coursemodule_name($id, $name) { 1649 global $CFG, $DB; 1650 require_once($CFG->libdir . '/gradelib.php'); 1651 1652 $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST); 1653 1654 $module = new \stdClass(); 1655 $module->id = $cm->instance; 1656 1657 // Escape strings as they would be by mform. 1658 if (!empty($CFG->formatstringstriptags)) { 1659 $module->name = clean_param($name, PARAM_TEXT); 1660 } else { 1661 $module->name = clean_param($name, PARAM_CLEANHTML); 1662 } 1663 if ($module->name === $cm->name || strval($module->name) === '') { 1664 return false; 1665 } 1666 if (\core_text::strlen($module->name) > 255) { 1667 throw new \moodle_exception('maximumchars', 'moodle', '', 255); 1668 } 1669 1670 $module->timemodified = time(); 1671 $DB->update_record($cm->modname, $module); 1672 $cm->name = $module->name; 1673 \core\event\course_module_updated::create_from_cm($cm)->trigger(); 1674 rebuild_course_cache($cm->course, true); 1675 1676 // Attempt to update the grade item if relevant. 1677 $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance)); 1678 $grademodule->cmidnumber = $cm->idnumber; 1679 $grademodule->modname = $cm->modname; 1680 grade_update_mod_grades($grademodule); 1681 1682 return true; 1683 } 1684 1685 /** 1686 * This function will handle the whole deletion process of a module. This includes calling 1687 * the modules delete_instance function, deleting files, events, grades, conditional data, 1688 * the data in the course_module and course_sections table and adding a module deletion 1689 * event to the DB. 1690 * 1691 * @param int $cmid the course module id 1692 * @since Moodle 2.5 1693 */ 1694 function course_delete_module($cmid) { 1695 global $CFG, $DB; 1696 1697 require_once($CFG->libdir.'/gradelib.php'); 1698 require_once($CFG->libdir.'/questionlib.php'); 1699 require_once($CFG->dirroot.'/blog/lib.php'); 1700 require_once($CFG->dirroot.'/calendar/lib.php'); 1701 1702 // Get the course module. 1703 if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) { 1704 return true; 1705 } 1706 1707 // Get the module context. 1708 $modcontext = context_module::instance($cm->id); 1709 1710 // Get the course module name. 1711 $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST); 1712 1713 // Get the file location of the delete_instance function for this module. 1714 $modlib = "$CFG->dirroot/mod/$modulename/lib.php"; 1715 1716 // Include the file required to call the delete_instance function for this module. 1717 if (file_exists($modlib)) { 1718 require_once($modlib); 1719 } else { 1720 throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null, 1721 "Cannot delete this module as the file mod/$modulename/lib.php is missing."); 1722 } 1723 1724 $deleteinstancefunction = $modulename . '_delete_instance'; 1725 1726 // Ensure the delete_instance function exists for this module. 1727 if (!function_exists($deleteinstancefunction)) { 1728 throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null, 1729 "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php."); 1730 } 1731 1732 // Allow plugins to use this course module before we completely delete it. 1733 if ($pluginsfunction = get_plugins_with_function('pre_course_module_delete')) { 1734 foreach ($pluginsfunction as $plugintype => $plugins) { 1735 foreach ($plugins as $pluginfunction) { 1736 $pluginfunction($cm); 1737 } 1738 } 1739 } 1740 1741 // Delete activity context questions and question categories. 1742 question_delete_activity($cm); 1743 1744 // Call the delete_instance function, if it returns false throw an exception. 1745 if (!$deleteinstancefunction($cm->instance)) { 1746 throw new moodle_exception('cannotdeletemoduleinstance', '', '', null, 1747 "Cannot delete the module $modulename (instance)."); 1748 } 1749 1750 // Remove all module files in case modules forget to do that. 1751 $fs = get_file_storage(); 1752 $fs->delete_area_files($modcontext->id); 1753 1754 // Delete events from calendar. 1755 if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) { 1756 foreach($events as $event) { 1757 $calendarevent = calendar_event::load($event->id); 1758 $calendarevent->delete(); 1759 } 1760 } 1761 1762 // Delete grade items, outcome items and grades attached to modules. 1763 if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename, 1764 'iteminstance' => $cm->instance, 'courseid' => $cm->course))) { 1765 foreach ($grade_items as $grade_item) { 1766 $grade_item->delete('moddelete'); 1767 } 1768 } 1769 1770 // Delete completion and availability data; it is better to do this even if the 1771 // features are not turned on, in case they were turned on previously (these will be 1772 // very quick on an empty table). 1773 $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id)); 1774 $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id, 1775 'course' => $cm->course, 1776 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY)); 1777 1778 // Delete all tag instances associated with the instance of this module. 1779 core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id); 1780 core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id); 1781 1782 // Notify the competency subsystem. 1783 \core_competency\api::hook_course_module_deleted($cm); 1784 1785 // Delete the context. 1786 context_helper::delete_instance(CONTEXT_MODULE, $cm->id); 1787 1788 // Delete the module from the course_modules table. 1789 $DB->delete_records('course_modules', array('id' => $cm->id)); 1790 1791 // Delete module from that section. 1792 if (!delete_mod_from_section($cm->id, $cm->section)) { 1793 throw new moodle_exception('cannotdeletemodulefromsection', '', '', null, 1794 "Cannot delete the module $modulename (instance) from section."); 1795 } 1796 1797 // Trigger event for course module delete action. 1798 $event = \core\event\course_module_deleted::create(array( 1799 'courseid' => $cm->course, 1800 'context' => $modcontext, 1801 'objectid' => $cm->id, 1802 'other' => array( 1803 'modulename' => $modulename, 1804 'instanceid' => $cm->instance, 1805 ) 1806 )); 1807 $event->add_record_snapshot('course_modules', $cm); 1808 $event->trigger(); 1809 rebuild_course_cache($cm->course, true); 1810 } 1811 1812 function delete_mod_from_section($modid, $sectionid) { 1813 global $DB; 1814 1815 if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) { 1816 1817 $modarray = explode(",", $section->sequence); 1818 1819 if ($key = array_keys ($modarray, $modid)) { 1820 array_splice($modarray, $key[0], 1); 1821 $newsequence = implode(",", $modarray); 1822 $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id)); 1823 rebuild_course_cache($section->course, true); 1824 return true; 1825 } else { 1826 return false; 1827 } 1828 1829 } 1830 return false; 1831 } 1832 1833 /** 1834 * Moves a section within a course, from a position to another. 1835 * Be very careful: $section and $destination refer to section number, 1836 * not id!. 1837 * 1838 * @param object $course 1839 * @param int $section Section number (not id!!!) 1840 * @param int $destination 1841 * @param bool $ignorenumsections 1842 * @return boolean Result 1843 */ 1844 function move_section_to($course, $section, $destination, $ignorenumsections = false) { 1845 /// Moves a whole course section up and down within the course 1846 global $USER, $DB; 1847 1848 if (!$destination && $destination != 0) { 1849 return true; 1850 } 1851 1852 // compartibility with course formats using field 'numsections' 1853 $courseformatoptions = course_get_format($course)->get_format_options(); 1854 if ((!$ignorenumsections && array_key_exists('numsections', $courseformatoptions) && 1855 ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) { 1856 return false; 1857 } 1858 1859 // Get all sections for this course and re-order them (2 of them should now share the same section number) 1860 if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id), 1861 'section ASC, id ASC', 'id, section')) { 1862 return false; 1863 } 1864 1865 $movedsections = reorder_sections($sections, $section, $destination); 1866 1867 // Update all sections. Do this in 2 steps to avoid breaking database 1868 // uniqueness constraint 1869 $transaction = $DB->start_delegated_transaction(); 1870 foreach ($movedsections as $id => $position) { 1871 if ($sections[$id] !== $position) { 1872 $DB->set_field('course_sections', 'section', -$position, array('id' => $id)); 1873 } 1874 } 1875 foreach ($movedsections as $id => $position) { 1876 if ($sections[$id] !== $position) { 1877 $DB->set_field('course_sections', 'section', $position, array('id' => $id)); 1878 } 1879 } 1880 1881 // If we move the highlighted section itself, then just highlight the destination. 1882 // Adjust the higlighted section location if we move something over it either direction. 1883 if ($section == $course->marker) { 1884 course_set_marker($course->id, $destination); 1885 } elseif ($section > $course->marker && $course->marker >= $destination) { 1886 course_set_marker($course->id, $course->marker+1); 1887 } elseif ($section < $course->marker && $course->marker <= $destination) { 1888 course_set_marker($course->id, $course->marker-1); 1889 } 1890 1891 $transaction->allow_commit(); 1892 rebuild_course_cache($course->id, true); 1893 return true; 1894 } 1895 1896 /** 1897 * This method will delete a course section and may delete all modules inside it. 1898 * 1899 * No permissions are checked here, use {@link course_can_delete_section()} to 1900 * check if section can actually be deleted. 1901 * 1902 * @param int|stdClass $course 1903 * @param int|stdClass|section_info $section 1904 * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it. 1905 * @return bool whether section was deleted 1906 */ 1907 function course_delete_section($course, $section, $forcedeleteifnotempty = true) { 1908 global $DB; 1909 1910 // Prepare variables. 1911 $courseid = (is_object($course)) ? $course->id : (int)$course; 1912 $sectionnum = (is_object($section)) ? $section->section : (int)$section; 1913 $section = $DB->get_record('course_sections', array('course' => $courseid, 'section' => $sectionnum)); 1914 if (!$section) { 1915 // No section exists, can't proceed. 1916 return false; 1917 } 1918 $format = course_get_format($course); 1919 $sectionname = $format->get_section_name($section); 1920 1921 // Delete section. 1922 $result = $format->delete_section($section, $forcedeleteifnotempty); 1923 1924 // Trigger an event for course section deletion. 1925 if ($result) { 1926 $context = context_course::instance($courseid); 1927 $event = \core\event\course_section_deleted::create( 1928 array( 1929 'objectid' => $section->id, 1930 'courseid' => $courseid, 1931 'context' => $context, 1932 'other' => array( 1933 'sectionnum' => $section->section, 1934 'sectionname' => $sectionname, 1935 ) 1936 ) 1937 ); 1938 $event->add_record_snapshot('course_sections', $section); 1939 $event->trigger(); 1940 } 1941 return $result; 1942 } 1943 1944 /** 1945 * Updates the course section 1946 * 1947 * This function does not check permissions or clean values - this has to be done prior to calling it. 1948 * 1949 * @param int|stdClass $course 1950 * @param stdClass $section record from course_sections table - it will be updated with the new values 1951 * @param array|stdClass $data 1952 */ 1953 function course_update_section($course, $section, $data) { 1954 global $DB; 1955 1956 $courseid = (is_object($course)) ? $course->id : (int)$course; 1957 1958 // Some fields can not be updated using this method. 1959 $data = array_diff_key((array)$data, array('id', 'course', 'section', 'sequence')); 1960 $changevisibility = (array_key_exists('visible', $data) && (bool)$data['visible'] != (bool)$section->visible); 1961 if (array_key_exists('name', $data) && \core_text::strlen($data['name']) > 255) { 1962 throw new moodle_exception('maximumchars', 'moodle', '', 255); 1963 } 1964 1965 // Update record in the DB and course format options. 1966 $data['id'] = $section->id; 1967 $DB->update_record('course_sections', $data); 1968 rebuild_course_cache($courseid, true); 1969 course_get_format($courseid)->update_section_format_options($data); 1970 1971 // Update fields of the $section object. 1972 foreach ($data as $key => $value) { 1973 if (property_exists($section, $key)) { 1974 $section->$key = $value; 1975 } 1976 } 1977 1978 // Trigger an event for course section update. 1979 $event = \core\event\course_section_updated::create( 1980 array( 1981 'objectid' => $section->id, 1982 'courseid' => $courseid, 1983 'context' => context_course::instance($courseid), 1984 'other' => array('sectionnum' => $section->section) 1985 ) 1986 ); 1987 $event->trigger(); 1988 1989 // If section visibility was changed, hide the modules in this section too. 1990 if ($changevisibility && !empty($section->sequence)) { 1991 $modules = explode(',', $section->sequence); 1992 foreach ($modules as $moduleid) { 1993 if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) { 1994 if ($data['visible']) { 1995 // As we unhide the section, we use the previously saved visibility stored in visibleold. 1996 set_coursemodule_visible($moduleid, $cm->visibleold); 1997 } else { 1998 // We hide the section, so we hide the module but we store the original state in visibleold. 1999 set_coursemodule_visible($moduleid, 0); 2000 $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid)); 2001 } 2002 \core\event\course_module_updated::create_from_cm($cm)->trigger(); 2003 } 2004 } 2005 } 2006 } 2007 2008 /** 2009 * Checks if the current user can delete a section (if course format allows it and user has proper permissions). 2010 * 2011 * @param int|stdClass $course 2012 * @param int|stdClass|section_info $section 2013 * @return bool 2014 */ 2015 function course_can_delete_section($course, $section) { 2016 if (is_object($section)) { 2017 $section = $section->section; 2018 } 2019 if (!$section) { 2020 // Not possible to delete 0-section. 2021 return false; 2022 } 2023 // Course format should allow to delete sections. 2024 if (!course_get_format($course)->can_delete_section($section)) { 2025 return false; 2026 } 2027 // Make sure user has capability to update course and move sections. 2028 $context = context_course::instance(is_object($course) ? $course->id : $course); 2029 if (!has_all_capabilities(array('moodle/course:movesections', 'moodle/course:update'), $context)) { 2030 return false; 2031 } 2032 // Make sure user has capability to delete each activity in this section. 2033 $modinfo = get_fast_modinfo($course); 2034 if (!empty($modinfo->sections[$section])) { 2035 foreach ($modinfo->sections[$section] as $cmid) { 2036 if (!has_capability('moodle/course:manageactivities', context_module::instance($cmid))) { 2037 return false; 2038 } 2039 } 2040 } 2041 return true; 2042 } 2043 2044 /** 2045 * Reordering algorithm for course sections. Given an array of section->section indexed by section->id, 2046 * an original position number and a target position number, rebuilds the array so that the 2047 * move is made without any duplication of section positions. 2048 * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to 2049 * insert a section before the first one, you must give 0 as the target (section 0 can never be moved). 2050 * 2051 * @param array $sections 2052 * @param int $origin_position 2053 * @param int $target_position 2054 * @return array 2055 */ 2056 function reorder_sections($sections, $origin_position, $target_position) { 2057 if (!is_array($sections)) { 2058 return false; 2059 } 2060 2061 // We can't move section position 0 2062 if ($origin_position < 1) { 2063 echo "We can't move section position 0"; 2064 return false; 2065 } 2066 2067 // Locate origin section in sections array 2068 if (!$origin_key = array_search($origin_position, $sections)) { 2069 echo "searched position not in sections array"; 2070 return false; // searched position not in sections array 2071 } 2072 2073 // Extract origin section 2074 $origin_section = $sections[$origin_key]; 2075 unset($sections[$origin_key]); 2076 2077 // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!) 2078 $found = false; 2079 $append_array = array(); 2080 foreach ($sections as $id => $position) { 2081 if ($found) { 2082 $append_array[$id] = $position; 2083 unset($sections[$id]); 2084 } 2085 if ($position == $target_position) { 2086 if ($target_position < $origin_position) { 2087 $append_array[$id] = $position; 2088 unset($sections[$id]); 2089 } 2090 $found = true; 2091 } 2092 } 2093 2094 // Append moved section 2095 $sections[$origin_key] = $origin_section; 2096 2097 // Append rest of array (if applicable) 2098 if (!empty($append_array)) { 2099 foreach ($append_array as $id => $position) { 2100 $sections[$id] = $position; 2101 } 2102 } 2103 2104 // Renumber positions 2105 $position = 0; 2106 foreach ($sections as $id => $p) { 2107 $sections[$id] = $position; 2108 $position++; 2109 } 2110 2111 return $sections; 2112 2113 } 2114 2115 /** 2116 * Move the module object $mod to the specified $section 2117 * If $beforemod exists then that is the module 2118 * before which $modid should be inserted 2119 * 2120 * @param stdClass|cm_info $mod 2121 * @param stdClass|section_info $section 2122 * @param int|stdClass $beforemod id or object with field id corresponding to the module 2123 * before which the module needs to be included. Null for inserting in the 2124 * end of the section 2125 * @return int new value for module visibility (0 or 1) 2126 */ 2127 function moveto_module($mod, $section, $beforemod=NULL) { 2128 global $OUTPUT, $DB; 2129 2130 // Current module visibility state - return value of this function. 2131 $modvisible = $mod->visible; 2132 2133 // Remove original module from original section. 2134 if (! delete_mod_from_section($mod->id, $mod->section)) { 2135 echo $OUTPUT->notification("Could not delete module from existing section"); 2136 } 2137 2138 // If moving to a hidden section then hide module. 2139 if ($mod->section != $section->id) { 2140 if (!$section->visible && $mod->visible) { 2141 // Module was visible but must become hidden after moving to hidden section. 2142 $modvisible = 0; 2143 set_coursemodule_visible($mod->id, 0); 2144 // Set visibleold to 1 so module will be visible when section is made visible. 2145 $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id)); 2146 } 2147 if ($section->visible && !$mod->visible) { 2148 // Hidden module was moved to the visible section, restore the module visibility from visibleold. 2149 set_coursemodule_visible($mod->id, $mod->visibleold); 2150 $modvisible = $mod->visibleold; 2151 } 2152 } 2153 2154 // Add the module into the new section. 2155 course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod); 2156 return $modvisible; 2157 } 2158 2159 /** 2160 * Returns the list of all editing actions that current user can perform on the module 2161 * 2162 * @param cm_info $mod The module to produce editing buttons for 2163 * @param int $indent The current indenting (default -1 means no move left-right actions) 2164 * @param int $sr The section to link back to (used for creating the links) 2165 * @return array array of action_link or pix_icon objects 2166 */ 2167 function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { 2168 global $COURSE, $SITE; 2169 2170 static $str; 2171 2172 $coursecontext = context_course::instance($mod->course); 2173 $modcontext = context_module::instance($mod->id); 2174 2175 $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign'); 2176 $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport'); 2177 2178 // No permission to edit anything. 2179 if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) { 2180 return array(); 2181 } 2182 2183 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext); 2184 2185 if (!isset($str)) { 2186 $str = get_strings(array('delete', 'move', 'moveright', 'moveleft', 2187 'editsettings', 'duplicate', 'hide', 'show'), 'moodle'); 2188 $str->assign = get_string('assignroles', 'role'); 2189 $str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone")); 2190 $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate")); 2191 $str->groupsvisible = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible")); 2192 } 2193 2194 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey())); 2195 2196 if ($sr !== null) { 2197 $baseurl->param('sr', $sr); 2198 } 2199 $actions = array(); 2200 2201 // Update. 2202 if ($hasmanageactivities) { 2203 $actions['update'] = new action_menu_link_secondary( 2204 new moodle_url($baseurl, array('update' => $mod->id)), 2205 new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2206 $str->editsettings, 2207 array('class' => 'editing_update', 'data-action' => 'update') 2208 ); 2209 } 2210 2211 // Indent. 2212 if ($hasmanageactivities && $indent >= 0) { 2213 $indentlimits = new stdClass(); 2214 $indentlimits->min = 0; 2215 $indentlimits->max = 16; 2216 if (right_to_left()) { // Exchange arrows on RTL 2217 $rightarrow = 't/left'; 2218 $leftarrow = 't/right'; 2219 } else { 2220 $rightarrow = 't/right'; 2221 $leftarrow = 't/left'; 2222 } 2223 2224 if ($indent >= $indentlimits->max) { 2225 $enabledclass = 'hidden'; 2226 } else { 2227 $enabledclass = ''; 2228 } 2229 $actions['moveright'] = new action_menu_link_secondary( 2230 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')), 2231 new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2232 $str->moveright, 2233 array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true) 2234 ); 2235 2236 if ($indent <= $indentlimits->min) { 2237 $enabledclass = 'hidden'; 2238 } else { 2239 $enabledclass = ''; 2240 } 2241 $actions['moveleft'] = new action_menu_link_secondary( 2242 new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')), 2243 new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2244 $str->moveleft, 2245 array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true) 2246 ); 2247 2248 } 2249 2250 // Hide/Show. 2251 if (has_capability('moodle/course:activityvisibility', $modcontext)) { 2252 if ($mod->visible) { 2253 $actions['hide'] = new action_menu_link_secondary( 2254 new moodle_url($baseurl, array('hide' => $mod->id)), 2255 new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2256 $str->hide, 2257 array('class' => 'editing_hide', 'data-action' => 'hide') 2258 ); 2259 } else { 2260 $actions['show'] = new action_menu_link_secondary( 2261 new moodle_url($baseurl, array('show' => $mod->id)), 2262 new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2263 $str->show, 2264 array('class' => 'editing_show', 'data-action' => 'show') 2265 ); 2266 } 2267 } 2268 2269 // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php) 2270 if (has_all_capabilities($dupecaps, $coursecontext) && 2271 plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) { 2272 $actions['duplicate'] = new action_menu_link_secondary( 2273 new moodle_url($baseurl, array('duplicate' => $mod->id)), 2274 new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2275 $str->duplicate, 2276 array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr) 2277 ); 2278 } 2279 2280 // Groupmode. 2281 if ($hasmanageactivities && !$mod->coursegroupmodeforce) { 2282 if (plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) { 2283 if ($mod->effectivegroupmode == SEPARATEGROUPS) { 2284 $nextgroupmode = VISIBLEGROUPS; 2285 $grouptitle = $str->groupsseparate; 2286 $actionname = 'groupsseparate'; 2287 $groupimage = 'i/groups'; 2288 } else if ($mod->effectivegroupmode == VISIBLEGROUPS) { 2289 $nextgroupmode = NOGROUPS; 2290 $grouptitle = $str->groupsvisible; 2291 $actionname = 'groupsvisible'; 2292 $groupimage = 'i/groupv'; 2293 } else { 2294 $nextgroupmode = SEPARATEGROUPS; 2295 $grouptitle = $str->groupsnone; 2296 $actionname = 'groupsnone'; 2297 $groupimage = 'i/groupn'; 2298 } 2299 2300 $actions[$actionname] = new action_menu_link_primary( 2301 new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)), 2302 new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')), 2303 $grouptitle, 2304 array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive') 2305 ); 2306 } else { 2307 $actions['nogroupsupport'] = new action_menu_filler(); 2308 } 2309 } 2310 2311 // Assign. 2312 if (has_capability('moodle/role:assign', $modcontext)){ 2313 $actions['assign'] = new action_menu_link_secondary( 2314 new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)), 2315 new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2316 $str->assign, 2317 array('class' => 'editing_assign', 'data-action' => 'assignroles') 2318 ); 2319 } 2320 2321 // Delete. 2322 if ($hasmanageactivities) { 2323 $actions['delete'] = new action_menu_link_secondary( 2324 new moodle_url($baseurl, array('delete' => $mod->id)), 2325 new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2326 $str->delete, 2327 array('class' => 'editing_delete', 'data-action' => 'delete') 2328 ); 2329 } 2330 2331 return $actions; 2332 } 2333 2334 /** 2335 * Returns the move action. 2336 * 2337 * @param cm_info $mod The module to produce a move button for 2338 * @param int $sr The section to link back to (used for creating the links) 2339 * @return The markup for the move action, or an empty string if not available. 2340 */ 2341 function course_get_cm_move(cm_info $mod, $sr = null) { 2342 global $OUTPUT; 2343 2344 static $str; 2345 static $baseurl; 2346 2347 $modcontext = context_module::instance($mod->id); 2348 $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext); 2349 2350 if (!isset($str)) { 2351 $str = get_strings(array('move')); 2352 } 2353 2354 if (!isset($baseurl)) { 2355 $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey())); 2356 2357 if ($sr !== null) { 2358 $baseurl->param('sr', $sr); 2359 } 2360 } 2361 2362 if ($hasmanageactivities) { 2363 $pixicon = 'i/dragdrop'; 2364 2365 if (!course_ajax_enabled($mod->get_course())) { 2366 // Override for course frontpage until we get drag/drop working there. 2367 $pixicon = 't/move'; 2368 } 2369 2370 return html_writer::link( 2371 new moodle_url($baseurl, array('copy' => $mod->id)), 2372 $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')), 2373 array('class' => 'editing_move', 'data-action' => 'move') 2374 ); 2375 } 2376 return ''; 2377 } 2378 2379 /** 2380 * given a course object with shortname & fullname, this function will 2381 * truncate the the number of chars allowed and add ... if it was too long 2382 */ 2383 function course_format_name ($course,$max=100) { 2384 2385 $context = context_course::instance($course->id); 2386 $shortname = format_string($course->shortname, true, array('context' => $context)); 2387 $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); 2388 $str = $shortname.': '. $fullname; 2389 if (core_text::strlen($str) <= $max) { 2390 return $str; 2391 } 2392 else { 2393 return core_text::substr($str,0,$max-3).'...'; 2394 } 2395 } 2396 2397 /** 2398 * Is the user allowed to add this type of module to this course? 2399 * @param object $course the course settings. Only $course->id is used. 2400 * @param string $modname the module name. E.g. 'forum' or 'quiz'. 2401 * @return bool whether the current user is allowed to add this type of module to this course. 2402 */ 2403 function course_allowed_module($course, $modname) { 2404 if (is_numeric($modname)) { 2405 throw new coding_exception('Function course_allowed_module no longer 2406 supports numeric module ids. Please update your code to pass the module name.'); 2407 } 2408 2409 $capability = 'mod/' . $modname . ':addinstance'; 2410 if (!get_capability_info($capability)) { 2411 // Debug warning that the capability does not exist, but no more than once per page. 2412 static $warned = array(); 2413 $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); 2414 if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) { 2415 debugging('The module ' . $modname . ' does not define the standard capability ' . 2416 $capability , DEBUG_DEVELOPER); 2417 $warned[$modname] = 1; 2418 } 2419 2420 // If the capability does not exist, the module can always be added. 2421 return true; 2422 } 2423 2424 $coursecontext = context_course::instance($course->id); 2425 return has_capability($capability, $coursecontext); 2426 } 2427 2428 /** 2429 * Efficiently moves many courses around while maintaining 2430 * sortorder in order. 2431 * 2432 * @param array $courseids is an array of course ids 2433 * @param int $categoryid 2434 * @return bool success 2435 */ 2436 function move_courses($courseids, $categoryid) { 2437 global $DB; 2438 2439 if (empty($courseids)) { 2440 // Nothing to do. 2441 return false; 2442 } 2443 2444 if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) { 2445 return false; 2446 } 2447 2448 $courseids = array_reverse($courseids); 2449 $newparent = context_coursecat::instance($category->id); 2450 $i = 1; 2451 2452 list($where, $params) = $DB->get_in_or_equal($courseids); 2453 $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname'); 2454 foreach ($dbcourses as $dbcourse) { 2455 $course = new stdClass(); 2456 $course->id = $dbcourse->id; 2457 $course->category = $category->id; 2458 $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++; 2459 if ($category->visible == 0) { 2460 // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get 2461 // to previous state if somebody unhides the category. 2462 $course->visible = 0; 2463 } 2464 2465 $DB->update_record('course', $course); 2466 2467 // Update context, so it can be passed to event. 2468 $context = context_course::instance($course->id); 2469 $context->update_moved($newparent); 2470 2471 // Trigger a course updated event. 2472 $event = \core\event\course_updated::create(array( 2473 'objectid' => $course->id, 2474 'context' => context_course::instance($course->id), 2475 'other' => array('shortname' => $dbcourse->shortname, 2476 'fullname' => $dbcourse->fullname) 2477 )); 2478 $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id)); 2479 $event->trigger(); 2480 } 2481 fix_course_sortorder(); 2482 cache_helper::purge_by_event('changesincourse'); 2483 2484 return true; 2485 } 2486 2487 /** 2488 * Returns the display name of the given section that the course prefers 2489 * 2490 * Implementation of this function is provided by course format 2491 * @see format_base::get_section_name() 2492 * 2493 * @param int|stdClass $courseorid The course to get the section name for (object or just course id) 2494 * @param int|stdClass $section Section object from database or just field course_sections.section 2495 * @return string Display name that the course format prefers, e.g. "Week 2" 2496 */ 2497 function get_section_name($courseorid, $section) { 2498 return course_get_format($courseorid)->get_section_name($section); 2499 } 2500 2501 /** 2502 * Tells if current course format uses sections 2503 * 2504 * @param string $format Course format ID e.g. 'weeks' $course->format 2505 * @return bool 2506 */ 2507 function course_format_uses_sections($format) { 2508 $course = new stdClass(); 2509 $course->format = $format; 2510 return course_get_format($course)->uses_sections(); 2511 } 2512 2513 /** 2514 * Returns the information about the ajax support in the given source format 2515 * 2516 * The returned object's property (boolean)capable indicates that 2517 * the course format supports Moodle course ajax features. 2518 * 2519 * @param string $format 2520 * @return stdClass 2521 */ 2522 function course_format_ajax_support($format) { 2523 $course = new stdClass(); 2524 $course->format = $format; 2525 return course_get_format($course)->supports_ajax(); 2526 } 2527 2528 /** 2529 * Can the current user delete this course? 2530 * Course creators have exception, 2531 * 1 day after the creation they can sill delete the course. 2532 * @param int $courseid 2533 * @return boolean 2534 */ 2535 function can_delete_course($courseid) { 2536 global $USER; 2537 2538 $context = context_course::instance($courseid); 2539 2540 if (has_capability('moodle/course:delete', $context)) { 2541 return true; 2542 } 2543 2544 // hack: now try to find out if creator created this course recently (1 day) 2545 if (!has_capability('moodle/course:create', $context)) { 2546 return false; 2547 } 2548 2549 $since = time() - 60*60*24; 2550 $course = get_course($courseid); 2551 2552 if ($course->timecreated < $since) { 2553 return false; // Return if the course was not created in last 24 hours. 2554 } 2555 2556 $logmanger = get_log_manager(); 2557 $readers = $logmanger->get_readers('\core\log\sql_reader'); 2558 $reader = reset($readers); 2559 2560 if (empty($reader)) { 2561 return false; // No log reader found. 2562 } 2563 2564 // A proper reader. 2565 $select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since"; 2566 $params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created'); 2567 2568 return (bool)$reader->get_events_select_count($select, $params); 2569 } 2570 2571 /** 2572 * Save the Your name for 'Some role' strings. 2573 * 2574 * @param integer $courseid the id of this course. 2575 * @param array $data the data that came from the course settings form. 2576 */ 2577 function save_local_role_names($courseid, $data) { 2578 global $DB; 2579 $context = context_course::instance($courseid); 2580 2581 foreach ($data as $fieldname => $value) { 2582 if (strpos($fieldname, 'role_') !== 0) { 2583 continue; 2584 } 2585 list($ignored, $roleid) = explode('_', $fieldname); 2586 2587 // make up our mind whether we want to delete, update or insert 2588 if (!$value) { 2589 $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid)); 2590 2591 } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) { 2592 $rolename->name = $value; 2593 $DB->update_record('role_names', $rolename); 2594 2595 } else { 2596 $rolename = new stdClass; 2597 $rolename->contextid = $context->id; 2598 $rolename->roleid = $roleid; 2599 $rolename->name = $value; 2600 $DB->insert_record('role_names', $rolename); 2601 } 2602 // This will ensure the course contacts cache is purged.. 2603 coursecat::role_assignment_changed($roleid, $context); 2604 } 2605 } 2606 2607 /** 2608 * Returns options to use in course overviewfiles filemanager 2609 * 2610 * @param null|stdClass|course_in_list|int $course either object that has 'id' property or just the course id; 2611 * may be empty if course does not exist yet (course create form) 2612 * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc. 2613 * or null if overviewfiles are disabled 2614 */ 2615 function course_overviewfiles_options($course) { 2616 global $CFG; 2617 if (empty($CFG->courseoverviewfileslimit)) { 2618 return null; 2619 } 2620 $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY); 2621 if (in_array('*', $accepted_types) || empty($accepted_types)) { 2622 $accepted_types = '*'; 2623 } else { 2624 // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered. 2625 // Make sure extensions are prefixed with dot unless they are valid typegroups 2626 foreach ($accepted_types as $i => $type) { 2627 if (substr($type, 0, 1) !== '.') { 2628 require_once($CFG->libdir. '/filelib.php'); 2629 if (!count(file_get_typegroup('extension', $type))) { 2630 // It does not start with dot and is not a valid typegroup, this is most likely extension. 2631 $accepted_types[$i] = '.'. $type; 2632 $corrected = true; 2633 } 2634 } 2635 } 2636 if (!empty($corrected)) { 2637 set_config('courseoverviewfilesext', join(',', $accepted_types)); 2638 } 2639 } 2640 $options = array( 2641 'maxfiles' => $CFG->courseoverviewfileslimit, 2642 'maxbytes' => $CFG->maxbytes, 2643 'subdirs' => 0, 2644 'accepted_types' => $accepted_types 2645 ); 2646 if (!empty($course->id)) { 2647 $options['context'] = context_course::instance($course->id); 2648 } else if (is_int($course) && $course > 0) { 2649 $options['context'] = context_course::instance($course); 2650 } 2651 return $options; 2652 } 2653 2654 /** 2655 * Create a course and either return a $course object 2656 * 2657 * Please note this functions does not verify any access control, 2658 * the calling code is responsible for all validation (usually it is the form definition). 2659 * 2660 * @param array $editoroptions course description editor options 2661 * @param object $data - all the data needed for an entry in the 'course' table 2662 * @return object new course instance 2663 */ 2664 function create_course($data, $editoroptions = NULL) { 2665 global $DB, $CFG; 2666 2667 //check the categoryid - must be given for all new courses 2668 $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST); 2669 2670 // Check if the shortname already exists. 2671 if (!empty($data->shortname)) { 2672 if ($DB->record_exists('course', array('shortname' => $data->shortname))) { 2673 throw new moodle_exception('shortnametaken', '', '', $data->shortname); 2674 } 2675 } 2676 2677 // Check if the idnumber already exists. 2678 if (!empty($data->idnumber)) { 2679 if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) { 2680 throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber); 2681 } 2682 } 2683 2684 // Check if timecreated is given. 2685 $data->timecreated = !empty($data->timecreated) ? $data->timecreated : time(); 2686 $data->timemodified = $data->timecreated; 2687 2688 // place at beginning of any category 2689 $data->sortorder = 0; 2690 2691 if ($editoroptions) { 2692 // summary text is updated later, we need context to store the files first 2693 $data->summary = ''; 2694 $data->summary_format = FORMAT_HTML; 2695 } 2696 2697 if (!isset($data->visible)) { 2698 // data not from form, add missing visibility info 2699 $data->visible = $category->visible; 2700 } 2701 $data->visibleold = $data->visible; 2702 2703 $newcourseid = $DB->insert_record('course', $data); 2704 $context = context_course::instance($newcourseid, MUST_EXIST); 2705 2706 if ($editoroptions) { 2707 // Save the files used in the summary editor and store 2708 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0); 2709 $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid)); 2710 $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid)); 2711 } 2712 if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) { 2713 // Save the course overviewfiles 2714 $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0); 2715 } 2716 2717 // update course format options 2718 course_get_format($newcourseid)->update_course_format_options($data); 2719 2720 $course = course_get_format($newcourseid)->get_course(); 2721 2722 fix_course_sortorder(); 2723 // purge appropriate caches in case fix_course_sortorder() did not change anything 2724 cache_helper::purge_by_event('changesincourse'); 2725 2726 // new context created - better mark it as dirty 2727 $context->mark_dirty(); 2728 2729 // Trigger a course created event. 2730 $event = \core\event\course_created::create(array( 2731 'objectid' => $course->id, 2732 'context' => context_course::instance($course->id), 2733 'other' => array('shortname' => $course->shortname, 2734 'fullname' => $course->fullname) 2735 )); 2736 $event->trigger(); 2737 2738 // Setup the blocks 2739 blocks_add_default_course_blocks($course); 2740 2741 // Create a default section. 2742 course_create_sections_if_missing($course, 0); 2743 2744 // Save any custom role names. 2745 save_local_role_names($course->id, (array)$data); 2746 2747 // set up enrolments 2748 enrol_course_updated(true, $course, $data); 2749 2750 // Update course tags. 2751 if (isset($data->tags)) { 2752 core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags); 2753 } 2754 2755 return $course; 2756 } 2757 2758 /** 2759 * Update a course. 2760 * 2761 * Please note this functions does not verify any access control, 2762 * the calling code is responsible for all validation (usually it is the form definition). 2763 * 2764 * @param object $data - all the data needed for an entry in the 'course' table 2765 * @param array $editoroptions course description editor options 2766 * @return void 2767 */ 2768 function update_course($data, $editoroptions = NULL) { 2769 global $DB, $CFG; 2770 2771 $data->timemodified = time(); 2772 2773 $oldcourse = course_get_format($data->id)->get_course(); 2774 $context = context_course::instance($oldcourse->id); 2775 2776 if ($editoroptions) { 2777 $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0); 2778 } 2779 if ($overviewfilesoptions = course_overviewfiles_options($data->id)) { 2780 $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0); 2781 } 2782 2783 // Check we don't have a duplicate shortname. 2784 if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) { 2785 if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) { 2786 throw new moodle_exception('shortnametaken', '', '', $data->shortname); 2787 } 2788 } 2789 2790 // Check we don't have a duplicate idnumber. 2791 if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) { 2792 if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) { 2793 throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber); 2794 } 2795 } 2796 2797 if (!isset($data->category) or empty($data->category)) { 2798 // prevent nulls and 0 in category field 2799 unset($data->category); 2800 } 2801 $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category); 2802 2803 if (!isset($data->visible)) { 2804 // data not from form, add missing visibility info 2805 $data->visible = $oldcourse->visible; 2806 } 2807 2808 if ($data->visible != $oldcourse->visible) { 2809 // reset the visibleold flag when manually hiding/unhiding course 2810 $data->visibleold = $data->visible; 2811 $changesincoursecat = true; 2812 } else { 2813 if ($movecat) { 2814 $newcategory = $DB->get_record('course_categories', array('id'=>$data->category)); 2815 if (empty($newcategory->visible)) { 2816 // make sure when moving into hidden category the course is hidden automatically 2817 $data->visible = 0; 2818 } 2819 } 2820 } 2821 2822 // Update with the new data 2823 $DB->update_record('course', $data); 2824 // make sure the modinfo cache is reset 2825 rebuild_course_cache($data->id); 2826 2827 // update course format options with full course data 2828 course_get_format($data->id)->update_course_format_options($data, $oldcourse); 2829 2830 $course = $DB->get_record('course', array('id'=>$data->id)); 2831 2832 if ($movecat) { 2833 $newparent = context_coursecat::instance($course->category); 2834 $context->update_moved($newparent); 2835 } 2836 $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder)); 2837 if ($fixcoursesortorder) { 2838 fix_course_sortorder(); 2839 } 2840 2841 // purge appropriate caches in case fix_course_sortorder() did not change anything 2842 cache_helper::purge_by_event('changesincourse'); 2843 if ($changesincoursecat) { 2844 cache_helper::purge_by_event('changesincoursecat'); 2845 } 2846 2847 // Test for and remove blocks which aren't appropriate anymore 2848 blocks_remove_inappropriate($course); 2849 2850 // Save any custom role names. 2851 save_local_role_names($course->id, $data); 2852 2853 // update enrol settings 2854 enrol_course_updated(false, $course, $data); 2855 2856 // Update course tags. 2857 if (isset($data->tags)) { 2858 core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags); 2859 } 2860 2861 // Trigger a course updated event. 2862 $event = \core\event\course_updated::create(array( 2863 'objectid' => $course->id, 2864 'context' => context_course::instance($course->id), 2865 'other' => array('shortname' => $course->shortname, 2866 'fullname' => $course->fullname) 2867 )); 2868 2869 $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id)); 2870 $event->trigger(); 2871 2872 if ($oldcourse->format !== $course->format) { 2873 // Remove all options stored for the previous format 2874 // We assume that new course format migrated everything it needed watching trigger 2875 // 'course_updated' and in method format_XXX::update_course_format_options() 2876 $DB->delete_records('course_format_options', 2877 array('courseid' => $course->id, 'format' => $oldcourse->format)); 2878 } 2879 } 2880 2881 /** 2882 * Average number of participants 2883 * @return integer 2884 */ 2885 function average_number_of_participants() { 2886 global $DB, $SITE; 2887 2888 //count total of enrolments for visible course (except front page) 2889 $sql = 'SELECT COUNT(*) FROM ( 2890 SELECT DISTINCT ue.userid, e.courseid 2891 FROM {user_enrolments} ue, {enrol} e, {course} c 2892 WHERE ue.enrolid = e.id 2893 AND e.courseid <> :siteid 2894 AND c.id = e.courseid 2895 AND c.visible = 1) total'; 2896 $params = array('siteid' => $SITE->id); 2897 $enrolmenttotal = $DB->count_records_sql($sql, $params); 2898 2899 2900 //count total of visible courses (minus front page) 2901 $coursetotal = $DB->count_records('course', array('visible' => 1)); 2902 $coursetotal = $coursetotal - 1 ; 2903 2904 //average of enrolment 2905 if (empty($coursetotal)) { 2906 $participantaverage = 0; 2907 } else { 2908 $participantaverage = $enrolmenttotal / $coursetotal; 2909 } 2910 2911 return $participantaverage; 2912 } 2913 2914 /** 2915 * Average number of course modules 2916 * @return integer 2917 */ 2918 function average_number_of_courses_modules() { 2919 global $DB, $SITE; 2920 2921 //count total of visible course module (except front page) 2922 $sql = 'SELECT COUNT(*) FROM ( 2923 SELECT cm.course, cm.module 2924 FROM {course} c, {course_modules} cm 2925 WHERE c.id = cm.course 2926 AND c.id <> :siteid 2927 AND cm.visible = 1 2928 AND c.visible = 1) total'; 2929 $params = array('siteid' => $SITE->id); 2930 $moduletotal = $DB->count_records_sql($sql, $params); 2931 2932 2933 //count total of visible courses (minus front page) 2934 $coursetotal = $DB->count_records('course', array('visible' => 1)); 2935 $coursetotal = $coursetotal - 1 ; 2936 2937 //average of course module 2938 if (empty($coursetotal)) { 2939 $coursemoduleaverage = 0; 2940 } else { 2941 $coursemoduleaverage = $moduletotal / $coursetotal; 2942 } 2943 2944 return $coursemoduleaverage; 2945 } 2946 2947 /** 2948 * This class pertains to course requests and contains methods associated with 2949 * create, approving, and removing course requests. 2950 * 2951 * Please note we do not allow embedded images here because there is no context 2952 * to store them with proper access control. 2953 * 2954 * @copyright 2009 Sam Hemelryk 2955 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2956 * @since Moodle 2.0 2957 * 2958 * @property-read int $id 2959 * @property-read string $fullname 2960 * @property-read string $shortname 2961 * @property-read string $summary 2962 * @property-read int $summaryformat 2963 * @property-read int $summarytrust 2964 * @property-read string $reason 2965 * @property-read int $requester 2966 */ 2967 class course_request { 2968 2969 /** 2970 * This is the stdClass that stores the properties for the course request 2971 * and is externally accessed through the __get magic method 2972 * @var stdClass 2973 */ 2974 protected $properties; 2975 2976 /** 2977 * An array of options for the summary editor used by course request forms. 2978 * This is initially set by {@link summary_editor_options()} 2979 * @var array 2980 * @static 2981 */ 2982 protected static $summaryeditoroptions; 2983 2984 /** 2985 * Static function to prepare the summary editor for working with a course 2986 * request. 2987 * 2988 * @static 2989 * @param null|stdClass $data Optional, an object containing the default values 2990 * for the form, these may be modified when preparing the 2991 * editor so this should be called before creating the form 2992 * @return stdClass An object that can be used to set the default values for 2993 * an mforms form 2994 */ 2995 public static function prepare($data=null) { 2996 if ($data === null) { 2997 $data = new stdClass; 2998 } 2999 $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options()); 3000 return $data; 3001 } 3002 3003 /** 3004 * Static function to create a new course request when passed an array of properties 3005 * for it. 3006 * 3007 * This function also handles saving any files that may have been used in the editor 3008 * 3009 * @static 3010 * @param stdClass $data 3011 * @return course_request The newly created course request 3012 */ 3013 public static function create($data) { 3014 global $USER, $DB, $CFG; 3015 $data->requester = $USER->id; 3016 3017 // Setting the default category if none set. 3018 if (empty($data->category) || empty($CFG->requestcategoryselection)) { 3019 $data->category = $CFG->defaultrequestcategory; 3020 } 3021 3022 // Summary is a required field so copy the text over 3023 $data->summary = $data->summary_editor['text']; 3024 $data->summaryformat = $data->summary_editor['format']; 3025 3026 $data->id = $DB->insert_record('course_request', $data); 3027 3028 // Create a new course_request object and return it 3029 $request = new course_request($data); 3030 3031 // Notify the admin if required. 3032 if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) { 3033 3034 $a = new stdClass; 3035 $a->link = "$CFG->wwwroot/course/pending.php"; 3036 $a->user = fullname($USER); 3037 $subject = get_string('courserequest'); 3038 $message = get_string('courserequestnotifyemail', 'admin', $a); 3039 foreach ($users as $user) { 3040 $request->notify($user, $USER, 'courserequested', $subject, $message); 3041 } 3042 } 3043 3044 return $request; 3045 } 3046 3047 /** 3048 * Returns an array of options to use with a summary editor 3049 * 3050 * @uses course_request::$summaryeditoroptions 3051 * @return array An array of options to use with the editor 3052 */ 3053 public static function summary_editor_options() { 3054 global $CFG; 3055 if (self::$summaryeditoroptions === null) { 3056 self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0); 3057 } 3058 return self::$summaryeditoroptions; 3059 } 3060 3061 /** 3062 * Loads the properties for this course request object. Id is required and if 3063 * only id is provided then we load the rest of the properties from the database 3064 * 3065 * @param stdClass|int $properties Either an object containing properties 3066 * or the course_request id to load 3067 */ 3068 public function __construct($properties) { 3069 global $DB; 3070 if (empty($properties->id)) { 3071 if (empty($properties)) { 3072 throw new coding_exception('You must provide a course request id when creating a course_request object'); 3073 } 3074 $id = $properties; 3075 $properties = new stdClass; 3076 $properties->id = (int)$id; 3077 unset($id); 3078 } 3079 if (empty($properties->requester)) { 3080 if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) { 3081 print_error('unknowncourserequest'); 3082 } 3083 } else { 3084 $this->properties = $properties; 3085 } 3086 $this->properties->collision = null; 3087 } 3088 3089 /** 3090 * Returns the requested property 3091 * 3092 * @param string $key 3093 * @return mixed 3094 */ 3095 public function __get($key) { 3096 return $this->properties->$key; 3097 } 3098 3099 /** 3100 * Override this to ensure empty($request->blah) calls return a reliable answer... 3101 * 3102 * This is required because we define the __get method 3103 * 3104 * @param mixed $key 3105 * @return bool True is it not empty, false otherwise 3106 */ 3107 public function __isset($key) { 3108 return (!empty($this->properties->$key)); 3109 } 3110 3111 /** 3112 * Returns the user who requested this course 3113 * 3114 * Uses a static var to cache the results and cut down the number of db queries 3115 * 3116 * @staticvar array $requesters An array of cached users 3117 * @return stdClass The user who requested the course 3118 */ 3119 public function get_requester() { 3120 global $DB; 3121 static $requesters= array(); 3122 if (!array_key_exists($this->properties->requester, $requesters)) { 3123 $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester)); 3124 } 3125 return $requesters[$this->properties->requester]; 3126 } 3127 3128 /** 3129 * Checks that the shortname used by the course does not conflict with any other 3130 * courses that exist 3131 * 3132 * @param string|null $shortnamemark The string to append to the requests shortname 3133 * should a conflict be found 3134 * @return bool true is there is a conflict, false otherwise 3135 */ 3136 public function check_shortname_collision($shortnamemark = '[*]') { 3137 global $DB; 3138 3139 if ($this->properties->collision !== null) { 3140 return $this->properties->collision; 3141 } 3142 3143 if (empty($this->properties->shortname)) { 3144 debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER); 3145 $this->properties->collision = false; 3146 } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) { 3147 if (!empty($shortnamemark)) { 3148 $this->properties->shortname .= ' '.$shortnamemark; 3149 } 3150 $this->properties->collision = true; 3151 } else { 3152 $this->properties->collision = false; 3153 } 3154 return $this->properties->collision; 3155 } 3156 3157 /** 3158 * Returns the category where this course request should be created 3159 * 3160 * Note that we don't check here that user has a capability to view 3161 * hidden categories if he has capabilities 'moodle/site:approvecourse' and 3162 * 'moodle/course:changecategory' 3163 * 3164 * @return coursecat 3165 */ 3166 public function get_category() { 3167 global $CFG; 3168 require_once($CFG->libdir.'/coursecatlib.php'); 3169 // If the category is not set, if the current user does not have the rights to change the category, or if the 3170 // category does not exist, we set the default category to the course to be approved. 3171 // The system level is used because the capability moodle/site:approvecourse is based on a system level. 3172 if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) || 3173 (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) { 3174 $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true); 3175 } 3176 if (!$category) { 3177 $category = coursecat::get_default(); 3178 } 3179 return $category; 3180 } 3181 3182 /** 3183 * This function approves the request turning it into a course 3184 * 3185 * This function converts the course request into a course, at the same time 3186 * transferring any files used in the summary to the new course and then removing 3187 * the course request and the files associated with it. 3188 * 3189 * @return int The id of the course that was created from this request 3190 */ 3191 public function approve() { 3192 global $CFG, $DB, $USER; 3193 3194 $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST); 3195 3196 $courseconfig = get_config('moodlecourse'); 3197 3198 // Transfer appropriate settings 3199 $data = clone($this->properties); 3200 unset($data->id); 3201 unset($data->reason); 3202 unset($data->requester); 3203 3204 // Set category 3205 $category = $this->get_category(); 3206 $data->category = $category->id; 3207 // Set misc settings 3208 $data->requested = 1; 3209 3210 // Apply course default settings 3211 $data->format = $courseconfig->format; 3212 $data->newsitems = $courseconfig->newsitems; 3213 $data->showgrades = $courseconfig->showgrades; 3214 $data->showreports = $courseconfig->showreports; 3215 $data->maxbytes = $courseconfig->maxbytes; 3216 $data->groupmode = $courseconfig->groupmode; 3217 $data->groupmodeforce = $courseconfig->groupmodeforce; 3218 $data->visible = $courseconfig->visible; 3219 $data->visibleold = $data->visible; 3220 $data->lang = $courseconfig->lang; 3221 $data->enablecompletion = $courseconfig->enablecompletion; 3222 3223 $course = create_course($data); 3224 $context = context_course::instance($course->id, MUST_EXIST); 3225 3226 // add enrol instances 3227 if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) { 3228 if ($manual = enrol_get_plugin('manual')) { 3229 $manual->add_default_instance($course); 3230 } 3231 } 3232 3233 // enrol the requester as teacher if necessary 3234 if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) { 3235 enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid); 3236 } 3237 3238 $this->delete(); 3239 3240 $a = new stdClass(); 3241 $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); 3242 $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id; 3243 $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a)); 3244 3245 return $course->id; 3246 } 3247 3248 /** 3249 * Reject a course request 3250 * 3251 * This function rejects a course request, emailing the requesting user the 3252 * provided notice and then removing the request from the database 3253 * 3254 * @param string $notice The message to display to the user 3255 */ 3256 public function reject($notice) { 3257 global $USER, $DB; 3258 $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST); 3259 $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice)); 3260 $this->delete(); 3261 } 3262 3263 /** 3264 * Deletes the course request and any associated files 3265 */ 3266 public function delete() { 3267 global $DB; 3268 $DB->delete_records('course_request', array('id' => $this->properties->id)); 3269 } 3270 3271 /** 3272 * Send a message from one user to another using events_trigger 3273 * 3274 * @param object $touser 3275 * @param object $fromuser 3276 * @param string $name 3277 * @param string $subject 3278 * @param string $message 3279 */ 3280 protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) { 3281 $eventdata = new stdClass(); 3282 $eventdata->component = 'moodle'; 3283 $eventdata->name = $name; 3284 $eventdata->userfrom = $fromuser; 3285 $eventdata->userto = $touser; 3286 $eventdata->subject = $subject; 3287 $eventdata->fullmessage = $message; 3288 $eventdata->fullmessageformat = FORMAT_PLAIN; 3289 $eventdata->fullmessagehtml = ''; 3290 $eventdata->smallmessage = ''; 3291 $eventdata->notification = 1; 3292 message_send($eventdata); 3293 } 3294 } 3295 3296 /** 3297 * Return a list of page types 3298 * @param string $pagetype current page type 3299 * @param context $parentcontext Block's parent context 3300 * @param context $currentcontext Current context of block 3301 * @return array array of page types 3302 */ 3303 function course_page_type_list($pagetype, $parentcontext, $currentcontext) { 3304 if ($pagetype === 'course-index' || $pagetype === 'course-index-category') { 3305 // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page 3306 $pagetypes = array('*' => get_string('page-x', 'pagetype'), 3307 'course-index-*' => get_string('page-course-index-x', 'pagetype'), 3308 ); 3309 } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) { 3310 // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.) 3311 $pagetypes = array('*' => get_string('page-x', 'pagetype')); 3312 } else { 3313 // Otherwise consider it a page inside a course even if $currentcontext is null 3314 $pagetypes = array('*' => get_string('page-x', 'pagetype'), 3315 'course-*' => get_string('page-course-x', 'pagetype'), 3316 'course-view-*' => get_string('page-course-view-x', 'pagetype') 3317 ); 3318 } 3319 return $pagetypes; 3320 } 3321 3322 /** 3323 * Determine whether course ajax should be enabled for the specified course 3324 * 3325 * @param stdClass $course The course to test against 3326 * @return boolean Whether course ajax is enabled or note 3327 */ 3328 function course_ajax_enabled($course) { 3329 global $CFG, $PAGE, $SITE; 3330 3331 // The user must be editing for AJAX to be included 3332 if (!$PAGE->user_is_editing()) { 3333 return false; 3334 } 3335 3336 // Check that the theme suports 3337 if (!$PAGE->theme->enablecourseajax) { 3338 return false; 3339 } 3340 3341 // Check that the course format supports ajax functionality 3342 // The site 'format' doesn't have information on course format support 3343 if ($SITE->id !== $course->id) { 3344 $courseformatajaxsupport = course_format_ajax_support($course->format); 3345 if (!$courseformatajaxsupport->capable) { 3346 return false; 3347 } 3348 } 3349 3350 // All conditions have been met so course ajax should be enabled 3351 return true; 3352 } 3353 3354 /** 3355 * Include the relevant javascript and language strings for the resource 3356 * toolbox YUI module 3357 * 3358 * @param integer $id The ID of the course being applied to 3359 * @param array $usedmodules An array containing the names of the modules in use on the page 3360 * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site 3361 * @param stdClass $config An object containing configuration parameters for ajax modules including: 3362 * * resourceurl The URL to post changes to for resource changes 3363 * * sectionurl The URL to post changes to for section changes 3364 * * pageparams Additional parameters to pass through in the post 3365 * @return bool 3366 */ 3367 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) { 3368 global $CFG, $PAGE, $SITE; 3369 3370 // Ensure that ajax should be included 3371 if (!course_ajax_enabled($course)) { 3372 return false; 3373 } 3374 3375 if (!$config) { 3376 $config = new stdClass(); 3377 } 3378 3379 // The URL to use for resource changes 3380 if (!isset($config->resourceurl)) { 3381 $config->resourceurl = '/course/rest.php'; 3382 } 3383 3384 // The URL to use for section changes 3385 if (!isset($config->sectionurl)) { 3386 $config->sectionurl = '/course/rest.php'; 3387 } 3388 3389 // Any additional parameters which need to be included on page submission 3390 if (!isset($config->pageparams)) { 3391 $config->pageparams = array(); 3392 } 3393 3394 // Include toolboxes 3395 $PAGE->requires->yui_module('moodle-course-toolboxes', 3396 'M.course.init_resource_toolbox', 3397 array(array( 3398 'courseid' => $course->id, 3399 'ajaxurl' => $config->resourceurl, 3400 'config' => $config, 3401 )) 3402 ); 3403 $PAGE->requires->yui_module('moodle-course-toolboxes', 3404 'M.course.init_section_toolbox', 3405 array(array( 3406 'courseid' => $course->id, 3407 'format' => $course->format, 3408 'ajaxurl' => $config->sectionurl, 3409 'config' => $config, 3410 )) 3411 ); 3412 3413 // Include course dragdrop 3414 if (course_format_uses_sections($course->format)) { 3415 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop', 3416 array(array( 3417 'courseid' => $course->id, 3418 'ajaxurl' => $config->sectionurl, 3419 'config' => $config, 3420 )), null, true); 3421 3422 $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop', 3423 array(array( 3424 'courseid' => $course->id, 3425 'ajaxurl' => $config->resourceurl, 3426 'config' => $config, 3427 )), null, true); 3428 } 3429 3430 // Require various strings for the command toolbox 3431 $PAGE->requires->strings_for_js(array( 3432 'moveleft', 3433 'deletechecktype', 3434 'deletechecktypename', 3435 'edittitle', 3436 'edittitleinstructions', 3437 'show', 3438 'hide', 3439 'highlight', 3440 'highlightoff', 3441 'groupsnone', 3442 'groupsvisible', 3443 'groupsseparate', 3444 'clicktochangeinbrackets', 3445 'markthistopic', 3446 'markedthistopic', 3447 'movesection', 3448 'movecoursemodule', 3449 'movecoursesection', 3450 'movecontent', 3451 'tocontent', 3452 'emptydragdropregion', 3453 'afterresource', 3454 'aftersection', 3455 'totopofsection', 3456 ), 'moodle'); 3457 3458 // Include section-specific strings for formats which support sections. 3459 if (course_format_uses_sections($course->format)) { 3460 $PAGE->requires->strings_for_js(array( 3461 'showfromothers', 3462 'hidefromothers', 3463 ), 'format_' . $course->format); 3464 } 3465 3466 // For confirming resource deletion we need the name of the module in question 3467 foreach ($usedmodules as $module => $modname) { 3468 $PAGE->requires->string_for_js('pluginname', $module); 3469 } 3470 3471 // Load drag and drop upload AJAX. 3472 require_once($CFG->dirroot.'/course/dnduploadlib.php'); 3473 dndupload_add_to_course($course, $enabledmodules); 3474 3475 return true; 3476 } 3477 3478 /** 3479 * Returns the sorted list of available course formats, filtered by enabled if necessary 3480 * 3481 * @param bool $enabledonly return only formats that are enabled 3482 * @return array array of sorted format names 3483 */ 3484 function get_sorted_course_formats($enabledonly = false) { 3485 global $CFG; 3486 $formats = core_component::get_plugin_list('format'); 3487 3488 if (!empty($CFG->format_plugins_sortorder)) { 3489 $order = explode(',', $CFG->format_plugins_sortorder); 3490 $order = array_merge(array_intersect($order, array_keys($formats)), 3491 array_diff(array_keys($formats), $order)); 3492 } else { 3493 $order = array_keys($formats); 3494 } 3495 if (!$enabledonly) { 3496 return $order; 3497 } 3498 $sortedformats = array(); 3499 foreach ($order as $formatname) { 3500 if (!get_config('format_'.$formatname, 'disabled')) { 3501 $sortedformats[] = $formatname; 3502 } 3503 } 3504 return $sortedformats; 3505 } 3506 3507 /** 3508 * The URL to use for the specified course (with section) 3509 * 3510 * @param int|stdClass $courseorid The course to get the section name for (either object or just course id) 3511 * @param int|stdClass $section Section object from database or just field course_sections.section 3512 * if omitted the course view page is returned 3513 * @param array $options options for view URL. At the moment core uses: 3514 * 'navigation' (bool) if true and section has no separate page, the function returns null 3515 * 'sr' (int) used by multipage formats to specify to which section to return 3516 * @return moodle_url The url of course 3517 */ 3518 function course_get_url($courseorid, $section = null, $options = array()) { 3519 return course_get_format($courseorid)->get_view_url($section, $options); 3520 } 3521 3522 /** 3523 * Create a module. 3524 * 3525 * It includes: 3526 * - capability checks and other checks 3527 * - create the module from the module info 3528 * 3529 * @param object $module 3530 * @return object the created module info 3531 * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course 3532 */ 3533 function create_module($moduleinfo) { 3534 global $DB, $CFG; 3535 3536 require_once($CFG->dirroot . '/course/modlib.php'); 3537 3538 // Check manadatory attributs. 3539 $mandatoryfields = array('modulename', 'course', 'section', 'visible'); 3540 if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) { 3541 $mandatoryfields[] = 'introeditor'; 3542 } 3543 foreach($mandatoryfields as $mandatoryfield) { 3544 if (!isset($moduleinfo->{$mandatoryfield})) { 3545 throw new moodle_exception('createmodulemissingattribut', '', '', $mandatoryfield); 3546 } 3547 } 3548 3549 // Some additional checks (capability / existing instances). 3550 $course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST); 3551 list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section); 3552 3553 // Add the module. 3554 $moduleinfo->module = $module->id; 3555 $moduleinfo = add_moduleinfo($moduleinfo, $course, null); 3556 3557 return $moduleinfo; 3558 } 3559 3560 /** 3561 * Update a module. 3562 * 3563 * It includes: 3564 * - capability and other checks 3565 * - update the module 3566 * 3567 * @param object $module 3568 * @return object the updated module info 3569 * @throws moodle_exception if current user is not allowed to update the module 3570 */ 3571 function update_module($moduleinfo) { 3572 global $DB, $CFG; 3573 3574 require_once($CFG->dirroot . '/course/modlib.php'); 3575 3576 // Check the course module exists. 3577 $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST); 3578 3579 // Check the course exists. 3580 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); 3581 3582 // Some checks (capaibility / existing instances). 3583 list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm); 3584 3585 // Retrieve few information needed by update_moduleinfo. 3586 $moduleinfo->modulename = $cm->modname; 3587 if (!isset($moduleinfo->scale)) { 3588 $moduleinfo->scale = 0; 3589 } 3590 $moduleinfo->type = 'mod'; 3591 3592 // Update the module. 3593 list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null); 3594 3595 return $moduleinfo; 3596 } 3597 3598 /** 3599 * Duplicate a module on the course for ajax. 3600 * 3601 * @see mod_duplicate_module() 3602 * @param object $course The course 3603 * @param object $cm The course module to duplicate 3604 * @param int $sr The section to link back to (used for creating the links) 3605 * @throws moodle_exception if the plugin doesn't support duplication 3606 * @return Object containing: 3607 * - fullcontent: The HTML markup for the created CM 3608 * - cmid: The CMID of the newly created CM 3609 * - redirect: Whether to trigger a redirect following this change 3610 */ 3611 function mod_duplicate_activity($course, $cm, $sr = null) { 3612 global $PAGE; 3613 3614 $newcm = duplicate_module($course, $cm); 3615 3616 $resp = new stdClass(); 3617 if ($newcm) { 3618 $courserenderer = $PAGE->get_renderer('core', 'course'); 3619 $completioninfo = new completion_info($course); 3620 $modulehtml = $courserenderer->course_section_cm($course, $completioninfo, 3621 $newcm, null, array()); 3622 3623 $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr); 3624 $resp->cmid = $newcm->id; 3625 } else { 3626 // Trigger a redirect. 3627 $resp->redirect = true; 3628 } 3629 return $resp; 3630 } 3631 3632 /** 3633 * Api to duplicate a module. 3634 * 3635 * @param object $course course object. 3636 * @param object $cm course module object to be duplicated. 3637 * @since Moodle 2.8 3638 * 3639 * @throws Exception 3640 * @throws coding_exception 3641 * @throws moodle_exception 3642 * @throws restore_controller_exception 3643 * 3644 * @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm. 3645 */ 3646 function duplicate_module($course, $cm) { 3647 global $CFG, $DB, $USER; 3648 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 3649 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 3650 require_once($CFG->libdir . '/filelib.php'); 3651 3652 $a = new stdClass(); 3653 $a->modtype = get_string('modulename', $cm->modname); 3654 $a->modname = format_string($cm->name); 3655 3656 if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) { 3657 throw new moodle_exception('duplicatenosupport', 'error', '', $a); 3658 } 3659 3660 // Backup the activity. 3661 3662 $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE, 3663 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); 3664 3665 $backupid = $bc->get_backupid(); 3666 $backupbasepath = $bc->get_plan()->get_basepath(); 3667 3668 $bc->execute_plan(); 3669 3670 $bc->destroy(); 3671 3672 // Restore the backup immediately. 3673 3674 $rc = new restore_controller($backupid, $course->id, 3675 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); 3676 3677 $cmcontext = context_module::instance($cm->id); 3678 if (!$rc->execute_precheck()) { 3679 $precheckresults = $rc->get_precheck_results(); 3680 if (is_array($precheckresults) && !empty($precheckresults['errors'])) { 3681 if (empty($CFG->keeptempdirectoriesonbackup)) { 3682 fulldelete($backupbasepath); 3683 } 3684 } 3685 } 3686 3687 $rc->execute_plan(); 3688 3689 // Now a bit hacky part follows - we try to get the cmid of the newly 3690 // restored copy of the module. 3691 $newcmid = null; 3692 $tasks = $rc->get_plan()->get_tasks(); 3693 foreach ($tasks as $task) { 3694 if (is_subclass_of($task, 'restore_activity_task')) { 3695 if ($task->get_old_contextid() == $cmcontext->id) { 3696 $newcmid = $task->get_moduleid(); 3697 break; 3698 } 3699 } 3700 } 3701 3702 // If we know the cmid of the new course module, let us move it 3703 // right below the original one. otherwise it will stay at the 3704 // end of the section. 3705 if ($newcmid) { 3706 $info = get_fast_modinfo($course); 3707 $newcm = $info->get_cm($newcmid); 3708 $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course)); 3709 moveto_module($newcm, $section, $cm); 3710 moveto_module($cm, $section, $newcm); 3711 3712 // Trigger course module created event. We can trigger the event only if we know the newcmid. 3713 $event = \core\event\course_module_created::create_from_cm($newcm); 3714 $event->trigger(); 3715 } 3716 rebuild_course_cache($cm->course); 3717 3718 $rc->destroy(); 3719 3720 if (empty($CFG->keeptempdirectoriesonbackup)) { 3721 fulldelete($backupbasepath); 3722 } 3723 3724 return isset($newcm) ? $newcm : null; 3725 } 3726 3727 /** 3728 * Compare two objects to find out their correct order based on timestamp (to be used by usort). 3729 * Sorts by descending order of time. 3730 * 3731 * @param stdClass $a First object 3732 * @param stdClass $b Second object 3733 * @return int 0,1,-1 representing the order 3734 */ 3735 function compare_activities_by_time_desc($a, $b) { 3736 // Make sure the activities actually have a timestamp property. 3737 if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) { 3738 return 0; 3739 } 3740 // We treat instances without timestamp as if they have a timestamp of 0. 3741 if ((!property_exists($a, 'timestamp')) && (property_exists($b,'timestamp'))) { 3742 return 1; 3743 } 3744 if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) { 3745 return -1; 3746 } 3747 if ($a->timestamp == $b->timestamp) { 3748 return 0; 3749 } 3750 return ($a->timestamp > $b->timestamp) ? -1 : 1; 3751 } 3752 3753 /** 3754 * Compare two objects to find out their correct order based on timestamp (to be used by usort). 3755 * Sorts by ascending order of time. 3756 * 3757 * @param stdClass $a First object 3758 * @param stdClass $b Second object 3759 * @return int 0,1,-1 representing the order 3760 */ 3761 function compare_activities_by_time_asc($a, $b) { 3762 // Make sure the activities actually have a timestamp property. 3763 if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) { 3764 return 0; 3765 } 3766 // We treat instances without timestamp as if they have a timestamp of 0. 3767 if ((!property_exists($a, 'timestamp')) && (property_exists($b, 'timestamp'))) { 3768 return -1; 3769 } 3770 if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) { 3771 return 1; 3772 } 3773 if ($a->timestamp == $b->timestamp) { 3774 return 0; 3775 } 3776 return ($a->timestamp < $b->timestamp) ? -1 : 1; 3777 } 3778 3779 /** 3780 * Changes the visibility of a course. 3781 * 3782 * @param int $courseid The course to change. 3783 * @param bool $show True to make it visible, false otherwise. 3784 * @return bool 3785 */ 3786 function course_change_visibility($courseid, $show = true) { 3787 $course = new stdClass; 3788 $course->id = $courseid; 3789 $course->visible = ($show) ? '1' : '0'; 3790 $course->visibleold = $course->visible; 3791 update_course($course); 3792 return true; 3793 } 3794 3795 /** 3796 * Changes the course sortorder by one, moving it up or down one in respect to sort order. 3797 * 3798 * @param stdClass|course_in_list $course 3799 * @param bool $up If set to true the course will be moved up one. Otherwise down one. 3800 * @return bool 3801 */ 3802 function course_change_sortorder_by_one($course, $up) { 3803 global $DB; 3804 $params = array($course->sortorder, $course->category); 3805 if ($up) { 3806 $select = 'sortorder < ? AND category = ?'; 3807 $sort = 'sortorder DESC'; 3808 } else { 3809 $select = 'sortorder > ? AND category = ?'; 3810 $sort = 'sortorder ASC'; 3811 } 3812 fix_course_sortorder(); 3813 $swapcourse = $DB->get_records_select('course', $select, $params, $sort, '*', 0, 1); 3814 if ($swapcourse) { 3815 $swapcourse = reset($swapcourse); 3816 $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $course->id)); 3817 $DB->set_field('course', 'sortorder', $course->sortorder, array('id' => $swapcourse->id)); 3818 // Finally reorder courses. 3819 fix_course_sortorder(); 3820 cache_helper::purge_by_event('changesincourse'); 3821 return true; 3822 } 3823 return false; 3824 } 3825 3826 /** 3827 * Changes the sort order of courses in a category so that the first course appears after the second. 3828 * 3829 * @param int|stdClass $courseorid The course to focus on. 3830 * @param int $moveaftercourseid The course to shifter after or 0 if you want it to be the first course in the category. 3831 * @return bool 3832 */ 3833 function course_change_sortorder_after_course($courseorid, $moveaftercourseid) { 3834 global $DB; 3835 3836 if (!is_object($courseorid)) { 3837 $course = get_course($courseorid); 3838 } else { 3839 $course = $courseorid; 3840 } 3841 3842 if ((int)$moveaftercourseid === 0) { 3843 // We've moving the course to the start of the queue. 3844 $sql = 'SELECT sortorder 3845 FROM {course} 3846 WHERE category = :categoryid 3847 ORDER BY sortorder'; 3848 $params = array( 3849 'categoryid' => $course->category 3850 ); 3851 $sortorder = $DB->get_field_sql($sql, $params, IGNORE_MULTIPLE); 3852 3853 $sql = 'UPDATE {course} 3854 SET sortorder = sortorder + 1 3855 WHERE category = :categoryid 3856 AND id <> :id'; 3857 $params = array( 3858 'categoryid' => $course->category, 3859 'id' => $course->id, 3860 ); 3861 $DB->execute($sql, $params); 3862 $DB->set_field('course', 'sortorder', $sortorder, array('id' => $course->id)); 3863 } else if ($course->id === $moveaftercourseid) { 3864 // They're the same - moronic. 3865 debugging("Invalid move after course given.", DEBUG_DEVELOPER); 3866 return false; 3867 } else { 3868 // Moving this course after the given course. It could be before it could be after. 3869 $moveaftercourse = get_course($moveaftercourseid); 3870 if ($course->category !== $moveaftercourse->category) { 3871 debugging("Cannot re-order courses. The given courses do not belong to the same category.", DEBUG_DEVELOPER); 3872 return false; 3873 } 3874 // Increment all courses in the same category that are ordered after the moveafter course. 3875 // This makes a space for the course we're moving. 3876 $sql = 'UPDATE {course} 3877 SET sortorder = sortorder + 1 3878 WHERE category = :categoryid 3879 AND sortorder > :sortorder'; 3880 $params = array( 3881 'categoryid' => $moveaftercourse->category, 3882 'sortorder' => $moveaftercourse->sortorder 3883 ); 3884 $DB->execute($sql, $params); 3885 $DB->set_field('course', 'sortorder', $moveaftercourse->sortorder + 1, array('id' => $course->id)); 3886 } 3887 fix_course_sortorder(); 3888 cache_helper::purge_by_event('changesincourse'); 3889 return true; 3890 } 3891 3892 /** 3893 * Trigger course viewed event. This API function is used when course view actions happens, 3894 * usually in course/view.php but also in external functions. 3895 * 3896 * @param stdClass $context course context object 3897 * @param int $sectionnumber section number 3898 * @since Moodle 2.9 3899 */ 3900 function course_view($context, $sectionnumber = 0) { 3901 3902 $eventdata = array('context' => $context); 3903 3904 if (!empty($sectionnumber)) { 3905 $eventdata['other']['coursesectionnumber'] = $sectionnumber; 3906 } 3907 3908 $event = \core\event\course_viewed::create($eventdata); 3909 $event->trigger(); 3910 } 3911 3912 /** 3913 * Returns courses tagged with a specified tag. 3914 * 3915 * @param core_tag_tag $tag 3916 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 3917 * are displayed on the page and the per-page limit may be bigger 3918 * @param int $fromctx context id where the link was displayed, may be used by callbacks 3919 * to display items in the same context first 3920 * @param int $ctx context id where to search for records 3921 * @param bool $rec search in subcontexts as well 3922 * @param int $page 0-based number of page being displayed 3923 * @return \core_tag\output\tagindex 3924 */ 3925 function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) { 3926 global $CFG, $PAGE; 3927 require_once($CFG->libdir . '/coursecatlib.php'); 3928 3929 $perpage = $exclusivemode ? $CFG->coursesperpage : 5; 3930 $displayoptions = array( 3931 'limit' => $perpage, 3932 'offset' => $page * $perpage, 3933 'viewmoreurl' => null, 3934 ); 3935 3936 $courserenderer = $PAGE->get_renderer('core', 'course'); 3937 $totalcount = coursecat::search_courses_count(array('tagid' => $tag->id, 'ctx' => $ctx, 'rec' => $rec)); 3938 $content = $courserenderer->tagged_courses($tag->id, $exclusivemode, $ctx, $rec, $displayoptions); 3939 $totalpages = ceil($totalcount / $perpage); 3940 3941 return new core_tag\output\tagindex($tag, 'core', 'course', $content, 3942 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); 3943 } 3944 3945 /** 3946 * Implements callback inplace_editable() allowing to edit values in-place 3947 * 3948 * @param string $itemtype 3949 * @param int $itemid 3950 * @param mixed $newvalue 3951 * @return \core\output\inplace_editable 3952 */ 3953 function core_course_inplace_editable($itemtype, $itemid, $newvalue) { 3954 if ($itemtype === 'activityname') { 3955 return \core_course\output\course_module_name::update($itemid, $newvalue); 3956 } 3957 } 3958 3959 /** 3960 * Returns course modules tagged with a specified tag ready for output on tag/index.php page 3961 * 3962 * This is a callback used by the tag area core/course_modules to search for course modules 3963 * tagged with a specific tag. 3964 * 3965 * @param core_tag_tag $tag 3966 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 3967 * are displayed on the page and the per-page limit may be bigger 3968 * @param int $fromcontextid context id where the link was displayed, may be used by callbacks 3969 * to display items in the same context first 3970 * @param int $contextid context id where to search for records 3971 * @param bool $recursivecontext search in subcontexts as well 3972 * @param int $page 0-based number of page being displayed 3973 * @return \core_tag\output\tagindex 3974 */ 3975 function course_get_tagged_course_modules($tag, $exclusivemode = false, $fromcontextid = 0, $contextid = 0, 3976 $recursivecontext = 1, $page = 0) { 3977 global $OUTPUT; 3978 $perpage = $exclusivemode ? 20 : 5; 3979 3980 // Build select query. 3981 $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); 3982 $query = "SELECT cm.id AS cmid, c.id AS courseid, $ctxselect 3983 FROM {course_modules} cm 3984 JOIN {tag_instance} tt ON cm.id = tt.itemid 3985 JOIN {course} c ON cm.course = c.id 3986 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel 3987 WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component 3988 AND c.id %COURSEFILTER% AND cm.id %ITEMFILTER%"; 3989 3990 $params = array('itemtype' => 'course_modules', 'tagid' => $tag->id, 'component' => 'core', 3991 'coursemodulecontextlevel' => CONTEXT_MODULE); 3992 if ($contextid) { 3993 $context = context::instance_by_id($contextid); 3994 $query .= $recursivecontext ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid'; 3995 $params['contextid'] = $context->id; 3996 $params['path'] = $context->path.'/%'; 3997 } 3998 3999 $query .= ' ORDER BY'; 4000 if ($fromcontextid) { 4001 // In order-clause specify that modules from inside "fromctx" context should be returned first. 4002 $fromcontext = context::instance_by_id($fromcontextid); 4003 $query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'; 4004 $params['fromcontextid'] = $fromcontext->id; 4005 $params['frompath'] = $fromcontext->path.'/%'; 4006 } 4007 $query .= ' c.sortorder, cm.id'; 4008 $totalpages = $page + 1; 4009 4010 // Use core_tag_index_builder to build and filter the list of items. 4011 // Request one item more than we need so we know if next page exists. 4012 $builder = new core_tag_index_builder('core', 'course_modules', $query, $params, $page * $perpage, $perpage + 1); 4013 while ($item = $builder->has_item_that_needs_access_check()) { 4014 context_helper::preload_from_record($item); 4015 $courseid = $item->courseid; 4016 if (!$builder->can_access_course($courseid)) { 4017 $builder->set_accessible($item, false); 4018 continue; 4019 } 4020 $modinfo = get_fast_modinfo($builder->get_course($courseid)); 4021 // Set accessibility of this item and all other items in the same course. 4022 $builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder) { 4023 if ($taggeditem->courseid == $courseid) { 4024 $cm = $modinfo->get_cm($taggeditem->cmid); 4025 $builder->set_accessible($taggeditem, $cm->uservisible); 4026 } 4027 }); 4028 } 4029 4030 $items = $builder->get_items(); 4031 if (count($items) > $perpage) { 4032 $totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists. 4033 array_pop($items); 4034 } 4035 4036 // Build the display contents. 4037 if ($items) { 4038 $tagfeed = new core_tag\output\tagfeed(); 4039 foreach ($items as $item) { 4040 context_helper::preload_from_record($item); 4041 $course = $builder->get_course($item->courseid); 4042 $modinfo = get_fast_modinfo($course); 4043 $cm = $modinfo->get_cm($item->cmid); 4044 $courseurl = course_get_url($item->courseid, $cm->sectionnum); 4045 $cmname = $cm->get_formatted_name(); 4046 if (!$exclusivemode) { 4047 $cmname = shorten_text($cmname, 100); 4048 } 4049 $cmname = html_writer::link($cm->url?:$courseurl, $cmname); 4050 $coursename = format_string($course->fullname, true, 4051 array('context' => context_course::instance($item->courseid))); 4052 $coursename = html_writer::link($courseurl, $coursename); 4053 $icon = html_writer::empty_tag('img', array('src' => $cm->get_icon_url())); 4054 $tagfeed->add($icon, $cmname, $coursename); 4055 } 4056 4057 $content = $OUTPUT->render_from_template('core_tag/tagfeed', 4058 $tagfeed->export_for_template($OUTPUT)); 4059 4060 return new core_tag\output\tagindex($tag, 'core', 'course_modules', $content, 4061 $exclusivemode, $fromcontextid, $contextid, $recursivecontext, $page, $totalpages); 4062 } 4063 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |