Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the parent class for moodle blocks, block_base.
  19   *
  20   * @package    core_block
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
  22   */
  23  
  24  /// Constants
  25  
  26  /**
  27   * Block type of list. Contents of block should be set as an associative array in the content object as items ($this->content->items). Optionally include footer text in $this->content->footer.
  28   */
  29  define('BLOCK_TYPE_LIST',    1);
  30  
  31  /**
  32   * Block type of text. Contents of block should be set to standard html text in the content object as items ($this->content->text). Optionally include footer text in $this->content->footer.
  33   */
  34  define('BLOCK_TYPE_TEXT',    2);
  35  /**
  36   * Block type of tree. $this->content->items is a list of tree_item objects and $this->content->footer is a string.
  37   */
  38  define('BLOCK_TYPE_TREE',    3);
  39  
  40  /**
  41   * Class for describing a moodle block, all Moodle blocks derive from this class
  42   *
  43   * @author Jon Papaioannou
  44   * @package core_block
  45   */
  46  class block_base {
  47  
  48      /**
  49       * Internal var for storing/caching translated strings
  50       * @var string $str
  51       */
  52      var $str;
  53  
  54      /**
  55       * The title of the block to be displayed in the block title area.
  56       * @var string $title
  57       */
  58      var $title         = NULL;
  59  
  60      /**
  61       * The name of the block to be displayed in the block title area if the title is empty.
  62       * @var string arialabel
  63       */
  64      var $arialabel         = NULL;
  65  
  66      /**
  67       * The type of content that this block creates. Currently support options - BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT
  68       * @var int $content_type
  69       */
  70      var $content_type  = BLOCK_TYPE_TEXT;
  71  
  72      /**
  73       * An object to contain the information to be displayed in the block.
  74       * @var stdObject $content
  75       */
  76      var $content       = NULL;
  77  
  78      /**
  79       * The initialized instance of this block object.
  80       * @var block $instance
  81       */
  82      var $instance      = NULL;
  83  
  84      /**
  85       * The page that this block is appearing on.
  86       * @var moodle_page
  87       */
  88      public $page       = NULL;
  89  
  90      /**
  91       * This blocks's context.
  92       * @var stdClass
  93       */
  94      public $context    = NULL;
  95  
  96      /**
  97       * An object containing the instance configuration information for the current instance of this block.
  98       * @var stdObject $config
  99       */
 100      var $config        = NULL;
 101  
 102      /**
 103       * How often the cronjob should run, 0 if not at all.
 104       * @var int $cron
 105       */
 106  
 107      var $cron          = NULL;
 108  
 109  /// Class Functions
 110  
 111      /**
 112       * Fake constructor to keep PHP5 happy
 113       *
 114       */
 115      function __construct() {
 116          $this->init();
 117      }
 118  
 119      /**
 120       * Function that can be overridden to do extra cleanup before
 121       * the database tables are deleted. (Called once per block, not per instance!)
 122       */
 123      function before_delete() {
 124      }
 125  
 126      /**
 127       * Returns the block name, as present in the class name,
 128       * the database, the block directory, etc etc.
 129       *
 130       * @return string
 131       */
 132      function name() {
 133          // Returns the block name, as present in the class name,
 134          // the database, the block directory, etc etc.
 135          static $myname;
 136          if ($myname === NULL) {
 137              $myname = strtolower(get_class($this));
 138              $myname = substr($myname, strpos($myname, '_') + 1);
 139          }
 140          return $myname;
 141      }
 142  
 143      /**
 144       * Parent class version of this function simply returns NULL
 145       * This should be implemented by the derived class to return
 146       * the content object.
 147       *
 148       * @return stdObject
 149       */
 150      function get_content() {
 151          // This should be implemented by the derived class.
 152          return NULL;
 153      }
 154  
 155      /**
 156       * Returns the class $title var value.
 157       *
 158       * Intentionally doesn't check if a title is set.
 159       * This is already done in {@link _self_test()}
 160       *
 161       * @return string $this->title
 162       */
 163      function get_title() {
 164          // Intentionally doesn't check if a title is set. This is already done in _self_test()
 165          return $this->title;
 166      }
 167  
 168      /**
 169       * Returns the class $content_type var value.
 170       *
 171       * Intentionally doesn't check if content_type is set.
 172       * This is already done in {@link _self_test()}
 173       *
 174       * @return string $this->content_type
 175       */
 176      function get_content_type() {
 177          // Intentionally doesn't check if a content_type is set. This is already done in _self_test()
 178          return $this->content_type;
 179      }
 180  
 181      /**
 182       * Returns true or false, depending on whether this block has any content to display
 183       * and whether the user has permission to view the block
 184       *
 185       * @return boolean
 186       */
 187      function is_empty() {
 188          if ( !has_capability('moodle/block:view', $this->context) ) {
 189              return true;
 190          }
 191  
 192          $this->get_content();
 193          return(empty($this->content->text) && empty($this->content->footer));
 194      }
 195  
 196      /**
 197       * First sets the current value of $this->content to NULL
 198       * then calls the block's {@link get_content()} function
 199       * to set its value back.
 200       *
 201       * @return stdObject
 202       */
 203      function refresh_content() {
 204          // Nothing special here, depends on content()
 205          $this->content = NULL;
 206          return $this->get_content();
 207      }
 208  
 209      /**
 210       * Return a block_contents object representing the full contents of this block.
 211       *
 212       * This internally calls ->get_content(), and then adds the editing controls etc.
 213       *
 214       * You probably should not override this method, but instead override
 215       * {@link html_attributes()}, {@link formatted_contents()} or {@link get_content()},
 216       * {@link hide_header()}, {@link (get_edit_controls)}, etc.
 217       *
 218       * @return block_contents a representation of the block, for rendering.
 219       * @since Moodle 2.0.
 220       */
 221      public function get_content_for_output($output) {
 222          global $CFG;
 223  
 224          // We can exit early if the current user doesn't have the capability to view the block.
 225          if (!has_capability('moodle/block:view', $this->context)) {
 226              return null;
 227          }
 228  
 229          $bc = new block_contents($this->html_attributes());
 230          $bc->attributes['data-block'] = $this->name();
 231          $bc->blockinstanceid = $this->instance->id;
 232          $bc->blockpositionid = $this->instance->blockpositionid;
 233  
 234          if ($this->instance->visible) {
 235              $bc->content = $this->formatted_contents($output);
 236              if (!empty($this->content->footer)) {
 237                  $bc->footer = $this->content->footer;
 238              }
 239          } else {
 240              $bc->add_class('invisibleblock');
 241          }
 242  
 243          if (!$this->hide_header()) {
 244              $bc->title = $this->title;
 245          }
 246  
 247          if (empty($bc->title)) {
 248              $bc->arialabel = new lang_string('pluginname', get_class($this));
 249              $this->arialabel = $bc->arialabel;
 250          }
 251  
 252          if ($this->page->user_is_editing()) {
 253              $bc->controls = $this->page->blocks->edit_controls($this);
 254          } else {
 255              // we must not use is_empty on hidden blocks
 256              if ($this->is_empty() && !$bc->controls) {
 257                  return null;
 258              }
 259          }
 260  
 261          if (empty($CFG->allowuserblockhiding)
 262                  || (empty($bc->content) && empty($bc->footer))
 263                  || !$this->instance_can_be_collapsed()) {
 264              $bc->collapsible = block_contents::NOT_HIDEABLE;
 265          } else if (get_user_preferences('block' . $bc->blockinstanceid . 'hidden', false)) {
 266              $bc->collapsible = block_contents::HIDDEN;
 267          } else {
 268              $bc->collapsible = block_contents::VISIBLE;
 269          }
 270  
 271          if ($this->instance_can_be_docked() && !$this->hide_header()) {
 272              $bc->dockable = true;
 273          }
 274  
 275          $bc->annotation = ''; // TODO MDL-19398 need to work out what to say here.
 276  
 277          return $bc;
 278      }
 279  
 280  
 281      /**
 282       * Return an object containing all the block content to be returned by external functions.
 283       *
 284       * If your block is returning formatted content or provide files for download, you should override this method to use the
 285       * external_format_text, external_format_string functions for formatting or external_util::get_area_files for files.
 286       *
 287       * @param  core_renderer $output the rendered used for output
 288       * @return stdClass      object containing the block title, central content, footer and linked files (if any).
 289       * @since  Moodle 3.6
 290       */
 291      public function get_content_for_external($output) {
 292          $bc = new stdClass;
 293          $bc->title = null;
 294          $bc->content = null;
 295          $bc->contentformat = FORMAT_HTML;
 296          $bc->footer = null;
 297          $bc->files = [];
 298  
 299          if ($this->instance->visible) {
 300              $bc->content = $this->formatted_contents($output);
 301              if (!empty($this->content->footer)) {
 302                  $bc->footer = $this->content->footer;
 303              }
 304          }
 305  
 306          if (!$this->hide_header()) {
 307              $bc->title = $this->title;
 308          }
 309  
 310          return $bc;
 311      }
 312  
 313      /**
 314       * Return the plugin config settings for external functions.
 315       *
 316       * In some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.
 317       *
 318       * @return stdClass the configs for both the block instance and plugin (as object with name -> value)
 319       * @since Moodle 3.8
 320       */
 321      public function get_config_for_external() {
 322          return (object) [
 323              'instance' => new stdClass(),
 324              'plugin' => new stdClass(),
 325          ];
 326      }
 327  
 328      /**
 329       * Convert the contents of the block to HTML.
 330       *
 331       * This is used by block base classes like block_list to convert the structured
 332       * $this->content->list and $this->content->icons arrays to HTML. So, in most
 333       * blocks, you probaby want to override the {@link get_contents()} method,
 334       * which generates that structured representation of the contents.
 335       *
 336       * @param $output The core_renderer to use when generating the output.
 337       * @return string the HTML that should appearn in the body of the block.
 338       * @since Moodle 2.0.
 339       */
 340      protected function formatted_contents($output) {
 341          $this->get_content();
 342          $this->get_required_javascript();
 343          if (!empty($this->content->text)) {
 344              return $this->content->text;
 345          } else {
 346              return '';
 347          }
 348      }
 349  
 350      /**
 351       * Tests if this block has been implemented correctly.
 352       * Also, $errors isn't used right now
 353       *
 354       * @return boolean
 355       */
 356  
 357      function _self_test() {
 358          // Tests if this block has been implemented correctly.
 359          // Also, $errors isn't used right now
 360          $errors = array();
 361  
 362          $correct = true;
 363          if ($this->get_title() === NULL) {
 364              $errors[] = 'title_not_set';
 365              $correct = false;
 366          }
 367          if (!in_array($this->get_content_type(), array(BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT, BLOCK_TYPE_TREE))) {
 368              $errors[] = 'invalid_content_type';
 369              $correct = false;
 370          }
 371          //following selftest was not working when roles&capabilities were used from block
 372  /*        if ($this->get_content() === NULL) {
 373              $errors[] = 'content_not_set';
 374              $correct = false;
 375          }*/
 376          $formats = $this->applicable_formats();
 377          if (empty($formats) || array_sum($formats) === 0) {
 378              $errors[] = 'no_formats';
 379              $correct = false;
 380          }
 381  
 382          return $correct;
 383      }
 384  
 385      /**
 386       * Subclasses should override this and return true if the
 387       * subclass block has a settings.php file.
 388       *
 389       * @return boolean
 390       */
 391      function has_config() {
 392          return false;
 393      }
 394  
 395      /**
 396       * Default behavior: save all variables as $CFG properties
 397       * You don't need to override this if you 're satisfied with the above
 398       *
 399       * @deprecated since Moodle 2.9 MDL-49385 - Please use Admin Settings functionality to save block configuration.
 400       */
 401      function config_save($data) {
 402          throw new coding_exception('config_save() can not be used any more, use Admin Settings functionality to save block configuration.');
 403      }
 404  
 405      /**
 406       * Which page types this block may appear on.
 407       *
 408       * The information returned here is processed by the
 409       * {@link blocks_name_allowed_in_format()} function. Look there if you need
 410       * to know exactly how this works.
 411       *
 412       * Default case: everything except mod and tag.
 413       *
 414       * @return array page-type prefix => true/false.
 415       */
 416      function applicable_formats() {
 417          // Default case: the block can be used in courses and site index, but not in activities
 418          return array('all' => true, 'mod' => false, 'tag' => false);
 419      }
 420  
 421  
 422      /**
 423       * Default return is false - header will be shown
 424       * @return boolean
 425       */
 426      function hide_header() {
 427          return false;
 428      }
 429  
 430      /**
 431       * Return any HTML attributes that you want added to the outer <div> that
 432       * of the block when it is output.
 433       *
 434       * Because of the way certain JS events are wired it is a good idea to ensure
 435       * that the default values here still get set.
 436       * I found the easiest way to do this and still set anything you want is to
 437       * override it within your block in the following way
 438       *
 439       * <code php>
 440       * function html_attributes() {
 441       *    $attributes = parent::html_attributes();
 442       *    $attributes['class'] .= ' mynewclass';
 443       *    return $attributes;
 444       * }
 445       * </code>
 446       *
 447       * @return array attribute name => value.
 448       */
 449      function html_attributes() {
 450          $attributes = array(
 451              'id' => 'inst' . $this->instance->id,
 452              'class' => 'block_' . $this->name() . ' block',
 453              'role' => $this->get_aria_role()
 454          );
 455          if ($this->hide_header()) {
 456              $attributes['class'] .= ' no-header';
 457          }
 458          if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_' . $this->instance->id, 0)) {
 459              $attributes['class'] .= ' dock_on_load';
 460          }
 461          return $attributes;
 462      }
 463  
 464      /**
 465       * Set up a particular instance of this class given data from the block_insances
 466       * table and the current page. (See {@link block_manager::load_blocks()}.)
 467       *
 468       * @param stdClass $instance data from block_insances, block_positions, etc.
 469       * @param moodle_page $the page this block is on.
 470       */
 471      function _load_instance($instance, $page) {
 472          if (!empty($instance->configdata)) {
 473              $this->config = unserialize_object(base64_decode($instance->configdata));
 474          }
 475          $this->instance = $instance;
 476          $this->context = context_block::instance($instance->id);
 477          $this->page = $page;
 478          $this->specialization();
 479      }
 480  
 481      /**
 482       * Allows the block to load any JS it requires into the page.
 483       *
 484       * By default this function simply permits the user to dock the block if it is dockable.
 485       *
 486       * Left null as of MDL-64506.
 487       */
 488      function get_required_javascript() {
 489      }
 490  
 491      /**
 492       * This function is called on your subclass right after an instance is loaded
 493       * Use this function to act on instance data just after it's loaded and before anything else is done
 494       * For instance: if your block will have different title's depending on location (site, course, blog, etc)
 495       */
 496      function specialization() {
 497          // Just to make sure that this method exists.
 498      }
 499  
 500      /**
 501       * Is each block of this type going to have instance-specific configuration?
 502       * Normally, this setting is controlled by {@link instance_allow_multiple()}: if multiple
 503       * instances are allowed, then each will surely need its own configuration. However, in some
 504       * cases it may be necessary to provide instance configuration to blocks that do not want to
 505       * allow multiple instances. In that case, make this function return true.
 506       * I stress again that this makes a difference ONLY if {@link instance_allow_multiple()} returns false.
 507       * @return boolean
 508       */
 509      function instance_allow_config() {
 510          return false;
 511      }
 512  
 513      /**
 514       * Are you going to allow multiple instances of each block?
 515       * If yes, then it is assumed that the block WILL USE per-instance configuration
 516       * @return boolean
 517       */
 518      function instance_allow_multiple() {
 519          // Are you going to allow multiple instances of each block?
 520          // If yes, then it is assumed that the block WILL USE per-instance configuration
 521          return false;
 522      }
 523  
 524      /**
 525       * Serialize and store config data
 526       */
 527      function instance_config_save($data, $nolongerused = false) {
 528          global $DB;
 529          $DB->update_record('block_instances', ['id' => $this->instance->id,
 530                  'configdata' => base64_encode(serialize($data)), 'timemodified' => time()]);
 531      }
 532  
 533      /**
 534       * Replace the instance's configuration data with those currently in $this->config;
 535       */
 536      function instance_config_commit($nolongerused = false) {
 537          global $DB;
 538          $this->instance_config_save($this->config);
 539      }
 540  
 541      /**
 542       * Do any additional initialization you may need at the time a new block instance is created
 543       * @return boolean
 544       */
 545      function instance_create() {
 546          return true;
 547      }
 548  
 549      /**
 550       * Copy any block-specific data when copying to a new block instance.
 551       * @param int $fromid the id number of the block instance to copy from
 552       * @return boolean
 553       */
 554      public function instance_copy($fromid) {
 555          return true;
 556      }
 557  
 558      /**
 559       * Delete everything related to this instance if you have been using persistent storage other than the configdata field.
 560       * @return boolean
 561       */
 562      function instance_delete() {
 563          return true;
 564      }
 565  
 566      /**
 567       * Allows the block class to have a say in the user's ability to edit (i.e., configure) blocks of this type.
 568       * The framework has first say in whether this will be allowed (e.g., no editing allowed unless in edit mode)
 569       * but if the framework does allow it, the block can still decide to refuse.
 570       * @return boolean
 571       */
 572      function user_can_edit() {
 573          global $USER;
 574  
 575          if (has_capability('moodle/block:edit', $this->context)) {
 576              return true;
 577          }
 578  
 579          // The blocks in My Moodle are a special case.  We want them to inherit from the user context.
 580          if (!empty($USER->id)
 581              && $this->instance->parentcontextid == $this->page->context->id   // Block belongs to this page
 582              && $this->page->context->contextlevel == CONTEXT_USER             // Page belongs to a user
 583              && $this->page->context->instanceid == $USER->id) {               // Page belongs to this user
 584              return has_capability('moodle/my:manageblocks', $this->page->context);
 585          }
 586  
 587          return false;
 588      }
 589  
 590      /**
 591       * Allows the block class to have a say in the user's ability to create new instances of this block.
 592       * The framework has first say in whether this will be allowed (e.g., no adding allowed unless in edit mode)
 593       * but if the framework does allow it, the block can still decide to refuse.
 594       * This function has access to the complete page object, the creation related to which is being determined.
 595       *
 596       * @param moodle_page $page
 597       * @return boolean
 598       */
 599      function user_can_addto($page) {
 600          // List of formats this block supports.
 601          $formats = $this->applicable_formats();
 602  
 603          // The blocks in My Moodle are a special case and use a different capability.
 604          $mypagetypes = my_page_type_list($page->pagetype); // Get list of possible my page types.
 605  
 606          if (array_key_exists($page->pagetype, $mypagetypes)) { // Ensure we are on a page with a my page type.
 607              // If the block cannot be displayed on /my it is ok if the myaddinstance capability is not defined.
 608              // Is 'my' explicitly forbidden?
 609              // If 'all' has not been allowed, has 'my' been explicitly allowed?
 610              if ((isset($formats['my']) && $formats['my'] == false)
 611                  || (empty($formats['all']) && empty($formats['my']))) {
 612  
 613                  // Block cannot be added to /my regardless of capabilities.
 614                  return false;
 615              } else {
 616                  $capability = 'block/' . $this->name() . ':myaddinstance';
 617                  return $this->has_add_block_capability($page, $capability)
 618                         && has_capability('moodle/my:manageblocks', $page->context);
 619              }
 620          }
 621          // Check if this is a block only used on /my.
 622          unset($formats['my']);
 623          if (empty($formats)) {
 624              // Block can only be added to /my - return false.
 625              return false;
 626          }
 627  
 628          $capability = 'block/' . $this->name() . ':addinstance';
 629          if ($this->has_add_block_capability($page, $capability)
 630                  && has_capability('moodle/block:edit', $page->context)) {
 631              return true;
 632          }
 633  
 634          return false;
 635      }
 636  
 637      /**
 638       * Returns true if the user can add a block to a page.
 639       *
 640       * @param moodle_page $page
 641       * @param string $capability the capability to check
 642       * @return boolean true if user can add a block, false otherwise.
 643       */
 644      private function has_add_block_capability($page, $capability) {
 645          // Check if the capability exists.
 646          if (!get_capability_info($capability)) {
 647              // Debug warning that the capability does not exist, but no more than once per page.
 648              static $warned = array();
 649              if (!isset($warned[$this->name()])) {
 650                  debugging('The block ' .$this->name() . ' does not define the standard capability ' .
 651                          $capability , DEBUG_DEVELOPER);
 652                  $warned[$this->name()] = 1;
 653              }
 654              // If the capability does not exist, the block can always be added.
 655              return true;
 656          } else {
 657              return has_capability($capability, $page->context);
 658          }
 659      }
 660  
 661      static function get_extra_capabilities() {
 662          return array('moodle/block:view', 'moodle/block:edit');
 663      }
 664  
 665      /**
 666       * Can be overridden by the block to prevent the block from being dockable.
 667       *
 668       * @return bool
 669       *
 670       * Return false as per MDL-64506
 671       */
 672      public function instance_can_be_docked() {
 673          return false;
 674      }
 675  
 676      /**
 677       * If overridden and set to false by the block it will not be hidable when
 678       * editing is turned on.
 679       *
 680       * @return bool
 681       */
 682      public function instance_can_be_hidden() {
 683          return true;
 684      }
 685  
 686      /**
 687       * If overridden and set to false by the block it will not be collapsible.
 688       *
 689       * @return bool
 690       */
 691      public function instance_can_be_collapsed() {
 692          return true;
 693      }
 694  
 695      /** @callback callback functions for comments api */
 696      public static function comment_template($options) {
 697          $ret = <<<EOD
 698  <div class="comment-userpicture">___picture___</div>
 699  <div class="comment-content">
 700      ___name___ - <span>___time___</span>
 701      <div>___content___</div>
 702  </div>
 703  EOD;
 704          return $ret;
 705      }
 706      public static function comment_permissions($options) {
 707          return array('view'=>true, 'post'=>true);
 708      }
 709      public static function comment_url($options) {
 710          return null;
 711      }
 712      public static function comment_display($comments, $options) {
 713          return $comments;
 714      }
 715      public static function comment_add(&$comments, $options) {
 716          return true;
 717      }
 718  
 719      /**
 720       * Returns the aria role attribute that best describes this block.
 721       *
 722       * Region is the default, but this should be overridden by a block is there is a region child, or even better
 723       * a landmark child.
 724       *
 725       * Options are as follows:
 726       *    - landmark
 727       *      - application
 728       *      - banner
 729       *      - complementary
 730       *      - contentinfo
 731       *      - form
 732       *      - main
 733       *      - navigation
 734       *      - search
 735       *
 736       * @return string
 737       */
 738      public function get_aria_role() {
 739          return 'complementary';
 740      }
 741  }
 742  
 743  /**
 744   * Specialized class for displaying a block with a list of icons/text labels
 745   *
 746   * The get_content method should set $this->content->items and (optionally)
 747   * $this->content->icons, instead of $this->content->text.
 748   *
 749   * @author Jon Papaioannou
 750   * @package core_block
 751   */
 752  
 753  class block_list extends block_base {
 754      var $content_type  = BLOCK_TYPE_LIST;
 755  
 756      function is_empty() {
 757          if ( !has_capability('moodle/block:view', $this->context) ) {
 758              return true;
 759          }
 760  
 761          $this->get_content();
 762          return (empty($this->content->items) && empty($this->content->footer));
 763      }
 764  
 765      protected function formatted_contents($output) {
 766          $this->get_content();
 767          $this->get_required_javascript();
 768          if (!empty($this->content->items)) {
 769              return $output->list_block_contents($this->content->icons, $this->content->items);
 770          } else {
 771              return '';
 772          }
 773      }
 774  
 775      function html_attributes() {
 776          $attributes = parent::html_attributes();
 777          $attributes['class'] .= ' list_block';
 778          return $attributes;
 779      }
 780  
 781  }
 782  
 783  /**
 784   * Specialized class for displaying a tree menu.
 785   *
 786   * The {@link get_content()} method involves setting the content of
 787   * <code>$this->content->items</code> with an array of {@link tree_item}
 788   * objects (these are the top-level nodes). The {@link tree_item::children}
 789   * property may contain more tree_item objects, and so on. The tree_item class
 790   * itself is abstract and not intended for use, use one of it's subclasses.
 791   *
 792   * Unlike {@link block_list}, the icons are specified as part of the items,
 793   * not in a separate array.
 794   *
 795   * @author Alan Trick
 796   * @package core_block
 797   * @internal this extends block_list so we get is_empty() for free
 798   */
 799  class block_tree extends block_list {
 800  
 801      /**
 802       * @var int specifies the manner in which contents should be added to this
 803       * block type. In this case <code>$this->content->items</code> is used with
 804       * {@link tree_item}s.
 805       */
 806      public $content_type = BLOCK_TYPE_TREE;
 807  
 808      /**
 809       * Make the formatted HTML ouput.
 810       *
 811       * Also adds the required javascript call to the page output.
 812       *
 813       * @param core_renderer $output
 814       * @return string HTML
 815       */
 816      protected function formatted_contents($output) {
 817          // based of code in admin_tree
 818          global $PAGE; // TODO change this when there is a proper way for blocks to get stuff into head.
 819          static $eventattached;
 820          if ($eventattached===null) {
 821              $eventattached = true;
 822          }
 823          if (!$this->content) {
 824              $this->content = new stdClass;
 825              $this->content->items = array();
 826          }
 827          $this->get_required_javascript();
 828          $this->get_content();
 829          $content = $output->tree_block_contents($this->content->items,array('class'=>'block_tree list'));
 830          if (isset($this->id) && !is_numeric($this->id)) {
 831              $content = $output->box($content, 'block_tree_box', $this->id);
 832          }
 833          return $content;
 834      }
 835  }