[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/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 handles 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->dirroot.'/blog/lib.php');
1652      require_once($CFG->dirroot.'/calendar/lib.php');
1653      require_once($CFG->dirroot . '/tag/lib.php');
1654  
1655      // Get the course module.
1656      if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
1657          return true;
1658      }
1659  
1660      // Get the module context.
1661      $modcontext = context_module::instance($cm->id);
1662  
1663      // Get the course module name.
1664      $modulename = $DB->get_field('modules', 'name', array('id' => $cm->module), MUST_EXIST);
1665  
1666      // Get the file location of the delete_instance function for this module.
1667      $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
1668  
1669      // Include the file required to call the delete_instance function for this module.
1670      if (file_exists($modlib)) {
1671          require_once($modlib);
1672      } else {
1673          throw new moodle_exception('cannotdeletemodulemissinglib', '', '', null,
1674              "Cannot delete this module as the file mod/$modulename/lib.php is missing.");
1675      }
1676  
1677      $deleteinstancefunction = $modulename . '_delete_instance';
1678  
1679      // Ensure the delete_instance function exists for this module.
1680      if (!function_exists($deleteinstancefunction)) {
1681          throw new moodle_exception('cannotdeletemodulemissingfunc', '', '', null,
1682              "Cannot delete this module as the function {$modulename}_delete_instance is missing in mod/$modulename/lib.php.");
1683      }
1684  
1685      // Call the delete_instance function, if it returns false throw an exception.
1686      if (!$deleteinstancefunction($cm->instance)) {
1687          throw new moodle_exception('cannotdeletemoduleinstance', '', '', null,
1688              "Cannot delete the module $modulename (instance).");
1689      }
1690  
1691      // Remove all module files in case modules forget to do that.
1692      $fs = get_file_storage();
1693      $fs->delete_area_files($modcontext->id);
1694  
1695      // Delete events from calendar.
1696      if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
1697          foreach($events as $event) {
1698              $calendarevent = calendar_event::load($event->id);
1699              $calendarevent->delete();
1700          }
1701      }
1702  
1703      // Delete grade items, outcome items and grades attached to modules.
1704      if ($grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $modulename,
1705                                                     'iteminstance' => $cm->instance, 'courseid' => $cm->course))) {
1706          foreach ($grade_items as $grade_item) {
1707              $grade_item->delete('moddelete');
1708          }
1709      }
1710  
1711      // Delete completion and availability data; it is better to do this even if the
1712      // features are not turned on, in case they were turned on previously (these will be
1713      // very quick on an empty table).
1714      $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
1715      $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
1716                                                              'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
1717  
1718      // Delete all tag instances associated with the instance of this module.
1719      tag_delete_instances('mod_' . $modulename, $modcontext->id);
1720  
1721      // Delete the context.
1722      context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
1723  
1724      // Delete the module from the course_modules table.
1725      $DB->delete_records('course_modules', array('id' => $cm->id));
1726  
1727      // Delete module from that section.
1728      if (!delete_mod_from_section($cm->id, $cm->section)) {
1729          throw new moodle_exception('cannotdeletemodulefromsection', '', '', null,
1730              "Cannot delete the module $modulename (instance) from section.");
1731      }
1732  
1733      // Trigger event for course module delete action.
1734      $event = \core\event\course_module_deleted::create(array(
1735          'courseid' => $cm->course,
1736          'context'  => $modcontext,
1737          'objectid' => $cm->id,
1738          'other'    => array(
1739              'modulename' => $modulename,
1740              'instanceid'   => $cm->instance,
1741          )
1742      ));
1743      $event->add_record_snapshot('course_modules', $cm);
1744      $event->trigger();
1745      rebuild_course_cache($cm->course, true);
1746  }
1747  
1748  function delete_mod_from_section($modid, $sectionid) {
1749      global $DB;
1750  
1751      if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) {
1752  
1753          $modarray = explode(",", $section->sequence);
1754  
1755          if ($key = array_keys ($modarray, $modid)) {
1756              array_splice($modarray, $key[0], 1);
1757              $newsequence = implode(",", $modarray);
1758              $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
1759              rebuild_course_cache($section->course, true);
1760              return true;
1761          } else {
1762              return false;
1763          }
1764  
1765      }
1766      return false;
1767  }
1768  
1769  /**
1770   * Moves a section within a course, from a position to another.
1771   * Be very careful: $section and $destination refer to section number,
1772   * not id!.
1773   *
1774   * @param object $course
1775   * @param int $section Section number (not id!!!)
1776   * @param int $destination
1777   * @return boolean Result
1778   */
1779  function move_section_to($course, $section, $destination) {
1780  /// Moves a whole course section up and down within the course
1781      global $USER, $DB;
1782  
1783      if (!$destination && $destination != 0) {
1784          return true;
1785      }
1786  
1787      // compartibility with course formats using field 'numsections'
1788      $courseformatoptions = course_get_format($course)->get_format_options();
1789      if ((array_key_exists('numsections', $courseformatoptions) &&
1790              ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
1791          return false;
1792      }
1793  
1794      // Get all sections for this course and re-order them (2 of them should now share the same section number)
1795      if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
1796              'section ASC, id ASC', 'id, section')) {
1797          return false;
1798      }
1799  
1800      $movedsections = reorder_sections($sections, $section, $destination);
1801  
1802      // Update all sections. Do this in 2 steps to avoid breaking database
1803      // uniqueness constraint
1804      $transaction = $DB->start_delegated_transaction();
1805      foreach ($movedsections as $id => $position) {
1806          if ($sections[$id] !== $position) {
1807              $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
1808          }
1809      }
1810      foreach ($movedsections as $id => $position) {
1811          if ($sections[$id] !== $position) {
1812              $DB->set_field('course_sections', 'section', $position, array('id' => $id));
1813          }
1814      }
1815  
1816      // If we move the highlighted section itself, then just highlight the destination.
1817      // Adjust the higlighted section location if we move something over it either direction.
1818      if ($section == $course->marker) {
1819          course_set_marker($course->id, $destination);
1820      } elseif ($section > $course->marker && $course->marker >= $destination) {
1821          course_set_marker($course->id, $course->marker+1);
1822      } elseif ($section < $course->marker && $course->marker <= $destination) {
1823          course_set_marker($course->id, $course->marker-1);
1824      }
1825  
1826      $transaction->allow_commit();
1827      rebuild_course_cache($course->id, true);
1828      return true;
1829  }
1830  
1831  /**
1832   * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
1833   * an original position number and a target position number, rebuilds the array so that the
1834   * move is made without any duplication of section positions.
1835   * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
1836   * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
1837   *
1838   * @param array $sections
1839   * @param int $origin_position
1840   * @param int $target_position
1841   * @return array
1842   */
1843  function reorder_sections($sections, $origin_position, $target_position) {
1844      if (!is_array($sections)) {
1845          return false;
1846      }
1847  
1848      // We can't move section position 0
1849      if ($origin_position < 1) {
1850          echo "We can't move section position 0";
1851          return false;
1852      }
1853  
1854      // Locate origin section in sections array
1855      if (!$origin_key = array_search($origin_position, $sections)) {
1856          echo "searched position not in sections array";
1857          return false; // searched position not in sections array
1858      }
1859  
1860      // Extract origin section
1861      $origin_section = $sections[$origin_key];
1862      unset($sections[$origin_key]);
1863  
1864      // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
1865      $found = false;
1866      $append_array = array();
1867      foreach ($sections as $id => $position) {
1868          if ($found) {
1869              $append_array[$id] = $position;
1870              unset($sections[$id]);
1871          }
1872          if ($position == $target_position) {
1873              if ($target_position < $origin_position) {
1874                  $append_array[$id] = $position;
1875                  unset($sections[$id]);
1876              }
1877              $found = true;
1878          }
1879      }
1880  
1881      // Append moved section
1882      $sections[$origin_key] = $origin_section;
1883  
1884      // Append rest of array (if applicable)
1885      if (!empty($append_array)) {
1886          foreach ($append_array as $id => $position) {
1887              $sections[$id] = $position;
1888          }
1889      }
1890  
1891      // Renumber positions
1892      $position = 0;
1893      foreach ($sections as $id => $p) {
1894          $sections[$id] = $position;
1895          $position++;
1896      }
1897  
1898      return $sections;
1899  
1900  }
1901  
1902  /**
1903   * Move the module object $mod to the specified $section
1904   * If $beforemod exists then that is the module
1905   * before which $modid should be inserted
1906   *
1907   * @param stdClass|cm_info $mod
1908   * @param stdClass|section_info $section
1909   * @param int|stdClass $beforemod id or object with field id corresponding to the module
1910   *     before which the module needs to be included. Null for inserting in the
1911   *     end of the section
1912   * @return int new value for module visibility (0 or 1)
1913   */
1914  function moveto_module($mod, $section, $beforemod=NULL) {
1915      global $OUTPUT, $DB;
1916  
1917      // Current module visibility state - return value of this function.
1918      $modvisible = $mod->visible;
1919  
1920      // Remove original module from original section.
1921      if (! delete_mod_from_section($mod->id, $mod->section)) {
1922          echo $OUTPUT->notification("Could not delete module from existing section");
1923      }
1924  
1925      // If moving to a hidden section then hide module.
1926      if ($mod->section != $section->id) {
1927          if (!$section->visible && $mod->visible) {
1928              // Module was visible but must become hidden after moving to hidden section.
1929              $modvisible = 0;
1930              set_coursemodule_visible($mod->id, 0);
1931              // Set visibleold to 1 so module will be visible when section is made visible.
1932              $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
1933          }
1934          if ($section->visible && !$mod->visible) {
1935              // Hidden module was moved to the visible section, restore the module visibility from visibleold.
1936              set_coursemodule_visible($mod->id, $mod->visibleold);
1937              $modvisible = $mod->visibleold;
1938          }
1939      }
1940  
1941      // Add the module into the new section.
1942      course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod);
1943      return $modvisible;
1944  }
1945  
1946  /**
1947   * Returns the list of all editing actions that current user can perform on the module
1948   *
1949   * @param cm_info $mod The module to produce editing buttons for
1950   * @param int $indent The current indenting (default -1 means no move left-right actions)
1951   * @param int $sr The section to link back to (used for creating the links)
1952   * @return array array of action_link or pix_icon objects
1953   */
1954  function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
1955      global $COURSE, $SITE;
1956  
1957      static $str;
1958  
1959      $coursecontext = context_course::instance($mod->course);
1960      $modcontext = context_module::instance($mod->id);
1961  
1962      $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
1963      $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
1964  
1965      // No permission to edit anything.
1966      if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
1967          return array();
1968      }
1969  
1970      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
1971  
1972      if (!isset($str)) {
1973          $str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
1974              'editsettings', 'duplicate', 'hide', 'show'), 'moodle');
1975          $str->assign         = get_string('assignroles', 'role');
1976          $str->groupsnone     = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
1977          $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
1978          $str->groupsvisible  = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
1979      }
1980  
1981      $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
1982  
1983      if ($sr !== null) {
1984          $baseurl->param('sr', $sr);
1985      }
1986      $actions = array();
1987  
1988      // Update.
1989      if ($hasmanageactivities) {
1990          $actions['update'] = new action_menu_link_secondary(
1991              new moodle_url($baseurl, array('update' => $mod->id)),
1992              new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1993              $str->editsettings,
1994              array('class' => 'editing_update', 'data-action' => 'update')
1995          );
1996      }
1997  
1998      // Indent.
1999      if ($hasmanageactivities && $indent >= 0) {
2000          $indentlimits = new stdClass();
2001          $indentlimits->min = 0;
2002          $indentlimits->max = 16;
2003          if (right_to_left()) {   // Exchange arrows on RTL
2004              $rightarrow = 't/left';
2005              $leftarrow  = 't/right';
2006          } else {
2007              $rightarrow = 't/right';
2008              $leftarrow  = 't/left';
2009          }
2010  
2011          if ($indent >= $indentlimits->max) {
2012              $enabledclass = 'hidden';
2013          } else {
2014              $enabledclass = '';
2015          }
2016          $actions['moveright'] = new action_menu_link_secondary(
2017              new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
2018              new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2019              $str->moveright,
2020              array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true)
2021          );
2022  
2023          if ($indent <= $indentlimits->min) {
2024              $enabledclass = 'hidden';
2025          } else {
2026              $enabledclass = '';
2027          }
2028          $actions['moveleft'] = new action_menu_link_secondary(
2029              new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
2030              new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2031              $str->moveleft,
2032              array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true)
2033          );
2034  
2035      }
2036  
2037      // Hide/Show.
2038      if (has_capability('moodle/course:activityvisibility', $modcontext)) {
2039          if ($mod->visible) {
2040              $actions['hide'] = new action_menu_link_secondary(
2041                  new moodle_url($baseurl, array('hide' => $mod->id)),
2042                  new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2043                  $str->hide,
2044                  array('class' => 'editing_hide', 'data-action' => 'hide')
2045              );
2046          } else {
2047              $actions['show'] = new action_menu_link_secondary(
2048                  new moodle_url($baseurl, array('show' => $mod->id)),
2049                  new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2050                  $str->show,
2051                  array('class' => 'editing_show', 'data-action' => 'show')
2052              );
2053          }
2054      }
2055  
2056      // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
2057      // Note that restoring on front page is never allowed.
2058      if ($mod->course != SITEID && has_all_capabilities($dupecaps, $coursecontext) &&
2059              plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
2060          $actions['duplicate'] = new action_menu_link_secondary(
2061              new moodle_url($baseurl, array('duplicate' => $mod->id)),
2062              new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2063              $str->duplicate,
2064              array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr)
2065          );
2066      }
2067  
2068      // Groupmode.
2069      if ($hasmanageactivities && !$mod->coursegroupmodeforce) {
2070          if (plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
2071              if ($mod->effectivegroupmode == SEPARATEGROUPS) {
2072                  $nextgroupmode = VISIBLEGROUPS;
2073                  $grouptitle = $str->groupsseparate;
2074                  $actionname = 'groupsseparate';
2075                  $groupimage = 'i/groups';
2076              } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
2077                  $nextgroupmode = NOGROUPS;
2078                  $grouptitle = $str->groupsvisible;
2079                  $actionname = 'groupsvisible';
2080                  $groupimage = 'i/groupv';
2081              } else {
2082                  $nextgroupmode = SEPARATEGROUPS;
2083                  $grouptitle = $str->groupsnone;
2084                  $actionname = 'groupsnone';
2085                  $groupimage = 'i/groupn';
2086              }
2087  
2088              $actions[$actionname] = new action_menu_link_primary(
2089                  new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
2090                  new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')),
2091                  $grouptitle,
2092                  array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive')
2093              );
2094          } else {
2095              $actions['nogroupsupport'] = new action_menu_filler();
2096          }
2097      }
2098  
2099      // Assign.
2100      if (has_capability('moodle/role:assign', $modcontext)){
2101          $actions['assign'] = new action_menu_link_secondary(
2102              new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
2103              new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2104              $str->assign,
2105              array('class' => 'editing_assign', 'data-action' => 'assignroles')
2106          );
2107      }
2108  
2109      // Delete.
2110      if ($hasmanageactivities) {
2111          $actions['delete'] = new action_menu_link_secondary(
2112              new moodle_url($baseurl, array('delete' => $mod->id)),
2113              new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2114              $str->delete,
2115              array('class' => 'editing_delete', 'data-action' => 'delete')
2116          );
2117      }
2118  
2119      return $actions;
2120  }
2121  
2122  /**
2123   * Returns the rename action.
2124   *
2125   * @param cm_info $mod The module to produce editing buttons for
2126   * @param int $sr The section to link back to (used for creating the links)
2127   * @return The markup for the rename action, or an empty string if not available.
2128   */
2129  function course_get_cm_rename_action(cm_info $mod, $sr = null) {
2130      global $COURSE, $OUTPUT;
2131  
2132      static $str;
2133      static $baseurl;
2134  
2135      $modcontext = context_module::instance($mod->id);
2136      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2137  
2138      if (!isset($str)) {
2139          $str = get_strings(array('edittitle'));
2140      }
2141  
2142      if (!isset($baseurl)) {
2143          $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2144      }
2145  
2146      if ($sr !== null) {
2147          $baseurl->param('sr', $sr);
2148      }
2149  
2150      // AJAX edit title.
2151      if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
2152                  (($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
2153          // we will not display link if we are on some other-course page (where we should not see this module anyway)
2154          return html_writer::span(
2155              html_writer::link(
2156                  new moodle_url($baseurl, array('update' => $mod->id)),
2157                  $OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
2158                  array(
2159                      'class' => 'editing_title',
2160                      'data-action' => 'edittitle',
2161                      'title' => $str->edittitle,
2162                  )
2163              )
2164          );
2165      }
2166      return '';
2167  }
2168  
2169  /**
2170   * Returns the move action.
2171   *
2172   * @param cm_info $mod The module to produce a move button for
2173   * @param int $sr The section to link back to (used for creating the links)
2174   * @return The markup for the move action, or an empty string if not available.
2175   */
2176  function course_get_cm_move(cm_info $mod, $sr = null) {
2177      global $OUTPUT;
2178  
2179      static $str;
2180      static $baseurl;
2181  
2182      $modcontext = context_module::instance($mod->id);
2183      $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
2184  
2185      if (!isset($str)) {
2186          $str = get_strings(array('move'));
2187      }
2188  
2189      if (!isset($baseurl)) {
2190          $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
2191  
2192          if ($sr !== null) {
2193              $baseurl->param('sr', $sr);
2194          }
2195      }
2196  
2197      if ($hasmanageactivities) {
2198          $pixicon = 'i/dragdrop';
2199  
2200          if (!course_ajax_enabled($mod->get_course())) {
2201              // Override for course frontpage until we get drag/drop working there.
2202              $pixicon = 't/move';
2203          }
2204  
2205          return html_writer::link(
2206              new moodle_url($baseurl, array('copy' => $mod->id)),
2207              $OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
2208              array('class' => 'editing_move', 'data-action' => 'move')
2209          );
2210      }
2211      return '';
2212  }
2213  
2214  /**
2215   * given a course object with shortname & fullname, this function will
2216   * truncate the the number of chars allowed and add ... if it was too long
2217   */
2218  function course_format_name ($course,$max=100) {
2219  
2220      $context = context_course::instance($course->id);
2221      $shortname = format_string($course->shortname, true, array('context' => $context));
2222      $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
2223      $str = $shortname.': '. $fullname;
2224      if (core_text::strlen($str) <= $max) {
2225          return $str;
2226      }
2227      else {
2228          return core_text::substr($str,0,$max-3).'...';
2229      }
2230  }
2231  
2232  /**
2233   * Is the user allowed to add this type of module to this course?
2234   * @param object $course the course settings. Only $course->id is used.
2235   * @param string $modname the module name. E.g. 'forum' or 'quiz'.
2236   * @return bool whether the current user is allowed to add this type of module to this course.
2237   */
2238  function course_allowed_module($course, $modname) {
2239      if (is_numeric($modname)) {
2240          throw new coding_exception('Function course_allowed_module no longer
2241                  supports numeric module ids. Please update your code to pass the module name.');
2242      }
2243  
2244      $capability = 'mod/' . $modname . ':addinstance';
2245      if (!get_capability_info($capability)) {
2246          // Debug warning that the capability does not exist, but no more than once per page.
2247          static $warned = array();
2248          $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
2249          if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
2250              debugging('The module ' . $modname . ' does not define the standard capability ' .
2251                      $capability , DEBUG_DEVELOPER);
2252              $warned[$modname] = 1;
2253          }
2254  
2255          // If the capability does not exist, the module can always be added.
2256          return true;
2257      }
2258  
2259      $coursecontext = context_course::instance($course->id);
2260      return has_capability($capability, $coursecontext);
2261  }
2262  
2263  /**
2264   * Efficiently moves many courses around while maintaining
2265   * sortorder in order.
2266   *
2267   * @param array $courseids is an array of course ids
2268   * @param int $categoryid
2269   * @return bool success
2270   */
2271  function move_courses($courseids, $categoryid) {
2272      global $DB;
2273  
2274      if (empty($courseids)) {
2275          // Nothing to do.
2276          return false;
2277      }
2278  
2279      if (!$category = $DB->get_record('course_categories', array('id' => $categoryid))) {
2280          return false;
2281      }
2282  
2283      $courseids = array_reverse($courseids);
2284      $newparent = context_coursecat::instance($category->id);
2285      $i = 1;
2286  
2287      list($where, $params) = $DB->get_in_or_equal($courseids);
2288      $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params, '', 'id, category, shortname, fullname');
2289      foreach ($dbcourses as $dbcourse) {
2290          $course = new stdClass();
2291          $course->id = $dbcourse->id;
2292          $course->category  = $category->id;
2293          $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
2294          if ($category->visible == 0) {
2295              // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
2296              // to previous state if somebody unhides the category.
2297              $course->visible = 0;
2298          }
2299  
2300          $DB->update_record('course', $course);
2301  
2302          // Update context, so it can be passed to event.
2303          $context = context_course::instance($course->id);
2304          $context->update_moved($newparent);
2305  
2306          // Trigger a course updated event.
2307          $event = \core\event\course_updated::create(array(
2308              'objectid' => $course->id,
2309              'context' => context_course::instance($course->id),
2310              'other' => array('shortname' => $dbcourse->shortname,
2311                               'fullname' => $dbcourse->fullname)
2312          ));
2313          $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
2314          $event->trigger();
2315      }
2316      fix_course_sortorder();
2317      cache_helper::purge_by_event('changesincourse');
2318  
2319      return true;
2320  }
2321  
2322  /**
2323   * Returns the display name of the given section that the course prefers
2324   *
2325   * Implementation of this function is provided by course format
2326   * @see format_base::get_section_name()
2327   *
2328   * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
2329   * @param int|stdClass $section Section object from database or just field course_sections.section
2330   * @return string Display name that the course format prefers, e.g. "Week 2"
2331   */
2332  function get_section_name($courseorid, $section) {
2333      return course_get_format($courseorid)->get_section_name($section);
2334  }
2335  
2336  /**
2337   * Tells if current course format uses sections
2338   *
2339   * @param string $format Course format ID e.g. 'weeks' $course->format
2340   * @return bool
2341   */
2342  function course_format_uses_sections($format) {
2343      $course = new stdClass();
2344      $course->format = $format;
2345      return course_get_format($course)->uses_sections();
2346  }
2347  
2348  /**
2349   * Returns the information about the ajax support in the given source format
2350   *
2351   * The returned object's property (boolean)capable indicates that
2352   * the course format supports Moodle course ajax features.
2353   *
2354   * @param string $format
2355   * @return stdClass
2356   */
2357  function course_format_ajax_support($format) {
2358      $course = new stdClass();
2359      $course->format = $format;
2360      return course_get_format($course)->supports_ajax();
2361  }
2362  
2363  /**
2364   * Can the current user delete this course?
2365   * Course creators have exception,
2366   * 1 day after the creation they can sill delete the course.
2367   * @param int $courseid
2368   * @return boolean
2369   */
2370  function can_delete_course($courseid) {
2371      global $USER;
2372  
2373      $context = context_course::instance($courseid);
2374  
2375      if (has_capability('moodle/course:delete', $context)) {
2376          return true;
2377      }
2378  
2379      // hack: now try to find out if creator created this course recently (1 day)
2380      if (!has_capability('moodle/course:create', $context)) {
2381          return false;
2382      }
2383  
2384      $since = time() - 60*60*24;
2385      $course = get_course($courseid);
2386  
2387      if ($course->timecreated < $since) {
2388          return false; // Return if the course was not created in last 24 hours.
2389      }
2390  
2391      $logmanger = get_log_manager();
2392      $readers = $logmanger->get_readers('\core\log\sql_select_reader');
2393      $reader = reset($readers);
2394  
2395      if (empty($reader)) {
2396          return false; // No log reader found.
2397      }
2398  
2399      // A proper reader.
2400      $select = "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since";
2401      $params = array('userid' => $USER->id, 'since' => $since, 'courseid' => $course->id, 'eventname' => '\core\event\course_created');
2402  
2403      return (bool)$reader->get_events_select_count($select, $params);
2404  }
2405  
2406  /**
2407   * Save the Your name for 'Some role' strings.
2408   *
2409   * @param integer $courseid the id of this course.
2410   * @param array $data the data that came from the course settings form.
2411   */
2412  function save_local_role_names($courseid, $data) {
2413      global $DB;
2414      $context = context_course::instance($courseid);
2415  
2416      foreach ($data as $fieldname => $value) {
2417          if (strpos($fieldname, 'role_') !== 0) {
2418              continue;
2419          }
2420          list($ignored, $roleid) = explode('_', $fieldname);
2421  
2422          // make up our mind whether we want to delete, update or insert
2423          if (!$value) {
2424              $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
2425  
2426          } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
2427              $rolename->name = $value;
2428              $DB->update_record('role_names', $rolename);
2429  
2430          } else {
2431              $rolename = new stdClass;
2432              $rolename->contextid = $context->id;
2433              $rolename->roleid = $roleid;
2434              $rolename->name = $value;
2435              $DB->insert_record('role_names', $rolename);
2436          }
2437      }
2438  }
2439  
2440  /**
2441   * Returns options to use in course overviewfiles filemanager
2442   *
2443   * @param null|stdClass|course_in_list|int $course either object that has 'id' property or just the course id;
2444   *     may be empty if course does not exist yet (course create form)
2445   * @return array|null array of options such as maxfiles, maxbytes, accepted_types, etc.
2446   *     or null if overviewfiles are disabled
2447   */
2448  function course_overviewfiles_options($course) {
2449      global $CFG;
2450      if (empty($CFG->courseoverviewfileslimit)) {
2451          return null;
2452      }
2453      $accepted_types = preg_split('/\s*,\s*/', trim($CFG->courseoverviewfilesext), -1, PREG_SPLIT_NO_EMPTY);
2454      if (in_array('*', $accepted_types) || empty($accepted_types)) {
2455          $accepted_types = '*';
2456      } else {
2457          // Since config for $CFG->courseoverviewfilesext is a text box, human factor must be considered.
2458          // Make sure extensions are prefixed with dot unless they are valid typegroups
2459          foreach ($accepted_types as $i => $type) {
2460              if (substr($type, 0, 1) !== '.') {
2461                  require_once($CFG->libdir. '/filelib.php');
2462                  if (!count(file_get_typegroup('extension', $type))) {
2463                      // It does not start with dot and is not a valid typegroup, this is most likely extension.
2464                      $accepted_types[$i] = '.'. $type;
2465                      $corrected = true;
2466                  }
2467              }
2468          }
2469          if (!empty($corrected)) {
2470              set_config('courseoverviewfilesext', join(',', $accepted_types));
2471          }
2472      }
2473      $options = array(
2474          'maxfiles' => $CFG->courseoverviewfileslimit,
2475          'maxbytes' => $CFG->maxbytes,
2476          'subdirs' => 0,
2477          'accepted_types' => $accepted_types
2478      );
2479      if (!empty($course->id)) {
2480          $options['context'] = context_course::instance($course->id);
2481      } else if (is_int($course) && $course > 0) {
2482          $options['context'] = context_course::instance($course);
2483      }
2484      return $options;
2485  }
2486  
2487  /**
2488   * Create a course and either return a $course object
2489   *
2490   * Please note this functions does not verify any access control,
2491   * the calling code is responsible for all validation (usually it is the form definition).
2492   *
2493   * @param array $editoroptions course description editor options
2494   * @param object $data  - all the data needed for an entry in the 'course' table
2495   * @return object new course instance
2496   */
2497  function create_course($data, $editoroptions = NULL) {
2498      global $DB;
2499  
2500      //check the categoryid - must be given for all new courses
2501      $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
2502  
2503      // Check if the shortname already exists.
2504      if (!empty($data->shortname)) {
2505          if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
2506              throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2507          }
2508      }
2509  
2510      // Check if the idnumber already exists.
2511      if (!empty($data->idnumber)) {
2512          if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
2513              throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2514          }
2515      }
2516  
2517      // Check if timecreated is given.
2518      $data->timecreated  = !empty($data->timecreated) ? $data->timecreated : time();
2519      $data->timemodified = $data->timecreated;
2520  
2521      // place at beginning of any category
2522      $data->sortorder = 0;
2523  
2524      if ($editoroptions) {
2525          // summary text is updated later, we need context to store the files first
2526          $data->summary = '';
2527          $data->summary_format = FORMAT_HTML;
2528      }
2529  
2530      if (!isset($data->visible)) {
2531          // data not from form, add missing visibility info
2532          $data->visible = $category->visible;
2533      }
2534      $data->visibleold = $data->visible;
2535  
2536      $newcourseid = $DB->insert_record('course', $data);
2537      $context = context_course::instance($newcourseid, MUST_EXIST);
2538  
2539      if ($editoroptions) {
2540          // Save the files used in the summary editor and store
2541          $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2542          $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
2543          $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
2544      }
2545      if ($overviewfilesoptions = course_overviewfiles_options($newcourseid)) {
2546          // Save the course overviewfiles
2547          $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2548      }
2549  
2550      // update course format options
2551      course_get_format($newcourseid)->update_course_format_options($data);
2552  
2553      $course = course_get_format($newcourseid)->get_course();
2554  
2555      // Setup the blocks
2556      blocks_add_default_course_blocks($course);
2557  
2558      // Create a default section.
2559      course_create_sections_if_missing($course, 0);
2560  
2561      fix_course_sortorder();
2562      // purge appropriate caches in case fix_course_sortorder() did not change anything
2563      cache_helper::purge_by_event('changesincourse');
2564  
2565      // new context created - better mark it as dirty
2566      $context->mark_dirty();
2567  
2568      // Save any custom role names.
2569      save_local_role_names($course->id, (array)$data);
2570  
2571      // set up enrolments
2572      enrol_course_updated(true, $course, $data);
2573  
2574      // Trigger a course created event.
2575      $event = \core\event\course_created::create(array(
2576          'objectid' => $course->id,
2577          'context' => context_course::instance($course->id),
2578          'other' => array('shortname' => $course->shortname,
2579                           'fullname' => $course->fullname)
2580      ));
2581      $event->trigger();
2582  
2583      return $course;
2584  }
2585  
2586  /**
2587   * Update a course.
2588   *
2589   * Please note this functions does not verify any access control,
2590   * the calling code is responsible for all validation (usually it is the form definition).
2591   *
2592   * @param object $data  - all the data needed for an entry in the 'course' table
2593   * @param array $editoroptions course description editor options
2594   * @return void
2595   */
2596  function update_course($data, $editoroptions = NULL) {
2597      global $DB;
2598  
2599      $data->timemodified = time();
2600  
2601      $oldcourse = course_get_format($data->id)->get_course();
2602      $context   = context_course::instance($oldcourse->id);
2603  
2604      if ($editoroptions) {
2605          $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
2606      }
2607      if ($overviewfilesoptions = course_overviewfiles_options($data->id)) {
2608          $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
2609      }
2610  
2611      // Check we don't have a duplicate shortname.
2612      if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
2613          if ($DB->record_exists_sql('SELECT id from {course} WHERE shortname = ? AND id <> ?', array($data->shortname, $data->id))) {
2614              throw new moodle_exception('shortnametaken', '', '', $data->shortname);
2615          }
2616      }
2617  
2618      // Check we don't have a duplicate idnumber.
2619      if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
2620          if ($DB->record_exists_sql('SELECT id from {course} WHERE idnumber = ? AND id <> ?', array($data->idnumber, $data->id))) {
2621              throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
2622          }
2623      }
2624  
2625      if (!isset($data->category) or empty($data->category)) {
2626          // prevent nulls and 0 in category field
2627          unset($data->category);
2628      }
2629      $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category);
2630  
2631      if (!isset($data->visible)) {
2632          // data not from form, add missing visibility info
2633          $data->visible = $oldcourse->visible;
2634      }
2635  
2636      if ($data->visible != $oldcourse->visible) {
2637          // reset the visibleold flag when manually hiding/unhiding course
2638          $data->visibleold = $data->visible;
2639          $changesincoursecat = true;
2640      } else {
2641          if ($movecat) {
2642              $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
2643              if (empty($newcategory->visible)) {
2644                  // make sure when moving into hidden category the course is hidden automatically
2645                  $data->visible = 0;
2646              }
2647          }
2648      }
2649  
2650      // Update with the new data
2651      $DB->update_record('course', $data);
2652      // make sure the modinfo cache is reset
2653      rebuild_course_cache($data->id);
2654  
2655      // update course format options with full course data
2656      course_get_format($data->id)->update_course_format_options($data, $oldcourse);
2657  
2658      $course = $DB->get_record('course', array('id'=>$data->id));
2659  
2660      if ($movecat) {
2661          $newparent = context_coursecat::instance($course->category);
2662          $context->update_moved($newparent);
2663      }
2664      $fixcoursesortorder = $movecat || (isset($data->sortorder) && ($oldcourse->sortorder != $data->sortorder));
2665      if ($fixcoursesortorder) {
2666          fix_course_sortorder();
2667      }
2668  
2669      // purge appropriate caches in case fix_course_sortorder() did not change anything
2670      cache_helper::purge_by_event('changesincourse');
2671      if ($changesincoursecat) {
2672          cache_helper::purge_by_event('changesincoursecat');
2673      }
2674  
2675      // Test for and remove blocks which aren't appropriate anymore
2676      blocks_remove_inappropriate($course);
2677  
2678      // Save any custom role names.
2679      save_local_role_names($course->id, $data);
2680  
2681      // update enrol settings
2682      enrol_course_updated(false, $course, $data);
2683  
2684      // Trigger a course updated event.
2685      $event = \core\event\course_updated::create(array(
2686          'objectid' => $course->id,
2687          'context' => context_course::instance($course->id),
2688          'other' => array('shortname' => $course->shortname,
2689                           'fullname' => $course->fullname)
2690      ));
2691  
2692      $event->set_legacy_logdata(array($course->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id));
2693      $event->trigger();
2694  
2695      if ($oldcourse->format !== $course->format) {
2696          // Remove all options stored for the previous format
2697          // We assume that new course format migrated everything it needed watching trigger
2698          // 'course_updated' and in method format_XXX::update_course_format_options()
2699          $DB->delete_records('course_format_options',
2700                  array('courseid' => $course->id, 'format' => $oldcourse->format));
2701      }
2702  }
2703  
2704  /**
2705   * Average number of participants
2706   * @return integer
2707   */
2708  function average_number_of_participants() {
2709      global $DB, $SITE;
2710  
2711      //count total of enrolments for visible course (except front page)
2712      $sql = 'SELECT COUNT(*) FROM (
2713          SELECT DISTINCT ue.userid, e.courseid
2714          FROM {user_enrolments} ue, {enrol} e, {course} c
2715          WHERE ue.enrolid = e.id
2716              AND e.courseid <> :siteid
2717              AND c.id = e.courseid
2718              AND c.visible = 1) total';
2719      $params = array('siteid' => $SITE->id);
2720      $enrolmenttotal = $DB->count_records_sql($sql, $params);
2721  
2722  
2723      //count total of visible courses (minus front page)
2724      $coursetotal = $DB->count_records('course', array('visible' => 1));
2725      $coursetotal = $coursetotal - 1 ;
2726  
2727      //average of enrolment
2728      if (empty($coursetotal)) {
2729          $participantaverage = 0;
2730      } else {
2731          $participantaverage = $enrolmenttotal / $coursetotal;
2732      }
2733  
2734      return $participantaverage;
2735  }
2736  
2737  /**
2738   * Average number of course modules
2739   * @return integer
2740   */
2741  function average_number_of_courses_modules() {
2742      global $DB, $SITE;
2743  
2744      //count total of visible course module (except front page)
2745      $sql = 'SELECT COUNT(*) FROM (
2746          SELECT cm.course, cm.module
2747          FROM {course} c, {course_modules} cm
2748          WHERE c.id = cm.course
2749              AND c.id <> :siteid
2750              AND cm.visible = 1
2751              AND c.visible = 1) total';
2752      $params = array('siteid' => $SITE->id);
2753      $moduletotal = $DB->count_records_sql($sql, $params);
2754  
2755  
2756      //count total of visible courses (minus front page)
2757      $coursetotal = $DB->count_records('course', array('visible' => 1));
2758      $coursetotal = $coursetotal - 1 ;
2759  
2760      //average of course module
2761      if (empty($coursetotal)) {
2762          $coursemoduleaverage = 0;
2763      } else {
2764          $coursemoduleaverage = $moduletotal / $coursetotal;
2765      }
2766  
2767      return $coursemoduleaverage;
2768  }
2769  
2770  /**
2771   * This class pertains to course requests and contains methods associated with
2772   * create, approving, and removing course requests.
2773   *
2774   * Please note we do not allow embedded images here because there is no context
2775   * to store them with proper access control.
2776   *
2777   * @copyright 2009 Sam Hemelryk
2778   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2779   * @since Moodle 2.0
2780   *
2781   * @property-read int $id
2782   * @property-read string $fullname
2783   * @property-read string $shortname
2784   * @property-read string $summary
2785   * @property-read int $summaryformat
2786   * @property-read int $summarytrust
2787   * @property-read string $reason
2788   * @property-read int $requester
2789   */
2790  class course_request {
2791  
2792      /**
2793       * This is the stdClass that stores the properties for the course request
2794       * and is externally accessed through the __get magic method
2795       * @var stdClass
2796       */
2797      protected $properties;
2798  
2799      /**
2800       * An array of options for the summary editor used by course request forms.
2801       * This is initially set by {@link summary_editor_options()}
2802       * @var array
2803       * @static
2804       */
2805      protected static $summaryeditoroptions;
2806  
2807      /**
2808       * Static function to prepare the summary editor for working with a course
2809       * request.
2810       *
2811       * @static
2812       * @param null|stdClass $data Optional, an object containing the default values
2813       *                       for the form, these may be modified when preparing the
2814       *                       editor so this should be called before creating the form
2815       * @return stdClass An object that can be used to set the default values for
2816       *                   an mforms form
2817       */
2818      public static function prepare($data=null) {
2819          if ($data === null) {
2820              $data = new stdClass;
2821          }
2822          $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
2823          return $data;
2824      }
2825  
2826      /**
2827       * Static function to create a new course request when passed an array of properties
2828       * for it.
2829       *
2830       * This function also handles saving any files that may have been used in the editor
2831       *
2832       * @static
2833       * @param stdClass $data
2834       * @return course_request The newly created course request
2835       */
2836      public static function create($data) {
2837          global $USER, $DB, $CFG;
2838          $data->requester = $USER->id;
2839  
2840          // Setting the default category if none set.
2841          if (empty($data->category) || empty($CFG->requestcategoryselection)) {
2842              $data->category = $CFG->defaultrequestcategory;
2843          }
2844  
2845          // Summary is a required field so copy the text over
2846          $data->summary       = $data->summary_editor['text'];
2847          $data->summaryformat = $data->summary_editor['format'];
2848  
2849          $data->id = $DB->insert_record('course_request', $data);
2850  
2851          // Create a new course_request object and return it
2852          $request = new course_request($data);
2853  
2854          // Notify the admin if required.
2855          if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
2856  
2857              $a = new stdClass;
2858              $a->link = "$CFG->wwwroot/course/pending.php";
2859              $a->user = fullname($USER);
2860              $subject = get_string('courserequest');
2861              $message = get_string('courserequestnotifyemail', 'admin', $a);
2862              foreach ($users as $user) {
2863                  $request->notify($user, $USER, 'courserequested', $subject, $message);
2864              }
2865          }
2866  
2867          return $request;
2868      }
2869  
2870      /**
2871       * Returns an array of options to use with a summary editor
2872       *
2873       * @uses course_request::$summaryeditoroptions
2874       * @return array An array of options to use with the editor
2875       */
2876      public static function summary_editor_options() {
2877          global $CFG;
2878          if (self::$summaryeditoroptions === null) {
2879              self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
2880          }
2881          return self::$summaryeditoroptions;
2882      }
2883  
2884      /**
2885       * Loads the properties for this course request object. Id is required and if
2886       * only id is provided then we load the rest of the properties from the database
2887       *
2888       * @param stdClass|int $properties Either an object containing properties
2889       *                      or the course_request id to load
2890       */
2891      public function __construct($properties) {
2892          global $DB;
2893          if (empty($properties->id)) {
2894              if (empty($properties)) {
2895                  throw new coding_exception('You must provide a course request id when creating a course_request object');
2896              }
2897              $id = $properties;
2898              $properties = new stdClass;
2899              $properties->id = (int)$id;
2900              unset($id);
2901          }
2902          if (empty($properties->requester)) {
2903              if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
2904                  print_error('unknowncourserequest');
2905              }
2906          } else {
2907              $this->properties = $properties;
2908          }
2909          $this->properties->collision = null;
2910      }
2911  
2912      /**
2913       * Returns the requested property
2914       *
2915       * @param string $key
2916       * @return mixed
2917       */
2918      public function __get($key) {
2919          return $this->properties->$key;
2920      }
2921  
2922      /**
2923       * Override this to ensure empty($request->blah) calls return a reliable answer...
2924       *
2925       * This is required because we define the __get method
2926       *
2927       * @param mixed $key
2928       * @return bool True is it not empty, false otherwise
2929       */
2930      public function __isset($key) {
2931          return (!empty($this->properties->$key));
2932      }
2933  
2934      /**
2935       * Returns the user who requested this course
2936       *
2937       * Uses a static var to cache the results and cut down the number of db queries
2938       *
2939       * @staticvar array $requesters An array of cached users
2940       * @return stdClass The user who requested the course
2941       */
2942      public function get_requester() {
2943          global $DB;
2944          static $requesters= array();
2945          if (!array_key_exists($this->properties->requester, $requesters)) {
2946              $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
2947          }
2948          return $requesters[$this->properties->requester];
2949      }
2950  
2951      /**
2952       * Checks that the shortname used by the course does not conflict with any other
2953       * courses that exist
2954       *
2955       * @param string|null $shortnamemark The string to append to the requests shortname
2956       *                     should a conflict be found
2957       * @return bool true is there is a conflict, false otherwise
2958       */
2959      public function check_shortname_collision($shortnamemark = '[*]') {
2960          global $DB;
2961  
2962          if ($this->properties->collision !== null) {
2963              return $this->properties->collision;
2964          }
2965  
2966          if (empty($this->properties->shortname)) {
2967              debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
2968              $this->properties->collision = false;
2969          } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
2970              if (!empty($shortnamemark)) {
2971                  $this->properties->shortname .= ' '.$shortnamemark;
2972              }
2973              $this->properties->collision = true;
2974          } else {
2975              $this->properties->collision = false;
2976          }
2977          return $this->properties->collision;
2978      }
2979  
2980      /**
2981       * Returns the category where this course request should be created
2982       *
2983       * Note that we don't check here that user has a capability to view
2984       * hidden categories if he has capabilities 'moodle/site:approvecourse' and
2985       * 'moodle/course:changecategory'
2986       *
2987       * @return coursecat
2988       */
2989      public function get_category() {
2990          global $CFG;
2991          require_once($CFG->libdir.'/coursecatlib.php');
2992          // If the category is not set, if the current user does not have the rights to change the category, or if the
2993          // category does not exist, we set the default category to the course to be approved.
2994          // The system level is used because the capability moodle/site:approvecourse is based on a system level.
2995          if (empty($this->properties->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
2996                  (!$category = coursecat::get($this->properties->category, IGNORE_MISSING, true))) {
2997              $category = coursecat::get($CFG->defaultrequestcategory, IGNORE_MISSING, true);
2998          }
2999          if (!$category) {
3000              $category = coursecat::get_default();
3001          }
3002          return $category;
3003      }
3004  
3005      /**
3006       * This function approves the request turning it into a course
3007       *
3008       * This function converts the course request into a course, at the same time
3009       * transferring any files used in the summary to the new course and then removing
3010       * the course request and the files associated with it.
3011       *
3012       * @return int The id of the course that was created from this request
3013       */
3014      public function approve() {
3015          global $CFG, $DB, $USER;
3016  
3017          $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
3018  
3019          $courseconfig = get_config('moodlecourse');
3020  
3021          // Transfer appropriate settings
3022          $data = clone($this->properties);
3023          unset($data->id);
3024          unset($data->reason);
3025          unset($data->requester);
3026  
3027          // Set category
3028          $category = $this->get_category();
3029          $data->category = $category->id;
3030          // Set misc settings
3031          $data->requested = 1;
3032  
3033          // Apply course default settings
3034          $data->format             = $courseconfig->format;
3035          $data->newsitems          = $courseconfig->newsitems;
3036          $data->showgrades         = $courseconfig->showgrades;
3037          $data->showreports        = $courseconfig->showreports;
3038          $data->maxbytes           = $courseconfig->maxbytes;
3039          $data->groupmode          = $courseconfig->groupmode;
3040          $data->groupmodeforce     = $courseconfig->groupmodeforce;
3041          $data->visible            = $courseconfig->visible;
3042          $data->visibleold         = $data->visible;
3043          $data->lang               = $courseconfig->lang;
3044  
3045          $course = create_course($data);
3046          $context = context_course::instance($course->id, MUST_EXIST);
3047  
3048          // add enrol instances
3049          if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
3050              if ($manual = enrol_get_plugin('manual')) {
3051                  $manual->add_default_instance($course);
3052              }
3053          }
3054  
3055          // enrol the requester as teacher if necessary
3056          if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
3057              enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
3058          }
3059  
3060          $this->delete();
3061  
3062          $a = new stdClass();
3063          $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
3064          $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
3065          $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
3066  
3067          return $course->id;
3068      }
3069  
3070      /**
3071       * Reject a course request
3072       *
3073       * This function rejects a course request, emailing the requesting user the
3074       * provided notice and then removing the request from the database
3075       *
3076       * @param string $notice The message to display to the user
3077       */
3078      public function reject($notice) {
3079          global $USER, $DB;
3080          $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
3081          $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
3082          $this->delete();
3083      }
3084  
3085      /**
3086       * Deletes the course request and any associated files
3087       */
3088      public function delete() {
3089          global $DB;
3090          $DB->delete_records('course_request', array('id' => $this->properties->id));
3091      }
3092  
3093      /**
3094       * Send a message from one user to another using events_trigger
3095       *
3096       * @param object $touser
3097       * @param object $fromuser
3098       * @param string $name
3099       * @param string $subject
3100       * @param string $message
3101       */
3102      protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
3103          $eventdata = new stdClass();
3104          $eventdata->component         = 'moodle';
3105          $eventdata->name              = $name;
3106          $eventdata->userfrom          = $fromuser;
3107          $eventdata->userto            = $touser;
3108          $eventdata->subject           = $subject;
3109          $eventdata->fullmessage       = $message;
3110          $eventdata->fullmessageformat = FORMAT_PLAIN;
3111          $eventdata->fullmessagehtml   = '';
3112          $eventdata->smallmessage      = '';
3113          $eventdata->notification      = 1;
3114          message_send($eventdata);
3115      }
3116  }
3117  
3118  /**
3119   * Return a list of page types
3120   * @param string $pagetype current page type
3121   * @param context $parentcontext Block's parent context
3122   * @param context $currentcontext Current context of block
3123   * @return array array of page types
3124   */
3125  function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
3126      if ($pagetype === 'course-index' || $pagetype === 'course-index-category') {
3127          // For courses and categories browsing pages (/course/index.php) add option to show on ANY category page
3128          $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3129              'course-index-*' => get_string('page-course-index-x', 'pagetype'),
3130          );
3131      } else if ($currentcontext && (!($coursecontext = $currentcontext->get_course_context(false)) || $coursecontext->instanceid == SITEID)) {
3132          // We know for sure that despite pagetype starts with 'course-' this is not a page in course context (i.e. /course/search.php, etc.)
3133          $pagetypes = array('*' => get_string('page-x', 'pagetype'));
3134      } else {
3135          // Otherwise consider it a page inside a course even if $currentcontext is null
3136          $pagetypes = array('*' => get_string('page-x', 'pagetype'),
3137              'course-*' => get_string('page-course-x', 'pagetype'),
3138              'course-view-*' => get_string('page-course-view-x', 'pagetype')
3139          );
3140      }
3141      return $pagetypes;
3142  }
3143  
3144  /**
3145   * Determine whether course ajax should be enabled for the specified course
3146   *
3147   * @param stdClass $course The course to test against
3148   * @return boolean Whether course ajax is enabled or note
3149   */
3150  function course_ajax_enabled($course) {
3151      global $CFG, $PAGE, $SITE;
3152  
3153      // The user must be editing for AJAX to be included
3154      if (!$PAGE->user_is_editing()) {
3155          return false;
3156      }
3157  
3158      // Check that the theme suports
3159      if (!$PAGE->theme->enablecourseajax) {
3160          return false;
3161      }
3162  
3163      // Check that the course format supports ajax functionality
3164      // The site 'format' doesn't have information on course format support
3165      if ($SITE->id !== $course->id) {
3166          $courseformatajaxsupport = course_format_ajax_support($course->format);
3167          if (!$courseformatajaxsupport->capable) {
3168              return false;
3169          }
3170      }
3171  
3172      // All conditions have been met so course ajax should be enabled
3173      return true;
3174  }
3175  
3176  /**
3177   * Include the relevant javascript and language strings for the resource
3178   * toolbox YUI module
3179   *
3180   * @param integer $id The ID of the course being applied to
3181   * @param array $usedmodules An array containing the names of the modules in use on the page
3182   * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
3183   * @param stdClass $config An object containing configuration parameters for ajax modules including:
3184   *          * resourceurl   The URL to post changes to for resource changes
3185   *          * sectionurl    The URL to post changes to for section changes
3186   *          * pageparams    Additional parameters to pass through in the post
3187   * @return bool
3188   */
3189  function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
3190      global $CFG, $PAGE, $SITE;
3191  
3192      // Ensure that ajax should be included
3193      if (!course_ajax_enabled($course)) {
3194          return false;
3195      }
3196  
3197      if (!$config) {
3198          $config = new stdClass();
3199      }
3200  
3201      // The URL to use for resource changes
3202      if (!isset($config->resourceurl)) {
3203          $config->resourceurl = '/course/rest.php';
3204      }
3205  
3206      // The URL to use for section changes
3207      if (!isset($config->sectionurl)) {
3208          $config->sectionurl = '/course/rest.php';
3209      }
3210  
3211      // Any additional parameters which need to be included on page submission
3212      if (!isset($config->pageparams)) {
3213          $config->pageparams = array();
3214      }
3215  
3216      // Include toolboxes
3217      $PAGE->requires->yui_module('moodle-course-toolboxes',
3218              'M.course.init_resource_toolbox',
3219              array(array(
3220                  'courseid' => $course->id,
3221                  'ajaxurl' => $config->resourceurl,
3222                  'config' => $config,
3223              ))
3224      );
3225      $PAGE->requires->yui_module('moodle-course-toolboxes',
3226              'M.course.init_section_toolbox',
3227              array(array(
3228                  'courseid' => $course->id,
3229                  'format' => $course->format,
3230                  'ajaxurl' => $config->sectionurl,
3231                  'config' => $config,
3232              ))
3233      );
3234  
3235      // Include course dragdrop
3236      if (course_format_uses_sections($course->format)) {
3237          $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
3238              array(array(
3239                  'courseid' => $course->id,
3240                  'ajaxurl' => $config->sectionurl,
3241                  'config' => $config,
3242              )), null, true);
3243  
3244          $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
3245              array(array(
3246                  'courseid' => $course->id,
3247                  'ajaxurl' => $config->resourceurl,
3248                  'config' => $config,
3249              )), null, true);
3250      }
3251  
3252      // Require various strings for the command toolbox
3253      $PAGE->requires->strings_for_js(array(
3254              'moveleft',
3255              'deletechecktype',
3256              'deletechecktypename',
3257              'edittitle',
3258              'edittitleinstructions',
3259              'show',
3260              'hide',
3261              'groupsnone',
3262              'groupsvisible',
3263              'groupsseparate',
3264              'clicktochangeinbrackets',
3265              'markthistopic',
3266              'markedthistopic',
3267              'movesection',
3268              'movecoursemodule',
3269              'movecoursesection',
3270              'movecontent',
3271              'tocontent',
3272              'emptydragdropregion',
3273              'afterresource',
3274              'aftersection',
3275              'totopofsection',
3276          ), 'moodle');
3277  
3278      // Include section-specific strings for formats which support sections.
3279      if (course_format_uses_sections($course->format)) {
3280          $PAGE->requires->strings_for_js(array(
3281                  'showfromothers',
3282                  'hidefromothers',
3283              ), 'format_' . $course->format);
3284      }
3285  
3286      // For confirming resource deletion we need the name of the module in question
3287      foreach ($usedmodules as $module => $modname) {
3288          $PAGE->requires->string_for_js('pluginname', $module);
3289      }
3290  
3291      // Load drag and drop upload AJAX.
3292      require_once($CFG->dirroot.'/course/dnduploadlib.php');
3293      dndupload_add_to_course($course, $enabledmodules);
3294  
3295      return true;
3296  }
3297  
3298  /**
3299   * Returns the sorted list of available course formats, filtered by enabled if necessary
3300   *
3301   * @param bool $enabledonly return only formats that are enabled
3302   * @return array array of sorted format names
3303   */
3304  function get_sorted_course_formats($enabledonly = false) {
3305      global $CFG;
3306      $formats = core_component::get_plugin_list('format');
3307  
3308      if (!empty($CFG->format_plugins_sortorder)) {
3309          $order = explode(',', $CFG->format_plugins_sortorder);
3310          $order = array_merge(array_intersect($order, array_keys($formats)),
3311                      array_diff(array_keys($formats), $order));
3312      } else {
3313          $order = array_keys($formats);
3314      }
3315      if (!$enabledonly) {
3316          return $order;
3317      }
3318      $sortedformats = array();
3319      foreach ($order as $formatname) {
3320          if (!get_config('format_'.$formatname, 'disabled')) {
3321              $sortedformats[] = $formatname;
3322          }
3323      }
3324      return $sortedformats;
3325  }
3326  
3327  /**
3328   * The URL to use for the specified course (with section)
3329   *
3330   * @param int|stdClass $courseorid The course to get the section name for (either object or just course id)
3331   * @param int|stdClass $section Section object from database or just field course_sections.section
3332   *     if omitted the course view page is returned
3333   * @param array $options options for view URL. At the moment core uses:
3334   *     'navigation' (bool) if true and section has no separate page, the function returns null
3335   *     'sr' (int) used by multipage formats to specify to which section to return
3336   * @return moodle_url The url of course
3337   */
3338  function course_get_url($courseorid, $section = null, $options = array()) {
3339      return course_get_format($courseorid)->get_view_url($section, $options);
3340  }
3341  
3342  /**
3343   * Create a module.
3344   *
3345   * It includes:
3346   *      - capability checks and other checks
3347   *      - create the module from the module info
3348   *
3349   * @param object $module
3350   * @return object the created module info
3351   * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
3352   */
3353  function create_module($moduleinfo) {
3354      global $DB, $CFG;
3355  
3356      require_once($CFG->dirroot . '/course/modlib.php');
3357  
3358      // Check manadatory attributs.
3359      $mandatoryfields = array('modulename', 'course', 'section', 'visible');
3360      if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
3361          $mandatoryfields[] = 'introeditor';
3362      }
3363      foreach($mandatoryfields as $mandatoryfield) {
3364          if (!isset($moduleinfo->{$mandatoryfield})) {
3365              throw new moodle_exception('createmodulemissingattribut', '', '', $mandatoryfield);
3366          }
3367      }
3368  
3369      // Some additional checks (capability / existing instances).
3370      $course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST);
3371      list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section);
3372  
3373      // Add the module.
3374      $moduleinfo->module = $module->id;
3375      $moduleinfo = add_moduleinfo($moduleinfo, $course, null);
3376  
3377      return $moduleinfo;
3378  }
3379  
3380  /**
3381   * Update a module.
3382   *
3383   * It includes:
3384   *      - capability and other checks
3385   *      - update the module
3386   *
3387   * @param object $module
3388   * @return object the updated module info
3389   * @throws moodle_exception if current user is not allowed to update the module
3390   */
3391  function update_module($moduleinfo) {
3392      global $DB, $CFG;
3393  
3394      require_once($CFG->dirroot . '/course/modlib.php');
3395  
3396      // Check the course module exists.
3397      $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
3398  
3399      // Check the course exists.
3400      $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
3401  
3402      // Some checks (capaibility / existing instances).
3403      list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
3404  
3405      // Retrieve few information needed by update_moduleinfo.
3406      $moduleinfo->modulename = $cm->modname;
3407      if (!isset($moduleinfo->scale)) {
3408          $moduleinfo->scale = 0;
3409      }
3410      $moduleinfo->type = 'mod';
3411  
3412      // Update the module.
3413      list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null);
3414  
3415      return $moduleinfo;
3416  }
3417  
3418  /**
3419   * Duplicate a module on the course for ajax.
3420   *
3421   * @see mod_duplicate_module()
3422   * @param object $course The course
3423   * @param object $cm The course module to duplicate
3424   * @param int $sr The section to link back to (used for creating the links)
3425   * @throws moodle_exception if the plugin doesn't support duplication
3426   * @return Object containing:
3427   * - fullcontent: The HTML markup for the created CM
3428   * - cmid: The CMID of the newly created CM
3429   * - redirect: Whether to trigger a redirect following this change
3430   */
3431  function mod_duplicate_activity($course, $cm, $sr = null) {
3432      global $PAGE;
3433  
3434      $newcm = duplicate_module($course, $cm);
3435  
3436      $resp = new stdClass();
3437      if ($newcm) {
3438          $courserenderer = $PAGE->get_renderer('core', 'course');
3439          $completioninfo = new completion_info($course);
3440          $modulehtml = $courserenderer->course_section_cm($course, $completioninfo,
3441                  $newcm, null, array());
3442  
3443          $resp->fullcontent = $courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sr);
3444          $resp->cmid = $newcm->id;
3445      } else {
3446          // Trigger a redirect.
3447          $resp->redirect = true;
3448      }
3449      return $resp;
3450  }
3451  
3452  /**
3453   * Api to duplicate a module.
3454   *
3455   * @param object $course course object.
3456   * @param object $cm course module object to be duplicated.
3457   * @since Moodle 2.8
3458   *
3459   * @throws Exception
3460   * @throws coding_exception
3461   * @throws moodle_exception
3462   * @throws restore_controller_exception
3463   *
3464   * @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm.
3465   */
3466  function duplicate_module($course, $cm) {
3467      global $CFG, $DB, $USER;
3468      require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
3469      require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
3470      require_once($CFG->libdir . '/filelib.php');
3471  
3472      $a          = new stdClass();
3473      $a->modtype = get_string('modulename', $cm->modname);
3474      $a->modname = format_string($cm->name);
3475  
3476      if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) {
3477          throw new moodle_exception('duplicatenosupport', 'error', '', $a);
3478      }
3479  
3480      // Backup the activity.
3481  
3482      $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
3483              backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
3484  
3485      $backupid       = $bc->get_backupid();
3486      $backupbasepath = $bc->get_plan()->get_basepath();
3487  
3488      $bc->execute_plan();
3489  
3490      $bc->destroy();
3491  
3492      // Restore the backup immediately.
3493  
3494      $rc = new restore_controller($backupid, $course->id,
3495              backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
3496  
3497      $cmcontext = context_module::instance($cm->id);
3498      if (!$rc->execute_precheck()) {
3499          $precheckresults = $rc->get_precheck_results();
3500          if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
3501              if (empty($CFG->keeptempdirectoriesonbackup)) {
3502                  fulldelete($backupbasepath);
3503              }
3504          }
3505      }
3506  
3507      $rc->execute_plan();
3508  
3509      // Now a bit hacky part follows - we try to get the cmid of the newly
3510      // restored copy of the module.
3511      $newcmid = null;
3512      $tasks = $rc->get_plan()->get_tasks();
3513      foreach ($tasks as $task) {
3514          if (is_subclass_of($task, 'restore_activity_task')) {
3515              if ($task->get_old_contextid() == $cmcontext->id) {
3516                  $newcmid = $task->get_moduleid();
3517                  break;
3518              }
3519          }
3520      }
3521  
3522      // If we know the cmid of the new course module, let us move it
3523      // right below the original one. otherwise it will stay at the
3524      // end of the section.
3525      if ($newcmid) {
3526          $info = get_fast_modinfo($course);
3527          $newcm = $info->get_cm($newcmid);
3528          $section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
3529          moveto_module($newcm, $section, $cm);
3530          moveto_module($cm, $section, $newcm);
3531  
3532          // Trigger course module created event. We can trigger the event only if we know the newcmid.
3533          $event = \core\event\course_module_created::create_from_cm($newcm);
3534          $event->trigger();
3535      }
3536      rebuild_course_cache($cm->course);
3537  
3538      $rc->destroy();
3539  
3540      if (empty($CFG->keeptempdirectoriesonbackup)) {
3541          fulldelete($backupbasepath);
3542      }
3543  
3544      return isset($newcm) ? $newcm : null;
3545  }
3546  
3547  /**
3548   * Compare two objects to find out their correct order based on timestamp (to be used by usort).
3549   * Sorts by descending order of time.
3550   *
3551   * @param stdClass $a First object
3552   * @param stdClass $b Second object
3553   * @return int 0,1,-1 representing the order
3554   */
3555  function compare_activities_by_time_desc($a, $b) {
3556      // Make sure the activities actually have a timestamp property.
3557      if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3558          return 0;
3559      }
3560      // We treat instances without timestamp as if they have a timestamp of 0.
3561      if ((!property_exists($a, 'timestamp')) && (property_exists($b,'timestamp'))) {
3562          return 1;
3563      }
3564      if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3565          return -1;
3566      }
3567      if ($a->timestamp == $b->timestamp) {
3568          return 0;
3569      }
3570      return ($a->timestamp > $b->timestamp) ? -1 : 1;
3571  }
3572  
3573  /**
3574   * Compare two objects to find out their correct order based on timestamp (to be used by usort).
3575   * Sorts by ascending order of time.
3576   *
3577   * @param stdClass $a First object
3578   * @param stdClass $b Second object
3579   * @return int 0,1,-1 representing the order
3580   */
3581  function compare_activities_by_time_asc($a, $b) {
3582      // Make sure the activities actually have a timestamp property.
3583      if ((!property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3584        return 0;
3585      }
3586      // We treat instances without timestamp as if they have a timestamp of 0.
3587      if ((!property_exists($a, 'timestamp')) && (property_exists($b, 'timestamp'))) {
3588          return -1;
3589      }
3590      if ((property_exists($a, 'timestamp')) && (!property_exists($b, 'timestamp'))) {
3591          return 1;
3592      }
3593      if ($a->timestamp == $b->timestamp) {
3594          return 0;
3595      }
3596      return ($a->timestamp < $b->timestamp) ? -1 : 1;
3597  }
3598  
3599  /**
3600   * Changes the visibility of a course.
3601   *
3602   * @param int $courseid The course to change.
3603   * @param bool $show True to make it visible, false otherwise.
3604   * @return bool
3605   */
3606  function course_change_visibility($courseid, $show = true) {
3607      $course = new stdClass;
3608      $course->id = $courseid;
3609      $course->visible = ($show) ? '1' : '0';
3610      $course->visibleold = $course->visible;
3611      update_course($course);
3612      return true;
3613  }
3614  
3615  /**
3616   * Changes the course sortorder by one, moving it up or down one in respect to sort order.
3617   *
3618   * @param stdClass|course_in_list $course
3619   * @param bool $up If set to true the course will be moved up one. Otherwise down one.
3620   * @return bool
3621   */
3622  function course_change_sortorder_by_one($course, $up) {
3623      global $DB;
3624      $params = array($course->sortorder, $course->category);
3625      if ($up) {
3626          $select = 'sortorder < ? AND category = ?';
3627          $sort = 'sortorder DESC';
3628      } else {
3629          $select = 'sortorder > ? AND category = ?';
3630          $sort = 'sortorder ASC';
3631      }
3632      fix_course_sortorder();
3633      $swapcourse = $DB->get_records_select('course', $select, $params, $sort, '*', 0, 1);
3634      if ($swapcourse) {
3635          $swapcourse = reset($swapcourse);
3636          $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $course->id));
3637          $DB->set_field('course', 'sortorder', $course->sortorder, array('id' => $swapcourse->id));
3638          // Finally reorder courses.
3639          fix_course_sortorder();
3640          cache_helper::purge_by_event('changesincourse');
3641          return true;
3642      }
3643      return false;
3644  }
3645  
3646  /**
3647   * Changes the sort order of courses in a category so that the first course appears after the second.
3648   *
3649   * @param int|stdClass $courseorid The course to focus on.
3650   * @param int $moveaftercourseid The course to shifter after or 0 if you want it to be the first course in the category.
3651   * @return bool
3652   */
3653  function course_change_sortorder_after_course($courseorid, $moveaftercourseid) {
3654      global $DB;
3655  
3656      if (!is_object($courseorid)) {
3657          $course = get_course($courseorid);
3658      } else {
3659          $course = $courseorid;
3660      }
3661  
3662      if ((int)$moveaftercourseid === 0) {
3663          // We've moving the course to the start of the queue.
3664          $sql = 'SELECT sortorder
3665                        FROM {course}
3666                       WHERE category = :categoryid
3667                    ORDER BY sortorder';
3668          $params = array(
3669              'categoryid' => $course->category
3670          );
3671          $sortorder = $DB->get_field_sql($sql, $params, IGNORE_MULTIPLE);
3672  
3673          $sql = 'UPDATE {course}
3674                     SET sortorder = sortorder + 1
3675                   WHERE category = :categoryid
3676                     AND id <> :id';
3677          $params = array(
3678              'categoryid' => $course->category,
3679              'id' => $course->id,
3680          );
3681          $DB->execute($sql, $params);
3682          $DB->set_field('course', 'sortorder', $sortorder, array('id' => $course->id));
3683      } else if ($course->id === $moveaftercourseid) {
3684          // They're the same - moronic.
3685          debugging("Invalid move after course given.", DEBUG_DEVELOPER);
3686          return false;
3687      } else {
3688          // Moving this course after the given course. It could be before it could be after.
3689          $moveaftercourse = get_course($moveaftercourseid);
3690          if ($course->category !== $moveaftercourse->category) {
3691              debugging("Cannot re-order courses. The given courses do not belong to the same category.", DEBUG_DEVELOPER);
3692              return false;
3693          }
3694          // Increment all courses in the same category that are ordered after the moveafter course.
3695          // This makes a space for the course we're moving.
3696          $sql = 'UPDATE {course}
3697                         SET sortorder = sortorder + 1
3698                       WHERE category = :categoryid
3699                         AND sortorder > :sortorder';
3700          $params = array(
3701              'categoryid' => $moveaftercourse->category,
3702              'sortorder' => $moveaftercourse->sortorder
3703          );
3704          $DB->execute($sql, $params);
3705          $DB->set_field('course', 'sortorder', $moveaftercourse->sortorder + 1, array('id' => $course->id));
3706      }
3707      fix_course_sortorder();
3708      cache_helper::purge_by_event('changesincourse');
3709      return true;
3710  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1