[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/ -> blocklib.php (source)

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Block Class and Functions
  20   *
  21   * This file defines the {@link block_manager} class,
  22   *
  23   * @package    core
  24   * @subpackage block
  25   * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**#@+
  32   * Default names for the block regions in the standard theme.
  33   */
  34  define('BLOCK_POS_LEFT',  'side-pre');
  35  define('BLOCK_POS_RIGHT', 'side-post');
  36  /**#@-*/
  37  
  38  define('BUI_CONTEXTS_FRONTPAGE_ONLY', 0);
  39  define('BUI_CONTEXTS_FRONTPAGE_SUBS', 1);
  40  define('BUI_CONTEXTS_ENTIRE_SITE', 2);
  41  
  42  define('BUI_CONTEXTS_CURRENT', 0);
  43  define('BUI_CONTEXTS_CURRENT_SUBS', 1);
  44  
  45  /**
  46   * Exception thrown when someone tried to do something with a block that does
  47   * not exist on a page.
  48   *
  49   * @copyright 2009 Tim Hunt
  50   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51   * @since     Moodle 2.0
  52   */
  53  class block_not_on_page_exception extends moodle_exception {
  54      /**
  55       * Constructor
  56       * @param int $instanceid the block instance id of the block that was looked for.
  57       * @param object $page the current page.
  58       */
  59      public function __construct($instanceid, $page) {
  60          $a = new stdClass;
  61          $a->instanceid = $instanceid;
  62          $a->url = $page->url->out();
  63          parent::__construct('blockdoesnotexistonpage', '', $page->url->out(), $a);
  64      }
  65  }
  66  
  67  /**
  68   * This class keeps track of the block that should appear on a moodle_page.
  69   *
  70   * The page to work with as passed to the constructor.
  71   *
  72   * @copyright 2009 Tim Hunt
  73   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  74   * @since     Moodle 2.0
  75   */
  76  class block_manager {
  77      /**
  78       * The UI normally only shows block weights between -MAX_WEIGHT and MAX_WEIGHT,
  79       * although other weights are valid.
  80       */
  81      const MAX_WEIGHT = 10;
  82  
  83  /// Field declarations =========================================================
  84  
  85      /**
  86       * the moodle_page we are managing blocks for.
  87       * @var moodle_page
  88       */
  89      protected $page;
  90  
  91      /** @var array region name => 1.*/
  92      protected $regions = array();
  93  
  94      /** @var string the region where new blocks are added.*/
  95      protected $defaultregion = null;
  96  
  97      /** @var array will be $DB->get_records('blocks') */
  98      protected $allblocks = null;
  99  
 100      /**
 101       * @var array blocks that this user can add to this page. Will be a subset
 102       * of $allblocks, but with array keys block->name. Access this via the
 103       * {@link get_addable_blocks()} method to ensure it is lazy-loaded.
 104       */
 105      protected $addableblocks = null;
 106  
 107      /**
 108       * Will be an array region-name => array(db rows loaded in load_blocks);
 109       * @var array
 110       */
 111      protected $birecordsbyregion = null;
 112  
 113      /**
 114       * array region-name => array(block objects); populated as necessary by
 115       * the ensure_instances_exist method.
 116       * @var array
 117       */
 118      protected $blockinstances = array();
 119  
 120      /**
 121       * array region-name => array(block_contents objects) what actually needs to
 122       * be displayed in each region.
 123       * @var array
 124       */
 125      protected $visibleblockcontent = array();
 126  
 127      /**
 128       * array region-name => array(block_contents objects) extra block-like things
 129       * to be displayed in each region, before the real blocks.
 130       * @var array
 131       */
 132      protected $extracontent = array();
 133  
 134      /**
 135       * Used by the block move id, to track whether a block is currently being moved.
 136       *
 137       * When you click on the move icon of a block, first the page needs to reload with
 138       * extra UI for choosing a new position for a particular block. In that situation
 139       * this field holds the id of the block being moved.
 140       *
 141       * @var integer|null
 142       */
 143      protected $movingblock = null;
 144  
 145      /**
 146       * Show only fake blocks
 147       */
 148      protected $fakeblocksonly = false;
 149  
 150  /// Constructor ================================================================
 151  
 152      /**
 153       * Constructor.
 154       * @param object $page the moodle_page object object we are managing the blocks for,
 155       * or a reasonable faxilimily. (See the comment at the top of this class
 156       * and {@link http://en.wikipedia.org/wiki/Duck_typing})
 157       */
 158      public function __construct($page) {
 159          $this->page = $page;
 160      }
 161  
 162  /// Getter methods =============================================================
 163  
 164      /**
 165       * Get an array of all region names on this page where a block may appear
 166       *
 167       * @return array the internal names of the regions on this page where block may appear.
 168       */
 169      public function get_regions() {
 170          if (is_null($this->defaultregion)) {
 171              $this->page->initialise_theme_and_output();
 172          }
 173          return array_keys($this->regions);
 174      }
 175  
 176      /**
 177       * Get the region name of the region blocks are added to by default
 178       *
 179       * @return string the internal names of the region where new blocks are added
 180       * by default, and where any blocks from an unrecognised region are shown.
 181       * (Imagine that blocks were added with one theme selected, then you switched
 182       * to a theme with different block positions.)
 183       */
 184      public function get_default_region() {
 185          $this->page->initialise_theme_and_output();
 186          return $this->defaultregion;
 187      }
 188  
 189      /**
 190       * The list of block types that may be added to this page.
 191       *
 192       * @return array block name => record from block table.
 193       */
 194      public function get_addable_blocks() {
 195          $this->check_is_loaded();
 196  
 197          if (!is_null($this->addableblocks)) {
 198              return $this->addableblocks;
 199          }
 200  
 201          // Lazy load.
 202          $this->addableblocks = array();
 203  
 204          $allblocks = blocks_get_record();
 205          if (empty($allblocks)) {
 206              return $this->addableblocks;
 207          }
 208  
 209          $unaddableblocks = self::get_undeletable_block_types();
 210          $pageformat = $this->page->pagetype;
 211          foreach($allblocks as $block) {
 212              if (!$bi = block_instance($block->name)) {
 213                  continue;
 214              }
 215              if ($block->visible && !in_array($block->name, $unaddableblocks) &&
 216                      ($bi->instance_allow_multiple() || !$this->is_block_present($block->name)) &&
 217                      blocks_name_allowed_in_format($block->name, $pageformat) &&
 218                      $bi->user_can_addto($this->page)) {
 219                  $this->addableblocks[$block->name] = $block;
 220              }
 221          }
 222  
 223          return $this->addableblocks;
 224      }
 225  
 226      /**
 227       * Given a block name, find out of any of them are currently present in the page
 228  
 229       * @param string $blockname - the basic name of a block (eg "navigation")
 230       * @return boolean - is there one of these blocks in the current page?
 231       */
 232      public function is_block_present($blockname) {
 233          if (empty($this->blockinstances)) {
 234              return false;
 235          }
 236  
 237          foreach ($this->blockinstances as $region) {
 238              foreach ($region as $instance) {
 239                  if (empty($instance->instance->blockname)) {
 240                      continue;
 241                  }
 242                  if ($instance->instance->blockname == $blockname) {
 243                      return true;
 244                  }
 245              }
 246          }
 247          return false;
 248      }
 249  
 250      /**
 251       * Find out if a block type is known by the system
 252       *
 253       * @param string $blockname the name of the type of block.
 254       * @param boolean $includeinvisible if false (default) only check 'visible' blocks, that is, blocks enabled by the admin.
 255       * @return boolean true if this block in installed.
 256       */
 257      public function is_known_block_type($blockname, $includeinvisible = false) {
 258          $blocks = $this->get_installed_blocks();
 259          foreach ($blocks as $block) {
 260              if ($block->name == $blockname && ($includeinvisible || $block->visible)) {
 261                  return true;
 262              }
 263          }
 264          return false;
 265      }
 266  
 267      /**
 268       * Find out if a region exists on a page
 269       *
 270       * @param string $region a region name
 271       * @return boolean true if this region exists on this page.
 272       */
 273      public function is_known_region($region) {
 274          if (empty($region)) {
 275              return false;
 276          }
 277          return array_key_exists($region, $this->regions);
 278      }
 279  
 280      /**
 281       * Get an array of all blocks within a given region
 282       *
 283       * @param string $region a block region that exists on this page.
 284       * @return array of block instances.
 285       */
 286      public function get_blocks_for_region($region) {
 287          $this->check_is_loaded();
 288          $this->ensure_instances_exist($region);
 289          return $this->blockinstances[$region];
 290      }
 291  
 292      /**
 293       * Returns an array of block content objects that exist in a region
 294       *
 295       * @param string $region a block region that exists on this page.
 296       * @return array of block block_contents objects for all the blocks in a region.
 297       */
 298      public function get_content_for_region($region, $output) {
 299          $this->check_is_loaded();
 300          $this->ensure_content_created($region, $output);
 301          return $this->visibleblockcontent[$region];
 302      }
 303  
 304      /**
 305       * Helper method used by get_content_for_region.
 306       * @param string $region region name
 307       * @param float $weight weight. May be fractional, since you may want to move a block
 308       * between ones with weight 2 and 3, say ($weight would be 2.5).
 309       * @return string URL for moving block $this->movingblock to this position.
 310       */
 311      protected function get_move_target_url($region, $weight) {
 312          return new moodle_url($this->page->url, array('bui_moveid' => $this->movingblock,
 313                  'bui_newregion' => $region, 'bui_newweight' => $weight, 'sesskey' => sesskey()));
 314      }
 315  
 316      /**
 317       * Determine whether a region contains anything. (Either any real blocks, or
 318       * the add new block UI.)
 319       *
 320       * (You may wonder why the $output parameter is required. Unfortunately,
 321       * because of the way that blocks work, the only reliable way to find out
 322       * if a block will be visible is to get the content for output, and to
 323       * get the content, you need a renderer. Fortunately, this is not a
 324       * performance problem, because we cache the output that is generated, and
 325       * in almost every case where we call region_has_content, we are about to
 326       * output the blocks anyway, so we are not doing wasted effort.)
 327       *
 328       * @param string $region a block region that exists on this page.
 329       * @param core_renderer $output a core_renderer. normally the global $OUTPUT.
 330       * @return boolean Whether there is anything in this region.
 331       */
 332      public function region_has_content($region, $output) {
 333  
 334          if (!$this->is_known_region($region)) {
 335              return false;
 336          }
 337          $this->check_is_loaded();
 338          $this->ensure_content_created($region, $output);
 339          // if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
 340          // Mark Nielsen's patch - part 1
 341          if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks() && $this->movingblock) {
 342              // If editing is on, we need all the block regions visible, for the
 343              // move blocks UI.
 344              return true;
 345          }
 346          return !empty($this->visibleblockcontent[$region]) || !empty($this->extracontent[$region]);
 347      }
 348  
 349      /**
 350       * Get an array of all of the installed blocks.
 351       *
 352       * @return array contents of the block table.
 353       */
 354      public function get_installed_blocks() {
 355          global $DB;
 356          if (is_null($this->allblocks)) {
 357              $this->allblocks = $DB->get_records('block');
 358          }
 359          return $this->allblocks;
 360      }
 361  
 362      /**
 363       * @return array names of block types that cannot be added or deleted. E.g. array('navigation','settings').
 364       */
 365      public static function get_undeletable_block_types() {
 366          global $CFG;
 367  
 368          if (!isset($CFG->undeletableblocktypes) || (!is_array($CFG->undeletableblocktypes) && !is_string($CFG->undeletableblocktypes))) {
 369              return array('navigation','settings');
 370          } else if (is_string($CFG->undeletableblocktypes)) {
 371              return explode(',', $CFG->undeletableblocktypes);
 372          } else {
 373              return $CFG->undeletableblocktypes;
 374          }
 375      }
 376  
 377  /// Setter methods =============================================================
 378  
 379      /**
 380       * Add a region to a page
 381       *
 382       * @param string $region add a named region where blocks may appear on the current page.
 383       *      This is an internal name, like 'side-pre', not a string to display in the UI.
 384       * @param bool $custom True if this is a custom block region, being added by the page rather than the theme layout.
 385       */
 386      public function add_region($region, $custom = true) {
 387          global $SESSION;
 388          $this->check_not_yet_loaded();
 389          if ($custom) {
 390              if (array_key_exists($region, $this->regions)) {
 391                  // This here is EXACTLY why we should not be adding block regions into a page. It should
 392                  // ALWAYS be done in a theme layout.
 393                  debugging('A custom region conflicts with a block region in the theme.', DEBUG_DEVELOPER);
 394              }
 395              // We need to register this custom region against the page type being used.
 396              // This allows us to check, when performing block actions, that unrecognised regions can be worked with.
 397              $type = $this->page->pagetype;
 398              if (!isset($SESSION->custom_block_regions)) {
 399                  $SESSION->custom_block_regions = array($type => array($region));
 400              } else if (!isset($SESSION->custom_block_regions[$type])) {
 401                  $SESSION->custom_block_regions[$type] = array($region);
 402              } else if (!in_array($region, $SESSION->custom_block_regions[$type])) {
 403                  $SESSION->custom_block_regions[$type][] = $region;
 404              }
 405          }
 406          $this->regions[$region] = 1;
 407      }
 408  
 409      /**
 410       * Add an array of regions
 411       * @see add_region()
 412       *
 413       * @param array $regions this utility method calls add_region for each array element.
 414       */
 415      public function add_regions($regions, $custom = true) {
 416          foreach ($regions as $region) {
 417              $this->add_region($region, $custom);
 418          }
 419      }
 420  
 421      /**
 422       * Finds custom block regions associated with a page type and registers them with this block manager.
 423       *
 424       * @param string $pagetype
 425       */
 426      public function add_custom_regions_for_pagetype($pagetype) {
 427          global $SESSION;
 428          if (isset($SESSION->custom_block_regions[$pagetype])) {
 429              foreach ($SESSION->custom_block_regions[$pagetype] as $customregion) {
 430                  $this->add_region($customregion, false);
 431              }
 432          }
 433      }
 434  
 435      /**
 436       * Set the default region for new blocks on the page
 437       *
 438       * @param string $defaultregion the internal names of the region where new
 439       * blocks should be added by default, and where any blocks from an
 440       * unrecognised region are shown.
 441       */
 442      public function set_default_region($defaultregion) {
 443          $this->check_not_yet_loaded();
 444          if ($defaultregion) {
 445              $this->check_region_is_known($defaultregion);
 446          }
 447          $this->defaultregion = $defaultregion;
 448      }
 449  
 450      /**
 451       * Add something that looks like a block, but which isn't an actual block_instance,
 452       * to this page.
 453       *
 454       * @param block_contents $bc the content of the block-like thing.
 455       * @param string $region a block region that exists on this page.
 456       */
 457      public function add_fake_block($bc, $region) {
 458          $this->page->initialise_theme_and_output();
 459          if (!$this->is_known_region($region)) {
 460              $region = $this->get_default_region();
 461          }
 462          if (array_key_exists($region, $this->visibleblockcontent)) {
 463              throw new coding_exception('block_manager has already prepared the blocks in region ' .
 464                      $region . 'for output. It is too late to add a fake block.');
 465          }
 466          if (!isset($bc->attributes['data-block'])) {
 467              $bc->attributes['data-block'] = '_fake';
 468          }
 469          $bc->attributes['class'] .= ' block_fake';
 470          $this->extracontent[$region][] = $bc;
 471      }
 472  
 473      /**
 474       * Checks to see whether all of the blocks within the given region are docked
 475       *
 476       * @see region_uses_dock
 477       * @param string $region
 478       * @return bool True if all of the blocks within that region are docked
 479       */
 480      public function region_completely_docked($region, $output) {
 481          global $CFG;
 482          // If theme doesn't allow docking or allowblockstodock is not set, then return.
 483          if (!$this->page->theme->enable_dock || empty($CFG->allowblockstodock)) {
 484              return false;
 485          }
 486  
 487          // Do not dock the region when the user attemps to move a block.
 488          if ($this->movingblock) {
 489              return false;
 490          }
 491  
 492          // Block regions should not be docked during editing when all the blocks are hidden.
 493          if ($this->page->user_is_editing() && $this->page->user_can_edit_blocks()) {
 494              return false;
 495          }
 496  
 497          $this->check_is_loaded();
 498          $this->ensure_content_created($region, $output);
 499          if (!$this->region_has_content($region, $output)) {
 500              // If the region has no content then nothing is docked at all of course.
 501              return false;
 502          }
 503          foreach ($this->visibleblockcontent[$region] as $instance) {
 504              if (!get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
 505                  return false;
 506              }
 507          }
 508          return true;
 509      }
 510  
 511      /**
 512       * Checks to see whether any of the blocks within the given regions are docked
 513       *
 514       * @see region_completely_docked
 515       * @param array|string $regions array of regions (or single region)
 516       * @return bool True if any of the blocks within that region are docked
 517       */
 518      public function region_uses_dock($regions, $output) {
 519          if (!$this->page->theme->enable_dock) {
 520              return false;
 521          }
 522          $this->check_is_loaded();
 523          foreach((array)$regions as $region) {
 524              $this->ensure_content_created($region, $output);
 525              foreach($this->visibleblockcontent[$region] as $instance) {
 526                  if(!empty($instance->content) && get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
 527                      return true;
 528                  }
 529              }
 530          }
 531          return false;
 532      }
 533  
 534  /// Actions ====================================================================
 535  
 536      /**
 537       * This method actually loads the blocks for our page from the database.
 538       *
 539       * @param boolean|null $includeinvisible
 540       *      null (default) - load hidden blocks if $this->page->user_is_editing();
 541       *      true - load hidden blocks.
 542       *      false - don't load hidden blocks.
 543       */
 544      public function load_blocks($includeinvisible = null) {
 545          global $DB, $CFG;
 546  
 547          if (!is_null($this->birecordsbyregion)) {
 548              // Already done.
 549              return;
 550          }
 551  
 552          if ($CFG->version < 2009050619) {
 553              // Upgrade/install not complete. Don't try too show any blocks.
 554              $this->birecordsbyregion = array();
 555              return;
 556          }
 557  
 558          // Ensure we have been initialised.
 559          if (is_null($this->defaultregion)) {
 560              $this->page->initialise_theme_and_output();
 561              // If there are still no block regions, then there are no blocks on this page.
 562              if (empty($this->regions)) {
 563                  $this->birecordsbyregion = array();
 564                  return;
 565              }
 566          }
 567  
 568          // Check if we need to load normal blocks
 569          if ($this->fakeblocksonly) {
 570              $this->birecordsbyregion = $this->prepare_per_region_arrays();
 571              return;
 572          }
 573  
 574          if (is_null($includeinvisible)) {
 575              $includeinvisible = $this->page->user_is_editing();
 576          }
 577          if ($includeinvisible) {
 578              $visiblecheck = '';
 579          } else {
 580              $visiblecheck = 'AND (bp.visible = 1 OR bp.visible IS NULL)';
 581          }
 582  
 583          $context = $this->page->context;
 584          $contexttest = 'bi.parentcontextid IN (:contextid2, :contextid3)';
 585          $parentcontextparams = array();
 586          $parentcontextids = $context->get_parent_context_ids();
 587          if ($parentcontextids) {
 588              list($parentcontexttest, $parentcontextparams) =
 589                      $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED, 'parentcontext');
 590              $contexttest = "($contexttest OR (bi.showinsubcontexts = 1 AND bi.parentcontextid $parentcontexttest))";
 591          }
 592  
 593          $pagetypepatterns = matching_page_type_patterns($this->page->pagetype);
 594          list($pagetypepatterntest, $pagetypepatternparams) =
 595                  $DB->get_in_or_equal($pagetypepatterns, SQL_PARAMS_NAMED, 'pagetypepatterntest');
 596  
 597          $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
 598          $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
 599  
 600          $systemcontext = context_system::instance();
 601          $params = array(
 602              'contextlevel' => CONTEXT_BLOCK,
 603              'subpage1' => $this->page->subpage,
 604              'subpage2' => $this->page->subpage,
 605              'contextid1' => $context->id,
 606              'contextid2' => $context->id,
 607              'contextid3' => $systemcontext->id,
 608              'pagetype' => $this->page->pagetype,
 609          );
 610          if ($this->page->subpage === '') {
 611              $params['subpage1'] = '';
 612              $params['subpage2'] = '';
 613          }
 614          $sql = "SELECT
 615                      bi.id,
 616                      bp.id AS blockpositionid,
 617                      bi.blockname,
 618                      bi.parentcontextid,
 619                      bi.showinsubcontexts,
 620                      bi.pagetypepattern,
 621                      bi.subpagepattern,
 622                      bi.defaultregion,
 623                      bi.defaultweight,
 624                      COALESCE(bp.visible, 1) AS visible,
 625                      COALESCE(bp.region, bi.defaultregion) AS region,
 626                      COALESCE(bp.weight, bi.defaultweight) AS weight,
 627                      bi.configdata
 628                      $ccselect
 629  
 630                  FROM {block_instances} bi
 631                  JOIN {block} b ON bi.blockname = b.name
 632                  LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
 633                                                    AND bp.contextid = :contextid1
 634                                                    AND bp.pagetype = :pagetype
 635                                                    AND bp.subpage = :subpage1
 636                  $ccjoin
 637  
 638                  WHERE
 639                  $contexttest
 640                  AND bi.pagetypepattern $pagetypepatterntest
 641                  AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
 642                  $visiblecheck
 643                  AND b.visible = 1
 644  
 645                  ORDER BY
 646                      COALESCE(bp.region, bi.defaultregion),
 647                      COALESCE(bp.weight, bi.defaultweight),
 648                      bi.id";
 649          $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams);
 650  
 651          $this->birecordsbyregion = $this->prepare_per_region_arrays();
 652          $unknown = array();
 653          foreach ($blockinstances as $bi) {
 654              context_helper::preload_from_record($bi);
 655              if ($this->is_known_region($bi->region)) {
 656                  $this->birecordsbyregion[$bi->region][] = $bi;
 657              } else {
 658                  $unknown[] = $bi;
 659              }
 660          }
 661  
 662          // Pages don't necessarily have a defaultregion. The  one time this can
 663          // happen is when there are no theme block regions, but the script itself
 664          // has a block region in the main content area.
 665          if (!empty($this->defaultregion)) {
 666              $this->birecordsbyregion[$this->defaultregion] =
 667                      array_merge($this->birecordsbyregion[$this->defaultregion], $unknown);
 668          }
 669      }
 670  
 671      /**
 672       * Add a block to the current page, or related pages. The block is added to
 673       * context $this->page->contextid. If $pagetypepattern $subpagepattern
 674       *
 675       * @param string $blockname The type of block to add.
 676       * @param string $region the block region on this page to add the block to.
 677       * @param integer $weight determines the order where this block appears in the region.
 678       * @param boolean $showinsubcontexts whether this block appears in subcontexts, or just the current context.
 679       * @param string|null $pagetypepattern which page types this block should appear on. Defaults to just the current page type.
 680       * @param string|null $subpagepattern which subpage this block should appear on. NULL = any (the default), otherwise only the specified subpage.
 681       */
 682      public function add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern = NULL, $subpagepattern = NULL) {
 683          global $DB;
 684          // Allow invisible blocks because this is used when adding default page blocks, which
 685          // might include invisible ones if the user makes some default blocks invisible
 686          $this->check_known_block_type($blockname, true);
 687          $this->check_region_is_known($region);
 688  
 689          if (empty($pagetypepattern)) {
 690              $pagetypepattern = $this->page->pagetype;
 691          }
 692  
 693          $blockinstance = new stdClass;
 694          $blockinstance->blockname = $blockname;
 695          $blockinstance->parentcontextid = $this->page->context->id;
 696          $blockinstance->showinsubcontexts = !empty($showinsubcontexts);
 697          $blockinstance->pagetypepattern = $pagetypepattern;
 698          $blockinstance->subpagepattern = $subpagepattern;
 699          $blockinstance->defaultregion = $region;
 700          $blockinstance->defaultweight = $weight;
 701          $blockinstance->configdata = '';
 702          $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
 703  
 704          // Ensure the block context is created.
 705          context_block::instance($blockinstance->id);
 706  
 707          // If the new instance was created, allow it to do additional setup
 708          if ($block = block_instance($blockname, $blockinstance)) {
 709              $block->instance_create();
 710          }
 711      }
 712  
 713      public function add_block_at_end_of_default_region($blockname) {
 714          $defaulregion = $this->get_default_region();
 715  
 716          $lastcurrentblock = end($this->birecordsbyregion[$defaulregion]);
 717          if ($lastcurrentblock) {
 718              $weight = $lastcurrentblock->weight + 1;
 719          } else {
 720              $weight = 0;
 721          }
 722  
 723          if ($this->page->subpage) {
 724              $subpage = $this->page->subpage;
 725          } else {
 726              $subpage = null;
 727          }
 728  
 729          // Special case. Course view page type include the course format, but we
 730          // want to add the block non-format-specifically.
 731          $pagetypepattern = $this->page->pagetype;
 732          if (strpos($pagetypepattern, 'course-view') === 0) {
 733              $pagetypepattern = 'course-view-*';
 734          }
 735  
 736          // We should end using this for ALL the blocks, making always the 1st option
 737          // the default one to be used. Until then, this is one hack to avoid the
 738          // 'pagetypewarning' message on blocks initial edition (MDL-27829) caused by
 739          // non-existing $pagetypepattern set. This way at least we guarantee one "valid"
 740          // (the FIRST $pagetypepattern will be set)
 741  
 742          // We are applying it to all blocks created in mod pages for now and only if the
 743          // default pagetype is not one of the available options
 744          if (preg_match('/^mod-.*-/', $pagetypepattern)) {
 745              $pagetypelist = generate_page_type_patterns($this->page->pagetype, null, $this->page->context);
 746              // Only go for the first if the pagetype is not a valid option
 747              if (is_array($pagetypelist) && !array_key_exists($pagetypepattern, $pagetypelist)) {
 748                  $pagetypepattern = key($pagetypelist);
 749              }
 750          }
 751          // Surely other pages like course-report will need this too, they just are not important
 752          // enough now. This will be decided in the coming days. (MDL-27829, MDL-28150)
 753  
 754          $this->add_block($blockname, $defaulregion, $weight, false, $pagetypepattern, $subpage);
 755      }
 756  
 757      /**
 758       * Convenience method, calls add_block repeatedly for all the blocks in $blocks. Optionally, a starting weight
 759       * can be used to decide the starting point that blocks are added in the region, the weight is passed to {@link add_block}
 760       * and incremented by the position of the block in the $blocks array
 761       *
 762       * @param array $blocks array with array keys the region names, and values an array of block names.
 763       * @param string $pagetypepattern optional. Passed to {@link add_block()}
 764       * @param string $subpagepattern optional. Passed to {@link add_block()}
 765       * @param boolean $showinsubcontexts optional. Passed to {@link add_block()}
 766       * @param integer $weight optional. Determines the starting point that the blocks are added in the region.
 767       */
 768      public function add_blocks($blocks, $pagetypepattern = NULL, $subpagepattern = NULL, $showinsubcontexts=false, $weight=0) {
 769          $initialweight = $weight;
 770          $this->add_regions(array_keys($blocks), false);
 771          foreach ($blocks as $region => $regionblocks) {
 772              foreach ($regionblocks as $offset => $blockname) {
 773                  $weight = $initialweight + $offset;
 774                  $this->add_block($blockname, $region, $weight, $showinsubcontexts, $pagetypepattern, $subpagepattern);
 775              }
 776          }
 777      }
 778  
 779      /**
 780       * Move a block to a new position on this page.
 781       *
 782       * If this block cannot appear on any other pages, then we change defaultposition/weight
 783       * in the block_instances table. Otherwise we just set the position on this page.
 784       *
 785       * @param $blockinstanceid the block instance id.
 786       * @param $newregion the new region name.
 787       * @param $newweight the new weight.
 788       */
 789      public function reposition_block($blockinstanceid, $newregion, $newweight) {
 790          global $DB;
 791  
 792          $this->check_region_is_known($newregion);
 793          $inst = $this->find_instance($blockinstanceid);
 794  
 795          $bi = $inst->instance;
 796          if ($bi->weight == $bi->defaultweight && $bi->region == $bi->defaultregion &&
 797                  !$bi->showinsubcontexts && strpos($bi->pagetypepattern, '*') === false &&
 798                  (!$this->page->subpage || $bi->subpagepattern)) {
 799  
 800              // Set default position
 801              $newbi = new stdClass;
 802              $newbi->id = $bi->id;
 803              $newbi->defaultregion = $newregion;
 804              $newbi->defaultweight = $newweight;
 805              $DB->update_record('block_instances', $newbi);
 806  
 807              if ($bi->blockpositionid) {
 808                  $bp = new stdClass;
 809                  $bp->id = $bi->blockpositionid;
 810                  $bp->region = $newregion;
 811                  $bp->weight = $newweight;
 812                  $DB->update_record('block_positions', $bp);
 813              }
 814  
 815          } else {
 816              // Just set position on this page.
 817              $bp = new stdClass;
 818              $bp->region = $newregion;
 819              $bp->weight = $newweight;
 820  
 821              if ($bi->blockpositionid) {
 822                  $bp->id = $bi->blockpositionid;
 823                  $DB->update_record('block_positions', $bp);
 824  
 825              } else {
 826                  $bp->blockinstanceid = $bi->id;
 827                  $bp->contextid = $this->page->context->id;
 828                  $bp->pagetype = $this->page->pagetype;
 829                  if ($this->page->subpage) {
 830                      $bp->subpage = $this->page->subpage;
 831                  } else {
 832                      $bp->subpage = '';
 833                  }
 834                  $bp->visible = $bi->visible;
 835                  $DB->insert_record('block_positions', $bp);
 836              }
 837          }
 838      }
 839  
 840      /**
 841       * Find a given block by its instance id
 842       *
 843       * @param integer $instanceid
 844       * @return block_base
 845       */
 846      public function find_instance($instanceid) {
 847          foreach ($this->regions as $region => $notused) {
 848              $this->ensure_instances_exist($region);
 849              foreach($this->blockinstances[$region] as $instance) {
 850                  if ($instance->instance->id == $instanceid) {
 851                      return $instance;
 852                  }
 853              }
 854          }
 855          throw new block_not_on_page_exception($instanceid, $this->page);
 856      }
 857  
 858  /// Inner workings =============================================================
 859  
 860      /**
 861       * Check whether the page blocks have been loaded yet
 862       *
 863       * @return void Throws coding exception if already loaded
 864       */
 865      protected function check_not_yet_loaded() {
 866          if (!is_null($this->birecordsbyregion)) {
 867              throw new coding_exception('block_manager has already loaded the blocks, to it is too late to change things that might affect which blocks are visible.');
 868          }
 869      }
 870  
 871      /**
 872       * Check whether the page blocks have been loaded yet
 873       *
 874       * Nearly identical to the above function {@link check_not_yet_loaded()} except different message
 875       *
 876       * @return void Throws coding exception if already loaded
 877       */
 878      protected function check_is_loaded() {
 879          if (is_null($this->birecordsbyregion)) {
 880              throw new coding_exception('block_manager has not yet loaded the blocks, to it is too soon to request the information you asked for.');
 881          }
 882      }
 883  
 884      /**
 885       * Check if a block type is known and usable
 886       *
 887       * @param string $blockname The block type name to search for
 888       * @param bool $includeinvisible Include disabled block types in the initial pass
 889       * @return void Coding Exception thrown if unknown or not enabled
 890       */
 891      protected function check_known_block_type($blockname, $includeinvisible = false) {
 892          if (!$this->is_known_block_type($blockname, $includeinvisible)) {
 893              if ($this->is_known_block_type($blockname, true)) {
 894                  throw new coding_exception('Unknown block type ' . $blockname);
 895              } else {
 896                  throw new coding_exception('Block type ' . $blockname . ' has been disabled by the administrator.');
 897              }
 898          }
 899      }
 900  
 901      /**
 902       * Check if a region is known by its name
 903       *
 904       * @param string $region
 905       * @return void Coding Exception thrown if the region is not known
 906       */
 907      protected function check_region_is_known($region) {
 908          if (!$this->is_known_region($region)) {
 909              throw new coding_exception('Trying to reference an unknown block region ' . $region);
 910          }
 911      }
 912  
 913      /**
 914       * Returns an array of region names as keys and nested arrays for values
 915       *
 916       * @return array an array where the array keys are the region names, and the array
 917       * values are empty arrays.
 918       */
 919      protected function prepare_per_region_arrays() {
 920          $result = array();
 921          foreach ($this->regions as $region => $notused) {
 922              $result[$region] = array();
 923          }
 924          return $result;
 925      }
 926  
 927      /**
 928       * Create a set of new block instance from a record array
 929       *
 930       * @param array $birecords An array of block instance records
 931       * @return array An array of instantiated block_instance objects
 932       */
 933      protected function create_block_instances($birecords) {
 934          $results = array();
 935          foreach ($birecords as $record) {
 936              if ($blockobject = block_instance($record->blockname, $record, $this->page)) {
 937                  $results[] = $blockobject;
 938              }
 939          }
 940          return $results;
 941      }
 942  
 943      /**
 944       * Create all the block instances for all the blocks that were loaded by
 945       * load_blocks. This is used, for example, to ensure that all blocks get a
 946       * chance to initialise themselves via the {@link block_base::specialize()}
 947       * method, before any output is done.
 948       */
 949      public function create_all_block_instances() {
 950          foreach ($this->get_regions() as $region) {
 951              $this->ensure_instances_exist($region);
 952          }
 953      }
 954  
 955      /**
 956       * Return an array of content objects from a set of block instances
 957       *
 958       * @param array $instances An array of block instances
 959       * @param renderer_base The renderer to use.
 960       * @param string $region the region name.
 961       * @return array An array of block_content (and possibly block_move_target) objects.
 962       */
 963      protected function create_block_contents($instances, $output, $region) {
 964          $results = array();
 965  
 966          $lastweight = 0;
 967          $lastblock = 0;
 968          if ($this->movingblock) {
 969              $first = reset($instances);
 970              if ($first) {
 971                  $lastweight = $first->instance->weight - 2;
 972              }
 973          }
 974  
 975          foreach ($instances as $instance) {
 976              $content = $instance->get_content_for_output($output);
 977              if (empty($content)) {
 978                  continue;
 979              }
 980  
 981              if ($this->movingblock && $lastweight != $instance->instance->weight &&
 982                      $content->blockinstanceid != $this->movingblock && $lastblock != $this->movingblock) {
 983                  $results[] = new block_move_target($this->get_move_target_url($region, ($lastweight + $instance->instance->weight)/2));
 984              }
 985  
 986              if ($content->blockinstanceid == $this->movingblock) {
 987                  $content->add_class('beingmoved');
 988                  $content->annotation .= get_string('movingthisblockcancel', 'block',
 989                          html_writer::link($this->page->url, get_string('cancel')));
 990              }
 991  
 992              $results[] = $content;
 993              $lastweight = $instance->instance->weight;
 994              $lastblock = $instance->instance->id;
 995          }
 996  
 997          if ($this->movingblock && $lastblock != $this->movingblock) {
 998              $results[] = new block_move_target($this->get_move_target_url($region, $lastweight + 1));
 999          }
1000          return $results;
1001      }
1002  
1003      /**
1004       * Ensure block instances exist for a given region
1005       *
1006       * @param string $region Check for bi's with the instance with this name
1007       */
1008      protected function ensure_instances_exist($region) {
1009          $this->check_region_is_known($region);
1010          if (!array_key_exists($region, $this->blockinstances)) {
1011              $this->blockinstances[$region] =
1012                      $this->create_block_instances($this->birecordsbyregion[$region]);
1013          }
1014      }
1015  
1016      /**
1017       * Ensure that there is some content within the given region
1018       *
1019       * @param string $region The name of the region to check
1020       */
1021      public function ensure_content_created($region, $output) {
1022          $this->ensure_instances_exist($region);
1023          if (!array_key_exists($region, $this->visibleblockcontent)) {
1024              $contents = array();
1025              if (array_key_exists($region, $this->extracontent)) {
1026                  $contents = $this->extracontent[$region];
1027              }
1028              $contents = array_merge($contents, $this->create_block_contents($this->blockinstances[$region], $output, $region));
1029              if ($region == $this->defaultregion) {
1030                  $addblockui = block_add_block_ui($this->page, $output);
1031                  if ($addblockui) {
1032                      $contents[] = $addblockui;
1033                  }
1034              }
1035              $this->visibleblockcontent[$region] = $contents;
1036          }
1037      }
1038  
1039  /// Process actions from the URL ===============================================
1040  
1041      /**
1042       * Get the appropriate list of editing icons for a block. This is used
1043       * to set {@link block_contents::$controls} in {@link block_base::get_contents_for_output()}.
1044       *
1045       * @param $output The core_renderer to use when generating the output. (Need to get icon paths.)
1046       * @return an array in the format for {@link block_contents::$controls}
1047       */
1048      public function edit_controls($block) {
1049          global $CFG;
1050  
1051          $controls = array();
1052          $actionurl = $this->page->url->out(false, array('sesskey'=> sesskey()));
1053          $blocktitle = $block->title;
1054          if (empty($blocktitle)) {
1055              $blocktitle = $block->arialabel;
1056          }
1057  
1058          if ($this->page->user_can_edit_blocks()) {
1059              // Move icon.
1060              $str = new lang_string('moveblock', 'block', $blocktitle);
1061              $controls[] = new action_menu_link_primary(
1062                  new moodle_url($actionurl, array('bui_moveid' => $block->instance->id)),
1063                  new pix_icon('t/move', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1064                  $str,
1065                  array('class' => 'editing_move')
1066              );
1067  
1068          }
1069  
1070          if ($this->page->user_can_edit_blocks() || $block->user_can_edit()) {
1071              // Edit config icon - always show - needed for positioning UI.
1072              $str = new lang_string('configureblock', 'block', $blocktitle);
1073              $controls[] = new action_menu_link_secondary(
1074                  new moodle_url($actionurl, array('bui_editid' => $block->instance->id)),
1075                  new pix_icon('t/edit', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1076                  $str,
1077                  array('class' => 'editing_edit')
1078              );
1079  
1080          }
1081  
1082          if ($this->page->user_can_edit_blocks() && $block->instance_can_be_hidden()) {
1083              // Show/hide icon.
1084              if ($block->instance->visible) {
1085                  $str = new lang_string('hideblock', 'block', $blocktitle);
1086                  $url = new moodle_url($actionurl, array('bui_hideid' => $block->instance->id));
1087                  $icon = new pix_icon('t/hide', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1088                  $attributes = array('class' => 'editing_hide');
1089              } else {
1090                  $str = new lang_string('showblock', 'block', $blocktitle);
1091                  $url = new moodle_url($actionurl, array('bui_showid' => $block->instance->id));
1092                  $icon = new pix_icon('t/show', $str, 'moodle', array('class' => 'iconsmall', 'title' => ''));
1093                  $attributes = array('class' => 'editing_show');
1094              }
1095              $controls[] = new action_menu_link_secondary($url, $icon, $str, $attributes);
1096          }
1097  
1098          // Display either "Assign roles" or "Permissions" or "Change permissions" icon (whichever first is available).
1099          $rolesurl = null;
1100  
1101          if (get_assignable_roles($block->context, ROLENAME_SHORT)) {
1102              $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id));
1103              $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
1104              $icon = 'i/assignroles';
1105          } else if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {
1106              $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id));
1107              $str = get_string('permissions', 'role');
1108              $icon = 'i/permissions';
1109          } else if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {
1110              $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id));
1111              $str = get_string('checkpermissions', 'role');
1112              $icon = 'i/checkpermissions';
1113          }
1114  
1115          if ($rolesurl) {
1116              // TODO: please note it is sloppy to pass urls through page parameters!!
1117              //      it is shortened because some web servers (e.g. IIS by default) give
1118              //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
1119              $return = $this->page->url->out(false);
1120              $return = str_replace($CFG->wwwroot . '/', '', $return);
1121              $rolesurl->param('returnurl', $return);
1122  
1123              $controls[] = new action_menu_link_secondary(
1124                  $rolesurl,
1125                  new pix_icon($icon, $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1126                  $str,
1127                  array('class' => 'editing_roles')
1128              );
1129          }
1130  
1131          if ($this->user_can_delete_block($block)) {
1132              // Delete icon.
1133              $str = new lang_string('deleteblock', 'block', $blocktitle);
1134              $controls[] = new action_menu_link_secondary(
1135                  new moodle_url($actionurl, array('bui_deleteid' => $block->instance->id)),
1136                  new pix_icon('t/delete', $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
1137                  $str,
1138                  array('class' => 'editing_delete')
1139              );
1140          }
1141  
1142          return $controls;
1143      }
1144  
1145      /**
1146       * @param block_base $block a block that appears on this page.
1147       * @return boolean boolean whether the currently logged in user is allowed to delete this block.
1148       */
1149      protected function user_can_delete_block($block) {
1150          return $this->page->user_can_edit_blocks() && $block->user_can_edit() &&
1151                  $block->user_can_addto($this->page) &&
1152                  !in_array($block->instance->blockname, self::get_undeletable_block_types());
1153      }
1154  
1155      /**
1156       * Process any block actions that were specified in the URL.
1157       *
1158       * @return boolean true if anything was done. False if not.
1159       */
1160      public function process_url_actions() {
1161          if (!$this->page->user_is_editing()) {
1162              return false;
1163          }
1164          return $this->process_url_add() || $this->process_url_delete() ||
1165              $this->process_url_show_hide() || $this->process_url_edit() ||
1166              $this->process_url_move();
1167      }
1168  
1169      /**
1170       * Handle adding a block.
1171       * @return boolean true if anything was done. False if not.
1172       */
1173      public function process_url_add() {
1174          $blocktype = optional_param('bui_addblock', null, PARAM_PLUGIN);
1175          if (!$blocktype) {
1176              return false;
1177          }
1178  
1179          require_sesskey();
1180  
1181          if (!$this->page->user_can_edit_blocks()) {
1182              throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('addblock'));
1183          }
1184  
1185          if (!array_key_exists($blocktype, $this->get_addable_blocks())) {
1186              throw new moodle_exception('cannotaddthisblocktype', '', $this->page->url->out(), $blocktype);
1187          }
1188  
1189          $this->add_block_at_end_of_default_region($blocktype);
1190  
1191          // If the page URL was a guess, it will contain the bui_... param, so we must make sure it is not there.
1192          $this->page->ensure_param_not_in_url('bui_addblock');
1193  
1194          return true;
1195      }
1196  
1197      /**
1198       * Handle deleting a block.
1199       * @return boolean true if anything was done. False if not.
1200       */
1201      public function process_url_delete() {
1202          global $CFG, $PAGE, $OUTPUT;
1203  
1204          $blockid = optional_param('bui_deleteid', null, PARAM_INT);
1205          $confirmdelete = optional_param('bui_confirm', null, PARAM_INT);
1206  
1207          if (!$blockid) {
1208              return false;
1209          }
1210  
1211          require_sesskey();
1212          $block = $this->page->blocks->find_instance($blockid);
1213          if (!$this->user_can_delete_block($block)) {
1214              throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('deleteablock'));
1215          }
1216  
1217          if (!$confirmdelete) {
1218              $deletepage = new moodle_page();
1219              $deletepage->set_pagelayout('admin');
1220              $deletepage->set_course($this->page->course);
1221              $deletepage->set_context($this->page->context);
1222              if ($this->page->cm) {
1223                  $deletepage->set_cm($this->page->cm);
1224              }
1225  
1226              $deleteurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1227              $deleteurlparams = $this->page->url->params();
1228              $deletepage->set_url($deleteurlbase, $deleteurlparams);
1229              $deletepage->set_block_actions_done();
1230              // At this point we are either going to redirect, or display the form, so
1231              // overwrite global $PAGE ready for this. (Formslib refers to it.)
1232              $PAGE = $deletepage;
1233              //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that too
1234              $output = $deletepage->get_renderer('core');
1235              $OUTPUT = $output;
1236  
1237              $site = get_site();
1238              $blocktitle = $block->get_title();
1239              $strdeletecheck = get_string('deletecheck', 'block', $blocktitle);
1240              $message = get_string('deleteblockcheck', 'block', $blocktitle);
1241  
1242              // If the block is being shown in sub contexts display a warning.
1243              if ($block->instance->showinsubcontexts == 1) {
1244                  $parentcontext = context::instance_by_id($block->instance->parentcontextid);
1245                  $systemcontext = context_system::instance();
1246                  $messagestring = new stdClass();
1247                  $messagestring->location = $parentcontext->get_context_name();
1248  
1249                  // Checking for blocks that may have visibility on the front page and pages added on that.
1250                  if ($parentcontext->id != $systemcontext->id && is_inside_frontpage($parentcontext)) {
1251                      $messagestring->pagetype = get_string('showonfrontpageandsubs', 'block');
1252                  } else {
1253                      $pagetypes = generate_page_type_patterns($this->page->pagetype, $parentcontext);
1254                      $messagestring->pagetype = $block->instance->pagetypepattern;
1255                      if (isset($pagetypes[$block->instance->pagetypepattern])) {
1256                          $messagestring->pagetype = $pagetypes[$block->instance->pagetypepattern];
1257                      }
1258                  }
1259  
1260                  $message = get_string('deleteblockwarning', 'block', $messagestring);
1261              }
1262  
1263              $PAGE->navbar->add($strdeletecheck);
1264              $PAGE->set_title($blocktitle . ': ' . $strdeletecheck);
1265              $PAGE->set_heading($site->fullname);
1266              echo $OUTPUT->header();
1267              $confirmurl = new moodle_url($deletepage->url, array('sesskey' => sesskey(), 'bui_deleteid' => $block->instance->id, 'bui_confirm' => 1));
1268              $cancelurl = new moodle_url($deletepage->url);
1269              $yesbutton = new single_button($confirmurl, get_string('yes'));
1270              $nobutton = new single_button($cancelurl, get_string('no'));
1271              echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
1272              echo $OUTPUT->footer();
1273              // Make sure that nothing else happens after we have displayed this form.
1274              exit;
1275          } else {
1276              blocks_delete_instance($block->instance);
1277              // bui_deleteid and bui_confirm should not be in the PAGE url.
1278              $this->page->ensure_param_not_in_url('bui_deleteid');
1279              $this->page->ensure_param_not_in_url('bui_confirm');
1280              return true;
1281          }
1282      }
1283  
1284      /**
1285       * Handle showing or hiding a block.
1286       * @return boolean true if anything was done. False if not.
1287       */
1288      public function process_url_show_hide() {
1289          if ($blockid = optional_param('bui_hideid', null, PARAM_INT)) {
1290              $newvisibility = 0;
1291          } else if ($blockid = optional_param('bui_showid', null, PARAM_INT)) {
1292              $newvisibility = 1;
1293          } else {
1294              return false;
1295          }
1296  
1297          require_sesskey();
1298  
1299          $block = $this->page->blocks->find_instance($blockid);
1300  
1301          if (!$this->page->user_can_edit_blocks()) {
1302              throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('hideshowblocks'));
1303          } else if (!$block->instance_can_be_hidden()) {
1304              return false;
1305          }
1306  
1307          blocks_set_visibility($block->instance, $this->page, $newvisibility);
1308  
1309          // If the page URL was a guses, it will contain the bui_... param, so we must make sure it is not there.
1310          $this->page->ensure_param_not_in_url('bui_hideid');
1311          $this->page->ensure_param_not_in_url('bui_showid');
1312  
1313          return true;
1314      }
1315  
1316      /**
1317       * Handle showing/processing the submission from the block editing form.
1318       * @return boolean true if the form was submitted and the new config saved. Does not
1319       *      return if the editing form was displayed. False otherwise.
1320       */
1321      public function process_url_edit() {
1322          global $CFG, $DB, $PAGE, $OUTPUT;
1323  
1324          $blockid = optional_param('bui_editid', null, PARAM_INT);
1325          if (!$blockid) {
1326              return false;
1327          }
1328  
1329          require_sesskey();
1330          require_once($CFG->dirroot . '/blocks/edit_form.php');
1331  
1332          $block = $this->find_instance($blockid);
1333  
1334          if (!$block->user_can_edit() && !$this->page->user_can_edit_blocks()) {
1335              throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1336          }
1337  
1338          $editpage = new moodle_page();
1339          $editpage->set_pagelayout('admin');
1340          $editpage->set_course($this->page->course);
1341          //$editpage->set_context($block->context);
1342          $editpage->set_context($this->page->context);
1343          if ($this->page->cm) {
1344              $editpage->set_cm($this->page->cm);
1345          }
1346          $editurlbase = str_replace($CFG->wwwroot . '/', '/', $this->page->url->out_omit_querystring());
1347          $editurlparams = $this->page->url->params();
1348          $editurlparams['bui_editid'] = $blockid;
1349          $editpage->set_url($editurlbase, $editurlparams);
1350          $editpage->set_block_actions_done();
1351          // At this point we are either going to redirect, or display the form, so
1352          // overwrite global $PAGE ready for this. (Formslib refers to it.)
1353          $PAGE = $editpage;
1354          //some functions like MoodleQuickForm::addHelpButton use $OUTPUT so we need to replace that to
1355          $output = $editpage->get_renderer('core');
1356          $OUTPUT = $output;
1357  
1358          $formfile = $CFG->dirroot . '/blocks/' . $block->name() . '/edit_form.php';
1359          if (is_readable($formfile)) {
1360              require_once($formfile);
1361              $classname = 'block_' . $block->name() . '_edit_form';
1362              if (!class_exists($classname)) {
1363                  $classname = 'block_edit_form';
1364              }
1365          } else {
1366              $classname = 'block_edit_form';
1367          }
1368  
1369          $mform = new $classname($editpage->url, $block, $this->page);
1370          $mform->set_data($block->instance);
1371  
1372          if ($mform->is_cancelled()) {
1373              redirect($this->page->url);
1374  
1375          } else if ($data = $mform->get_data()) {
1376              $bi = new stdClass;
1377              $bi->id = $block->instance->id;
1378  
1379              // This may get overwritten by the special case handling below.
1380              $bi->pagetypepattern = $data->bui_pagetypepattern;
1381              $bi->showinsubcontexts = (bool) $data->bui_contexts;
1382              if (empty($data->bui_subpagepattern) || $data->bui_subpagepattern == '%@NULL@%') {
1383                  $bi->subpagepattern = null;
1384              } else {
1385                  $bi->subpagepattern = $data->bui_subpagepattern;
1386              }
1387  
1388              $systemcontext = context_system::instance();
1389              $frontpagecontext = context_course::instance(SITEID);
1390              $parentcontext = context::instance_by_id($data->bui_parentcontextid);
1391  
1392              // Updating stickiness and contexts.  See MDL-21375 for details.
1393              if (has_capability('moodle/site:manageblocks', $parentcontext)) { // Check permissions in destination
1394  
1395                  // Explicitly set the default context
1396                  $bi->parentcontextid = $parentcontext->id;
1397  
1398                  if ($data->bui_editingatfrontpage) {   // The block is being edited on the front page
1399  
1400                      // The interface here is a special case because the pagetype pattern is
1401                      // totally derived from the context menu.  Here are the excpetions.   MDL-30340
1402  
1403                      switch ($data->bui_contexts) {
1404                          case BUI_CONTEXTS_ENTIRE_SITE:
1405                              // The user wants to show the block across the entire site
1406                              $bi->parentcontextid = $systemcontext->id;
1407                              $bi->showinsubcontexts = true;
1408                              $bi->pagetypepattern  = '*';
1409                              break;
1410                          case BUI_CONTEXTS_FRONTPAGE_SUBS:
1411                              // The user wants the block shown on the front page and all subcontexts
1412                              $bi->parentcontextid = $frontpagecontext->id;
1413                              $bi->showinsubcontexts = true;
1414                              $bi->pagetypepattern  = '*';
1415                              break;
1416                          case BUI_CONTEXTS_FRONTPAGE_ONLY:
1417                              // The user want to show the front page on the frontpage only
1418                              $bi->parentcontextid = $frontpagecontext->id;
1419                              $bi->showinsubcontexts = false;
1420                              $bi->pagetypepattern  = 'site-index';
1421                              // This is the only relevant page type anyway but we'll set it explicitly just
1422                              // in case the front page grows site-index-* subpages of its own later
1423                              break;
1424                      }
1425                  }
1426              }
1427  
1428              $bits = explode('-', $bi->pagetypepattern);
1429              // hacks for some contexts
1430              if (($parentcontext->contextlevel == CONTEXT_COURSE) && ($parentcontext->instanceid != SITEID)) {
1431                  // For course context
1432                  // is page type pattern is mod-*, change showinsubcontext to 1
1433                  if ($bits[0] == 'mod' || $bi->pagetypepattern == '*') {
1434                      $bi->showinsubcontexts = 1;
1435                  } else {
1436                      $bi->showinsubcontexts = 0;
1437                  }
1438              } else  if ($parentcontext->contextlevel == CONTEXT_USER) {
1439                  // for user context
1440                  // subpagepattern should be null
1441                  if ($bits[0] == 'user' or $bits[0] == 'my') {
1442                      // we don't need subpagepattern in usercontext
1443                      $bi->subpagepattern = null;
1444                  }
1445              }
1446  
1447              $bi->defaultregion = $data->bui_defaultregion;
1448              $bi->defaultweight = $data->bui_defaultweight;
1449              $DB->update_record('block_instances', $bi);
1450  
1451              if (!empty($block->config)) {
1452                  $config = clone($block->config);
1453              } else {
1454                  $config = new stdClass;
1455              }
1456              foreach ($data as $configfield => $value) {
1457                  if (strpos($configfield, 'config_') !== 0) {
1458                      continue;
1459                  }
1460                  $field = substr($configfield, 7);
1461                  $config->$field = $value;
1462              }
1463              $block->instance_config_save($config);
1464  
1465              $bp = new stdClass;
1466              $bp->visible = $data->bui_visible;
1467              $bp->region = $data->bui_region;
1468              $bp->weight = $data->bui_weight;
1469              $needbprecord = !$data->bui_visible || $data->bui_region != $data->bui_defaultregion ||
1470                      $data->bui_weight != $data->bui_defaultweight;
1471  
1472              if ($block->instance->blockpositionid && !$needbprecord) {
1473                  $DB->delete_records('block_positions', array('id' => $block->instance->blockpositionid));
1474  
1475              } else if ($block->instance->blockpositionid && $needbprecord) {
1476                  $bp->id = $block->instance->blockpositionid;
1477                  $DB->update_record('block_positions', $bp);
1478  
1479              } else if ($needbprecord) {
1480                  $bp->blockinstanceid = $block->instance->id;
1481                  $bp->contextid = $this->page->context->id;
1482                  $bp->pagetype = $this->page->pagetype;
1483                  if ($this->page->subpage) {
1484                      $bp->subpage = $this->page->subpage;
1485                  } else {
1486                      $bp->subpage = '';
1487                  }
1488                  $DB->insert_record('block_positions', $bp);
1489              }
1490  
1491              redirect($this->page->url);
1492  
1493          } else {
1494              $strheading = get_string('blockconfiga', 'moodle', $block->get_title());
1495              $editpage->set_title($strheading);
1496              $editpage->set_heading($strheading);
1497              $bits = explode('-', $this->page->pagetype);
1498              if ($bits[0] == 'tag' && !empty($this->page->subpage)) {
1499                  // better navbar for tag pages
1500                  $editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));
1501                  $tag = core_tag_tag::get($this->page->subpage);
1502                  // tag search page doesn't have subpageid
1503                  if ($tag) {
1504                      $editpage->navbar->add($tag->get_display_name(), $tag->get_view_url());
1505                  }
1506              }
1507              $editpage->navbar->add($block->get_title());
1508              $editpage->navbar->add(get_string('configuration'));
1509              echo $output->header();
1510              echo $output->heading($strheading, 2);
1511              $mform->display();
1512              echo $output->footer();
1513              exit;
1514          }
1515      }
1516  
1517      /**
1518       * Handle showing/processing the submission from the block editing form.
1519       * @return boolean true if the form was submitted and the new config saved. Does not
1520       *      return if the editing form was displayed. False otherwise.
1521       */
1522      public function process_url_move() {
1523          global $CFG, $DB, $PAGE;
1524  
1525          $blockid = optional_param('bui_moveid', null, PARAM_INT);
1526          if (!$blockid) {
1527              return false;
1528          }
1529  
1530          require_sesskey();
1531  
1532          $block = $this->find_instance($blockid);
1533  
1534          if (!$this->page->user_can_edit_blocks()) {
1535              throw new moodle_exception('nopermissions', '', $this->page->url->out(), get_string('editblock'));
1536          }
1537  
1538          $newregion = optional_param('bui_newregion', '', PARAM_ALPHANUMEXT);
1539          $newweight = optional_param('bui_newweight', null, PARAM_FLOAT);
1540          if (!$newregion || is_null($newweight)) {
1541              // Don't have a valid target position yet, must be just starting the move.
1542              $this->movingblock = $blockid;
1543              $this->page->ensure_param_not_in_url('bui_moveid');
1544              return false;
1545          }
1546  
1547          if (!$this->is_known_region($newregion)) {
1548              throw new moodle_exception('unknownblockregion', '', $this->page->url, $newregion);
1549          }
1550  
1551          // Move this block. This may involve moving other nearby blocks.
1552          $blocks = $this->birecordsbyregion[$newregion];
1553  
1554          $maxweight = self::MAX_WEIGHT;
1555          $minweight = -self::MAX_WEIGHT;
1556  
1557          // Initialise the used weights and spareweights array with the default values
1558          $spareweights = array();
1559          $usedweights = array();
1560          for ($i = $minweight; $i <= $maxweight; $i++) {
1561              $spareweights[$i] = $i;
1562              $usedweights[$i] = array();
1563          }
1564  
1565          // Check each block and sort out where we have used weights
1566          foreach ($blocks as $bi) {
1567              if ($bi->weight > $maxweight) {
1568                  // If this statement is true then the blocks weight is more than the
1569                  // current maximum. To ensure that we can get the best block position
1570                  // we will initialise elements within the usedweights and spareweights
1571                  // arrays between the blocks weight (which will then be the new max) and
1572                  // the current max
1573                  $parseweight = $bi->weight;
1574                  while (!array_key_exists($parseweight, $usedweights)) {
1575                      $usedweights[$parseweight] = array();
1576                      $spareweights[$parseweight] = $parseweight;
1577                      $parseweight--;
1578                  }
1579                  $maxweight = $bi->weight;
1580              } else if ($bi->weight < $minweight) {
1581                  // As above except this time the blocks weight is LESS than the
1582                  // the current minimum, so we will initialise the array from the
1583                  // blocks weight (new minimum) to the current minimum
1584                  $parseweight = $bi->weight;
1585                  while (!array_key_exists($parseweight, $usedweights)) {
1586                      $usedweights[$parseweight] = array();
1587                      $spareweights[$parseweight] = $parseweight;
1588                      $parseweight++;
1589                  }
1590                  $minweight = $bi->weight;
1591              }
1592              if ($bi->id != $block->instance->id) {
1593                  unset($spareweights[$bi->weight]);
1594                  $usedweights[$bi->weight][] = $bi->id;
1595              }
1596          }
1597  
1598          // First we find the nearest gap in the list of weights.
1599          $bestdistance = max(abs($newweight - self::MAX_WEIGHT), abs($newweight + self::MAX_WEIGHT)) + 1;
1600          $bestgap = null;
1601          foreach ($spareweights as $spareweight) {
1602              if (abs($newweight - $spareweight) < $bestdistance) {
1603                  $bestdistance = abs($newweight - $spareweight);
1604                  $bestgap = $spareweight;
1605              }
1606          }
1607  
1608          // If there is no gap, we have to go outside -self::MAX_WEIGHT .. self::MAX_WEIGHT.
1609          if (is_null($bestgap)) {
1610              $bestgap = self::MAX_WEIGHT + 1;
1611              while (!empty($usedweights[$bestgap])) {
1612                  $bestgap++;
1613              }
1614          }
1615  
1616          // Now we know the gap we are aiming for, so move all the blocks along.
1617          if ($bestgap < $newweight) {
1618              $newweight = floor($newweight);
1619              for ($weight = $bestgap + 1; $weight <= $newweight; $weight++) {
1620                  if (array_key_exists($weight, $usedweights)) {
1621                      foreach ($usedweights[$weight] as $biid) {
1622                          $this->reposition_block($biid, $newregion, $weight - 1);
1623                      }
1624                  }
1625              }
1626              $this->reposition_block($block->instance->id, $newregion, $newweight);
1627          } else {
1628              $newweight = ceil($newweight);
1629              for ($weight = $bestgap - 1; $weight >= $newweight; $weight--) {
1630                  if (array_key_exists($weight, $usedweights)) {
1631                      foreach ($usedweights[$weight] as $biid) {
1632                          $this->reposition_block($biid, $newregion, $weight + 1);
1633                      }
1634                  }
1635              }
1636              $this->reposition_block($block->instance->id, $newregion, $newweight);
1637          }
1638  
1639          $this->page->ensure_param_not_in_url('bui_moveid');
1640          $this->page->ensure_param_not_in_url('bui_newregion');
1641          $this->page->ensure_param_not_in_url('bui_newweight');
1642          return true;
1643      }
1644  
1645      /**
1646       * Turns the display of normal blocks either on or off.
1647       *
1648       * @param bool $setting
1649       */
1650      public function show_only_fake_blocks($setting = true) {
1651          $this->fakeblocksonly = $setting;
1652      }
1653  }
1654  
1655  /// Helper functions for working with block classes ============================
1656  
1657  /**
1658   * Call a class method (one that does not require a block instance) on a block class.
1659   *
1660   * @param string $blockname the name of the block.
1661   * @param string $method the method name.
1662   * @param array $param parameters to pass to the method.
1663   * @return mixed whatever the method returns.
1664   */
1665  function block_method_result($blockname, $method, $param = NULL) {
1666      if(!block_load_class($blockname)) {
1667          return NULL;
1668      }
1669      return call_user_func(array('block_'.$blockname, $method), $param);
1670  }
1671  
1672  /**
1673   * Creates a new instance of the specified block class.
1674   *
1675   * @param string $blockname the name of the block.
1676   * @param $instance block_instances DB table row (optional).
1677   * @param moodle_page $page the page this block is appearing on.
1678   * @return block_base the requested block instance.
1679   */
1680  function block_instance($blockname, $instance = NULL, $page = NULL) {
1681      if(!block_load_class($blockname)) {
1682          return false;
1683      }
1684      $classname = 'block_'.$blockname;
1685      $retval = new $classname;
1686      if($instance !== NULL) {
1687          if (is_null($page)) {
1688              global $PAGE;
1689              $page = $PAGE;
1690          }
1691          $retval->_load_instance($instance, $page);
1692      }
1693      return $retval;
1694  }
1695  
1696  /**
1697   * Load the block class for a particular type of block.
1698   *
1699   * @param string $blockname the name of the block.
1700   * @return boolean success or failure.
1701   */
1702  function block_load_class($blockname) {
1703      global $CFG;
1704  
1705      if(empty($blockname)) {
1706          return false;
1707      }
1708  
1709      $classname = 'block_'.$blockname;
1710  
1711      if(class_exists($classname)) {
1712          return true;
1713      }
1714  
1715      $blockpath = $CFG->dirroot.'/blocks/'.$blockname.'/block_'.$blockname.'.php';
1716  
1717      if (file_exists($blockpath)) {
1718          require_once($CFG->dirroot.'/blocks/moodleblock.class.php');
1719          include_once($blockpath);
1720      }else{
1721          //debugging("$blockname code does not exist in $blockpath", DEBUG_DEVELOPER);
1722          return false;
1723      }
1724  
1725      return class_exists($classname);
1726  }
1727  
1728  /**
1729   * Given a specific page type, return all the page type patterns that might
1730   * match it.
1731   *
1732   * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1733   * @return array an array of all the page type patterns that might match this page type.
1734   */
1735  function matching_page_type_patterns($pagetype) {
1736      $patterns = array($pagetype);
1737      $bits = explode('-', $pagetype);
1738      if (count($bits) == 3 && $bits[0] == 'mod') {
1739          if ($bits[2] == 'view') {
1740              $patterns[] = 'mod-*-view';
1741          } else if ($bits[2] == 'index') {
1742              $patterns[] = 'mod-*-index';
1743          }
1744      }
1745      while (count($bits) > 0) {
1746          $patterns[] = implode('-', $bits) . '-*';
1747          array_pop($bits);
1748      }
1749      $patterns[] = '*';
1750      return $patterns;
1751  }
1752  
1753  /**
1754   * Give an specific pattern, return all the page type patterns that would also match it.
1755   *
1756   * @param  string $pattern the pattern, e.g. 'mod-forum-*' or 'mod-quiz-view'.
1757   * @return array of all the page type patterns matching.
1758   */
1759  function matching_page_type_patterns_from_pattern($pattern) {
1760      $patterns = array($pattern);
1761      if ($pattern === '*') {
1762          return $patterns;
1763      }
1764  
1765      // Only keep the part before the star because we will append -* to all the bits.
1766      $star = strpos($pattern, '-*');
1767      if ($star !== false) {
1768          $pattern = substr($pattern, 0, $star);
1769      }
1770  
1771      $patterns = array_merge($patterns, matching_page_type_patterns($pattern));
1772      $patterns = array_unique($patterns);
1773  
1774      return $patterns;
1775  }
1776  
1777  /**
1778   * Given a specific page type, parent context and currect context, return all the page type patterns
1779   * that might be used by this block.
1780   *
1781   * @param string $pagetype for example 'course-view-weeks' or 'mod-quiz-view'.
1782   * @param stdClass $parentcontext Block's parent context
1783   * @param stdClass $currentcontext Current context of block
1784   * @return array an array of all the page type patterns that might match this page type.
1785   */
1786  function generate_page_type_patterns($pagetype, $parentcontext = null, $currentcontext = null) {
1787      global $CFG; // Required for includes bellow.
1788  
1789      $bits = explode('-', $pagetype);
1790  
1791      $core = core_component::get_core_subsystems();
1792      $plugins = core_component::get_plugin_types();
1793  
1794      //progressively strip pieces off the page type looking for a match
1795      $componentarray = null;
1796      for ($i = count($bits); $i > 0; $i--) {
1797          $possiblecomponentarray = array_slice($bits, 0, $i);
1798          $possiblecomponent = implode('', $possiblecomponentarray);
1799  
1800          // Check to see if the component is a core component
1801          if (array_key_exists($possiblecomponent, $core) && !empty($core[$possiblecomponent])) {
1802              $libfile = $core[$possiblecomponent].'/lib.php';
1803              if (file_exists($libfile)) {
1804                  require_once($libfile);
1805                  $function = $possiblecomponent.'_page_type_list';
1806                  if (function_exists($function)) {
1807                      if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1808                          break;
1809                      }
1810                  }
1811              }
1812          }
1813  
1814          //check the plugin directory and look for a callback
1815          if (array_key_exists($possiblecomponent, $plugins) && !empty($plugins[$possiblecomponent])) {
1816  
1817              //We've found a plugin type. Look for a plugin name by getting the next section of page type
1818              if (count($bits) > $i) {
1819                  $pluginname = $bits[$i];
1820                  $directory = core_component::get_plugin_directory($possiblecomponent, $pluginname);
1821                  if (!empty($directory)){
1822                      $libfile = $directory.'/lib.php';
1823                      if (file_exists($libfile)) {
1824                          require_once($libfile);
1825                          $function = $possiblecomponent.'_'.$pluginname.'_page_type_list';
1826                          if (!function_exists($function)) {
1827                              $function = $pluginname.'_page_type_list';
1828                          }
1829                          if (function_exists($function)) {
1830                              if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1831                                  break;
1832                              }
1833                          }
1834                      }
1835                  }
1836              }
1837  
1838              //we'll only get to here if we still don't have any patterns
1839              //the plugin type may have a callback
1840              $directory = $plugins[$possiblecomponent];
1841              $libfile = $directory.'/lib.php';
1842              if (file_exists($libfile)) {
1843                  require_once($libfile);
1844                  $function = $possiblecomponent.'_page_type_list';
1845                  if (function_exists($function)) {
1846                      if ($patterns = $function($pagetype, $parentcontext, $currentcontext)) {
1847                          break;
1848                      }
1849                  }
1850              }
1851          }
1852      }
1853  
1854      if (empty($patterns)) {
1855          $patterns = default_page_type_list($pagetype, $parentcontext, $currentcontext);
1856      }
1857  
1858      // Ensure that the * pattern is always available if editing block 'at distance', so
1859      // we always can 'bring back' it to the original context. MDL-30340
1860      if ((!isset($currentcontext) or !isset($parentcontext) or $currentcontext->id != $parentcontext->id) && !isset($patterns['*'])) {
1861          // TODO: We could change the string here, showing its 'bring back' meaning
1862          $patterns['*'] = get_string('page-x', 'pagetype');
1863      }
1864  
1865      return $patterns;
1866  }
1867  
1868  /**
1869   * Generates a default page type list when a more appropriate callback cannot be decided upon.
1870   *
1871   * @param string $pagetype
1872   * @param stdClass $parentcontext
1873   * @param stdClass $currentcontext
1874   * @return array
1875   */
1876  function default_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1877      // Generate page type patterns based on current page type if
1878      // callbacks haven't been defined
1879      $patterns = array($pagetype => $pagetype);
1880      $bits = explode('-', $pagetype);
1881      while (count($bits) > 0) {
1882          $pattern = implode('-', $bits) . '-*';
1883          $pagetypestringname = 'page-'.str_replace('*', 'x', $pattern);
1884          // guessing page type description
1885          if (get_string_manager()->string_exists($pagetypestringname, 'pagetype')) {
1886              $patterns[$pattern] = get_string($pagetypestringname, 'pagetype');
1887          } else {
1888              $patterns[$pattern] = $pattern;
1889          }
1890          array_pop($bits);
1891      }
1892      $patterns['*'] = get_string('page-x', 'pagetype');
1893      return $patterns;
1894  }
1895  
1896  /**
1897   * Generates the page type list for the my moodle page
1898   *
1899   * @param string $pagetype
1900   * @param stdClass $parentcontext
1901   * @param stdClass $currentcontext
1902   * @return array
1903   */
1904  function my_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1905      return array('my-index' => get_string('page-my-index', 'pagetype'));
1906  }
1907  
1908  /**
1909   * Generates the page type list for a module by either locating and using the modules callback
1910   * or by generating a default list.
1911   *
1912   * @param string $pagetype
1913   * @param stdClass $parentcontext
1914   * @param stdClass $currentcontext
1915   * @return array
1916   */
1917  function mod_page_type_list($pagetype, $parentcontext = null, $currentcontext = null) {
1918      $patterns = plugin_page_type_list($pagetype, $parentcontext, $currentcontext);
1919      if (empty($patterns)) {
1920          // if modules don't have callbacks
1921          // generate two default page type patterns for modules only
1922          $bits = explode('-', $pagetype);
1923          $patterns = array($pagetype => $pagetype);
1924          if ($bits[2] == 'view') {
1925              $patterns['mod-*-view'] = get_string('page-mod-x-view', 'pagetype');
1926          } else if ($bits[2] == 'index') {
1927              $patterns['mod-*-index'] = get_string('page-mod-x-index', 'pagetype');
1928          }
1929      }
1930      return $patterns;
1931  }
1932  /// Functions update the blocks if required by the request parameters ==========
1933  
1934  /**
1935   * Return a {@link block_contents} representing the add a new block UI, if
1936   * this user is allowed to see it.
1937   *
1938   * @return block_contents an appropriate block_contents, or null if the user
1939   * cannot add any blocks here.
1940   */
1941  function block_add_block_ui($page, $output) {
1942      global $CFG, $OUTPUT;
1943      if (!$page->user_is_editing() || !$page->user_can_edit_blocks()) {
1944          return null;
1945      }
1946  
1947      $bc = new block_contents();
1948      $bc->title = get_string('addblock');
1949      $bc->add_class('block_adminblock');
1950      $bc->attributes['data-block'] = 'adminblock';
1951  
1952      $missingblocks = $page->blocks->get_addable_blocks();
1953      if (empty($missingblocks)) {
1954          $bc->content = get_string('noblockstoaddhere');
1955          return $bc;
1956      }
1957  
1958      $menu = array();
1959      foreach ($missingblocks as $block) {
1960          $blockobject = block_instance($block->name);
1961          if ($blockobject !== false && $blockobject->user_can_addto($page)) {
1962              $menu[$block->name] = $blockobject->get_title();
1963          }
1964      }
1965      core_collator::asort($menu);
1966  
1967      $actionurl = new moodle_url($page->url, array('sesskey'=>sesskey()));
1968      $select = new single_select($actionurl, 'bui_addblock', $menu, null, array(''=>get_string('adddots')), 'add_block');
1969      $select->set_label(get_string('addblock'), array('class'=>'accesshide'));
1970      $bc->content = $OUTPUT->render($select);
1971      return $bc;
1972  }
1973  
1974  /**
1975   * Actually delete from the database any blocks that are currently on this page,
1976   * but which should not be there according to blocks_name_allowed_in_format.
1977   *
1978   * @todo Write/Fix this function. Currently returns immediately
1979   * @param $course
1980   */
1981  function blocks_remove_inappropriate($course) {
1982      // TODO
1983      return;
1984      /*
1985      $blockmanager = blocks_get_by_page($page);
1986  
1987      if (empty($blockmanager)) {
1988          return;
1989      }
1990  
1991      if (($pageformat = $page->pagetype) == NULL) {
1992          return;
1993      }
1994  
1995      foreach($blockmanager as $region) {
1996          foreach($region as $instance) {
1997              $block = blocks_get_record($instance->blockid);
1998              if(!blocks_name_allowed_in_format($block->name, $pageformat)) {
1999                 blocks_delete_instance($instance->instance);
2000              }
2001          }
2002      }*/
2003  }
2004  
2005  /**
2006   * Check that a given name is in a permittable format
2007   *
2008   * @param string $name
2009   * @param string $pageformat
2010   * @return bool
2011   */
2012  function blocks_name_allowed_in_format($name, $pageformat) {
2013      $accept = NULL;
2014      $maxdepth = -1;
2015      if (!$bi = block_instance($name)) {
2016          return false;
2017      }
2018  
2019      $formats = $bi->applicable_formats();
2020      if (!$formats) {
2021          $formats = array();
2022      }
2023      foreach ($formats as $format => $allowed) {
2024          $formatregex = '/^'.str_replace('*', '[^-]*', $format).'.*$/';
2025          $depth = substr_count($format, '-');
2026          if (preg_match($formatregex, $pageformat) && $depth > $maxdepth) {
2027              $maxdepth = $depth;
2028              $accept = $allowed;
2029          }
2030      }
2031      if ($accept === NULL) {
2032          $accept = !empty($formats['all']);
2033      }
2034      return $accept;
2035  }
2036  
2037  /**
2038   * Delete a block, and associated data.
2039   *
2040   * @param object $instance a row from the block_instances table
2041   * @param bool $nolongerused legacy parameter. Not used, but kept for backwards compatibility.
2042   * @param bool $skipblockstables for internal use only. Makes @see blocks_delete_all_for_context() more efficient.
2043   */
2044  function blocks_delete_instance($instance, $nolongerused = false, $skipblockstables = false) {
2045      global $DB;
2046  
2047      // Allow plugins to use this block before we completely delete it.
2048      if ($pluginsfunction = get_plugins_with_function('pre_block_delete')) {
2049          foreach ($pluginsfunction as $plugintype => $plugins) {
2050              foreach ($plugins as $pluginfunction) {
2051                  $pluginfunction($instance);
2052              }
2053          }
2054      }
2055  
2056      if ($block = block_instance($instance->blockname, $instance)) {
2057          $block->instance_delete();
2058      }
2059      context_helper::delete_instance(CONTEXT_BLOCK, $instance->id);
2060  
2061      if (!$skipblockstables) {
2062          $DB->delete_records('block_positions', array('blockinstanceid' => $instance->id));
2063          $DB->delete_records('block_instances', array('id' => $instance->id));
2064          $DB->delete_records_list('user_preferences', 'name', array('block'.$instance->id.'hidden','docked_block_instance_'.$instance->id));
2065      }
2066  }
2067  
2068  /**
2069   * Delete multiple blocks at once.
2070   *
2071   * @param array $instanceids A list of block instance ID.
2072   */
2073  function blocks_delete_instances($instanceids) {
2074      global $DB;
2075  
2076      $limit = 1000;
2077      $count = count($instanceids);
2078      $chunks = [$instanceids];
2079      if ($count > $limit) {
2080          $chunks = array_chunk($instanceids, $limit);
2081      }
2082  
2083      // Perform deletion for each chunk.
2084      foreach ($chunks as $chunk) {
2085          $instances = $DB->get_recordset_list('block_instances', 'id', $chunk);
2086          foreach ($instances as $instance) {
2087              blocks_delete_instance($instance, false, true);
2088          }
2089          $instances->close();
2090  
2091          $DB->delete_records_list('block_positions', 'blockinstanceid', $chunk);
2092          $DB->delete_records_list('block_instances', 'id', $chunk);
2093  
2094          $preferences = array();
2095          foreach ($chunk as $instanceid) {
2096              $preferences[] = 'block' . $instanceid . 'hidden';
2097              $preferences[] = 'docked_block_instance_' . $instanceid;
2098          }
2099          $DB->delete_records_list('user_preferences', 'name', $preferences);
2100      }
2101  }
2102  
2103  /**
2104   * Delete all the blocks that belong to a particular context.
2105   *
2106   * @param int $contextid the context id.
2107   */
2108  function blocks_delete_all_for_context($contextid) {
2109      global $DB;
2110      $instances = $DB->get_recordset('block_instances', array('parentcontextid' => $contextid));
2111      foreach ($instances as $instance) {
2112          blocks_delete_instance($instance, true);
2113      }
2114      $instances->close();
2115      $DB->delete_records('block_instances', array('parentcontextid' => $contextid));
2116      $DB->delete_records('block_positions', array('contextid' => $contextid));
2117  }
2118  
2119  /**
2120   * Set a block to be visible or hidden on a particular page.
2121   *
2122   * @param object $instance a row from the block_instances, preferably LEFT JOINed with the
2123   *      block_positions table as return by block_manager.
2124   * @param moodle_page $page the back to set the visibility with respect to.
2125   * @param integer $newvisibility 1 for visible, 0 for hidden.
2126   */
2127  function blocks_set_visibility($instance, $page, $newvisibility) {
2128      global $DB;
2129      if (!empty($instance->blockpositionid)) {
2130          // Already have local information on this page.
2131          $DB->set_field('block_positions', 'visible', $newvisibility, array('id' => $instance->blockpositionid));
2132          return;
2133      }
2134  
2135      // Create a new block_positions record.
2136      $bp = new stdClass;
2137      $bp->blockinstanceid = $instance->id;
2138      $bp->contextid = $page->context->id;
2139      $bp->pagetype = $page->pagetype;
2140      if ($page->subpage) {
2141          $bp->subpage = $page->subpage;
2142      }
2143      $bp->visible = $newvisibility;
2144      $bp->region = $instance->defaultregion;
2145      $bp->weight = $instance->defaultweight;
2146      $DB->insert_record('block_positions', $bp);
2147  }
2148  
2149  /**
2150   * Get the block record for a particular blockid - that is, a particular type os block.
2151   *
2152   * @param $int blockid block type id. If null, an array of all block types is returned.
2153   * @param bool $notusedanymore No longer used.
2154   * @return array|object row from block table, or all rows.
2155   */
2156  function blocks_get_record($blockid = NULL, $notusedanymore = false) {
2157      global $PAGE;
2158      $blocks = $PAGE->blocks->get_installed_blocks();
2159      if ($blockid === NULL) {
2160          return $blocks;
2161      } else if (isset($blocks[$blockid])) {
2162          return $blocks[$blockid];
2163      } else {
2164          return false;
2165      }
2166  }
2167  
2168  /**
2169   * Find a given block by its blockid within a provide array
2170   *
2171   * @param int $blockid
2172   * @param array $blocksarray
2173   * @return bool|object Instance if found else false
2174   */
2175  function blocks_find_block($blockid, $blocksarray) {
2176      if (empty($blocksarray)) {
2177          return false;
2178      }
2179      foreach($blocksarray as $blockgroup) {
2180          if (empty($blockgroup)) {
2181              continue;
2182          }
2183          foreach($blockgroup as $instance) {
2184              if($instance->blockid == $blockid) {
2185                  return $instance;
2186              }
2187          }
2188      }
2189      return false;
2190  }
2191  
2192  // Functions for programatically adding default blocks to pages ================
2193  
2194   /**
2195    * Parse a list of default blocks. See config-dist for a description of the format.
2196    *
2197    * @param string $blocksstr Determines the starting point that the blocks are added in the region.
2198    * @return array the parsed list of default blocks
2199    */
2200  function blocks_parse_default_blocks_list($blocksstr) {
2201      $blocks = array();
2202      $bits = explode(':', $blocksstr);
2203      if (!empty($bits)) {
2204          $leftbits = trim(array_shift($bits));
2205          if ($leftbits != '') {
2206              $blocks[BLOCK_POS_LEFT] = explode(',', $leftbits);
2207          }
2208      }
2209      if (!empty($bits)) {
2210          $rightbits = trim(array_shift($bits));
2211          if ($rightbits != '') {
2212              $blocks[BLOCK_POS_RIGHT] = explode(',', $rightbits);
2213          }
2214      }
2215      return $blocks;
2216  }
2217  
2218  /**
2219   * @return array the blocks that should be added to the site course by default.
2220   */
2221  function blocks_get_default_site_course_blocks() {
2222      global $CFG;
2223  
2224      if (!empty($CFG->defaultblocks_site)) {
2225          return blocks_parse_default_blocks_list($CFG->defaultblocks_site);
2226      } else {
2227          return array(
2228              BLOCK_POS_LEFT => array('site_main_menu'),
2229              BLOCK_POS_RIGHT => array('course_summary', 'calendar_month')
2230          );
2231      }
2232  }
2233  
2234  /**
2235   * Add the default blocks to a course.
2236   *
2237   * @param object $course a course object.
2238   */
2239  function blocks_add_default_course_blocks($course) {
2240      global $CFG;
2241  
2242      if (!empty($CFG->defaultblocks_override)) {
2243          $blocknames = blocks_parse_default_blocks_list($CFG->defaultblocks_override);
2244  
2245      } else if ($course->id == SITEID) {
2246          $blocknames = blocks_get_default_site_course_blocks();
2247  
2248      } else if (!empty($CFG->{'defaultblocks_' . $course->format})) {
2249          $blocknames = blocks_parse_default_blocks_list($CFG->{'defaultblocks_' . $course->format});
2250  
2251      } else {
2252          require_once($CFG->dirroot. '/course/lib.php');
2253          $blocknames = course_get_format($course)->get_default_blocks();
2254  
2255      }
2256  
2257      if ($course->id == SITEID) {
2258          $pagetypepattern = 'site-index';
2259      } else {
2260          $pagetypepattern = 'course-view-*';
2261      }
2262      $page = new moodle_page();
2263      $page->set_course($course);
2264      $page->blocks->add_blocks($blocknames, $pagetypepattern);
2265  }
2266  
2267  /**
2268   * Add the default system-context blocks. E.g. the admin tree.
2269   */
2270  function blocks_add_default_system_blocks() {
2271      global $DB;
2272  
2273      $page = new moodle_page();
2274      $page->set_context(context_system::instance());
2275      $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('navigation', 'settings')), '*', null, true);
2276      $page->blocks->add_blocks(array(BLOCK_POS_LEFT => array('admin_bookmarks')), 'admin-*', null, null, 2);
2277  
2278      if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {
2279          $subpagepattern = $defaultmypage->id;
2280      } else {
2281          $subpagepattern = null;
2282      }
2283  
2284      $newblocks = array('private_files', 'online_users', 'badges', 'calendar_month', 'calendar_upcoming');
2285      $newcontent = array('lp', 'course_overview');
2286      $page->blocks->add_blocks(array(BLOCK_POS_RIGHT => $newblocks, 'content' => $newcontent), 'my-index', $subpagepattern);
2287  }


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