Search moodle.org's
Developer Documentation


/course/ -> lib.php (source)
   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('&amp;', '&', $query); // both & and &amp; are stored in db :-|
 128          $parts = explode('&', $query);
 129          $eq = urlencode('=');
 130          foreach ($parts as $key=>$part) {
 131              $part = urlencode(urldecode($part));
 132              $part = str_replace($eq, '=', $part);
 133              $parts[$key] = $part;
 134          }
 135          $query = '?'.implode('&amp;', $parts);
 136      }
 137  
 138      return $script.$query;
 139  }
 140  
 141  
 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      if(!empty($CFG->enableavailability)) {
1002          require_once($CFG->libdir.'/conditionlib.php');
1003      }
1004  
1005      $course = $DB->get_record('course', array('id'=>$courseid));
1006  
1007      if (empty($course)) {
1008          throw new moodle_exception('courseidnotfound');
1009      }
1010  
1011      $mod = array();
1012  
1013      $rawmods = get_course_mods($courseid);
1014      if (empty($rawmods)) {
1015          return $mod; // always return array
1016      }
1017  
1018      if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
1019          // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
1020          if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
1021              debugging(join('<br>', $errormessages));
1022              $rawmods = get_course_mods($courseid);
1023              $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
1024          }
1025          // Build array of activities.
1026         foreach ($sections as $section) {
1027             if (!empty($section->sequence)) {
1028                 $sequence = explode(",", $section->sequence);
1029                 foreach ($sequence as $seq) {
1030                     if (empty($rawmods[$seq])) {
1031                         continue;
1032                     }
1033                     $mod[$seq] = new stdClass();
1034                     $mod[$seq]->id               = $rawmods[$seq]->instance;
1035                     $mod[$seq]->cm               = $rawmods[$seq]->id;
1036                     $mod[$seq]->mod              = $rawmods[$seq]->modname;
1037  
1038                      // Oh dear. Inconsistent names left here for backward compatibility.
1039                     $mod[$seq]->section          = $section->section;
1040                     $mod[$seq]->sectionid        = $rawmods[$seq]->section;
1041  
1042                     $mod[$seq]->module           = $rawmods[$seq]->module;
1043                     $mod[$seq]->added            = $rawmods[$seq]->added;
1044                     $mod[$seq]->score            = $rawmods[$seq]->score;
1045                     $mod[$seq]->idnumber         = $rawmods[$seq]->idnumber;
1046                     $mod[$seq]->visible          = $rawmods[$seq]->visible;
1047                     $mod[$seq]->visibleold       = $rawmods[$seq]->visibleold;
1048                     $mod[$seq]->groupmode        = $rawmods[$seq]->groupmode;
1049                     $mod[$seq]->groupingid       = $rawmods[$seq]->groupingid;
1050                     $mod[$seq]->indent           = $rawmods[$seq]->indent;
1051                     $mod[$seq]->completion       = $rawmods[$seq]->completion;
1052                     $mod[$seq]->extra            = "";
1053                     $mod[$seq]->completiongradeitemnumber =
1054                             $rawmods[$seq]->completiongradeitemnumber;
1055                     $mod[$seq]->completionview   = $rawmods[$seq]->completionview;
1056                     $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
1057                     $mod[$seq]->showdescription  = $rawmods[$seq]->showdescription;
1058                     $mod[$seq]->availability = $rawmods[$seq]->availability;
1059  
1060                     $modname = $mod[$seq]->mod;
1061                     $functionname = $modname."_get_coursemodule_info";
1062  
1063                     if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
1064                         continue;
1065                     }
1066  
1067                     include_once("$CFG->dirroot/mod/$modname/lib.php");
1068  
1069                     if ($hasfunction = function_exists($functionname)) {
1070                         if ($info = $functionname($rawmods[$seq])) {
1071                             if (!empty($info->icon)) {
1072                                 $mod[$seq]->icon = $info->icon;
1073                             }
1074                             if (!empty($info->iconcomponent)) {
1075                                 $mod[$seq]->iconcomponent = $info->iconcomponent;
1076                             }
1077                             if (!empty($info->name)) {
1078                                 $mod[$seq]->name = $info->name;
1079                             }
1080                             if ($info instanceof cached_cm_info) {
1081                                 // When using cached_cm_info you can include three new fields
1082                                 // that aren't available for legacy code
1083                                 if (!empty($info->content)) {
1084                                     $mod[$seq]->content = $info->content;
1085                                 }
1086                                 if (!empty($info->extraclasses)) {
1087                                     $mod[$seq]->extraclasses = $info->extraclasses;
1088                                 }
1089                                 if (!empty($info->iconurl)) {
1090                                     // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
1091                                     $url = new moodle_url($info->iconurl);
1092                                     $mod[$seq]->iconurl = $url->out(false);
1093                                 }
1094                                 if (!empty($info->onclick)) {
1095                                     $mod[$seq]->onclick = $info->onclick;
1096                                 }
1097                                 if (!empty($info->customdata)) {
1098                                     $mod[$seq]->customdata = $info->customdata;
1099                                 }
1100                             } else {
1101                                 // When using a stdclass, the (horrible) deprecated ->extra field
1102                                 // is available for BC
1103                                 if (!empty($info->extra)) {
1104                                     $mod[$seq]->extra = $info->extra;
1105                                 }
1106                             }
1107                         }
1108                     }
1109                     // When there is no modname_get_coursemodule_info function,
1110                     // but showdescriptions is enabled, then we use the 'intro'
1111                     // and 'introformat' fields in the module table
1112                     if (!$hasfunction && $rawmods[$seq]->showdescription) {
1113                         if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
1114                                 array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
1115                             // Set content from intro and introformat. Filters are disabled
1116                             // because we  filter it with format_text at display time
1117                             $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
1118                                     $modvalues, $rawmods[$seq]->id, false);
1119  
1120                             // To save making another query just below, put name in here
1121                             $mod[$seq]->name = $modvalues->name;
1122                         }
1123                     }
1124                     if (!isset($mod[$seq]->name)) {
1125                         $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
1126                     }
1127  
1128                      // Minimise the database size by unsetting default options when they are
1129                      // 'empty'. This list corresponds to code in the cm_info constructor.
1130                      foreach (array('idnumber', 'groupmode', 'groupingid',
1131                              'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
1132                              'icon', 'iconcomponent', 'customdata', 'availability', 'completionview',
1133                              'completionexpected', 'score', 'showdescription') as $property) {
1134                         if (property_exists($mod[$seq], $property) &&
1135                                 empty($mod[$seq]->{$property})) {
1136                             unset($mod[$seq]->{$property});
1137                         }
1138                     }
1139                     // Special case: this value is usually set to null, but may be 0
1140                     if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
1141                             is_null($mod[$seq]->completiongradeitemnumber)) {
1142                         unset($mod[$seq]->completiongradeitemnumber);
1143                     }
1144                 }
1145              }
1146          }
1147      }
1148      return $mod;
1149  }
1150  
1151  /**
1152   * Returns the localised human-readable names of all used modules
1153   *
1154   * @param bool $plural if true returns the plural forms of the names
1155   * @return array where key is the module name (component name without 'mod_') and
1156   *     the value is the human-readable string. Array sorted alphabetically by value
1157   */
1158  function get_module_types_names($plural = false) {
1159      static $modnames = null;
1160      global $DB, $CFG;
1161      if ($modnames === null) {
1162          $modnames = array(0 => array(), 1 => array());
1163          if ($allmods = $DB->get_records("modules")) {
1164              foreach ($allmods as $mod) {
1165                  if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) {
1166                      $modnames[0][$mod->name] = get_string("modulename", "$mod->name");
1167                      $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name");
1168                  }
1169              }
1170              core_collator::asort($modnames[0]);
1171              core_collator::asort($modnames[1]);
1172          }
1173      }
1174      return $modnames[(int)$plural];
1175  }
1176  
1177  /**
1178   * Set highlighted section. Only one section can be highlighted at the time.
1179   *
1180   * @param int $courseid course id
1181   * @param int $marker highlight section with this number, 0 means remove higlightin
1182   * @return void
1183   */
1184  function course_set_marker($courseid, $marker) {
1185      global $DB;
1186      $DB->set_field("course", "marker", $marker, array('id' => $courseid));
1187      format_base::reset_course_cache($courseid);
1188  }
1189  
1190  /**
1191   * For a given course section, marks it visible or hidden,
1192   * and does the same for every activity in that section
1193   *
1194   * @param int $courseid course id
1195   * @param int $sectionnumber The section number to adjust
1196   * @param int $visibility The new visibility
1197   * @return array A list of resources which were hidden in the section
1198   */
1199  function set_section_visible($courseid, $sectionnumber, $visibility) {
1200      global $DB;
1201  
1202      $resourcestotoggle = array();
1203      if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
1204          $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
1205  
1206          $event = \core\event\course_section_updated::create(array(
1207              'context' => context_course::instance($courseid),
1208              'objectid' => $section->id,
1209              'other' => array(
1210                  'sectionnum' => $sectionnumber
1211              )
1212          ));
1213          $event->add_record_snapshot('course_sections', $section);
1214          $event->trigger();
1215  
1216          if (!empty($section->sequence)) {
1217              $modules = explode(",", $section->sequence);
1218              foreach ($modules as $moduleid) {
1219                  if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
1220                      if ($visibility) {
1221                          // As we unhide the section, we use the previously saved visibility stored in visibleold.
1222                          set_coursemodule_visible($moduleid, $cm->visibleold);
1223                      } else {
1224                          // We hide the section, so we hide the module but we store the original state in visibleold.
1225                          set_coursemodule_visible($moduleid, 0);
1226                          $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
1227                      }
1228                      \core\event\course_module_updated::create_from_cm($cm)->trigger();
1229                  }
1230              }
1231          }
1232          rebuild_course_cache($courseid, true);
1233  
1234          // Determine which modules are visible for AJAX update
1235          if (!empty($modules)) {
1236              list($insql, $params) = $DB->get_in_or_equal($modules);
1237              $select = 'id ' . $insql . ' AND visible = ?';
1238              array_push($params, $visibility);
1239              if (!$visibility) {
1240                  $select .= ' AND visibleold = 1';
1241              }
1242              $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
1243          }
1244      }
1245      return $resourcestotoggle;
1246  }
1247  
1248  /**
1249   * Retrieve all metadata for the requested modules
1250   *
1251   * @param object $course The Course
1252   * @param array $modnames An array containing the list of modules and their
1253   * names
1254   * @param int $sectionreturn The section to return to
1255   * @return array A list of stdClass objects containing metadata about each
1256   * module
1257   */
1258  function get_module_metadata($course, $modnames, $sectionreturn = null) {
1259      global $CFG, $OUTPUT;
1260  
1261      // get_module_metadata will be called once per section on the page and courses may show
1262      // different modules to one another
1263      static $modlist = array();
1264      if (!isset($modlist[$course->id])) {
1265          $modlist[$course->id] = array();
1266      }
1267  
1268      $return = array();
1269      $urlbase = new moodle_url('/course/mod.php', array('id' => $course->id, 'sesskey' => sesskey()));
1270      if ($sectionreturn !== null) {
1271          $urlbase->param('sr', $sectionreturn);
1272      }
1273      foreach($modnames as $modname => $modnamestr) {
1274          if (!course_allowed_module($course, $modname)) {
1275              continue;
1276          }
1277          if (isset($modlist[$course->id][$modname])) {
1278              // This module is already cached
1279              $return[$modname] = $modlist[$course->id][$modname];
1280              continue;
1281          }
1282  
1283          // Include the module lib
1284          $libfile = "$CFG->dirroot/mod/$modname/lib.php";
1285          if (!file_exists($libfile)) {
1286              continue;
1287          }
1288          include_once($libfile);
1289  
1290          // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
1291          $gettypesfunc =  $modname.'_get_types';
1292          $types = MOD_SUBTYPE_NO_CHILDREN;
1293          if (function_exists($gettypesfunc)) {
1294              $types = $gettypesfunc();
1295          }
1296          if ($types !== MOD_SUBTYPE_NO_CHILDREN) {
1297              if (is_array($types) && count($types) > 0) {
1298                  $group = new stdClass();
1299                  $group->name = $modname;
1300                  $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
1301                  foreach($types as $type) {
1302                      if ($type->typestr === '--') {
1303                          continue;
1304                      }
1305                      if (strpos($type->typestr, '--') === 0) {
1306                          $group->title = str_replace('--', '', $type->typestr);
1307                          continue;
1308                      }
1309                      // Set the Sub Type metadata
1310                      $subtype = new stdClass();
1311                      $subtype->title = $type->typestr;
1312                      $subtype->type = str_replace('&amp;', '&', $type->type);
1313                      $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
1314                      $subtype->archetype = $type->modclass;
1315  
1316                      // The group archetype should match the subtype archetypes and all subtypes
1317                      // should have the same archetype
1318                      $group->archetype = $subtype->archetype;
1319  
1320                      if (!empty($type->help)) {
1321                          $subtype->help = $type->help;
1322                      } else if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
1323                          $subtype->help = get_string('help' . $subtype->name, $modname);
1324                      }
1325                      $subtype->link = new moodle_url($urlbase, array('add' => $modname, 'type' => $subtype->name));
1326                      $group->types[] = $subtype;
1327                  }
1328                  $modlist[$course->id][$modname] = $group;
1329              }
1330          } else {
1331              $module = new stdClass();
1332              $module->title = $modnamestr;
1333              $module->name = $modname;
1334              $module->link = new moodle_url($urlbase, array('add' => $modname));
1335              $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
1336              $sm = get_string_manager();
1337              if ($sm->string_exists('modulename_help', $modname)) {
1338                  $module->help = get_string('modulename_help', $modname);
1339                  if ($sm->string_exists('modulename_link', $modname)) {  // Link to further info in Moodle docs
1340                      $link = get_string('modulename_link', $modname);
1341                      $linktext = get_string('morehelp');
1342                      $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
1343                  }
1344              }
1345              $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
1346              $modlist[$course->id][$modname] = $module;
1347          }
1348          if (isset($modlist[$course->id][$modname])) {
1349              $return[$modname] = $modlist[$course->id][$modname];
1350          } else {
1351              debugging("Invalid module metadata configuration for {$modname}");
1352          }
1353      }
1354  
1355      return $return;
1356  }
1357  
1358  /**
1359   * Return the course category context for the category with id $categoryid, except
1360   * that if $categoryid is 0, return the system context.
1361   *
1362   * @param integer $categoryid a category id or 0.
1363   * @return context the corresponding context
1364   */
1365  function get_category_or_system_context($categoryid) {
1366      if ($categoryid) {
1367          return context_coursecat::instance($categoryid, IGNORE_MISSING);
1368      } else {
1369          return context_system::instance();
1370      }
1371  }
1372  
1373  /**
1374   * Returns full course categories trees to be used in html_writer::select()
1375   *
1376   * Calls {@link coursecat::make_categories_list()} to build the tree and
1377   * adds whitespace to denote nesting
1378   *
1379   * @return array array mapping coursecat id to the display name
1380   */
1381  function make_categories_options() {
1382      global $CFG;
1383      require_once($CFG->libdir. '/coursecatlib.php');
1384      $cats = coursecat::make_categories_list('', 0, ' / ');
1385      foreach ($cats as $key => $value) {
1386          // Prefix the value with the number of spaces equal to category depth (number of separators in the value).
1387          $cats[$key] = str_repeat('&nbsp;', substr_count($value, ' / ')). $value;
1388      }
1389      return $cats;
1390  }
1391  
1392  /**
1393   * Print the buttons relating to course requests.
1394   *
1395   * @param object $context current page context.
1396   */
1397  function print_course_request_buttons($context) {
1398      global $CFG, $DB, $OUTPUT;
1399      if (empty($CFG->enablecourserequests)) {
1400          return;
1401      }
1402      if (!has_capability('moodle/course:create', $context) && has_capability('moodle/course:request', $context)) {
1403      /// Print a button to request a new course
1404          echo $OUTPUT->single_button(new moodle_url('/course/request.php'), get_string('requestcourse'), 'get');
1405      }
1406      /// Print a button to manage pending requests
1407      if ($context->contextlevel == CONTEXT_SYSTEM && has_capability('moodle/site:approvecourse', $context)) {
1408          $disabled = !$DB->record_exists('course_request', array());
1409          echo $OUTPUT->single_button(new moodle_url('/course/pending.php'), get_string('coursespending'), 'get', array('disabled' => $disabled));
1410      }
1411  }
1412  
1413  /**
1414   * Does the user have permission to edit things in this category?
1415   *
1416   * @param integer $categoryid The id of the category we are showing, or 0 for system context.
1417   * @return boolean has_any_capability(array(...), ...); in the appropriate context.
1418   */
1419  function can_edit_in_category($categoryid = 0) {
1420      $context = get_category_or_system_context($categoryid);
1421      return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
1422  }
1423  
1424  /// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
1425  
1426  function add_course_module($mod) {
1427      global $DB;
1428  
1429      $mod->added = time();
1430      unset($mod->id);
1431  
1432      $cmid = $DB->insert_record("course_modules", $mod);
1433      rebuild_course_cache($mod->course, true);
1434      return $cmid;
1435  }
1436  
1437  /**
1438   * Creates missing course section(s) and rebuilds course cache
1439   *
1440   * @param int|stdClass $courseorid course id or course object
1441   * @param int|array $sections list of relative section numbers to create
1442   * @return bool if there were any sections created
1443   */
1444  function course_create_sections_if_missing($courseorid, $sections) {
1445      global $DB;
1446      if (!is_array($sections)) {
1447          $sections = array($sections);
1448      }
1449      $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all());
1450      if (is_object($courseorid)) {
1451          $courseorid = $courseorid->id;
1452      }
1453      $coursechanged = false;
1454      foreach ($sections as $sectionnum) {
1455          if (!in_array($sectionnum, $existing)) {
1456              $cw = new stdClass();
1457              $cw->course   = $courseorid;
1458              $cw->section  = $sectionnum;
1459              $cw->summary  = '';
1460              $cw->summaryformat = FORMAT_HTML;
1461              $cw->sequence = '';
1462              $id = $DB->insert_record("course_sections", $cw);
1463              $coursechanged = true;
1464          }
1465      }
1466      if ($coursechanged) {
1467          rebuild_course_cache($courseorid, true);
1468      }
1469      return $coursechanged;
1470  }
1471  
1472  /**
1473   * Adds an existing module to the section
1474   *
1475   * Updates both tables {course_sections} and {course_modules}
1476   *
1477   * Note: This function does not use modinfo PROVIDED that the section you are
1478   * adding the module to already exists. If the section does not exist, it will
1479   * build modinfo if necessary and create the section.
1480   *
1481   * @param int|stdClass $courseorid course id or course object
1482   * @param int $cmid id of the module already existing in course_modules table
1483   * @param int $sectionnum relative number of the section (field course_sections.section)
1484   *     If section does not exist it will be created
1485   * @param int|stdClass $beforemod id or object with field id corresponding to the module
1486   *     before which the module needs to be included. Null for inserting in the
1487   *     end of the section
1488   * @return int The course_sections ID where the module is inserted
1489   */
1490  function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) {
1491      global $DB, $COURSE;
1492      if (is_object($beforemod)) {
1493          $beforemod = $beforemod->id;
1494      }
1495      if (is_object($courseorid)) {
1496          $courseid = $courseorid->id;
1497      } else {
1498          $courseid = $courseorid;
1499      }
1500      // Do not try to use modinfo here, there is no guarantee it is valid!
1501      $section = $DB->get_record('course_sections',
1502              array('course' => $courseid, 'section' => $sectionnum), '*', IGNORE_MISSING);
1503      if (!$section) {
1504          // This function call requires modinfo.
1505          course_create_sections_if_missing($courseorid, $sectionnum);
1506          $section = $DB->get_record('course_sections',
1507                  array('course' => $courseid, 'section' => $sectionnum), '*', MUST_EXIST);
1508      }
1509  
1510      $modarray = explode(",", trim($section->sequence));
1511      if (empty($section->sequence)) {
1512          $newsequence = "$cmid";
1513      } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) {
1514          $insertarray = array($cmid, $beforemod);
1515          array_splice($modarray, $key[0], 1, $insertarray);
1516          $newsequence = implode(",", $modarray);
1517      } else {
1518          $newsequence = "$section->sequence,$cmid";
1519      }
1520      $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id));
1521      $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid));
1522      if (is_object($courseorid)) {
1523          rebuild_course_cache($courseorid->id, true);
1524      } else {
1525          rebuild_course_cache($courseorid, true);
1526      }
1527      return $section->id;     // Return course_sections ID that was used.
1528  }
1529  
1530  /**
1531   * Change the group mode of a course module.
1532   *
1533   * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
1534   * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
1535   *
1536   * @param int $id course module ID.
1537   * @param int $groupmode the new groupmode value.
1538   * @return bool True if the $groupmode was updated.
1539   */
1540  function set_coursemodule_groupmode($id, $groupmode) {
1541      global $DB;
1542      $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST);
1543      if ($cm->groupmode != $groupmode) {
1544          $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id));
1545          rebuild_course_cache($cm->course, true);
1546      }
1547      return ($cm->groupmode != $groupmode);
1548  }
1549  
1550  function set_coursemodule_idnumber($id, $idnumber) {
1551      global $DB;
1552      $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST);
1553      if ($cm->idnumber != $idnumber) {
1554          $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
1555          rebuild_course_cache($cm->course, true);
1556      }
1557      return ($cm->idnumber != $idnumber);
1558  }
1559  
1560  /**
1561   * Set the visibility of a module and inherent properties.
1562   *
1563   * Note: Do not forget to trigger the event \core\event\course_module_updated as it needs
1564   * to be triggered manually, refer to {@link \core\event\course_module_updated::create_from_cm()}.
1565   *
1566   * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
1567   * has been moved to {@link set_section_visible()} which was the only place from which
1568   * the parameter was used.
1569   *
1570   * @param int $id of the module
1571   * @param int $visible state of the module
1572   * @return bool false when the module was not found, true otherwise
1573   */
1574  function set_coursemodule_visible($id, $visible) {
1575      global $DB, $CFG;
1576      require_once($CFG->libdir.'/gradelib.php');
1577      require_once($CFG->dirroot.'/calendar/lib.php');
1578  
1579      // Trigger developer's attention when using the previously removed argument.
1580      if (func_num_args() > 2) {
1581          debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
1582              has been removed.', DEBUG_DEVELOPER);
1583      }
1584  
1585      if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
1586          return false;
1587      }
1588  
1589      // Create events and propagate visibility to associated grade items if the value has changed.
1590      // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
1591      if ($cm->visible == $visible) {
1592          return true;
1593      }
1594  
1595      if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
1596          return false;
1597      }
1598      if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
1599          foreach($events as $event) {
1600              if ($visible) {
1601                  $event = new calendar_event($event);
1602                  $event->toggle_visibility(true);
1603              } else {
1604                  $event = new calendar_event($event);
1605                  $event->toggle_visibility(false);
1606              }
1607          }
1608      }
1609  
1610      // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
1611      // affect visibleold to allow for an original visibility restore. See set_section_visible().
1612      $cminfo = new stdClass();
1613      $cminfo->id = $id;
1614      $cminfo->visible = $visible;
1615      $cminfo->visibleold = $visible;
1616      $DB->update_record('course_modules', $cminfo);
1617  
1618      // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
1619      // Note that this must be done after updating the row in course_modules, in case
1620      // the modules grade_item_update function needs to access $cm->visible.
1621      if (plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
1622              component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
1623          $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
1624          component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
1625      } else {
1626          $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
1627          if ($grade_items) {
1628              foreach ($grade_items as $grade_item) {
1629                  $grade_item->set_hidden(!$visible);
1630              }
1631          }
1632      }
1633  
1634      rebuild_course_cache($cm->course, true);
1635      return true;
1636  }
1637  
1638  /**
1639   * This function will handle the whole deletion process of a module. This includes calling
1640   * the modules delete_instance function, deleting files, events, grades, conditional data,
1641   * the data in the course_module and course_sections table and adding a module deletion
1642   * event to the DB.
1643   *
1644   * @param int $cmid the course module id
1645   * @since Moodle 2.5
1646   */
1647  function course_delete_module($cmid) {
1648      global $CFG, $DB;
1649  
1650      require_once($CFG->libdir.'/gradelib.php');
1651      require_once($CFG->libdir.'/questionlib.php');
1652      require_once($CFG->dirroot.'/blog/lib.php');
1653      require_once($CFG->dirroot.'/calendar/lib.php');
1654      require_once($CFG->dirroot.'/tag/lib.php');
1655  
1656      // Get the course module.
1657      if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
1658          return true;
1659      }
1660  
1661      // Get the module context.
1662      $modcontext = context_module::instance($cm->id);
1663  
1664      // Get the course module name.
1665      $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
1666  
1667      // Get the file location of the delete_instance function for this module.
1668      $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
1669  
1670      // Include the file required to call the delete_instance function for this module.
1671      if (file_exists($modlib)) {
1672          require_once($modlib);
1673      } else {
1674          throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
1675              "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
1676      }
1677  
1678      $deleteinstancefunction = $modulename . '_delete_instance';
1679  
1680      // Ensure the delete_instance function exists for this module.
1681      if (!function_exists($deleteinstancefunction)) {
1682          throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
1683              "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
1684      }
1685  
1686      // Delete activity context questions and question categories.
1687      question_delete_activity($cm);
1688  
1689      // Call the delete_instance function, if it returns false throw an exception.
1690      if (!$deleteinstancefunction($cm->instance)) {
1691          throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
1692              "Cannot delete the module $modulename (instance).");
1693      }
1694  
1695      // Remove all module files in case modules forget to do that.
1696      $fs = get_file_storage();
1697      $fs->delete_area_files($modcontext->id);
1698  
1699      // Delete events from calendar.
1700      if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
1701          foreach($events as $event) {
1702              $calendarevent = calendar_event::load($event->id);
1703              $calendarevent->delete();
1704          }
1705      }
1706  
1707      // Delete grade items, outcome items and grades attached to modules.
1708      if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
1709                                                     'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
1710          foreach ($grade_items as $grade_item) {
1711              $grade_item->delete('moddelete');
1712          }
1713      }
1714  
1715      // Delete completion and availability data; it is better to do this even if the
1716      // features are not turned on, in case they were turned on previously (these will be
1717      // very quick on an empty table).
1718      $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
1719      $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
1720                                                              'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
1721  
1722      // Delete all tag instances associated with the instance of this module.
1723      tag_delete_instances('mod_' . $modulename, $modcontext->id);
1724  
1725      // Delete the context.
1726      context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
1727  
1728      // Delete the module from the course_modules table.
1729      $DB->delete_records('course_modules', array('id' => $cm->id));
1730  
1731      // Delete module from that section.
1732      if (!delete_mod_from_section($cm->id, $cm->section)) {
1733          throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
1734              "Cannot delete the module $modulename (instance) from section.");
1735      }
1736  
1737      // Trigger event for course module delete action.
1738      $event = \core\event\course_module_deleted::create(array(
1739          'courseid' => $cm->course,
1740          'context'  => $modcontext,
1741          'objectid' => $cm->id,
1742          'other'    => array(
1743              'modulename' => $modulename,
1744              'instanceid'   => $cm->instance,
1745          )
1746      ));
1747      $event->add_record_snapshot('course_modules', $cm);
1748      $event->trigger();
1749      rebuild_course_cache($cm->course, true);
1750  }
1751  
1752  function delete_mod_from_section($modid, $sectionid) {
1753      global $DB;
1754  
1755      if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) {
1756  
1757          $modarray = explode(",", $section->sequence);
1758  
1759          if ($key = array_keys ($modarray, $modid)) {
1760              array_splice($modarray, $key[0], 1);
1761              $newsequence = implode(",", $modarray);
1762              $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
1763              rebuild_course_cache($section->course, true);
1764              return true;
1765          } else {
1766              return false;
1767          }
1768  
1769      }
1770      return false;
1771  }
1772  
1773  /**
1774   * Moves a section within a course, from a position to another.
1775   * Be very careful: $section and $destination refer to section number,
1776   * not id!.
1777   *
1778   * @param object $course
1779   * @param int $section Section number (not id!!!)
1780   * @param int $destination
1781   * @return boolean Result
1782   */
1783  function move_section_to($course, $section, $destination) {
1784  /// Moves a whole course section up and down within the course
1785      global $USER, $DB;
1786  
1787      if (!$destination && $destination != 0) {
1788          return true;
1789      }
1790  
1791      // compartibility with course formats using field 'numsections'
1792      $courseformatoptions = course_get_format($course)->get_format_options();
1793      if ((array_key_exists('numsections', $courseformatoptions) &&
1794              ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
1795          return false;
1796      }
1797  
1798      // Get all sections for this course and re-order them (2 of them should now share the same section number)
1799      if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
1800              'section ASC, id ASC', 'id, section')) {
1801          return false;
1802      }
1803  
1804      $movedsections = reorder_sections($sections, $section, $destination);
1805  
1806      // Update all sections. Do this in 2 steps to avoid breaking database
1807      // uniqueness constraint
1808      $transaction = $DB->start_delegated_transaction();
1809      foreach ($movedsections as $id => $position) {
1810          if ($sections[$id] !== $position) {
1811              $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
1812          }
1813      }
1814      foreach ($movedsections as $id => $position) {
1815          if ($sections[$id] !== $position) {
1816              $DB->set_field('course_sections', 'section', $position, array('id' => $id));
1817          }
1818      }
1819  
1820      // If we move the highlighted section itself, then just highlight the destination.
1821      // Adjust the higlighted section location if we move something over it either direction.
1822      if ($section == $course->marker) {
1823          course_set_marker($course->id, $destination);
1824      } elseif ($section > $course->marker && $course->marker >= $destination) {
1825          course_set_marker($course->id, $course->marker+1);
1826      } elseif ($section < $course->marker && $course->marker <= $destination) {
1827          course_set_marker($course->id, $course->marker-1);
1828      }
1829  
1830      $transaction->allow_commit();
1831      rebuild_course_cache($course->id, true);
1832      return true;
1833  }
1834  
1835  /**
1836   * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
1837   * an original position number and a target position number, rebuilds the array so that the
1838   * move is made without any duplication of section positions.
1839   * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
1840   * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
1841   *
1842   * @param array $sections
1843   * @param int $origin_position
1844   * @param int $target_position
1845   * @return array
1846   */
1847  function reorder_sections($sections, $origin_position, $target_position) {
1848      if (!is_array($sections)) {
1849          return false;
1850      }
1851  
1852      // We can't move section position 0
1853      if ($origin_position < 1) {
1854          echo "We can't move section position 0";
1855          return false;
1856      }
1857  
1858      // Locate origin section in sections array
1859      if (!$origin_key = array_search($origin_position, $sections)) {
1860          echo "searched position not in sections array";
1861          return false; // searched position not in sections array
1862      }
1863  
1864      // Extract origin section
1865      $origin_section = $sections[$origin_key];
1866      unset($sections[$origin_key]);
1867  
1868      // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
1869      $found = false;
1870      $append_array = array();
1871      foreach ($sections as $id => $position) {
1872          if ($found) {
1873              $append_array[$id] = $position;
1874              unset($sections[$id]);
1875          }
1876          if ($position == $target_position) {
1877              if ($target_position < $origin_position) {
1878                  $append_array[$id] = $position;
1879                  unset($sections[$id]);
1880              }
1881              $found = true;
1882          }
1883      }
1884  
1885      // Append moved section
1886      $sections[$origin_key] = $origin_section;
1887  
1888      // Append rest of array (if applicable)
1889      if (!empty($append_array)) {
1890          foreach ($append_array as $id => $position) {
1891              $sections[$id] = $position;
1892          }
1893      }
1894  
1895      // Renumber positions
1896      $position = 0;
1897      foreach ($sections as $id => $p) {
1898          $sections[$id] = $position;
1899          $position++;
1900      }
1901  
1902      return $sections;
1903  
1904  }
1905  
1906  /**
1907   * Move the module object $mod to the specified $section
1908   * If $beforemod exists then that is the module
1909   * before which $modid should be inserted
1910   *
1911   * @param stdClass|cm_info $mod
1912   * @param stdClass|section_info $section
1913   * @param int|stdClass $beforemod id or object with field id corresponding to the module
1914   *     before which the module needs to be included. Null for inserting in the
1915   *     end of the section
1916   * @return int new value for module visibility (0 or 1)
1917   */
1918  function moveto_module($mod, $section, $beforemod=NULL) {
1919      global $OUTPUT, $DB;
1920  
1921      // Current module visibility state - return value of this function.
1922      $modvisible = $mod->visible;
1923  
1924      // Remove original module from original section.
1925      if (! delete_mod_from_section($mod->id, $mod->section)) {
1926          echo $OUTPUT->notification("Could not delete module from existing section");
1927      }
1928  
1929      // If moving to a hidden section then hide module.
1930      if ($mod->section != $section->id) {
1931          if (!$section->visible && $mod->visible) {
1932              // Module was visible but must become hidden after moving to hidden section.
1933              $modvisible = 0;
1934              set_coursemodule_visible($mod->id, 0);
1935              // Set visibleold to 1 so module will be visible when section is made visible.
1936              $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
1937          }
1938          if ($section->visible && !$mod->visible) {
1939              // Hidden module was moved to the visible section, restore the module visibility from visibleold.
1940              set_coursemodule_visible($mod->id, $mod->visibleold);
1941              $modvisible = $mod->visibleold;
1942          }
1943      }
1944  
1945      // Add the module into the new section.
1946      course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod);
1947      return $modvisible;
1948  }
1949  
1950  /**
1951   * Returns the list of all editing actions that current user can perform on the module
1952   *
1953   * @param cm_info $mod The module to produce editing buttons for
1954   * @param int $indent The current indenting (default -1 means no move left-right actions)
1955   * @param int $sr The section to link back to (used for creating the links)
1956   * @return array array of action_link or pix_icon objects
1957   */
1958  function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
1959      global $COURSE, $SITE;
1960  
1961      static $str;
1962  
1963      $coursecontext = context_course::instance($mod->course);
1964      $modcontext = context_module::instance($mod->id);
1965  
1966      $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
1967      $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
1968  
1969      // No permission to edit anything.
1970      if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
1971          return array();
1972      }
1973  
1974      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
1975  
1976      if (!isset($str)) {
1977          $str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
1978              'editsettings', 'duplicate', 'hide', 'show'), 'moodle');
1979          $str->assign         = get_string('assignroles', 'role');
1980          $str->groupsnone     = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
1981          $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
1982          $str->groupsvisible  = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
1983      }
1984  
1985      $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
1986  
1987      if ($sr !== null) {
1988          $baseurl->param('sr', $sr);
1989      }
1990      $actions = array();
1991  
1992      // Update.
1993      if ($hasmanageactivities) {
1994          $actions['update'] = new action_menu_link_secondary(
1995              new moodle_url($baseurl, array('update' => $mod->id)),
1996              new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1997              $str->editsettings,
1998              array('class' => 'editing_update', 'data-action' => 'update')
1999          );
2000      }
2001  
2002      // Indent.
2003      if ($hasmanageactivities && $indent >= 0) {
2004          $indentlimits = new stdClass();
2005          $indentlimits->min = 0;
2006          $indentlimits->max = 16;
2007          if (right_to_left()) {   // Exchange arrows on RTL
2008              $rightarrow = 't/left';
2009              $leftarrow  = 't/right';
2010          } else {
2011              $rightarrow = 't/right';
2012              $leftarrow  = 't/left';
2013          }
2014  
2015          if ($indent >= $indentlimits->max) {
2016              $enabledclass = 'hidden';
2017          } else {
2018              $enabledclass = '';
2019          }
2020          $actions['moveright'] = new action_menu_link_secondary(
2021              new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
2022              new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2023              $str->moveright,
2024              array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true)
2025          );
2026  
2027          if ($indent <= $indentlimits->min) {
2028              $enabledclass = 'hidden';
2029          } else {
2030              $enabledclass = '';
2031          }
2032          $actions['moveleft'] = new action_menu_link_secondary(
2033              new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
2034              new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2035              $str->moveleft,
2036              array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true)
2037          );
2038  
2039      }
2040  
2041      // Hide/Show.
2042      if (has_capability('moodle/course:activityvisibility', $modcontext)) {
2043          if ($mod->visible) {
2044              $actions['hide'] = new action_menu_link_secondary(
2045                  new moodle_url($baseurl, array('hide' => $mod->id)),
2046                  new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2047                  $str->hide,
2048                  array('class' => 'editing_hide', 'data-action' => 'hide')
2049              );
2050          } else {
2051              $actions['show'] = new action_menu_link_secondary(
2052                  new moodle_url($baseurl, array('show' => $mod->id)),
2053                  new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2054                  $str->show,
2055                  array('class' => 'editing_show', 'data-action' => 'show')
2056              );
2057          }
2058      }
2059  
2060      // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
2061      // Note that restoring on front page is never allowed.
2062      if ($mod->course != SITEID && has_all_capabilities($dupecaps, $coursecontext) &&
2063              plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
2064          $actions['duplicate'] = new action_menu_link_secondary(
2065              new moodle_url($baseurl, array('duplicate' => $mod->id)),
2066              new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2067              $str->duplicate,
2068              array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr)
2069          );
2070      }
2071  
2072      // Groupmode.
2073      if ($hasmanageactivities && !$mod->coursegroupmodeforce) {
2074          if (plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
2075              if ($mod->effectivegroupmode == SEPARATEGROUPS) {
2076                  $nextgroupmode = VISIBLEGROUPS;
2077                  $grouptitle = $str->groupsseparate;
2078                  $actionname = 'groupsseparate';
2079                  $groupimage = 'i/groups';
2080              } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
2081                  $nextgroupmode = NOGROUPS;
2082                  $grouptitle = $str->groupsvisible;
2083                  $actionname = 'groupsvisible';
2084                  $groupimage = 'i/groupv';
2085              } else {
2086                  $nextgroupmode = SEPARATEGROUPS;
2087                  $grouptitle = $str->groupsnone;
2088                  $actionname = 'groupsnone';
2089                  $groupimage = 'i/groupn';
2090              }
2091  
2092              $actions[$actionname] = new action_menu_link_primary(
2093                  new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
2094                  new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')),
2095                  $grouptitle,
2096                  array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive')
2097              );
2098          } else {
2099              $actions['nogroupsupport'] = new action_menu_filler();
2100          }
2101      }
2102  
2103      // Assign.
2104      if (has_capability('moodle/role:assign', $modcontext)){
2105          $actions['assign'] = new action_menu_link_secondary(
2106              new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
2107              new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2108              $str->assign,
2109              array('class' => 'editing_assign', 'data-action' => 'assignroles')
2110          );
2111      }
2112  
2113      // Delete.
2114      if ($hasmanageactivities) {
2115          $actions['delete'] = new action_menu_link_secondary(
2116              new moodle_url($baseurl, array('delete' => $mod->id)),
2117              new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2118              $str->delete,
2119              array('class' => 'editing_delete', 'data-action' => 'delete')
2120          );
2121      }
2122  
2123      return $actions;
2124  }
2125  
2126  /**
2127   * Returns the rename action.
2128   *
2129   * @param cm_info $mod The module to produce editing buttons for
2130   * @param int $sr The section to link back to (used for creating the links)
2131   * @return The markup for the rename action, or an empty string if not available.
2132   */
2133  function course_get_cm_rename_action(cm_info $mod, $sr = null) {
2134      global $COURSE, $OUTPUT;
2135  
2136      static $str;
2137      static $baseurl;
2138  
2139      $modcontext = context_module::instance($mod->id);
2140      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2141  
2142      if (!isset($str)) {
2143          $str = get_strings(array('edittitle'));
2144      }
2145  
2146      if (!isset($baseurl)) {
2147          $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2148      }
2149  
2150      if ($sr !== null) {
2151          $baseurl->param('sr', $sr);
2152      }
2153  
2154      // AJAX edit title.
2155      if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
2156                  (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
2157          // we will not display link if we are on some other-course page (where we should not see this module anyway)
2158          return html_writer::span(
2159              html_writer::link(
2160                  new moodle_url($baseurl, array('update' => $mod->id)),
2161                  $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
2162                  array(
2163                      'class' => 'editing_title',
2164                      'data-action' => 'edittitle',
2165                      'title' => $str->edittitle,
2166                  )
2167              )
2168          );
2169      }
2170      return '';
2171  }
2172  
2173  /**
2174   * Returns the move action.
2175   *
2176   * @param cm_info $mod The module to produce a move button for
2177   * @param int $sr The section to link back to (used for creating the links)
2178   * @return The markup for the move action, or an empty string if not available.
2179   */
2180  function course_get_cm_move(cm_info $mod, $sr = null) {
2181      global $OUTPUT;
2182  
2183      static $str;
2184      static $baseurl;
2185  
2186      $modcontext = context_module::instance($mod->id);
2187      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2188  
2189      if (!isset($str)) {
2190          $str = get_strings(array('move'));
2191      }
2192  
2193      if (!isset($baseurl)) {
2194          $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2195  
2196          if ($sr !== null) {
2197              $baseurl->param('sr', $sr);
2198          }
2199      }
2200  
2201      if ($hasmanageactivities) {
2202          $pixicon = 'i/dragdrop';
2203  
2204          if (!course_ajax_enabled($mod->get_course())) {
2205              // Override for course frontpage until we get drag/drop working there.
2206              $pixicon = 't/move';
2207          }
2208  
2209          return html_writer::link(
2210              new moodle_url($baseurl, array('copy' => $mod->id)),
2211              $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2212              array('class' => 'editing_move', 'data-action' => 'move')
2213          );
2214      }
2215      return '';
2216  }
2217  
2218  /**
2219   * given a course object with shortname & fullname, this function will
2220   * truncate the the number of chars allowed and add ... if it was too long
2221   */
2222  function course_format_name ($course,$max=100) {
2223  
2224      $context = context_course::instance($course->id);
2225      $shortname = format_string($course->shortname, true, array('context' => $context));
2226      $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
2227      $str = $shortname.': '. $fullname;
2228      if (core_text::strlen($str) <= $max) {
2229          return $str;
2230      }
2231      else {
2232          return core_text::substr($str,0,$max-3).'...';
2233      }
2234  }
2235  
2236  /**
2237   * Is the user allowed to add this type of module to this course?
2238   * @param object $course the course settings. Only $course->id is used.
2239   * @param string $modname the module name. E.g. 'forum' or 'quiz'.
2240   * @return bool whether the current user is allowed to add this type of module to this course.
2241   */
2242  function course_allowed_module($course, $modname) {
2243      if (is_numeric($modname)) {
2244          throw new coding_exception('Function course_allowed_module no longer
2245                  supports numeric module ids. Please update your code to pass the module name.');
2246      }
2247  
2248      $capability = 'mod/' . $modname . ':addinstance';
2249      if (!get_capability_info($capability)) {
2250          // Debug warning that the capability does not exist, but no more than once per page.
2251          static $warned = array();
2252          $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2253          if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
2254              debugging('The module ' . $modname . ' does not define the standard capability ' .
2255                      $capability , DEBUG_DEVELOPER);
2256              $warned[$modname] = 1;
2257          }
2258  
2259          // If the capability does not exist, the module can always be added.
2260          return true;
2261      }
2262  
2263      $coursecontext = context_course::instance($course->id);
2264      return has_capability($capability, $coursecontext);
2265  }
2266  
2267  /**
2268   * Efficiently moves many courses around while maintaining
2269   * sortorder in order.
2270   *
2271   * @param array $courseids is an array of course ids
2272   * @param int $categoryid
2273   * @return bool success
2274   */
2275  function move_courses($courseids, $categoryid) {
2276      global $DB;
2277  
2278      if (empty($courseids)) {
2279          // Nothing to do.
2280          return false;
2281      }
2282  
2283      if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
2284          return false;
2285      }
2286  
2287      $courseids = array_reverse($courseids);
2288      $newparent = context_coursecat::instance($category->id);
2289      $i = 1;
2290  
2291      list($where, $params) = $DB->get_in_or_equal($courseids);
2292      $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname');
2293      foreach ($dbcourses as $dbcourse) {
2294          $course = new stdClass();
2295          $course->id = $dbcourse->id;
2296          $course->category  = $category->id;
2297          $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
2298          if ($category->visible == 0) {
2299              // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
2300              // to previous state if somebody unhides the category.
2301              $course->visible = 0;
2302          }
2303  
2304          $DB->update_record('course', $course);
2305  
2306          // Update context, so it can be passed to event.
2307          $context = context_course::instance($course->id);
2308          $context->update_moved($newparent);
2309  
2310          // Trigger a course updated event.
2311          $event = \core\event\course_updated::create(array(
2312              'objectid' => $course->id,
2313              'context' => context_course::instance($course->id),
2314              'other' => array('shortname' => $dbcourse->shortname,
2315                               'fullname' => $dbcourse->fullname)
2316          ));
2317          $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
2318          $event->trigger();
2319      }
2320      fix_course_sortorder();
2321      cache_helper::purge_by_event('changesincourse');
2322  
2323      return true;
2324  }
2325  
2326  /**
2327   * Returns the display name of the given section that the course prefers
2328   *
2329   * Implementation of this function is provided by course format
2330   * @see format_base::get_section_name()
2331   *
2332   * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
2333   * @param int|stdClass $section Section object from database or just field course_sections.section
2334   * @return string Display name that the course format prefers, e.g. "Week 2"
2335   */
2336  function get_section_name($courseorid, $section) {
2337      return course_get_format($courseorid)->get_section_name($section);
2338  }
2339  
2340  /**
2341   * Tells if current course format uses sections
2342   *
2343   * @param string $format Course format ID e.g. 'weeks' $course->format
2344   * @return bool
2345   */
2346  function course_format_uses_sections($format) {
2347      $course = new stdClass();
2348      $course->format = $format;
2349      return course_get_format($course)->uses_sections();
2350  }
2351  
2352  /**
2353   * Returns the information about the ajax support in the given source format
2354   *
2355   * The returned object's property (boolean)capable indicates that
2356   * the course format supports Moodle course ajax features.
2357   *
2358   * @param string $format
2359   * @return stdClass
2360   */
2361  function course_format_ajax_support($format) {
2362      $course = new stdClass();
2363      $course->format = $format;
2364      return course_get_format($course)->supports_ajax();
2365  }
2366  
2367  /**
2368   * Can the current user delete this course?
2369   * Course creators have exception,
2370   * 1 day after the creation they can sill delete the course.
2371   * @param int $courseid
2372   * @return boolean
2373   */
2374  function can_delete_course($courseid) {
2375      global $USER;
2376  
2377      $context = context_course::instance($courseid);
2378  
2379      if (has_capability('moodle/course:delete', $context)) {
2380          return true;
2381      }
2382  
2383      // hack: now try to find out if creator created this course recently (1 day)
2384      if (!has_capability('moodle/course:create', $context)) {
2385          return false;
2386      }
2387  
2388      $since = time() - 60*60*24;
2389      $course = get_course($courseid);
2390  
2391      if ($course->timecreated < $since) {
2392          return false; // Return if the course was not created in last 24 hours.
2393      }
2394  
2395      $logmanger = get_log_manager();
2396      $readers = $logmanger->get_readers('\core\log\sql_select_reader');
2397      $reader = reset($readers);
2398  
2399      if (empty($reader)) {
2400          return false; // No log reader found.
2401      }
2402  
2403      // A proper reader.
2404      $select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since";
2405      $params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created');
2406  
2407      return (bool)$reader->get_events_select_count($select, $params);
2408  }
2409  
2410  /**
2411   * Save the Your name for 'Some role' strings.
2412   *
2413   * @param integer $courseid the id of this course.
2414   * @param array $data the data that came from the course settings form.
2415   */
2416  function save_local_role_names($courseid, $data) {
2417      global $DB;
2418      $context = context_course::instance($courseid);
2419  
2420      foreach ($data as $fieldname => $value) {
2421          if (strpos($fieldname, 'role_') !== 0) {
2422              continue;
2423          }
2424          list($ignored, $roleid) = explode('_', $fieldname);
2425  
2426          // make up our mind whether we want to delete, update or insert
2427          if (!$value) {
2428              $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
2429  
2430          } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
2431              $rolename->name = $value;
2432              $DB->update_record('role_names', $rolename);
2433  
2434          } else {
2435              $rolename = new stdClass;
2436              $rolename->contextid = $context->id;
2437              $rolename->roleid = $roleid;
2438              $rolename->name = $value;
2439              $DB->insert_record('role_names', $rolename);
2440          }
2441      }
2442  }
2443  
2444  /**
2445   * Returns options to use in course overviewfiles filemanager
2446   *
2447   * @param null|stdClass|course_in_list|int $course either object that has 'id' property or just the course id;
2448   *     may be empty if course does not exist yet (course create form)
2449   * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc.
2450   *     or null if overviewfiles are disabled
2451   */
2452  function course_overviewfiles_options($course) {
2453      global $CFG;
2454      if (empty($CFG->courseoverviewfileslimit)) {
2455          return null;
2456      }
2457      $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY);
2458      if (in_array('*', $accepted_types) || empty($accepted_types)) {
2459          $accepted_types = '*';
2460      } else {
2461          // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered.
2462          // Make sure extensions are prefixed with dot unless they are valid typegroups
2463          foreach ($accepted_types as $i => $type) {
2464              if (substr($type, 0, 1) !== '.') {
2465                  require_once($CFG->libdir. '/filelib.php');
2466                  if (!count(file_get_typegroup('extension', $type))) {
2467                      // It does not start with dot and is not a valid typegroup, this is most likely extension.
2468                      $accepted_types[$i] = '.'. $type;
2469                      $corrected = true;
2470                  }
2471              }
2472          }
2473          if (!empty($corrected)) {
2474              set_config('courseoverviewfilesext', join(',', $accepted_types));
2475          }
2476      }
2477      $options = array(
2478          'maxfiles' => $CFG->courseoverviewfileslimit,
2479          'maxbytes' => $CFG->maxbytes,
2480          'subdirs' => 0,
2481          'accepted_types' => $accepted_types
2482      );
2483      if (!empty($course->id)) {
2484          $options['context'] = context_course::instance($course->id);
2485      } else if (is_int($course) && $course > 0) {
2486          $options['context'] = context_course::instance($course);
2487      }
2488      return $options;
2489  }
2490  
2491  /**
2492   * Create a course and either return a $course object
2493   *
2494   * Please note this functions does not verify any access control,
2495   * the calling code is responsible for all validation (usually it is the form definition).
2496   *
2497   * @param array $editoroptions course description editor options
2498   * @param object $data  - all the data needed for an entry in the 'course' table
2499   * @return object new course instance
2500   */
2501  function create_course($data, $editoroptions = NULL) {
2502      global $DB;
2503  
2504      //check the categoryid - must be given for all new courses
2505      $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
2506  
2507      // Check if the shortname already exists.
2508      if (!empty($data->shortname)) {
2509          if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
2510              throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2511          }
2512      }
2513  
2514      // Check if the idnumber already exists.
2515      if (!empty($data->idnumber)) {
2516          if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
2517              throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2518          }
2519      }
2520  
2521      // Check if timecreated is given.
2522      $data->timecreated  = !empty($data->timecreated) ? $data->timecreated : time();
2523      $data->timemodified = $data->timecreated;
2524  
2525      // place at beginning of any category
2526      $data->sortorder = 0;
2527  
2528      if ($editoroptions) {
2529          // summary text is updated later, we need context to store the files first
2530          $data->summary = '';
2531          $data->summary_format = FORMAT_HTML;
2532      }
2533  
2534      if (!isset($data->visible)) {
2535          // data not from form, add missing visibility info
2536          $data->visible = $category->visible;
2537      }
2538      $data->visibleold = $data->visible;
2539  
2540      $newcourseid = $DB->insert_record('course', $data);
2541      $context = context_course::instance($newcourseid, MUST_EXIST);
2542  
2543      if ($editoroptions) {
2544          // Save the files used in the summary editor and store
2545          $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2546          $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
2547          $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
2548      }
2549      if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) {
2550          // Save the course overviewfiles
2551          $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2552      }
2553  
2554      // update course format options
2555      course_get_format($newcourseid)->update_course_format_options($data);
2556  
2557      $course = course_get_format($newcourseid)->get_course();
2558  
2559      // Setup the blocks
2560      blocks_add_default_course_blocks($course);
2561  
2562      // Create a default section.
2563      course_create_sections_if_missing($course, 0);
2564  
2565      fix_course_sortorder();
2566      // purge appropriate caches in case fix_course_sortorder() did not change anything
2567      cache_helper::purge_by_event('changesincourse');
2568  
2569      // new context created - better mark it as dirty
2570      $context->mark_dirty();
2571  
2572      // Save any custom role names.
2573      save_local_role_names($course->id, (array)$data);
2574  
2575      // set up enrolments
2576      enrol_course_updated(true, $course, $data);
2577  
2578      // Trigger a course created event.
2579      $event = \core\event\course_created::create(array(
2580          'objectid' => $course->id,
2581          'context' => context_course::instance($course->id),
2582          'other' => array('shortname' => $course->shortname,
2583                           'fullname' => $course->fullname)
2584      ));
2585      $event->trigger();
2586  
2587      return $course;
2588  }
2589  
2590  /**
2591   * Update a course.
2592   *
2593   * Please note this functions does not verify any access control,
2594   * the calling code is responsible for all validation (usually it is the form definition).
2595   *
2596   * @param object $data  - all the data needed for an entry in the 'course' table
2597   * @param array $editoroptions course description editor options
2598   * @return void
2599   */
2600  function update_course($data, $editoroptions = NULL) {
2601      global $DB;
2602  
2603      $data->timemodified = time();
2604  
2605      $oldcourse = course_get_format($data->id)->get_course();
2606      $context   = context_course::instance($oldcourse->id);
2607  
2608      if ($editoroptions) {
2609          $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2610      }
2611      if ($overviewfilesoptions = course_overviewfiles_options($data->id)) {
2612          $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2613      }
2614  
2615      // Check we don't have a duplicate shortname.
2616      if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
2617          if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) {
2618              throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2619          }
2620      }
2621  
2622      // Check we don't have a duplicate idnumber.
2623      if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
2624          if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) {
2625              throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2626          }
2627      }
2628  
2629      if (!isset($data->category) or empty($data->category)) {
2630          // prevent nulls and 0 in category field
2631          unset($data->category);
2632      }
2633      $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category);
2634  
2635      if (!isset($data->visible)) {
2636          // data not from form, add missing visibility info
2637          $data->visible = $oldcourse->visible;
2638      }
2639  
2640      if ($data->visible != $oldcourse->visible) {
2641          // reset the visibleold flag when manually hiding/unhiding course
2642          $data->visibleold = $data->visible;
2643          $changesincoursecat = true;
2644      } else {
2645          if ($movecat) {
2646              $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
2647              if (empty($newcategory->visible)) {
2648                  // make sure when moving into hidden category the course is hidden automatically
2649                  $data->visible = 0;
2650              }
2651          }
2652      }
2653  
2654      // Update with the new data
2655      $DB->update_record('course', $data);
2656      // make sure the modinfo cache is reset
2657      rebuild_course_cache($data->id);
2658  
2659      // update course format options with full course data
2660      course_get_format($data->id)->update_course_format_options($data, $oldcourse);
2661  
2662      $course = $DB->get_record('course', array('id'=>$data->id));
2663  
2664      if ($movecat) {
2665          $newparent = context_coursecat::instance($course->category);
2666          $context->update_moved($newparent);
2667      }
2668      $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder));
2669      if ($fixcoursesortorder) {
2670          fix_course_sortorder();
2671      }
2672  
2673      // purge appropriate caches in case fix_course_sortorder() did not change anything
2674      cache_helper::purge_by_event('changesincourse');
2675      if ($changesincoursecat) {
2676          cache_helper::purge_by_event('changesincoursecat');
2677      }
2678  
2679      // Test for and remove blocks which aren't appropriate anymore
2680      blocks_remove_inappropriate($course);
2681  
2682      // Save any custom role names.
2683      save_local_role_names($course->id, $data);
2684  
2685      // update enrol settings
2686      enrol_course_updated(false, $course, $data);
2687  
2688      // Trigger a course updated event.
2689      $event = \core\event\course_updated::create(array(
2690          'objectid' => $course->id,
2691          'context' => context_course::instance($course->id),
2692          'other' => array('shortname' => $course->shortname,
2693                           'fullname' => $course->fullname)
2694      ));
2695  
2696      $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
2697      $event->trigger();
2698  
2699      if ($oldcourse->format !== $course->format) {
2700          // Remove all options stored for the previous format
2701          // We assume that new course format migrated everything it needed watching trigger
2702          // 'course_updated' and in method format_XXX::update_course_format_options()
2703          $DB->delete_records('course_format_options',
2704                  array('courseid' => $course->id, 'format' => $oldcourse->format));
2705      }
2706  }
2707  
2708  /**
2709   * Average number of participants
2710   * @return integer
2711   */
2712  function average_number_of_participants() {
2713      global $DB, $SITE;
2714  
2715      //count total of enrolments for visible course (except front page)
2716      $sql = 'SELECT COUNT(*) FROM (
2717          SELECT DISTINCT ue.userid, e.courseid
2718          FROM {user_enrolments} ue, {enrol} e, {course} c
2719          WHERE ue.enrolid = e.id
2720              AND e.courseid <> :siteid
2721              AND c.id = e.courseid
2722              AND c.visible = 1) total';
2723      $params = array('siteid' => $SITE->id);
2724      $enrolmenttotal = $DB->count_records_sql($sql, $params);
2725  
2726  
2727      //count total of visible courses (minus front page)
2728      $coursetotal = $DB->count_records('course', array('visible' => 1));
2729      $coursetotal = $coursetotal - 1 ;
2730  
2731      //average of enrolment
2732      if (empty($coursetotal)) {
2733          $participantaverage = 0;
2734      } else {
2735          $participantaverage = $enrolmenttotal / $coursetotal;
2736      }
2737  
2738      return $participantaverage;
2739  }
2740  
2741  /**
2742   * Average number of course modules
2743   * @return integer
2744   */
2745  function average_number_of_courses_modules() {
2746      global $DB, $SITE;
2747  
2748      //count total of visible course module (except front page)
2749      $sql = 'SELECT COUNT(*) FROM (
2750          SELECT cm.course, cm.module
2751          FROM {course} c, {course_modules} cm
2752          WHERE c.id = cm.course
2753              AND c.id <> :siteid
2754              AND cm.visible = 1
2755              AND c.visible = 1) total';
2756      $params = array('siteid' => $SITE->id);
2757      $moduletotal = $DB->count_records_sql($sql, $params);
2758  
2759  
2760      //count total of visible courses (minus front page)
2761      $coursetotal = $DB->count_records('course', array('visible' => 1));
2762      $coursetotal = $coursetotal - 1 ;
2763  
2764      //average of course module
2765      if (empty($coursetotal)) {
2766          $coursemoduleaverage = 0;
2767      } else {
2768          $coursemoduleaverage = $moduletotal / $coursetotal;
2769      }
2770  
2771      return $coursemoduleaverage;
2772  }
2773  
2774  /**
2775   * This class pertains to course requests and contains methods associated with
2776   * create, approving, and removing course requests.
2777   *
2778   * Please note we do not allow embedded images here because there is no context
2779   * to store them with proper access control.
2780   *
2781   * @copyright 2009 Sam Hemelryk
2782   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2783   * @since Moodle 2.0
2784   *
2785   * @property-read int $id
2786   * @property-read string $fullname
2787   * @property-read string $shortname
2788   * @property-read string $summary
2789   * @property-read int $summaryformat
2790   * @property-read int $summarytrust
2791   * @property-read string $reason
2792   * @property-read int $requester
2793   */
2794  class course_request {
2795  
2796      /**
2797       * This is the stdClass that stores the properties for the course request
2798       * and is externally accessed through the __get magic method
2799       * @var stdClass
2800       */
2801      protected $properties;
2802  
2803      /**
2804       * An array of options for the summary editor used by course request forms.
2805       * This is initially set by {@link summary_editor_options()}
2806       * @var array
2807       * @static
2808       */
2809      protected static $summaryeditoroptions;
2810  
2811      /**
2812       * Static function to prepare the summary editor for working with a course
2813       * request.
2814       *
2815       * @static
2816       * @param null|stdClass $data Optional, an object containing the default values
2817       *                       for the form, these may be modified when preparing the
2818       *                       editor so this should be called before creating the form
2819       * @return stdClass An object that can be used to set the default values for
2820       *                   an mforms form
2821       */
2822      public static function prepare($data=null) {
2823          if ($data === null) {
2824              $data = new stdClass;
2825          }
2826          $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
2827          return $data;
2828      }
2829  
2830      /**
2831       * Static function to create a new course request when passed an array of properties
2832       * for it.
2833       *
2834       * This function also handles saving any files that may have been used in the editor
2835       *
2836       * @static
2837       * @param stdClass $data
2838       * @return course_request The newly created course request
2839       */
2840      public static function create($data) {
2841          global $USER, $DB, $CFG;
2842          $data->requester = $USER->id;
2843  
2844          // Setting the default category if none set.
2845          if (empty($data->category) || empty($CFG->requestcategoryselection)) {
2846              $data->category = $CFG->defaultrequestcategory;
2847          }
2848  
2849          // Summary is a required field so copy the text over
2850          $data->summary       = $data->summary_editor['text'];
2851          $data->summaryformat = $data->summary_editor['format'];
2852  
2853          $data->id = $DB->insert_record('course_request', $data);
2854  
2855          // Create a new course_request object and return it
2856          $request = new course_request($data);
2857  
2858          // Notify the admin if required.
2859          if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
2860  
2861              $a = new stdClass;
2862              $a->link = "$CFG->wwwroot/course/pending.php";
2863              $a->user = fullname($USER);
2864              $subject = get_string('courserequest');
2865              $message = get_string('courserequestnotifyemail', 'admin', $a);
2866              foreach ($users as $user) {
2867                  $request->notify($user, $USER, 'courserequested', $subject, $message);
2868              }
2869          }
2870  
2871          return $request;
2872      }
2873  
2874      /**
2875       * Returns an array of options to use with a summary editor
2876       *
2877       * @uses course_request::$summaryeditoroptions
2878       * @return array An array of options to use with the editor
2879       */
2880      public static function summary_editor_options() {
2881          global $CFG;
2882          if (self::$summaryeditoroptions === null) {
2883              self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
2884          }
2885          return self::$summaryeditoroptions;
2886      }
2887  
2888      /**
2889       * Loads the properties for this course request object. Id is required and if
2890       * only id is provided then we load the rest of the properties from the database
2891       *
2892       * @param stdClass|int $properties Either an object containing properties
2893       *                      or the course_request id to load
2894       */
2895      public function __construct($properties) {
2896          global $DB;
2897          if (empty($properties->id)) {
2898              if (empty($properties)) {
2899                  throw new coding_exception('You must provide a course request id when creating a course_request object');
2900              }
2901              $id = $properties;
2902              $properties = new stdClass;
2903              $properties->id = (int)$id;
2904              unset($id);
2905          }
2906          if (empty($properties->requester)) {
2907              if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
2908                  print_error('unknowncourserequest');
2909              }
2910          } else {
2911              $this->properties = $properties;
2912          }
2913          $this->properties->collision = null;
2914      }
2915  
2916      /**
2917       * Returns the requested property
2918       *
2919       * @param string $key
2920       * @return mixed
2921       */
2922      public function __get($key) {
2923          return $this->properties->$key;
2924      }
2925  
2926      /**
2927       * Override this to ensure empty($request->blah) calls return a reliable answer...
2928       *
2929       * This is required because we define the __get method
2930       *
2931       * @param mixed $key
2932       * @return bool True is it not empty, false otherwise
2933       */
2934      public function __isset($key) {
2935          return (!empty($this->properties->$key));
2936      }
2937  
2938      /**
2939       * Returns the user who requested this course
2940       *
2941       * Uses a static var to cache the results and cut down the number of db queries
2942       *
2943       * @staticvar array $requesters An array of cached users
2944       * @return stdClass The user who requested the course
2945       */
2946      public function get_requester() {
2947          global $DB;
2948          static $requesters= array();
2949          if (!array_key_exists($this->properties->requester, $requesters)) {
2950              $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
2951          }
2952          return $requesters[$this->properties->requester];
2953      }
2954  
2955      /**
2956       * Checks that the shortname used by the course does not conflict with any other
2957       * courses that exist
2958       *
2959       * @param string|null $shortnamemark The string to append to the requests shortname
2960       *                     should a conflict be found
2961       * @return bool true is there is a conflict, false otherwise
2962       */
2963      public function check_shortname_collision($shortnamemark = '[*]') {
2964          global $DB;
2965  
2966          if ($this->properties->collision !== null) {
2967              return $this->properties->collision;
2968          }
2969  
2970          if (empty($this->properties->shortname)) {
2971              debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
2972              $this->properties->collision = false;
2973          } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
2974              if (!empty($shortnamemark)) {
2975                  $this->properties->shortname .= ' '.$shortnamemark;
2976              }
2977              $this->properties->collision = true;
2978          } else {
2979              $this->properties->collision = false;
2980          }
2981          return $this->properties->collision;
2982      }
2983  
2984      /**
2985       * Returns the category where this course request should be created
2986       *
2987       * Note that we don't check here that user has a capability to view
2988       * hidden categories if he has capabilities 'moodle/site:approvecourse' and
2989       * 'moodle/course:changecategory'
2990       *
2991       * @return coursecat
2992       */
2993      public function get_category() {
2994          global $CFG;
2995          require_once($CFG->libdir.'/coursecatlib.php');
2996          // If the category is not set, if the current user does not have the rights to change the category, or if the
2997          // category does not exist, we set the default category to the course to be approved.
2998          // The system level is used because the capability moodle/site:approvecourse is based on a system level.
2999          if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
3000                  (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) {
3001              $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true);
3002          }
3003          if (!$category) {
3004              $category = coursecat::get_default();
3005          }
3006          return $category;
3007      }
3008  
3009      /**
3010       * This function approves the request turning it into a course
3011       *
3012       * This function converts the course request into a course, at the same time
3013       * transferring any files used in the summary to the new course and then removing
3014       * the course request and the files associated with it.
3015       *
3016       * @return int The id of the course that was created from this request
3017       */
3018      public function approve() {
3019          global $CFG, $DB, $USER;
3020  
3021          $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
3022  
3023          $courseconfig = get_config('moodlecourse');
3024  
3025          // Transfer appropriate settings
3026          $data = clone($this->properties);
3027          unset($data->id);
3028          unset($data->reason);
3029          unset($data->requester);
3030  
3031          // Set category
3032          $category = $this->get_category();
3033          $data->category = $category->id;
3034          // Set misc settings
3035          $data->requested = 1;
3036  
3037          // Apply course default settings
3038          $data->format             = $courseconfig->format;
3039          $data->newsitems          = $courseconfig->newsitems;
3040          $data->showgrades         = $courseconfig->showgrades;
3041          $data->showreports        = $courseconfig->showreports;
3042          $data->maxbytes           = $courseconfig->maxbytes;
3043          $data->groupmode          = $courseconfig->groupmode;
3044          $data->groupmodeforce     = $courseconfig->groupmodeforce;
3045          $data->visible            = $courseconfig->visible;
3046          $data->visibleold         = $data->visible;
3047          $data->lang               = $courseconfig->lang;
3048  
3049          $course = create_course($data);
3050          $context = context_course::instance($course->id, MUST_EXIST);
3051  
3052          // add enrol instances
3053          if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
3054              if ($manual = enrol_get_plugin('manual')) {
3055                  $manual->add_default_instance($course);
3056              }
3057          }
3058  
3059          // enrol the requester as teacher if necessary
3060          if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
3061              enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
3062          }
3063  
3064          $this->delete();
3065  
3066          $a = new stdClass();
3067          $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
3068          $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
3069          $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
3070  
3071          return $course->id;
3072      }
3073  
3074      /**
3075       * Reject a course request
3076       *
3077       * This function rejects a course request, emailing the requesting user the
3078       * provided notice and then removing the request from the database
3079       *
3080       * @param string $notice The message to display to the user
3081       */
3082      public function reject($notice) {
3083          global $USER, $DB;
3084          $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
3085          $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
3086          $this->delete();
3087      }
3088  
3089      /**
3090       * Deletes the course request and any associated files
3091       */
3092      public function delete() {
3093          global $DB;
3094          $DB->delete_records('course_request', array('id' => $this->properties->id));
3095      }
3096  
3097      /**
3098       * Send a message from one user to another using events_trigger
3099       *
3100       * @param object $touser
3101       * @param object $fromuser
3102       * @param string $name
3103       * @param string $subject
3104       * @param string $message
3105       */
3106      protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
3107          $eventdata = new stdClass();
3108          $eventdata->component         = 'moodle';
3109          $eventdata->name              = $name;
3110          $eventdata->userfrom          = $fromuser;
3111          $eventdata->userto            = $touser;
3112          $eventdata->subject           = $subject;
3113          $eventdata->fullmessage       = $message;
3114          $eventdata->fullmessageformat = FORMAT_PLAIN;
3115          $eventdata->fullmessagehtml   = '';
3116          $eventdata->smallmessage      = '';
3117          $eventdata->notification      = 1;
3118          message_send($eventdata);
3119      }
3120  }
3121  
3122  /**
3123   * Return a list of page types
3124   * @param string $pagetype current page type
3125   * @param context $parentcontext Block's parent context
3126   * @param context $currentcontext Current context of block
3127   * @return array array of page types
3128   */
3129  function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
3130      if ($pagetype === 'course-index' || $pagetype === 'course-index-category') {
3131          // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page
3132          $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3133              'course-index-*' => get_string('page-course-index-x', 'pagetype'),
3134          );
3135      } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) {
3136          // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.)
3137          $pagetypes = array('*' => get_string('page-x', 'pagetype'));
3138      } else {
3139          // Otherwise consider it a page inside a course even if $currentcontext is null
3140          $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3141              'course-*' => get_string('page-course-x', 'pagetype'),
3142              'course-view-*' => get_string('page-course-view-x', 'pagetype')
3143          );
3144      }
3145      return $pagetypes;
3146  }
3147  
3148  /**
3149   * Determine whether course ajax should be enabled for the specified course
3150   *
3151   * @param stdClass $course The course to test against
3152   * @return boolean Whether course ajax is enabled or note
3153   */
3154  function course_ajax_enabled($course) {
3155      global $CFG, $PAGE, $SITE;
3156  
3157      // The user must be editing for AJAX to be included
3158      if (!$PAGE->user_is_editing()) {
3159          return false;
3160      }
3161  
3162      // Check that the theme suports
3163      if (!$PAGE->theme->enablecourseajax) {
3164          return false;
3165      }
3166  
3167      // Check that the course format supports ajax functionality
3168      // The site 'format' doesn't have information on course format support
3169      if ($SITE->id !== $course->id) {
3170          $courseformatajaxsupport = course_format_ajax_support($course->format);
3171          if (!$courseformatajaxsupport->capable) {
3172              return false;
3173          }
3174      }
3175  
3176      // All conditions have been met so course ajax should be enabled
3177      return true;
3178  }
3179  
3180  /**
3181   * Include the relevant javascript and language strings for the resource
3182   * toolbox YUI module
3183   *
3184   * @param integer $id The ID of the course being applied to
3185   * @param array $usedmodules An array containing the names of the modules in use on the page
3186   * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
3187   * @param stdClass $config An object containing configuration parameters for ajax modules including:
3188   *          * resourceurl   The URL to post changes to for resource changes
3189   *          * sectionurl    The URL to post changes to for section changes
3190   *          * pageparams    Additional parameters to pass through in the post
3191   * @return bool
3192   */
3193  function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
3194      global $CFG, $PAGE, $SITE;
3195  
3196      // Ensure that ajax should be included
3197      if (!course_ajax_enabled($course)) {
3198          return false;
3199      }
3200  
3201      if (!$config) {
3202          $config = new stdClass();
3203      }
3204  
3205      // The URL to use for resource changes
3206      if (!isset($config->resourceurl)) {
3207          $config->resourceurl = '/course/rest.php';
3208      }
3209  
3210      // The URL to use for section changes
3211      if (!isset($config->sectionurl)) {
3212          $config->sectionurl = '/course/rest.php';
3213      }
3214  
3215      // Any additional parameters which need to be included on page submission
3216      if (!isset($config->pageparams)) {
3217          $config->pageparams = array();
3218      }
3219  
3220      // Include toolboxes
3221      $PAGE->requires->yui_module('moodle-course-toolboxes',
3222              'M.course.init_resource_toolbox',
3223              array(array(
3224                  'courseid' => $course->id,
3225                  'ajaxurl' => $config->resourceurl,
3226                  'config' => $config,
3227              ))
3228      );
3229      $PAGE->requires->yui_module('moodle-course-toolboxes',
3230              'M.course.init_section_toolbox',
3231              array(array(
3232                  'courseid' => $course->id,
3233                  'format' => $course->format,
3234                  'ajaxurl' => $config->sectionurl,
3235                  'config' => $config,
3236              ))
3237      );
3238  
3239      // Include course dragdrop
3240      if (course_format_uses_sections($course->format)) {
3241          $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
3242              array(array(
3243                  'courseid' => $course->id,
3244                  'ajaxurl' => $config->sectionurl,
3245                  'config' => $config,
3246              )), null, true);
3247  
3248          $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
3249              array(array(
3250                  'courseid' => $course->id,
3251                  'ajaxurl' => $config->resourceurl,
3252                  'config' => $config,
3253              )), null, true);
3254      }
3255  
3256      // Require various strings for the command toolbox
3257      $PAGE->requires->strings_for_js(array(
3258              'moveleft',
3259              'deletechecktype',
3260              'deletechecktypename',
3261              'edittitle',
3262              'edittitleinstructions',
3263              'show',
3264              'hide',
3265              'groupsnone',
3266              'groupsvisible',
3267              'groupsseparate',
3268              'clicktochangeinbrackets',
3269              'markthistopic',
3270              'markedthistopic',
3271              'movesection',
3272              'movecoursemodule',
3273              'movecoursesection',
3274              'movecontent',
3275              'tocontent',
3276              'emptydragdropregion',
3277              'afterresource',
3278              'aftersection',
3279              'totopofsection',
3280          ), 'moodle');
3281  
3282      // Include section-specific strings for formats which support sections.
3283      if (course_format_uses_sections($course->format)) {
3284          $PAGE->requires->strings_for_js(array(
3285                  'showfromothers',
3286                  'hidefromothers',
3287              ), 'format_' . $course->format);
3288      }
3289  
3290      // For confirming resource deletion we need the name of the module in question
3291      foreach ($usedmodules as $module => $modname) {
3292          $PAGE->requires->string_for_js('pluginname', $module);
3293      }
3294  
3295      // Load drag and drop upload AJAX.
3296      require_once($CFG->dirroot.'/course/dnduploadlib.php');
3297      dndupload_add_to_course($course, $enabledmodules);
3298  
3299      return true;
3300  }
3301  
3302  /**
3303   * Returns the sorted list of available course formats, filtered by enabled if necessary
3304   *
3305   * @param bool $enabledonly return only formats that are enabled
3306   * @return array array of sorted format names
3307   */
3308  function get_sorted_course_formats($enabledonly = false) {
3309      global $CFG;
3310      $formats = core_component::get_plugin_list('format');
3311  
3312      if (!empty($CFG->format_plugins_sortorder)) {
3313          $order = explode(',', $CFG->format_plugins_sortorder);
3314          $order = array_merge(array_intersect($order, array_keys($formats)),
3315                      array_diff(array_keys($formats), $order));
3316      } else {
3317          $order = array_keys($formats);
3318      }
3319      if (!$enabledonly) {
3320          return $order;
3321      }
3322      $sortedformats = array();
3323      foreach ($order as $formatname) {
3324          if (!get_config('format_'.$formatname, 'disabled')) {
3325              $sortedformats[] = $formatname;
3326          }
3327      }
3328      return $sortedformats;
3329  }
3330  
3331  /**
3332   * The URL to use for the specified course (with section)
3333   *
3334   * @param int|stdClass $courseorid The course to get the section name for (either object or just course id)
3335   * @param int|stdClass $section Section object from database or just field course_sections.section
3336   *     if omitted the course view page is returned
3337   * @param array $options options for view URL. At the moment core uses:
3338   *     'navigation' (bool) if true and section has no separate page, the function returns null
3339   *     'sr' (int) used by multipage formats to specify to which section to return
3340   * @return moodle_url The url of course
3341   */
3342  function course_get_url($courseorid, $section = null, $options = array()) {
3343      return course_get_format($courseorid)->get_view_url($section, $options);
3344  }
3345  
3346  /**
3347   * Create a module.
3348   *
3349   * It includes:
3350   *      - capability checks and other checks
3351   *      - create the module from the module info
3352   *
3353   * @param object $module
3354   * @return object the created module info
3355   * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
3356   */
3357  function create_module($moduleinfo) {
3358      global $DB, $CFG;
3359  
3360      require_once($CFG->dirroot . '/course/modlib.php');
3361  
3362      // Check manadatory attributs.
3363      $mandatoryfields = array('modulename', 'course', 'section', 'visible');
3364      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
3365          $mandatoryfields[] = 'introeditor';
3366      }
3367      foreach($mandatoryfields as $mandatoryfield) {
3368          if (!isset($moduleinfo->{$mandatoryfield})) {
3369              throw new moodle_exception('createmodulemissingattribut', '', '', $mandatoryfield);
3370          }
3371      }
3372  
3373      // Some additional checks (capability / existing instances).
3374      $course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST);
3375      list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section);
3376  
3377      // Add the module.
3378      $moduleinfo->module = $module->id;
3379      $moduleinfo = add_moduleinfo($moduleinfo, $course, null);
3380  
3381      return $moduleinfo;
3382  }
3383  
3384  /**
3385   * Update a module.
3386   *
3387   * It includes:
3388   *      - capability and other checks
3389   *      - update the module
3390   *
3391   * @param object $module
3392   * @return object the updated module info
3393   * @throws moodle_exception if current user is not allowed to update the module
3394   */
3395  function update_module($moduleinfo) {
3396      global $DB, $CFG;
3397  
3398      require_once($CFG->dirroot . '/course/modlib.php');
3399  
3400      // Check the course module exists.
3401      $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
3402  
3403      // Check the course exists.
3404      $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
3405  
3406      // Some checks (capaibility / existing instances).
3407      list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
3408  
3409      // Retrieve few information needed by update_moduleinfo.
3410      $moduleinfo->modulename = $cm->modname;
3411      if (!isset($moduleinfo->scale)) {
3412          $moduleinfo->scale = 0;
3413      }
3414      $moduleinfo->type = 'mod';
3415  
3416      // Update the module.
3417      list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null);
3418  
3419      return $moduleinfo;
3420  }
3421  
3422  /**
3423   * Duplicate a module on the course for ajax.
3424   *
3425   * @see mod_duplicate_module()
3426   * @param object $course The course
3427   * @param object $cm The course module to duplicate
3428   * @param int $sr The section to link back to (used for creating the links)
3429   * @throws moodle_exception if the plugin doesn't support duplication
3430   * @return Object containing:
3431   * - fullcontent: The HTML markup for the created CM
3432   * - cmid: The CMID of the newly created CM
3433   * - redirect: Whether to trigger a redirect following this change
3434   */
3435  function mod_duplicate_activity($course, $cm, $sr = null) {
3436      global $PAGE;
3437  
3438      $newcm = duplicate_module($course, $cm);
3439  
3440      $resp = new stdClass();
3441      if ($newcm) {
3442          $courserenderer = $PAGE->get_renderer('core', 'course');
3443          $completioninfo = new completion_info($course);
3444          $modulehtml = $courserenderer->course_section_cm($course, $completioninfo,
3445                  $newcm, null, array());
3446  
3447          $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr);
3448          $resp->cmid = $newcm->id;
3449      } else {
3450          // Trigger a redirect.
3451          $resp->redirect = true;
3452      }
3453      return $resp;
3454  }
3455  
3456  /**
3457   * Api to duplicate a module.
3458   *
3459   * @param object $course course object.
3460   * @param object $cm course module object to be duplicated.
3461   * @since Moodle 2.8
3462   *
3463   * @throws Exception
3464   * @throws coding_exception
3465   * @throws moodle_exception
3466   * @throws restore_controller_exception
3467   *
3468   * @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm.
3469   */
3470  function duplicate_module($course, $cm) {
3471      global $CFG, $DB, $USER;
3472      require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
3473      require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
3474      require_once($CFG->libdir . '/filelib.php');
3475  
3476      $a          = new stdClass();
3477      $a->modtype = get_string('modulename', $cm->modname);
3478      $a->modname = format_string($cm->name);
3479  
3480      if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) {
3481          throw new moodle_exception('duplicatenosupport', 'error', '', $a);
3482      }
3483  
3484      // Backup the activity.
3485  
3486      $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
3487              backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
3488  
3489      $backupid       = $bc->get_backupid();
3490      $backupbasepath = $bc->get_plan()->get_basepath();
3491  
3492      $bc->execute_plan();
3493  
3494      $bc->destroy();
3495  
3496      // Restore the backup immediately.
3497  
3498      $rc = new restore_controller($backupid, $course->id,
3499              backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
3500  
3501      $cmcontext = context_module::instance($cm->id);
3502      if (!$rc->execute_precheck()) {
3503          $precheckresults = $rc->get_precheck_results();
3504          if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
3505              if (empty($CFG->keeptempdirectoriesonbackup)) {
3506                  fulldelete($backupbasepath);
3507              }
3508          }
3509      }
3510  
3511      $rc->execute_plan();
3512  
3513      // Now a bit hacky part follows - we try to get the cmid of the newly
3514      // restored copy of the module.
3515      $newcmid = null;
3516      $tasks = $rc->get_plan()->get_tasks();
3517      foreach ($tasks as $task) {
3518          if (is_subclass_of($task, 'restore_activity_task')) {
3519              if ($task->get_old_contextid() == $cmcontext->id) {
3520                  $newcmid = $task->get_moduleid();
3521                  break;
3522              }
3523          }
3524      }
3525  
3526      // If we know the cmid of the new course module, let us move it
3527      // right below the original one. otherwise it will stay at the
3528      // end of the section.
3529      if ($newcmid) {
3530          $info = get_fast_modinfo($course);
3531          $newcm = $info->get_cm($newcmid);
3532          $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
3533          moveto_module($newcm, $section, $cm);
3534          moveto_module($cm, $section, $newcm);
3535  
3536          // Trigger course module created event. We can trigger the event only if we know the newcmid.
3537          $event = \core\event\course_module_created::create_from_cm(