[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1