Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.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 category 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 coursecat extends context {
  33      /** @var int numeric context level value matching legacy CONTEXT_COURSECAT */
  34      public const LEVEL = 40;
  35  
  36      /**
  37       * Please use \core\context\coursecat::instance($coursecatid) 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\coursecat 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 'coursecat';
  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('category');
  67      }
  68  
  69      /**
  70       * Returns human readable context identifier.
  71       *
  72       * @param boolean $withprefix whether to prefix the name of the context with Category
  73       * @param boolean $short does not apply to course categories
  74       * @param boolean $escape Whether the returned name of the context 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 ($category = $DB->get_record('course_categories', array('id' => $this->_instanceid))) {
  82              if ($withprefix) {
  83                  $name = get_string('category').': ';
  84              }
  85              if (!$escape) {
  86                  $name .= format_string($category->name, true, array('context' => $this, 'escape' => false));
  87              } else {
  88                  $name .= format_string($category->name, true, array('context' => $this));
  89              }
  90          }
  91          return $name;
  92      }
  93  
  94      /**
  95       * Returns the most relevant URL for this context.
  96       *
  97       * @return moodle_url
  98       */
  99      public function get_url() {
 100          return new moodle_url('/course/index.php', array('categoryid' => $this->_instanceid));
 101      }
 102  
 103      /**
 104       * Returns context instance database name.
 105       *
 106       * @return string|null table name for all levels except system.
 107       */
 108      protected static function get_instance_table(): ?string {
 109          return 'course_categories';
 110      }
 111  
 112      /**
 113       * Returns list of columns that can be used from behat
 114       * to look up context by reference.
 115       *
 116       * @return array list of column names from instance table
 117       */
 118      protected static function get_behat_reference_columns(): array {
 119          return ['idnumber'];
 120      }
 121  
 122      /**
 123       * Returns list of all role archetypes that are compatible
 124       * with role assignments in context level.
 125       * @since Moodle 4.2
 126       *
 127       * @return int[]
 128       */
 129      protected static function get_compatible_role_archetypes(): array {
 130          return ['manager', 'coursecreator'];
 131      }
 132  
 133      /**
 134       * Returns list of all possible parent context levels.
 135       * @since Moodle 4.2
 136       *
 137       * @return int[]
 138       */
 139      public static function get_possible_parent_levels(): array {
 140          return [system::LEVEL, self::LEVEL];
 141      }
 142  
 143      /**
 144       * Returns array of relevant context capability records.
 145       *
 146       * @param string $sort
 147       * @return array
 148       */
 149      public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
 150          global $DB;
 151  
 152          $levels = \core\context_helper::get_child_levels(self::LEVEL);
 153          $levels[] = self::LEVEL;
 154  
 155          return $DB->get_records_list('capabilities', 'contextlevel', $levels, $sort);
 156      }
 157  
 158      /**
 159       * Returns course category context instance.
 160       *
 161       * @param int $categoryid id from {course_categories} table
 162       * @param int $strictness
 163       * @return coursecat|false context instance
 164       */
 165      public static function instance($categoryid, $strictness = MUST_EXIST) {
 166          global $DB;
 167  
 168          if ($context = context::cache_get(self::LEVEL, $categoryid)) {
 169              return $context;
 170          }
 171  
 172          if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $categoryid))) {
 173              if ($category = $DB->get_record('course_categories', array('id' => $categoryid), 'id,parent', $strictness)) {
 174                  if ($category->parent) {
 175                      $parentcontext = self::instance($category->parent);
 176                      $record = context::insert_context_record(self::LEVEL, $category->id, $parentcontext->path);
 177                  } else {
 178                      $record = context::insert_context_record(self::LEVEL, $category->id, '/'.SYSCONTEXTID, 0);
 179                  }
 180              }
 181          }
 182  
 183          if ($record) {
 184              $context = new coursecat($record);
 185              context::cache_add($context);
 186              return $context;
 187          }
 188  
 189          return false;
 190      }
 191  
 192      /**
 193       * Returns immediate child contexts of category and all subcategories,
 194       * children of subcategories and courses are not returned.
 195       *
 196       * @return array
 197       */
 198      public function get_child_contexts() {
 199          global $DB;
 200  
 201          if (empty($this->_path) || empty($this->_depth)) {
 202              debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
 203              return array();
 204          }
 205  
 206          $sql = "SELECT ctx.*
 207                    FROM {context} ctx
 208                   WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
 209          $params = array($this->_path.'/%', $this->depth + 1, self::LEVEL);
 210          $records = $DB->get_records_sql($sql, $params);
 211  
 212          $result = array();
 213          foreach ($records as $record) {
 214              $result[$record->id] = context::create_instance_from_record($record);
 215          }
 216  
 217          return $result;
 218      }
 219  
 220      /**
 221       * Create missing context instances at course category context level
 222       */
 223      protected static function create_level_instances() {
 224          global $DB;
 225  
 226          $sql = "SELECT ".self::LEVEL.", cc.id
 227                    FROM {course_categories} cc
 228                   WHERE NOT EXISTS (SELECT 'x'
 229                                       FROM {context} cx
 230                                      WHERE cc.id = cx.instanceid AND cx.contextlevel=".self::LEVEL.")";
 231          $contextdata = $DB->get_recordset_sql($sql);
 232          foreach ($contextdata as $context) {
 233              context::insert_context_record(self::LEVEL, $context->id, null);
 234          }
 235          $contextdata->close();
 236      }
 237  
 238      /**
 239       * Returns sql necessary for purging of stale context instances.
 240       *
 241       * @return string cleanup SQL
 242       */
 243      protected static function get_cleanup_sql() {
 244          $sql = "
 245                    SELECT c.*
 246                      FROM {context} c
 247           LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id
 248                     WHERE cc.id IS NULL AND c.contextlevel = ".self::LEVEL."
 249                 ";
 250  
 251          return $sql;
 252      }
 253  
 254      /**
 255       * Rebuild context paths and depths at course category context level.
 256       *
 257       * @param bool $force
 258       */
 259      protected static function build_paths($force) {
 260          global $DB;
 261  
 262          if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
 263              if ($force) {
 264                  $ctxemptyclause = $emptyclause = '';
 265              } else {
 266                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
 267                  $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
 268              }
 269  
 270              $base = '/'.SYSCONTEXTID;
 271  
 272              // Normal top level categories.
 273              $sql = "UPDATE {context}
 274                         SET depth=2,
 275                             path=".$DB->sql_concat("'$base/'", 'id')."
 276                       WHERE contextlevel=".self::LEVEL."
 277                             AND EXISTS (SELECT 'x'
 278                                           FROM {course_categories} cc
 279                                          WHERE cc.id = {context}.instanceid AND cc.depth=1)
 280                             $emptyclause";
 281              $DB->execute($sql);
 282  
 283              // Deeper categories - one query per depthlevel.
 284              $maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}");
 285              for ($n = 2; $n <= $maxdepth; $n++) {
 286                  $sql = "INSERT INTO {context_temp} (id, path, depth, locked)
 287                          SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
 288                            FROM {context} ctx
 289                            JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".self::LEVEL."
 290                                 AND cc.depth = $n)
 291                            JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".self::LEVEL.")
 292                           WHERE pctx.path IS NOT NULL AND pctx.depth > 0
 293                                 $ctxemptyclause";
 294                  $trans = $DB->start_delegated_transaction();
 295                  $DB->delete_records('context_temp');
 296                  $DB->execute($sql);
 297                  context::merge_context_temp_table();
 298                  $DB->delete_records('context_temp');
 299                  $trans->allow_commit();
 300  
 301              }
 302          }
 303      }
 304  }