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   * Block 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 block extends context {
  33      /** @var int numeric context level value matching legacy CONTEXT_BLOCK */
  34      public const LEVEL = 80;
  35  
  36      /**
  37       * Please use \core\context\block::instance($blockinstanceid) 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\block 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 'block';
  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('block');
  67      }
  68  
  69      /**
  70       * Returns human readable context identifier.
  71       *
  72       * @param boolean $withprefix whether to prefix the name of the context with Block
  73       * @param boolean $short does not apply to block context
  74       * @param boolean $escape does not apply to block context
  75       * @return string the human readable context name.
  76       */
  77      public function get_context_name($withprefix = true, $short = false, $escape = true) {
  78          global $DB, $CFG;
  79  
  80          $name = '';
  81          if ($blockinstance = $DB->get_record('block_instances', array('id' => $this->_instanceid))) {
  82              global $CFG;
  83              require_once("$CFG->dirroot/blocks/moodleblock.class.php");
  84              require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
  85              $blockname = "block_$blockinstance->blockname";
  86              if ($blockobject = new $blockname()) {
  87                  if ($withprefix) {
  88                      $name = get_string('block').': ';
  89                  }
  90                  $name .= $blockobject->title;
  91              }
  92          }
  93  
  94          return $name;
  95      }
  96  
  97      /**
  98       * Returns the most relevant URL for this context.
  99       *
 100       * @return moodle_url
 101       */
 102      public function get_url() {
 103          $parentcontexts = $this->get_parent_context();
 104          return $parentcontexts->get_url();
 105      }
 106  
 107      /**
 108       * Returns list of all possible parent context levels.
 109       * @since Moodle 4.2
 110       *
 111       * @return int[]
 112       */
 113      public static function get_possible_parent_levels(): array {
 114          // Blocks may be added to any other context instance.
 115          $alllevels = \core\context_helper::get_all_levels();
 116          unset($alllevels[self::LEVEL]);
 117          return array_keys($alllevels);
 118      }
 119  
 120      /**
 121       * Returns array of relevant context capability records.
 122       *
 123       * @param string $sort
 124       * @return array
 125       */
 126      public function get_capabilities(string $sort = self::DEFAULT_CAPABILITY_SORT) {
 127          global $DB;
 128  
 129          $bi = $DB->get_record('block_instances', array('id' => $this->_instanceid));
 130  
 131          $select = '(contextlevel = :level AND component = :component)';
 132          $params = [
 133              'level' => self::LEVEL,
 134              'component' => 'block_' . $bi->blockname,
 135          ];
 136  
 137          $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
 138          if ($extracaps) {
 139              list($extra, $extraparams) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
 140              $select .= " OR name $extra";
 141              $params = array_merge($params, $extraparams);
 142          }
 143  
 144          return $DB->get_records_select('capabilities', $select, $params, $sort);
 145      }
 146  
 147      /**
 148       * Is this context part of any course? If yes return course context.
 149       *
 150       * @param bool $strict true means throw exception if not found, false means return false if not found
 151       * @return course context of the enclosing course, null if not found or exception
 152       */
 153      public function get_course_context($strict = true) {
 154          $parentcontext = $this->get_parent_context();
 155          return $parentcontext->get_course_context($strict);
 156      }
 157  
 158      /**
 159       * Returns block context instance.
 160       *
 161       * @param int $blockinstanceid id from {block_instances} table.
 162       * @param int $strictness
 163       * @return block|false context instance
 164       */
 165      public static function instance($blockinstanceid, $strictness = MUST_EXIST) {
 166          global $DB;
 167  
 168          if ($context = context::cache_get(self::LEVEL, $blockinstanceid)) {
 169              return $context;
 170          }
 171  
 172          if (!$record = $DB->get_record('context', array('contextlevel' => self::LEVEL, 'instanceid' => $blockinstanceid))) {
 173              if ($bi = $DB->get_record('block_instances', array('id' => $blockinstanceid), 'id,parentcontextid', $strictness)) {
 174                  $parentcontext = context::instance_by_id($bi->parentcontextid);
 175                  $record = context::insert_context_record(self::LEVEL, $bi->id, $parentcontext->path);
 176              }
 177          }
 178  
 179          if ($record) {
 180              $context = new block($record);
 181              context::cache_add($context);
 182              return $context;
 183          }
 184  
 185          return false;
 186      }
 187  
 188      /**
 189       * Block do not have child contexts...
 190       * @return array
 191       */
 192      public function get_child_contexts() {
 193          return array();
 194      }
 195  
 196      /**
 197       * Create missing context instances at block context level
 198       */
 199      protected static function create_level_instances() {
 200          global $DB;
 201  
 202          $sql = <<<EOF
 203              INSERT INTO {context} (
 204                  contextlevel,
 205                  instanceid
 206              ) SELECT
 207                  :contextlevel,
 208                  bi.id as instanceid
 209                 FROM {block_instances} bi
 210                 WHERE NOT EXISTS (
 211                     SELECT 'x' FROM {context} cx WHERE bi.id = cx.instanceid AND cx.contextlevel = :existingcontextlevel
 212                 )
 213          EOF;
 214  
 215          $DB->execute($sql, [
 216              'contextlevel' => self::LEVEL,
 217              'existingcontextlevel' => self::LEVEL,
 218          ]);
 219      }
 220  
 221      /**
 222       * Returns sql necessary for purging of stale context instances.
 223       *
 224       * @return string cleanup SQL
 225       */
 226      protected static function get_cleanup_sql() {
 227          $sql = "
 228                    SELECT c.*
 229                      FROM {context} c
 230           LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id
 231                     WHERE bi.id IS NULL AND c.contextlevel = ".self::LEVEL."
 232                 ";
 233  
 234          return $sql;
 235      }
 236  
 237      /**
 238       * Rebuild context paths and depths at block context level.
 239       *
 240       * @param bool $force
 241       */
 242      protected static function build_paths($force) {
 243          global $DB;
 244  
 245          if ($force || $DB->record_exists_select('context', "contextlevel = ".self::LEVEL." AND (depth = 0 OR path IS NULL)")) {
 246              if ($force) {
 247                  $ctxemptyclause = '';
 248              } else {
 249                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
 250              }
 251  
 252              // The pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent.
 253              $sql = "INSERT INTO {context_temp} (id, path, depth, locked)
 254                      SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1, ctx.locked
 255                        FROM {context} ctx
 256                        JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = " . self::LEVEL . ")
 257                        JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
 258                       WHERE (pctx.path IS NOT NULL AND pctx.depth > 0)
 259                             $ctxemptyclause";
 260              $trans = $DB->start_delegated_transaction();
 261              $DB->delete_records('context_temp');
 262              $DB->execute($sql);
 263              context::merge_context_temp_table();
 264              $DB->delete_records('context_temp');
 265              $trans->allow_commit();
 266          }
 267      }
 268  }