Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  // This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
  16  
  17  namespace core\context;
  18  
  19  use core\context;
  20  use stdClass;
  21  use coding_exception, moodle_url;
  22  
  23  /**
  24   * Course context class
  25   *
  26   * @package   core_access
  27   * @category  access
  28   * @copyright Petr Skoda
  29   * @license   https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   * @since     Moodle 4.2
  31   */
  32  class course extends context {
  33      /** @var int numeric context level value matching legacy CONTEXT_COURSE */
  34      public const LEVEL = 50;
  35  
  36      /**
  37       * Please use \core\context\course::instance($courseid) if you need the instance of context.
  38       * Alternatively if you know only the context id use \core\context::instance_by_id($contextid)
  39       *
  40       * @param stdClass $record
  41       */
  42      protected function __construct(stdClass $record) {
  43          parent::__construct($record);
  44          if ($record->contextlevel != self::LEVEL) {
  45              throw new coding_exception('Invalid $record->contextlevel in core\context\course constructor.');
  46          }
  47      }
  48  
  49      /**
  50       * Returns short context name.
  51       *
  52       * @since Moodle 4.2
  53       *
  54       * @return string
  55       */
  56      public static function get_short_name(): string {
  57          return 'course';
  58      }
  59  
  60      /**
  61       * Returns human readable context level name.
  62       *
  63       * @return string the human readable context level name.
  64       */
  65      public static function get_level_name() {
  66          return get_string('course');
  67      }
  68  
  69      /**
  70       * Returns human readable context identifier.
  71       *
  72       * @param boolean $withprefix whether to prefix the name of the context with Course
  73       * @param boolean $short whether to use the short name of the thing.
  74       * @param bool $escape Whether the returned category name is to be HTML escaped or not.
  75       * @return string the human readable context name.
  76       */
  77      public function get_context_name($withprefix = true, $short = false, $escape = true) {
  78          global $DB;
  79  
  80          $name = '';
  81          if ($this->_instanceid == SITEID) {
  82              $name = get_string('frontpage', 'admin');
  83          } else {
  84              if ($course = $DB->get_record('course', array('id' => $this->_instanceid))) {
  85                  if ($withprefix) {
  86                      $name = get_string('course').': ';
  87                  }
  88                  if ($short) {
  89                      if (!$escape) {
  90                          $name .= format_string($course->shortname, true, array('context' => $this, 'escape' => false));
  91                      } else {
  92                          $name .= format_string($course->shortname, true, array('context' => $this));
  93                      }
  94                  } else {
  95                      if (!$escape) {
  96                          $name .= format_string(get_course_display_name_for_list($course), true, array('context' => $this,
  97                              'escape' => false));
  98                      } else {
  99                          $name .= format_string(get_course_display_name_for_list($course), true, array('context' => $this));
 100                      }
 101                  }
 102              }
 103          }
 104          return $name;
 105      }
 106  
 107      /**
 108       * Returns the most relevant URL for this context.
 109       *
 110       * @return moodle_url
 111       */
 112      public function get_url() {
 113          if ($this->_instanceid != SITEID) {
 114              return new moodle_url('/course/view.php', array('id' => $this->_instanceid));
 115          }
 116  
 117          return new moodle_url('/');
 118      }
 119  
 120      /**
 121       * Returns context instance database name.
 122       *
 123       * @return string|null table name for all levels except system.
 124       */
 125      protected static function get_instance_table(): ?string {
 126          return 'course';
 127      }
 128  
 129      /**
 130       * Returns list of columns that can be used from behat
 131       * to look up context by reference.
 132       *
 133       * @return array list of column names from instance table
 134       */
 135      protected static function get_behat_reference_columns(): array {
 136          return ['shortname'];
 137      }
 138  
 139      /**
 140       * Returns list of all role archetypes that are compatible
 141       * with role assignments in context level.
 142       * @since Moodle 4.2
 143       *
 144       * @return int[]
 145       */
 146      protected static function get_compatible_role_archetypes(): array {
 147          return ['manager', 'editingteacher', 'teacher', 'student'];
 148      }
 149  
 150      /**
 151       * Returns list of all possible parent context levels.
 152       * @since Moodle 4.2
 153       *
 154       * @return int[]
 155       */
 156      public static function get_possible_parent_levels(): array {
 157          return [coursecat::LEVEL];
 158      }
 159  
 160      /**
 161       * Returns array of relevant context capability records.
 162       *
 163       * @param string $sort
 164       * @return array
 165       */
 166      public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
 167          global $DB;
 168  
 169          $levels = \core\context_helper::get_child_levels(self::LEVEL);
 170          $levels[] = self::LEVEL;
 171  
 172          return $DB->get_records_list('capabilities', 'contextlevel', $levels, $sort);
 173      }
 174  
 175      /**
 176       * Is this context part of any course? If yes return course context.
 177       *
 178       * @param bool $strict true means throw exception if not found, false means return false if not found
 179       * @return course context of the enclosing course, null if not found or exception
 180       */
 181      public function get_course_context($strict = true) {
 182          return $this;
 183      }
 184  
 185      /**
 186       * Returns course context instance.
 187       *
 188       * @param int $courseid id from {course} table
 189       * @param int $strictness
 190       * @return course|false context instance
 191       */
 192      public static function instance($courseid, $strictness = MUST_EXIST) {
 193          global $DB;
 194  
 195          if ($context = context::cache_get(self::LEVEL, $courseid)) {
 196              return $context;
 197          }
 198  
 199          if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $courseid))) {
 200              if ($course = $DB->get_record('course', array('id' => $courseid), 'id,category', $strictness)) {
 201                  if ($course->category) {
 202                      $parentcontext = coursecat::instance($course->category);
 203                      $record = context::insert_context_record(self::LEVEL, $course->id, $parentcontext->path);
 204                  } else {
 205                      $record = context::insert_context_record(self::LEVEL, $course->id, '/'.SYSCONTEXTID, 0);
 206                  }
 207              }
 208          }
 209  
 210          if ($record) {
 211              $context = new course($record);
 212              context::cache_add($context);
 213              return $context;
 214          }
 215  
 216          return false;
 217      }
 218  
 219      /**
 220       * Create missing context instances at course context level
 221       */
 222      protected static function create_level_instances() {
 223          global $DB;
 224  
 225          $sql = "SELECT ".self::LEVEL.", c.id
 226                    FROM {course} c
 227                   WHERE NOT EXISTS (SELECT 'x'
 228                                       FROM {context} cx
 229                                      WHERE c.id = cx.instanceid AND cx.contextlevel=".self::LEVEL.")";
 230          $contextdata = $DB->get_recordset_sql($sql);
 231          foreach ($contextdata as $context) {
 232              context::insert_context_record(self::LEVEL, $context->id, null);
 233          }
 234          $contextdata->close();
 235      }
 236  
 237      /**
 238       * Returns sql necessary for purging of stale context instances.
 239       *
 240       * @return string cleanup SQL
 241       */
 242      protected static function get_cleanup_sql() {
 243          $sql = "
 244                    SELECT c.*
 245                      FROM {context} c
 246           LEFT OUTER JOIN {course} co ON c.instanceid = co.id
 247                     WHERE co.id IS NULL AND c.contextlevel = ".self::LEVEL."
 248                 ";
 249  
 250          return $sql;
 251      }
 252  
 253      /**
 254       * Rebuild context paths and depths at course context level.
 255       *
 256       * @param bool $force
 257       */
 258      protected static function build_paths($force) {
 259          global $DB;
 260  
 261          if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
 262              if ($force) {
 263                  $ctxemptyclause = $emptyclause = '';
 264              } else {
 265                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
 266                  $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
 267              }
 268  
 269              $base = '/'.SYSCONTEXTID;
 270  
 271              // Standard frontpage.
 272              $sql = "UPDATE {context}
 273                         SET depth = 2,
 274                             path = ".$DB->sql_concat("'$base/'", 'id')."
 275                       WHERE contextlevel = ".self::LEVEL."
 276                             AND EXISTS (SELECT 'x'
 277                                           FROM {course} c
 278                                          WHERE c.id = {context}.instanceid AND c.category = 0)
 279                             $emptyclause";
 280              $DB->execute($sql);
 281  
 282              // Standard courses.
 283              $sql = "INSERT INTO {context_temp} (id, path, depth, locked)
 284                      SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
 285                        FROM {context} ctx
 286                        JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".self::LEVEL." AND c.category <> 0)
 287                        JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".coursecat::LEVEL.")
 288                       WHERE pctx.path IS NOT NULL AND pctx.depth > 0
 289                             $ctxemptyclause";
 290              $trans = $DB->start_delegated_transaction();
 291              $DB->delete_records('context_temp');
 292              $DB->execute($sql);
 293              context::merge_context_temp_table();
 294              $DB->delete_records('context_temp');
 295              $trans->allow_commit();
 296          }
 297      }
 298  }