[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> outputcomponents.php (source)

   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   * Classes representing HTML elements, used by $OUTPUT methods
  19   *
  20   * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
  21   * for an overview.
  22   *
  23   * @package core
  24   * @category output
  25   * @copyright 2009 Tim Hunt
  26   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**
  32   * Interface marking other classes as suitable for renderer_base::render()
  33   *
  34   * @copyright 2010 Petr Skoda (skodak) [email protected]
  35   * @package core
  36   * @category output
  37   */
  38  interface renderable {
  39      // intentionally empty
  40  }
  41  
  42  /**
  43   * Data structure representing a file picker.
  44   *
  45   * @copyright 2010 Dongsheng Cai
  46   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   * @since Moodle 2.0
  48   * @package core
  49   * @category output
  50   */
  51  class file_picker implements renderable {
  52  
  53      /**
  54       * @var stdClass An object containing options for the file picker
  55       */
  56      public $options;
  57  
  58      /**
  59       * Constructs a file picker object.
  60       *
  61       * The following are possible options for the filepicker:
  62       *    - accepted_types  (*)
  63       *    - return_types    (FILE_INTERNAL)
  64       *    - env             (filepicker)
  65       *    - client_id       (uniqid)
  66       *    - itemid          (0)
  67       *    - maxbytes        (-1)
  68       *    - maxfiles        (1)
  69       *    - buttonname      (false)
  70       *
  71       * @param stdClass $options An object containing options for the file picker.
  72       */
  73      public function __construct(stdClass $options) {
  74          global $CFG, $USER, $PAGE;
  75          require_once($CFG->dirroot. '/repository/lib.php');
  76          $defaults = array(
  77              'accepted_types'=>'*',
  78              'return_types'=>FILE_INTERNAL,
  79              'env' => 'filepicker',
  80              'client_id' => uniqid(),
  81              'itemid' => 0,
  82              'maxbytes'=>-1,
  83              'maxfiles'=>1,
  84              'buttonname'=>false
  85          );
  86          foreach ($defaults as $key=>$value) {
  87              if (empty($options->$key)) {
  88                  $options->$key = $value;
  89              }
  90          }
  91  
  92          $options->currentfile = '';
  93          if (!empty($options->itemid)) {
  94              $fs = get_file_storage();
  95              $usercontext = context_user::instance($USER->id);
  96              if (empty($options->filename)) {
  97                  if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id DESC', false)) {
  98                      $file = reset($files);
  99                  }
 100              } else {
 101                  $file = $fs->get_file($usercontext->id, 'user', 'draft', $options->itemid, $options->filepath, $options->filename);
 102              }
 103              if (!empty($file)) {
 104                  $options->currentfile = html_writer::link(moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename()), $file->get_filename());
 105              }
 106          }
 107  
 108          // initilise options, getting files in root path
 109          $this->options = initialise_filepicker($options);
 110  
 111          // copying other options
 112          foreach ($options as $name=>$value) {
 113              if (!isset($this->options->$name)) {
 114                  $this->options->$name = $value;
 115              }
 116          }
 117      }
 118  }
 119  
 120  /**
 121   * Data structure representing a user picture.
 122   *
 123   * @copyright 2009 Nicolas Connault, 2010 Petr Skoda
 124   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 125   * @since Modle 2.0
 126   * @package core
 127   * @category output
 128   */
 129  class user_picture implements renderable {
 130      /**
 131       * @var array List of mandatory fields in user record here. (do not include
 132       * TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE)
 133       */
 134      protected static $fields = array('id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic',
 135              'middlename', 'alternatename', 'imagealt', 'email');
 136  
 137      /**
 138       * @var stdClass A user object with at least fields all columns specified
 139       * in $fields array constant set.
 140       */
 141      public $user;
 142  
 143      /**
 144       * @var int The course id. Used when constructing the link to the user's
 145       * profile, page course id used if not specified.
 146       */
 147      public $courseid;
 148  
 149      /**
 150       * @var bool Add course profile link to image
 151       */
 152      public $link = true;
 153  
 154      /**
 155       * @var int Size in pixels. Special values are (true/1 = 100px) and
 156       * (false/0 = 35px)
 157       * for backward compatibility.
 158       */
 159      public $size = 35;
 160  
 161      /**
 162       * @var bool Add non-blank alt-text to the image.
 163       * Default true, set to false when image alt just duplicates text in screenreaders.
 164       */
 165      public $alttext = true;
 166  
 167      /**
 168       * @var bool Whether or not to open the link in a popup window.
 169       */
 170      public $popup = false;
 171  
 172      /**
 173       * @var string Image class attribute
 174       */
 175      public $class = 'userpicture';
 176  
 177      /**
 178       * @var bool Whether to be visible to screen readers.
 179       */
 180      public $visibletoscreenreaders = true;
 181  
 182      /**
 183       * User picture constructor.
 184       *
 185       * @param stdClass $user user record with at least id, picture, imagealt, firstname and lastname set.
 186       *                 It is recommended to add also contextid of the user for performance reasons.
 187       */
 188      public function __construct(stdClass $user) {
 189          global $DB;
 190  
 191          if (empty($user->id)) {
 192              throw new coding_exception('User id is required when printing user avatar image.');
 193          }
 194  
 195          // only touch the DB if we are missing data and complain loudly...
 196          $needrec = false;
 197          foreach (self::$fields as $field) {
 198              if (!array_key_exists($field, $user)) {
 199                  $needrec = true;
 200                  debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. '
 201                            .'Please use user_picture::fields() to get the full list of required fields.', DEBUG_DEVELOPER);
 202                  break;
 203              }
 204          }
 205  
 206          if ($needrec) {
 207              $this->user = $DB->get_record('user', array('id'=>$user->id), self::fields(), MUST_EXIST);
 208          } else {
 209              $this->user = clone($user);
 210          }
 211      }
 212  
 213      /**
 214       * Returns a list of required user fields, useful when fetching required user info from db.
 215       *
 216       * In some cases we have to fetch the user data together with some other information,
 217       * the idalias is useful there because the id would otherwise override the main
 218       * id of the result record. Please note it has to be converted back to id before rendering.
 219       *
 220       * @param string $tableprefix name of database table prefix in query
 221       * @param array $extrafields extra fields to be included in result (do not include TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE)
 222       * @param string $idalias alias of id field
 223       * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id'
 224       * @return string
 225       */
 226      public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') {
 227          if (!$tableprefix and !$extrafields and !$idalias) {
 228              return implode(',', self::$fields);
 229          }
 230          if ($tableprefix) {
 231              $tableprefix .= '.';
 232          }
 233          foreach (self::$fields as $field) {
 234              if ($field === 'id' and $idalias and $idalias !== 'id') {
 235                  $fields[$field] = "$tableprefix$field AS $idalias";
 236              } else {
 237                  if ($fieldprefix and $field !== 'id') {
 238                      $fields[$field] = "$tableprefix$field AS $fieldprefix$field";
 239                  } else {
 240                      $fields[$field] = "$tableprefix$field";
 241                  }
 242              }
 243          }
 244          // add extra fields if not already there
 245          if ($extrafields) {
 246              foreach ($extrafields as $e) {
 247                  if ($e === 'id' or isset($fields[$e])) {
 248                      continue;
 249                  }
 250                  if ($fieldprefix) {
 251                      $fields[$e] = "$tableprefix$e AS $fieldprefix$e";
 252                  } else {
 253                      $fields[$e] = "$tableprefix$e";
 254                  }
 255              }
 256          }
 257          return implode(',', $fields);
 258      }
 259  
 260      /**
 261       * Extract the aliased user fields from a given record
 262       *
 263       * Given a record that was previously obtained using {@link self::fields()} with aliases,
 264       * this method extracts user related unaliased fields.
 265       *
 266       * @param stdClass $record containing user picture fields
 267       * @param array $extrafields extra fields included in the $record
 268       * @param string $idalias alias of the id field
 269       * @param string $fieldprefix prefix added to all columns in their aliases, does not apply to 'id'
 270       * @return stdClass object with unaliased user fields
 271       */
 272      public static function unalias(stdClass $record, array $extrafields = null, $idalias = 'id', $fieldprefix = '') {
 273  
 274          if (empty($idalias)) {
 275              $idalias = 'id';
 276          }
 277  
 278          $return = new stdClass();
 279  
 280          foreach (self::$fields as $field) {
 281              if ($field === 'id') {
 282                  if (property_exists($record, $idalias)) {
 283                      $return->id = $record->{$idalias};
 284                  }
 285              } else {
 286                  if (property_exists($record, $fieldprefix.$field)) {
 287                      $return->{$field} = $record->{$fieldprefix.$field};
 288                  }
 289              }
 290          }
 291          // add extra fields if not already there
 292          if ($extrafields) {
 293              foreach ($extrafields as $e) {
 294                  if ($e === 'id' or property_exists($return, $e)) {
 295                      continue;
 296                  }
 297                  $return->{$e} = $record->{$fieldprefix.$e};
 298              }
 299          }
 300  
 301          return $return;
 302      }
 303  
 304      /**
 305       * Works out the URL for the users picture.
 306       *
 307       * This method is recommended as it avoids costly redirects of user pictures
 308       * if requests are made for non-existent files etc.
 309       *
 310       * @param moodle_page $page
 311       * @param renderer_base $renderer
 312       * @return moodle_url
 313       */
 314      public function get_url(moodle_page $page, renderer_base $renderer = null) {
 315          global $CFG;
 316  
 317          if (is_null($renderer)) {
 318              $renderer = $page->get_renderer('core');
 319          }
 320  
 321          // Sort out the filename and size. Size is only required for the gravatar
 322          // implementation presently.
 323          if (empty($this->size)) {
 324              $filename = 'f2';
 325              $size = 35;
 326          } else if ($this->size === true or $this->size == 1) {
 327              $filename = 'f1';
 328              $size = 100;
 329          } else if ($this->size > 100) {
 330              $filename = 'f3';
 331              $size = (int)$this->size;
 332          } else if ($this->size >= 50) {
 333              $filename = 'f1';
 334              $size = (int)$this->size;
 335          } else {
 336              $filename = 'f2';
 337              $size = (int)$this->size;
 338          }
 339  
 340          $defaulturl = $renderer->pix_url('u/'.$filename); // default image
 341  
 342          if ((!empty($CFG->forcelogin) and !isloggedin()) ||
 343              (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) {
 344              // Protect images if login required and not logged in;
 345              // also if login is required for profile images and is not logged in or guest
 346              // do not use require_login() because it is expensive and not suitable here anyway.
 347              return $defaulturl;
 348          }
 349  
 350          // First try to detect deleted users - but do not read from database for performance reasons!
 351          if (!empty($this->user->deleted) or strpos($this->user->email, '@') === false) {
 352              // All deleted users should have email replaced by md5 hash,
 353              // all active users are expected to have valid email.
 354              return $defaulturl;
 355          }
 356  
 357          // Did the user upload a picture?
 358          if ($this->user->picture > 0) {
 359              if (!empty($this->user->contextid)) {
 360                  $contextid = $this->user->contextid;
 361              } else {
 362                  $context = context_user::instance($this->user->id, IGNORE_MISSING);
 363                  if (!$context) {
 364                      // This must be an incorrectly deleted user, all other users have context.
 365                      return $defaulturl;
 366                  }
 367                  $contextid = $context->id;
 368              }
 369  
 370              $path = '/';
 371              if (clean_param($page->theme->name, PARAM_THEME) == $page->theme->name) {
 372                  // We append the theme name to the file path if we have it so that
 373                  // in the circumstance that the profile picture is not available
 374                  // when the user actually requests it they still get the profile
 375                  // picture for the correct theme.
 376                  $path .= $page->theme->name.'/';
 377              }
 378              // Set the image URL to the URL for the uploaded file and return.
 379              $url = moodle_url::make_pluginfile_url($contextid, 'user', 'icon', NULL, $path, $filename);
 380              $url->param('rev', $this->user->picture);
 381              return $url;
 382          }
 383  
 384          if ($this->user->picture == 0 and !empty($CFG->enablegravatar)) {
 385              // Normalise the size variable to acceptable bounds
 386              if ($size < 1 || $size > 512) {
 387                  $size = 35;
 388              }
 389              // Hash the users email address
 390              $md5 = md5(strtolower(trim($this->user->email)));
 391              // Build a gravatar URL with what we know.
 392  
 393              // Find the best default image URL we can (MDL-35669)
 394              if (empty($CFG->gravatardefaulturl)) {
 395                  $absoluteimagepath = $page->theme->resolve_image_location('u/'.$filename, 'core');
 396                  if (strpos($absoluteimagepath, $CFG->dirroot) === 0) {
 397                      $gravatardefault = $CFG->wwwroot . substr($absoluteimagepath, strlen($CFG->dirroot));
 398                  } else {
 399                      $gravatardefault = $CFG->wwwroot . '/pix/u/' . $filename . '.png';
 400                  }
 401              } else {
 402                  $gravatardefault = $CFG->gravatardefaulturl;
 403              }
 404  
 405              // If the currently requested page is https then we'll return an
 406              // https gravatar page.
 407              if (is_https()) {
 408                  $gravatardefault = str_replace($CFG->wwwroot, $CFG->httpswwwroot, $gravatardefault); // Replace by secure url.
 409                  return new moodle_url("https://secure.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault));
 410              } else {
 411                  return new moodle_url("http://www.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault));
 412              }
 413          }
 414  
 415          return $defaulturl;
 416      }
 417  }
 418  
 419  /**
 420   * Data structure representing a help icon.
 421   *
 422   * @copyright 2010 Petr Skoda ([email protected])
 423   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 424   * @since Moodle 2.0
 425   * @package core
 426   * @category output
 427   */
 428  class help_icon implements renderable {
 429  
 430      /**
 431       * @var string lang pack identifier (without the "_help" suffix),
 432       * both get_string($identifier, $component) and get_string($identifier.'_help', $component)
 433       * must exist.
 434       */
 435      public $identifier;
 436  
 437      /**
 438       * @var string Component name, the same as in get_string()
 439       */
 440      public $component;
 441  
 442      /**
 443       * @var string Extra descriptive text next to the icon
 444       */
 445      public $linktext = null;
 446  
 447      /**
 448       * Constructor
 449       *
 450       * @param string $identifier string for help page title,
 451       *  string with _help suffix is used for the actual help text.
 452       *  string with _link suffix is used to create a link to further info (if it exists)
 453       * @param string $component
 454       */
 455      public function __construct($identifier, $component) {
 456          $this->identifier = $identifier;
 457          $this->component  = $component;
 458      }
 459  
 460      /**
 461       * Verifies that both help strings exists, shows debug warnings if not
 462       */
 463      public function diag_strings() {
 464          $sm = get_string_manager();
 465          if (!$sm->string_exists($this->identifier, $this->component)) {
 466              debugging("Help title string does not exist: [$this->identifier, $this->component]");
 467          }
 468          if (!$sm->string_exists($this->identifier.'_help', $this->component)) {
 469              debugging("Help contents string does not exist: [{$this->identifier}_help, $this->component]");
 470          }
 471      }
 472  }
 473  
 474  
 475  /**
 476   * Data structure representing an icon.
 477   *
 478   * @copyright 2010 Petr Skoda
 479   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 480   * @since Moodle 2.0
 481   * @package core
 482   * @category output
 483   */
 484  class pix_icon implements renderable {
 485  
 486      /**
 487       * @var string The icon name
 488       */
 489      var $pix;
 490  
 491      /**
 492       * @var string The component the icon belongs to.
 493       */
 494      var $component;
 495  
 496      /**
 497       * @var array An array of attributes to use on the icon
 498       */
 499      var $attributes = array();
 500  
 501      /**
 502       * Constructor
 503       *
 504       * @param string $pix short icon name
 505       * @param string $alt The alt text to use for the icon
 506       * @param string $component component name
 507       * @param array $attributes html attributes
 508       */
 509      public function __construct($pix, $alt, $component='moodle', array $attributes = null) {
 510          $this->pix        = $pix;
 511          $this->component  = $component;
 512          $this->attributes = (array)$attributes;
 513  
 514          $this->attributes['alt'] = $alt;
 515          if (empty($this->attributes['class'])) {
 516              $this->attributes['class'] = 'smallicon';
 517          }
 518          if (!isset($this->attributes['title'])) {
 519              $this->attributes['title'] = $this->attributes['alt'];
 520          } else if (empty($this->attributes['title'])) {
 521              // Remove the title attribute if empty, we probably want to use the parent node's title
 522              // and some browsers might overwrite it with an empty title.
 523              unset($this->attributes['title']);
 524          }
 525      }
 526  }
 527  
 528  /**
 529   * Data structure representing an emoticon image
 530   *
 531   * @copyright 2010 David Mudrak
 532   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 533   * @since Moodle 2.0
 534   * @package core
 535   * @category output
 536   */
 537  class pix_emoticon extends pix_icon implements renderable {
 538  
 539      /**
 540       * Constructor
 541       * @param string $pix short icon name
 542       * @param string $alt alternative text
 543       * @param string $component emoticon image provider
 544       * @param array $attributes explicit HTML attributes
 545       */
 546      public function __construct($pix, $alt, $component = 'moodle', array $attributes = array()) {
 547          if (empty($attributes['class'])) {
 548              $attributes['class'] = 'emoticon';
 549          }
 550          parent::__construct($pix, $alt, $component, $attributes);
 551      }
 552  }
 553  
 554  /**
 555   * Data structure representing a simple form with only one button.
 556   *
 557   * @copyright 2009 Petr Skoda
 558   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 559   * @since Moodle 2.0
 560   * @package core
 561   * @category output
 562   */
 563  class single_button implements renderable {
 564  
 565      /**
 566       * @var moodle_url Target url
 567       */
 568      var $url;
 569  
 570      /**
 571       * @var string Button label
 572       */
 573      var $label;
 574  
 575      /**
 576       * @var string Form submit method post or get
 577       */
 578      var $method = 'post';
 579  
 580      /**
 581       * @var string Wrapping div class
 582       */
 583      var $class = 'singlebutton';
 584  
 585      /**
 586       * @var bool True if button disabled, false if normal
 587       */
 588      var $disabled = false;
 589  
 590      /**
 591       * @var string Button tooltip
 592       */
 593      var $tooltip = null;
 594  
 595      /**
 596       * @var string Form id
 597       */
 598      var $formid;
 599  
 600      /**
 601       * @var array List of attached actions
 602       */
 603      var $actions = array();
 604  
 605      /**
 606       * Constructor
 607       * @param moodle_url $url
 608       * @param string $label button text
 609       * @param string $method get or post submit method
 610       */
 611      public function __construct(moodle_url $url, $label, $method='post') {
 612          $this->url    = clone($url);
 613          $this->label  = $label;
 614          $this->method = $method;
 615      }
 616  
 617      /**
 618       * Shortcut for adding a JS confirm dialog when the button is clicked.
 619       * The message must be a yes/no question.
 620       *
 621       * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
 622       */
 623      public function add_confirm_action($confirmmessage) {
 624          $this->add_action(new confirm_action($confirmmessage));
 625      }
 626  
 627      /**
 628       * Add action to the button.
 629       * @param component_action $action
 630       */
 631      public function add_action(component_action $action) {
 632          $this->actions[] = $action;
 633      }
 634  }
 635  
 636  
 637  /**
 638   * Simple form with just one select field that gets submitted automatically.
 639   *
 640   * If JS not enabled small go button is printed too.
 641   *
 642   * @copyright 2009 Petr Skoda
 643   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 644   * @since Moodle 2.0
 645   * @package core
 646   * @category output
 647   */
 648  class single_select implements renderable {
 649  
 650      /**
 651       * @var moodle_url Target url - includes hidden fields
 652       */
 653      var $url;
 654  
 655      /**
 656       * @var string Name of the select element.
 657       */
 658      var $name;
 659  
 660      /**
 661       * @var array $options associative array value=>label ex.: array(1=>'One, 2=>Two)
 662       *     it is also possible to specify optgroup as complex label array ex.:
 663       *         array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
 664       *         array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
 665       */
 666      var $options;
 667  
 668      /**
 669       * @var string Selected option
 670       */
 671      var $selected;
 672  
 673      /**
 674       * @var array Nothing selected
 675       */
 676      var $nothing;
 677  
 678      /**
 679       * @var array Extra select field attributes
 680       */
 681      var $attributes = array();
 682  
 683      /**
 684       * @var string Button label
 685       */
 686      var $label = '';
 687  
 688      /**
 689       * @var array Button label's attributes
 690       */
 691      var $labelattributes = array();
 692  
 693      /**
 694       * @var string Form submit method post or get
 695       */
 696      var $method = 'get';
 697  
 698      /**
 699       * @var string Wrapping div class
 700       */
 701      var $class = 'singleselect';
 702  
 703      /**
 704       * @var bool True if button disabled, false if normal
 705       */
 706      var $disabled = false;
 707  
 708      /**
 709       * @var string Button tooltip
 710       */
 711      var $tooltip = null;
 712  
 713      /**
 714       * @var string Form id
 715       */
 716      var $formid = null;
 717  
 718      /**
 719       * @var array List of attached actions
 720       */
 721      var $helpicon = null;
 722  
 723      /**
 724       * Constructor
 725       * @param moodle_url $url form action target, includes hidden fields
 726       * @param string $name name of selection field - the changing parameter in url
 727       * @param array $options list of options
 728       * @param string $selected selected element
 729       * @param array $nothing
 730       * @param string $formid
 731       */
 732      public function __construct(moodle_url $url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) {
 733          $this->url      = $url;
 734          $this->name     = $name;
 735          $this->options  = $options;
 736          $this->selected = $selected;
 737          $this->nothing  = $nothing;
 738          $this->formid   = $formid;
 739      }
 740  
 741      /**
 742       * Shortcut for adding a JS confirm dialog when the button is clicked.
 743       * The message must be a yes/no question.
 744       *
 745       * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
 746       */
 747      public function add_confirm_action($confirmmessage) {
 748          $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage)));
 749      }
 750  
 751      /**
 752       * Add action to the button.
 753       *
 754       * @param component_action $action
 755       */
 756      public function add_action(component_action $action) {
 757          $this->actions[] = $action;
 758      }
 759  
 760      /**
 761       * Adds help icon.
 762       *
 763       * @deprecated since Moodle 2.0
 764       */
 765      public function set_old_help_icon($helppage, $title, $component = 'moodle') {
 766          throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().');
 767      }
 768  
 769      /**
 770       * Adds help icon.
 771       *
 772       * @param string $identifier The keyword that defines a help page
 773       * @param string $component
 774       */
 775      public function set_help_icon($identifier, $component = 'moodle') {
 776          $this->helpicon = new help_icon($identifier, $component);
 777      }
 778  
 779      /**
 780       * Sets select's label
 781       *
 782       * @param string $label
 783       * @param array $attributes (optional)
 784       */
 785      public function set_label($label, $attributes = array()) {
 786          $this->label = $label;
 787          $this->labelattributes = $attributes;
 788  
 789      }
 790  }
 791  
 792  /**
 793   * Simple URL selection widget description.
 794   *
 795   * @copyright 2009 Petr Skoda
 796   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 797   * @since Moodle 2.0
 798   * @package core
 799   * @category output
 800   */
 801  class url_select implements renderable {
 802      /**
 803       * @var array $urls associative array value=>label ex.: array(1=>'One, 2=>Two)
 804       *     it is also possible to specify optgroup as complex label array ex.:
 805       *         array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
 806       *         array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
 807       */
 808      var $urls;
 809  
 810      /**
 811       * @var string Selected option
 812       */
 813      var $selected;
 814  
 815      /**
 816       * @var array Nothing selected
 817       */
 818      var $nothing;
 819  
 820      /**
 821       * @var array Extra select field attributes
 822       */
 823      var $attributes = array();
 824  
 825      /**
 826       * @var string Button label
 827       */
 828      var $label = '';
 829  
 830      /**
 831       * @var array Button label's attributes
 832       */
 833      var $labelattributes = array();
 834  
 835      /**
 836       * @var string Wrapping div class
 837       */
 838      var $class = 'urlselect';
 839  
 840      /**
 841       * @var bool True if button disabled, false if normal
 842       */
 843      var $disabled = false;
 844  
 845      /**
 846       * @var string Button tooltip
 847       */
 848      var $tooltip = null;
 849  
 850      /**
 851       * @var string Form id
 852       */
 853      var $formid = null;
 854  
 855      /**
 856       * @var array List of attached actions
 857       */
 858      var $helpicon = null;
 859  
 860      /**
 861       * @var string If set, makes button visible with given name for button
 862       */
 863      var $showbutton = null;
 864  
 865      /**
 866       * Constructor
 867       * @param array $urls list of options
 868       * @param string $selected selected element
 869       * @param array $nothing
 870       * @param string $formid
 871       * @param string $showbutton Set to text of button if it should be visible
 872       *   or null if it should be hidden (hidden version always has text 'go')
 873       */
 874      public function __construct(array $urls, $selected = '', $nothing = array('' => 'choosedots'), $formid = null, $showbutton = null) {
 875          $this->urls       = $urls;
 876          $this->selected   = $selected;
 877          $this->nothing    = $nothing;
 878          $this->formid     = $formid;
 879          $this->showbutton = $showbutton;
 880      }
 881  
 882      /**
 883       * Adds help icon.
 884       *
 885       * @deprecated since Moodle 2.0
 886       */
 887      public function set_old_help_icon($helppage, $title, $component = 'moodle') {
 888          throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().');
 889      }
 890  
 891      /**
 892       * Adds help icon.
 893       *
 894       * @param string $identifier The keyword that defines a help page
 895       * @param string $component
 896       */
 897      public function set_help_icon($identifier, $component = 'moodle') {
 898          $this->helpicon = new help_icon($identifier, $component);
 899      }
 900  
 901      /**
 902       * Sets select's label
 903       *
 904       * @param string $label
 905       * @param array $attributes (optional)
 906       */
 907      public function set_label($label, $attributes = array()) {
 908          $this->label = $label;
 909          $this->labelattributes = $attributes;
 910      }
 911  }
 912  
 913  /**
 914   * Data structure describing html link with special action attached.
 915   *
 916   * @copyright 2010 Petr Skoda
 917   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 918   * @since Moodle 2.0
 919   * @package core
 920   * @category output
 921   */
 922  class action_link implements renderable {
 923  
 924      /**
 925       * @var moodle_url Href url
 926       */
 927      public $url;
 928  
 929      /**
 930       * @var string Link text HTML fragment
 931       */
 932      public $text;
 933  
 934      /**
 935       * @var array HTML attributes
 936       */
 937      public $attributes;
 938  
 939      /**
 940       * @var array List of actions attached to link
 941       */
 942      public $actions;
 943  
 944      /**
 945       * @var pix_icon Optional pix icon to render with the link
 946       */
 947      public $icon;
 948  
 949      /**
 950       * Constructor
 951       * @param moodle_url $url
 952       * @param string $text HTML fragment
 953       * @param component_action $action
 954       * @param array $attributes associative array of html link attributes + disabled
 955       * @param pix_icon $icon optional pix_icon to render with the link text
 956       */
 957      public function __construct(moodle_url $url,
 958                                  $text,
 959                                  component_action $action=null,
 960                                  array $attributes=null,
 961                                  pix_icon $icon=null) {
 962          $this->url = clone($url);
 963          $this->text = $text;
 964          $this->attributes = (array)$attributes;
 965          if ($action) {
 966              $this->add_action($action);
 967          }
 968          $this->icon = $icon;
 969      }
 970  
 971      /**
 972       * Add action to the link.
 973       *
 974       * @param component_action $action
 975       */
 976      public function add_action(component_action $action) {
 977          $this->actions[] = $action;
 978      }
 979  
 980      /**
 981       * Adds a CSS class to this action link object
 982       * @param string $class
 983       */
 984      public function add_class($class) {
 985          if (empty($this->attributes['class'])) {
 986              $this->attributes['class'] = $class;
 987          } else {
 988              $this->attributes['class'] .= ' ' . $class;
 989          }
 990      }
 991  
 992      /**
 993       * Returns true if the specified class has been added to this link.
 994       * @param string $class
 995       * @return bool
 996       */
 997      public function has_class($class) {
 998          return strpos(' ' . $this->attributes['class'] . ' ', ' ' . $class . ' ') !== false;
 999      }
1000  }
1001  
1002  /**
1003   * Simple html output class
1004   *
1005   * @copyright 2009 Tim Hunt, 2010 Petr Skoda
1006   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1007   * @since Moodle 2.0
1008   * @package core
1009   * @category output
1010   */
1011  class html_writer {
1012  
1013      /**
1014       * Outputs a tag with attributes and contents
1015       *
1016       * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1017       * @param string $contents What goes between the opening and closing tags
1018       * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1019       * @return string HTML fragment
1020       */
1021      public static function tag($tagname, $contents, array $attributes = null) {
1022          return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname);
1023      }
1024  
1025      /**
1026       * Outputs an opening tag with attributes
1027       *
1028       * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1029       * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1030       * @return string HTML fragment
1031       */
1032      public static function start_tag($tagname, array $attributes = null) {
1033          return '<' . $tagname . self::attributes($attributes) . '>';
1034      }
1035  
1036      /**
1037       * Outputs a closing tag
1038       *
1039       * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1040       * @return string HTML fragment
1041       */
1042      public static function end_tag($tagname) {
1043          return '</' . $tagname . '>';
1044      }
1045  
1046      /**
1047       * Outputs an empty tag with attributes
1048       *
1049       * @param string $tagname The name of tag ('input', 'img', 'br' etc.)
1050       * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1051       * @return string HTML fragment
1052       */
1053      public static function empty_tag($tagname, array $attributes = null) {
1054          return '<' . $tagname . self::attributes($attributes) . ' />';
1055      }
1056  
1057      /**
1058       * Outputs a tag, but only if the contents are not empty
1059       *
1060       * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1061       * @param string $contents What goes between the opening and closing tags
1062       * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1063       * @return string HTML fragment
1064       */
1065      public static function nonempty_tag($tagname, $contents, array $attributes = null) {
1066          if ($contents === '' || is_null($contents)) {
1067              return '';
1068          }
1069          return self::tag($tagname, $contents, $attributes);
1070      }
1071  
1072      /**
1073       * Outputs a HTML attribute and value
1074       *
1075       * @param string $name The name of the attribute ('src', 'href', 'class' etc.)
1076       * @param string $value The value of the attribute. The value will be escaped with {@link s()}
1077       * @return string HTML fragment
1078       */
1079      public static function attribute($name, $value) {
1080          if ($value instanceof moodle_url) {
1081              return ' ' . $name . '="' . $value->out() . '"';
1082          }
1083  
1084          // special case, we do not want these in output
1085          if ($value === null) {
1086              return '';
1087          }
1088  
1089          // no sloppy trimming here!
1090          return ' ' . $name . '="' . s($value) . '"';
1091      }
1092  
1093      /**
1094       * Outputs a list of HTML attributes and values
1095       *
1096       * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1097       *       The values will be escaped with {@link s()}
1098       * @return string HTML fragment
1099       */
1100      public static function attributes(array $attributes = null) {
1101          $attributes = (array)$attributes;
1102          $output = '';
1103          foreach ($attributes as $name => $value) {
1104              $output .= self::attribute($name, $value);
1105          }
1106          return $output;
1107      }
1108  
1109      /**
1110       * Generates a simple image tag with attributes.
1111       *
1112       * @param string $src The source of image
1113       * @param string $alt The alternate text for image
1114       * @param array $attributes The tag attributes (array('height' => $max_height, 'class' => 'class1') etc.)
1115       * @return string HTML fragment
1116       */
1117      public static function img($src, $alt, array $attributes = null) {
1118          $attributes = (array)$attributes;
1119          $attributes['src'] = $src;
1120          $attributes['alt'] = $alt;
1121  
1122          return self::empty_tag('img', $attributes);
1123      }
1124  
1125      /**
1126       * Generates random html element id.
1127       *
1128       * @staticvar int $counter
1129       * @staticvar type $uniq
1130       * @param string $base A string fragment that will be included in the random ID.
1131       * @return string A unique ID
1132       */
1133      public static function random_id($base='random') {
1134          static $counter = 0;
1135          static $uniq;
1136  
1137          if (!isset($uniq)) {
1138              $uniq = uniqid();
1139          }
1140  
1141          $counter++;
1142          return $base.$uniq.$counter;
1143      }
1144  
1145      /**
1146       * Generates a simple html link
1147       *
1148       * @param string|moodle_url $url The URL
1149       * @param string $text The text
1150       * @param array $attributes HTML attributes
1151       * @return string HTML fragment
1152       */
1153      public static function link($url, $text, array $attributes = null) {
1154          $attributes = (array)$attributes;
1155          $attributes['href']  = $url;
1156          return self::tag('a', $text, $attributes);
1157      }
1158  
1159      /**
1160       * Generates a simple checkbox with optional label
1161       *
1162       * @param string $name The name of the checkbox
1163       * @param string $value The value of the checkbox
1164       * @param bool $checked Whether the checkbox is checked
1165       * @param string $label The label for the checkbox
1166       * @param array $attributes Any attributes to apply to the checkbox
1167       * @return string html fragment
1168       */
1169      public static function checkbox($name, $value, $checked = true, $label = '', array $attributes = null) {
1170          $attributes = (array)$attributes;
1171          $output = '';
1172  
1173          if ($label !== '' and !is_null($label)) {
1174              if (empty($attributes['id'])) {
1175                  $attributes['id'] = self::random_id('checkbox_');
1176              }
1177          }
1178          $attributes['type']    = 'checkbox';
1179          $attributes['value']   = $value;
1180          $attributes['name']    = $name;
1181          $attributes['checked'] = $checked ? 'checked' : null;
1182  
1183          $output .= self::empty_tag('input', $attributes);
1184  
1185          if ($label !== '' and !is_null($label)) {
1186              $output .= self::tag('label', $label, array('for'=>$attributes['id']));
1187          }
1188  
1189          return $output;
1190      }
1191  
1192      /**
1193       * Generates a simple select yes/no form field
1194       *
1195       * @param string $name name of select element
1196       * @param bool $selected
1197       * @param array $attributes - html select element attributes
1198       * @return string HTML fragment
1199       */
1200      public static function select_yes_no($name, $selected=true, array $attributes = null) {
1201          $options = array('1'=>get_string('yes'), '0'=>get_string('no'));
1202          return self::select($options, $name, $selected, null, $attributes);
1203      }
1204  
1205      /**
1206       * Generates a simple select form field
1207       *
1208       * @param array $options associative array value=>label ex.:
1209       *                array(1=>'One, 2=>Two)
1210       *              it is also possible to specify optgroup as complex label array ex.:
1211       *                array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
1212       *                array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
1213       * @param string $name name of select element
1214       * @param string|array $selected value or array of values depending on multiple attribute
1215       * @param array|bool $nothing add nothing selected option, or false of not added
1216       * @param array $attributes html select element attributes
1217       * @return string HTML fragment
1218       */
1219      public static function select(array $options, $name, $selected = '', $nothing = array('' => 'choosedots'), array $attributes = null) {
1220          $attributes = (array)$attributes;
1221          if (is_array($nothing)) {
1222              foreach ($nothing as $k=>$v) {
1223                  if ($v === 'choose' or $v === 'choosedots') {
1224                      $nothing[$k] = get_string('choosedots');
1225                  }
1226              }
1227              $options = $nothing + $options; // keep keys, do not override
1228  
1229          } else if (is_string($nothing) and $nothing !== '') {
1230              // BC
1231              $options = array(''=>$nothing) + $options;
1232          }
1233  
1234          // we may accept more values if multiple attribute specified
1235          $selected = (array)$selected;
1236          foreach ($selected as $k=>$v) {
1237              $selected[$k] = (string)$v;
1238          }
1239  
1240          if (!isset($attributes['id'])) {
1241              $id = 'menu'.$name;
1242              // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
1243              $id = str_replace('[', '', $id);
1244              $id = str_replace(']', '', $id);
1245              $attributes['id'] = $id;
1246          }
1247  
1248          if (!isset($attributes['class'])) {
1249              $class = 'menu'.$name;
1250              // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading
1251              $class = str_replace('[', '', $class);
1252              $class = str_replace(']', '', $class);
1253              $attributes['class'] = $class;
1254          }
1255          $attributes['class'] = 'select ' . $attributes['class']; // Add 'select' selector always
1256  
1257          $attributes['name'] = $name;
1258  
1259          if (!empty($attributes['disabled'])) {
1260              $attributes['disabled'] = 'disabled';
1261          } else {
1262              unset($attributes['disabled']);
1263          }
1264  
1265          $output = '';
1266          foreach ($options as $value=>$label) {
1267              if (is_array($label)) {
1268                  // ignore key, it just has to be unique
1269                  $output .= self::select_optgroup(key($label), current($label), $selected);
1270              } else {
1271                  $output .= self::select_option($label, $value, $selected);
1272              }
1273          }
1274          return self::tag('select', $output, $attributes);
1275      }
1276  
1277      /**
1278       * Returns HTML to display a select box option.
1279       *
1280       * @param string $label The label to display as the option.
1281       * @param string|int $value The value the option represents
1282       * @param array $selected An array of selected options
1283       * @return string HTML fragment
1284       */
1285      private static function select_option($label, $value, array $selected) {
1286          $attributes = array();
1287          $value = (string)$value;
1288          if (in_array($value, $selected, true)) {
1289              $attributes['selected'] = 'selected';
1290          }
1291          $attributes['value'] = $value;
1292          return self::tag('option', $label, $attributes);
1293      }
1294  
1295      /**
1296       * Returns HTML to display a select box option group.
1297       *
1298       * @param string $groupname The label to use for the group
1299       * @param array $options The options in the group
1300       * @param array $selected An array of selected values.
1301       * @return string HTML fragment.
1302       */
1303      private static function select_optgroup($groupname, $options, array $selected) {
1304          if (empty($options)) {
1305              return '';
1306          }
1307          $attributes = array('label'=>$groupname);
1308          $output = '';
1309          foreach ($options as $value=>$label) {
1310              $output .= self::select_option($label, $value, $selected);
1311          }
1312          return self::tag('optgroup', $output, $attributes);
1313      }
1314  
1315      /**
1316       * This is a shortcut for making an hour selector menu.
1317       *
1318       * @param string $type The type of selector (years, months, days, hours, minutes)
1319       * @param string $name fieldname
1320       * @param int $currenttime A default timestamp in GMT
1321       * @param int $step minute spacing
1322       * @param array $attributes - html select element attributes
1323       * @return HTML fragment
1324       */
1325      public static function select_time($type, $name, $currenttime = 0, $step = 5, array $attributes = null) {
1326          if (!$currenttime) {
1327              $currenttime = time();
1328          }
1329          $currentdate = usergetdate($currenttime);
1330          $userdatetype = $type;
1331          $timeunits = array();
1332  
1333          switch ($type) {
1334              case 'years':
1335                  for ($i=1970; $i<=2020; $i++) {
1336                      $timeunits[$i] = $i;
1337                  }
1338                  $userdatetype = 'year';
1339                  break;
1340              case 'months':
1341                  for ($i=1; $i<=12; $i++) {
1342                      $timeunits[$i] = userdate(gmmktime(12,0,0,$i,15,2000), "%B");
1343                  }
1344                  $userdatetype = 'month';
1345                  $currentdate['month'] = (int)$currentdate['mon'];
1346                  break;
1347              case 'days':
1348                  for ($i=1; $i<=31; $i++) {
1349                      $timeunits[$i] = $i;
1350                  }
1351                  $userdatetype = 'mday';
1352                  break;
1353              case 'hours':
1354                  for ($i=0; $i<=23; $i++) {
1355                      $timeunits[$i] = sprintf("%02d",$i);
1356                  }
1357                  break;
1358              case 'minutes':
1359                  if ($step != 1) {
1360                      $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step;
1361                  }
1362  
1363                  for ($i=0; $i<=59; $i+=$step) {
1364                      $timeunits[$i] = sprintf("%02d",$i);
1365                  }
1366                  break;
1367              default:
1368                  throw new coding_exception("Time type $type is not supported by html_writer::select_time().");
1369          }
1370  
1371          if (empty($attributes['id'])) {
1372              $attributes['id'] = self::random_id('ts_');
1373          }
1374          $timerselector = self::select($timeunits, $name, $currentdate[$userdatetype], null, $attributes);
1375          $label = self::tag('label', get_string(substr($type, 0, -1), 'form'), array('for'=>$attributes['id'], 'class'=>'accesshide'));
1376  
1377          return $label.$timerselector;
1378      }
1379  
1380      /**
1381       * Shortcut for quick making of lists
1382       *
1383       * Note: 'list' is a reserved keyword ;-)
1384       *
1385       * @param array $items
1386       * @param array $attributes
1387       * @param string $tag ul or ol
1388       * @return string
1389       */
1390      public static function alist(array $items, array $attributes = null, $tag = 'ul') {
1391          $output = html_writer::start_tag($tag, $attributes)."\n";
1392          foreach ($items as $item) {
1393              $output .= html_writer::tag('li', $item)."\n";
1394          }
1395          $output .= html_writer::end_tag($tag);
1396          return $output;
1397      }
1398  
1399      /**
1400       * Returns hidden input fields created from url parameters.
1401       *
1402       * @param moodle_url $url
1403       * @param array $exclude list of excluded parameters
1404       * @return string HTML fragment
1405       */
1406      public static function input_hidden_params(moodle_url $url, array $exclude = null) {
1407          $exclude = (array)$exclude;
1408          $params = $url->params();
1409          foreach ($exclude as $key) {
1410              unset($params[$key]);
1411          }
1412  
1413          $output = '';
1414          foreach ($params as $key => $value) {
1415              $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value);
1416              $output .= self::empty_tag('input', $attributes)."\n";
1417          }
1418          return $output;
1419      }
1420  
1421      /**
1422       * Generate a script tag containing the the specified code.
1423       *
1424       * @param string $jscode the JavaScript code
1425       * @param moodle_url|string $url optional url of the external script, $code ignored if specified
1426       * @return string HTML, the code wrapped in <script> tags.
1427       */
1428      public static function script($jscode, $url=null) {
1429          if ($jscode) {
1430              $attributes = array('type'=>'text/javascript');
1431              return self::tag('script', "\n//<![CDATA[\n$jscode\n//]]>\n", $attributes) . "\n";
1432  
1433          } else if ($url) {
1434              $attributes = array('type'=>'text/javascript', 'src'=>$url);
1435              return self::tag('script', '', $attributes) . "\n";
1436  
1437          } else {
1438              return '';
1439          }
1440      }
1441  
1442      /**
1443       * Renders HTML table
1444       *
1445       * This method may modify the passed instance by adding some default properties if they are not set yet.
1446       * If this is not what you want, you should make a full clone of your data before passing them to this
1447       * method. In most cases this is not an issue at all so we do not clone by default for performance
1448       * and memory consumption reasons.
1449       *
1450       * Please do not use .r0/.r1 for css, as they will be removed in Moodle 2.9.
1451       * @todo MDL-43902 , remove r0 and r1 from tr classes.
1452       *
1453       * @param html_table $table data to be rendered
1454       * @return string HTML code
1455       */
1456      public static function table(html_table $table) {
1457          // prepare table data and populate missing properties with reasonable defaults
1458          if (!empty($table->align)) {
1459              foreach ($table->align as $key => $aa) {
1460                  if ($aa) {
1461                      $table->align[$key] = 'text-align:'. fix_align_rtl($aa) .';';  // Fix for RTL languages
1462                  } else {
1463                      $table->align[$key] = null;
1464                  }
1465              }
1466          }
1467          if (!empty($table->size)) {
1468              foreach ($table->size as $key => $ss) {
1469                  if ($ss) {
1470                      $table->size[$key] = 'width:'. $ss .';';
1471                  } else {
1472                      $table->size[$key] = null;
1473                  }
1474              }
1475          }
1476          if (!empty($table->wrap)) {
1477              foreach ($table->wrap as $key => $ww) {
1478                  if ($ww) {
1479                      $table->wrap[$key] = 'white-space:nowrap;';
1480                  } else {
1481                      $table->wrap[$key] = '';
1482                  }
1483              }
1484          }
1485          if (!empty($table->head)) {
1486              foreach ($table->head as $key => $val) {
1487                  if (!isset($table->align[$key])) {
1488                      $table->align[$key] = null;
1489                  }
1490                  if (!isset($table->size[$key])) {
1491                      $table->size[$key] = null;
1492                  }
1493                  if (!isset($table->wrap[$key])) {
1494                      $table->wrap[$key] = null;
1495                  }
1496  
1497              }
1498          }
1499          if (empty($table->attributes['class'])) {
1500              $table->attributes['class'] = 'generaltable';
1501          }
1502          if (!empty($table->tablealign)) {
1503              $table->attributes['class'] .= ' boxalign' . $table->tablealign;
1504          }
1505  
1506          // explicitly assigned properties override those defined via $table->attributes
1507          $table->attributes['class'] = trim($table->attributes['class']);
1508          $attributes = array_merge($table->attributes, array(
1509                  'id'            => $table->id,
1510                  'width'         => $table->width,
1511                  'summary'       => $table->summary,
1512                  'cellpadding'   => $table->cellpadding,
1513                  'cellspacing'   => $table->cellspacing,
1514              ));
1515          $output = html_writer::start_tag('table', $attributes) . "\n";
1516  
1517          $countcols = 0;
1518  
1519          if (!empty($table->head)) {
1520              $countcols = count($table->head);
1521  
1522              $output .= html_writer::start_tag('thead', array()) . "\n";
1523              $output .= html_writer::start_tag('tr', array()) . "\n";
1524              $keys = array_keys($table->head);
1525              $lastkey = end($keys);
1526  
1527              foreach ($table->head as $key => $heading) {
1528                  // Convert plain string headings into html_table_cell objects
1529                  if (!($heading instanceof html_table_cell)) {
1530                      $headingtext = $heading;
1531                      $heading = new html_table_cell();
1532                      $heading->text = $headingtext;
1533                      $heading->header = true;
1534                  }
1535  
1536                  if ($heading->header !== false) {
1537                      $heading->header = true;
1538                  }
1539  
1540                  if ($heading->header && empty($heading->scope)) {
1541                      $heading->scope = 'col';
1542                  }
1543  
1544                  $heading->attributes['class'] .= ' header c' . $key;
1545                  if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
1546                      $heading->colspan = $table->headspan[$key];
1547                      $countcols += $table->headspan[$key] - 1;
1548                  }
1549  
1550                  if ($key == $lastkey) {
1551                      $heading->attributes['class'] .= ' lastcol';
1552                  }
1553                  if (isset($table->colclasses[$key])) {
1554                      $heading->attributes['class'] .= ' ' . $table->colclasses[$key];
1555                  }
1556                  $heading->attributes['class'] = trim($heading->attributes['class']);
1557                  $attributes = array_merge($heading->attributes, array(
1558                          'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
1559                          'scope'     => $heading->scope,
1560                          'colspan'   => $heading->colspan,
1561                      ));
1562  
1563                  $tagtype = 'td';
1564                  if ($heading->header === true) {
1565                      $tagtype = 'th';
1566                  }
1567                  $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n";
1568              }
1569              $output .= html_writer::end_tag('tr') . "\n";
1570              $output .= html_writer::end_tag('thead') . "\n";
1571  
1572              if (empty($table->data)) {
1573                  // For valid XHTML strict every table must contain either a valid tr
1574                  // or a valid tbody... both of which must contain a valid td
1575                  $output .= html_writer::start_tag('tbody', array('class' => 'empty'));
1576                  $output .= html_writer::tag('tr', html_writer::tag('td', '', array('colspan'=>count($table->head))));
1577                  $output .= html_writer::end_tag('tbody');
1578              }
1579          }
1580  
1581          if (!empty($table->data)) {
1582              $oddeven    = 1;
1583              $keys       = array_keys($table->data);
1584              $lastrowkey = end($keys);
1585              $output .= html_writer::start_tag('tbody', array());
1586  
1587              foreach ($table->data as $key => $row) {
1588                  if (($row === 'hr') && ($countcols)) {
1589                      $output .= html_writer::tag('td', html_writer::tag('div', '', array('class' => 'tabledivider')), array('colspan' => $countcols));
1590                  } else {
1591                      // Convert array rows to html_table_rows and cell strings to html_table_cell objects
1592                      if (!($row instanceof html_table_row)) {
1593                          $newrow = new html_table_row();
1594  
1595                          foreach ($row as $cell) {
1596                              if (!($cell instanceof html_table_cell)) {
1597                                  $cell = new html_table_cell($cell);
1598                              }
1599                              $newrow->cells[] = $cell;
1600                          }
1601                          $row = $newrow;
1602                      }
1603  
1604                      $oddeven = $oddeven ? 0 : 1;
1605                      if (isset($table->rowclasses[$key])) {
1606                          $row->attributes['class'] .= ' ' . $table->rowclasses[$key];
1607                      }
1608  
1609                      $row->attributes['class'] .= ' r' . $oddeven;
1610                      if ($key == $lastrowkey) {
1611                          $row->attributes['class'] .= ' lastrow';
1612                      }
1613  
1614                      // Explicitly assigned properties should override those defined in the attributes.
1615                      $row->attributes['class'] = trim($row->attributes['class']);
1616                      $trattributes = array_merge($row->attributes, array(
1617                              'id'            => $row->id,
1618                              'style'         => $row->style,
1619                          ));
1620                      $output .= html_writer::start_tag('tr', $trattributes) . "\n";
1621                      $keys2 = array_keys($row->cells);
1622                      $lastkey = end($keys2);
1623  
1624                      $gotlastkey = false; //flag for sanity checking
1625                      foreach ($row->cells as $key => $cell) {
1626                          if ($gotlastkey) {
1627                              //This should never happen. Why do we have a cell after the last cell?
1628                              mtrace("A cell with key ($key) was found after the last key ($lastkey)");
1629                          }
1630  
1631                          if (!($cell instanceof html_table_cell)) {
1632                              $mycell = new html_table_cell();
1633                              $mycell->text = $cell;
1634                              $cell = $mycell;
1635                          }
1636  
1637                          if (($cell->header === true) && empty($cell->scope)) {
1638                              $cell->scope = 'row';
1639                          }
1640  
1641                          if (isset($table->colclasses[$key])) {
1642                              $cell->attributes['class'] .= ' ' . $table->colclasses[$key];
1643                          }
1644  
1645                          $cell->attributes['class'] .= ' cell c' . $key;
1646                          if ($key == $lastkey) {
1647                              $cell->attributes['class'] .= ' lastcol';
1648                              $gotlastkey = true;
1649                          }
1650                          $tdstyle = '';
1651                          $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
1652                          $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
1653                          $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
1654                          $cell->attributes['class'] = trim($cell->attributes['class']);
1655                          $tdattributes = array_merge($cell->attributes, array(
1656                                  'style' => $tdstyle . $cell->style,
1657                                  'colspan' => $cell->colspan,
1658                                  'rowspan' => $cell->rowspan,
1659                                  'id' => $cell->id,
1660                                  'abbr' => $cell->abbr,
1661                                  'scope' => $cell->scope,
1662                              ));
1663                          $tagtype = 'td';
1664                          if ($cell->header === true) {
1665                              $tagtype = 'th';
1666                          }
1667                          $output .= html_writer::tag($tagtype, $cell->text, $tdattributes) . "\n";
1668                      }
1669                  }
1670                  $output .= html_writer::end_tag('tr') . "\n";
1671              }
1672              $output .= html_writer::end_tag('tbody') . "\n";
1673          }
1674          $output .= html_writer::end_tag('table') . "\n";
1675  
1676          return $output;
1677      }
1678  
1679      /**
1680       * Renders form element label
1681       *
1682       * By default, the label is suffixed with a label separator defined in the
1683       * current language pack (colon by default in the English lang pack).
1684       * Adding the colon can be explicitly disabled if needed. Label separators
1685       * are put outside the label tag itself so they are not read by
1686       * screenreaders (accessibility).
1687       *
1688       * Parameter $for explicitly associates the label with a form control. When
1689       * set, the value of this attribute must be the same as the value of
1690       * the id attribute of the form control in the same document. When null,
1691       * the label being defined is associated with the control inside the label
1692       * element.
1693       *
1694       * @param string $text content of the label tag
1695       * @param string|null $for id of the element this label is associated with, null for no association
1696       * @param bool $colonize add label separator (colon) to the label text, if it is not there yet
1697       * @param array $attributes to be inserted in the tab, for example array('accesskey' => 'a')
1698       * @return string HTML of the label element
1699       */
1700      public static function label($text, $for, $colonize = true, array $attributes=array()) {
1701          if (!is_null($for)) {
1702              $attributes = array_merge($attributes, array('for' => $for));
1703          }
1704          $text = trim($text);
1705          $label = self::tag('label', $text, $attributes);
1706  
1707          // TODO MDL-12192 $colonize disabled for now yet
1708          // if (!empty($text) and $colonize) {
1709          //     // the $text may end with the colon already, though it is bad string definition style
1710          //     $colon = get_string('labelsep', 'langconfig');
1711          //     if (!empty($colon)) {
1712          //         $trimmed = trim($colon);
1713          //         if ((substr($text, -strlen($trimmed)) == $trimmed) or (substr($text, -1) == ':')) {
1714          //             //debugging('The label text should not end with colon or other label separator,
1715          //             //           please fix the string definition.', DEBUG_DEVELOPER);
1716          //         } else {
1717          //             $label .= $colon;
1718          //         }
1719          //     }
1720          // }
1721  
1722          return $label;
1723      }
1724  
1725      /**
1726       * Combines a class parameter with other attributes. Aids in code reduction
1727       * because the class parameter is very frequently used.
1728       *
1729       * If the class attribute is specified both in the attributes and in the
1730       * class parameter, the two values are combined with a space between.
1731       *
1732       * @param string $class Optional CSS class (or classes as space-separated list)
1733       * @param array $attributes Optional other attributes as array
1734       * @return array Attributes (or null if still none)
1735       */
1736      private static function add_class($class = '', array $attributes = null) {
1737          if ($class !== '') {
1738              $classattribute = array('class' => $class);
1739              if ($attributes) {
1740                  if (array_key_exists('class', $attributes)) {
1741                      $attributes['class'] = trim($attributes['class'] . ' ' . $class);
1742                  } else {
1743                      $attributes = $classattribute + $attributes;
1744                  }
1745              } else {
1746                  $attributes = $classattribute;
1747              }
1748          }
1749          return $attributes;
1750      }
1751  
1752      /**
1753       * Creates a <div> tag. (Shortcut function.)
1754       *
1755       * @param string $content HTML content of tag
1756       * @param string $class Optional CSS class (or classes as space-separated list)
1757       * @param array $attributes Optional other attributes as array
1758       * @return string HTML code for div
1759       */
1760      public static function div($content, $class = '', array $attributes = null) {
1761          return self::tag('div', $content, self::add_class($class, $attributes));
1762      }
1763  
1764      /**
1765       * Starts a <div> tag. (Shortcut function.)
1766       *
1767       * @param string $class Optional CSS class (or classes as space-separated list)
1768       * @param array $attributes Optional other attributes as array
1769       * @return string HTML code for open div tag
1770       */
1771      public static function start_div($class = '', array $attributes = null) {
1772          return self::start_tag('div', self::add_class($class, $attributes));
1773      }
1774  
1775      /**
1776       * Ends a <div> tag. (Shortcut function.)
1777       *
1778       * @return string HTML code for close div tag
1779       */
1780      public static function end_div() {
1781          return self::end_tag('div');
1782      }
1783  
1784      /**
1785       * Creates a <span> tag. (Shortcut function.)
1786       *
1787       * @param string $content HTML content of tag
1788       * @param string $class Optional CSS class (or classes as space-separated list)
1789       * @param array $attributes Optional other attributes as array
1790       * @return string HTML code for span
1791       */
1792      public static function span($content, $class = '', array $attributes = null) {
1793          return self::tag('span', $content, self::add_class($class, $attributes));
1794      }
1795  
1796      /**
1797       * Starts a <span> tag. (Shortcut function.)
1798       *
1799       * @param string $class Optional CSS class (or classes as space-separated list)
1800       * @param array $attributes Optional other attributes as array
1801       * @return string HTML code for open span tag
1802       */
1803      public static function start_span($class = '', array $attributes = null) {
1804          return self::start_tag('span', self::add_class($class, $attributes));
1805      }
1806  
1807      /**
1808       * Ends a <span> tag. (Shortcut function.)
1809       *
1810       * @return string HTML code for close span tag
1811       */
1812      public static function end_span() {
1813          return self::end_tag('span');
1814      }
1815  }
1816  
1817  /**
1818   * Simple javascript output class
1819   *
1820   * @copyright 2010 Petr Skoda
1821   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1822   * @since Moodle 2.0
1823   * @package core
1824   * @category output
1825   */
1826  class js_writer {
1827  
1828      /**
1829       * Returns javascript code calling the function
1830       *
1831       * @param string $function function name, can be complex like Y.Event.purgeElement
1832       * @param array $arguments parameters
1833       * @param int $delay execution delay in seconds
1834       * @return string JS code fragment
1835       */
1836      public static function function_call($function, array $arguments = null, $delay=0) {
1837          if ($arguments) {
1838              $arguments = array_map('json_encode', convert_to_array($arguments));
1839              $arguments = implode(', ', $arguments);
1840          } else {
1841              $arguments = '';
1842          }
1843          $js = "$function($arguments);";
1844  
1845          if ($delay) {
1846              $delay = $delay * 1000; // in miliseconds
1847              $js = "setTimeout(function() { $js }, $delay);";
1848          }
1849          return $js . "\n";
1850      }
1851  
1852      /**
1853       * Special function which adds Y as first argument of function call.
1854       *
1855       * @param string $function The function to call
1856       * @param array $extraarguments Any arguments to pass to it
1857       * @return string Some JS code
1858       */
1859      public static function function_call_with_Y($function, array $extraarguments = null) {
1860          if ($extraarguments) {
1861              $extraarguments = array_map('json_encode', convert_to_array($extraarguments));
1862              $arguments = 'Y, ' . implode(', ', $extraarguments);
1863          } else {
1864              $arguments = 'Y';
1865          }
1866          return "$function($arguments);\n";
1867      }
1868  
1869      /**
1870       * Returns JavaScript code to initialise a new object
1871       *
1872       * @param string $var If it is null then no var is assigned the new object.
1873       * @param string $class The class to initialise an object for.
1874       * @param array $arguments An array of args to pass to the init method.
1875       * @param array $requirements Any modules required for this class.
1876       * @param int $delay The delay before initialisation. 0 = no delay.
1877       * @return string Some JS code
1878       */
1879      public static function object_init($var, $class, array $arguments = null, array $requirements = null, $delay=0) {
1880          if (is_array($arguments)) {
1881              $arguments = array_map('json_encode', convert_to_array($arguments));
1882              $arguments = implode(', ', $arguments);
1883          }
1884  
1885          if ($var === null) {
1886              $js = "new $class(Y, $arguments);";
1887          } else if (strpos($var, '.')!==false) {
1888              $js = "$var = new $class(Y, $arguments);";
1889          } else {
1890              $js = "var $var = new $class(Y, $arguments);";
1891          }
1892  
1893          if ($delay) {
1894              $delay = $delay * 1000; // in miliseconds
1895              $js = "setTimeout(function() { $js }, $delay);";
1896          }
1897  
1898          if (count($requirements) > 0) {
1899              $requirements = implode("', '", $requirements);
1900              $js = "Y.use('$requirements', function(Y){ $js });";
1901          }
1902          return $js."\n";
1903      }
1904  
1905      /**
1906       * Returns code setting value to variable
1907       *
1908       * @param string $name
1909       * @param mixed $value json serialised value
1910       * @param bool $usevar add var definition, ignored for nested properties
1911       * @return string JS code fragment
1912       */
1913      public static function set_variable($name, $value, $usevar = true) {
1914          $output = '';
1915  
1916          if ($usevar) {
1917              if (strpos($name, '.')) {
1918                  $output .= '';
1919              } else {
1920                  $output .= 'var ';
1921              }
1922          }
1923  
1924          $output .= "$name = ".json_encode($value).";";
1925  
1926          return $output;
1927      }
1928  
1929      /**
1930       * Writes event handler attaching code
1931       *
1932       * @param array|string $selector standard YUI selector for elements, may be
1933       *     array or string, element id is in the form "#idvalue"
1934       * @param string $event A valid DOM event (click, mousedown, change etc.)
1935       * @param string $function The name of the function to call
1936       * @param array $arguments An optional array of argument parameters to pass to the function
1937       * @return string JS code fragment
1938       */
1939      public static function event_handler($selector, $event, $function, array $arguments = null) {
1940          $selector = json_encode($selector);
1941          $output = "Y.on('$event', $function, $selector, null";
1942          if (!empty($arguments)) {
1943              $output .= ', ' . json_encode($arguments);
1944          }
1945          return $output . ");\n";
1946      }
1947  }
1948  
1949  /**
1950   * Holds all the information required to render a <table> by {@link core_renderer::table()}
1951   *
1952   * Example of usage:
1953   * $t = new html_table();
1954   * ... // set various properties of the object $t as described below
1955   * echo html_writer::table($t);
1956   *
1957   * @copyright 2009 David Mudrak <[email protected]>
1958   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1959   * @since Moodle 2.0
1960   * @package core
1961   * @category output
1962   */
1963  class html_table {
1964  
1965      /**
1966       * @var string Value to use for the id attribute of the table
1967       */
1968      public $id = null;
1969  
1970      /**
1971       * @var array Attributes of HTML attributes for the <table> element
1972       */
1973      public $attributes = array();
1974  
1975      /**
1976       * @var array An array of headings. The n-th array item is used as a heading of the n-th column.
1977       * For more control over the rendering of the headers, an array of html_table_cell objects
1978       * can be passed instead of an array of strings.
1979       *
1980       * Example of usage:
1981       * $t->head = array('Student', 'Grade');
1982       */
1983      public $head;
1984  
1985      /**
1986       * @var array An array that can be used to make a heading span multiple columns.
1987       * In this example, {@link html_table:$data} is supposed to have three columns. For the first two columns,
1988       * the same heading is used. Therefore, {@link html_table::$head} should consist of two items.
1989       *
1990       * Example of usage:
1991       * $t->headspan = array(2,1);
1992       */
1993      public $headspan;
1994  
1995      /**
1996       * @var array An array of column alignments.
1997       * The value is used as CSS 'text-align' property. Therefore, possible
1998       * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective
1999       * of a left-to-right (LTR) language. For RTL, the values are flipped automatically.
2000       *
2001       * Examples of usage:
2002       * $t->align = array(null, 'right');
2003       * or
2004       * $t->align[1] = 'right';
2005       */
2006      public $align;
2007  
2008      /**
2009       * @var array The value is used as CSS 'size' property.
2010       *
2011       * Examples of usage:
2012       * $t->size = array('50%', '50%');
2013       * or
2014       * $t->size[1] = '120px';
2015       */
2016      public $size;
2017  
2018      /**
2019       * @var array An array of wrapping information.
2020       * The only possible value is 'nowrap' that sets the
2021       * CSS property 'white-space' to the value 'nowrap' in the given column.
2022       *
2023       * Example of usage:
2024       * $t->wrap = array(null, 'nowrap');
2025       */
2026      public $wrap;
2027  
2028      /**
2029       * @var array Array of arrays or html_table_row objects containing the data. Alternatively, if you have
2030       * $head specified, the string 'hr' (for horizontal ruler) can be used
2031       * instead of an array of cells data resulting in a divider rendered.
2032       *
2033       * Example of usage with array of arrays:
2034       * $row1 = array('Harry Potter', '76 %');
2035       * $row2 = array('Hermione Granger', '100 %');
2036       * $t->data = array($row1, $row2);
2037       *
2038       * Example with array of html_table_row objects: (used for more fine-grained control)
2039       * $cell1 = new html_table_cell();
2040       * $cell1->text = 'Harry Potter';
2041       * $cell1->colspan = 2;
2042       * $row1 = new html_table_row();
2043       * $row1->cells[] = $cell1;
2044       * $cell2 = new html_table_cell();
2045       * $cell2->text = 'Hermione Granger';
2046       * $cell3 = new html_table_cell();
2047       * $cell3->text = '100 %';
2048       * $row2 = new html_table_row();
2049       * $row2->cells = array($cell2, $cell3);
2050       * $t->data = array($row1, $row2);
2051       */
2052      public $data;
2053  
2054      /**
2055       * @deprecated since Moodle 2.0. Styling should be in the CSS.
2056       * @var string Width of the table, percentage of the page preferred.
2057       */
2058      public $width = null;
2059  
2060      /**
2061       * @deprecated since Moodle 2.0. Styling should be in the CSS.
2062       * @var string Alignment for the whole table. Can be 'right', 'left' or 'center' (default).
2063       */
2064      public $tablealign = null;
2065  
2066      /**
2067       * @deprecated since Moodle 2.0. Styling should be in the CSS.
2068       * @var int Padding on each cell, in pixels
2069       */
2070      public $cellpadding = null;
2071  
2072      /**
2073       * @var int Spacing between cells, in pixels
2074       * @deprecated since Moodle 2.0. Styling should be in the CSS.
2075       */
2076      public $cellspacing = null;
2077  
2078      /**
2079       * @var array Array of classes to add to particular rows, space-separated string.
2080       * Classes 'r0' or 'r1' are added automatically for every odd or even row,
2081       * respectively. Class 'lastrow' is added automatically for the last row
2082       * in the table.
2083       *
2084       * Example of usage:
2085       * $t->rowclasses[9] = 'tenth'
2086       */
2087      public $rowclasses;
2088  
2089      /**
2090       * @var array An array of classes to add to every cell in a particular column,
2091       * space-separated string. Class 'cell' is added automatically by the renderer.
2092       * Classes 'c0' or 'c1' are added automatically for every odd or even column,
2093       * respectively. Class 'lastcol' is added automatically for all last cells
2094       * in a row.
2095       *
2096       * Example of usage:
2097       * $t->colclasses = array(null, 'grade');
2098       */
2099      public $colclasses;
2100  
2101      /**
2102       * @var string Description of the contents for screen readers.
2103       */
2104      public $summary;
2105  
2106      /**
2107       * Constructor
2108       */
2109      public function __construct() {
2110          $this->attributes['class'] = '';
2111      }
2112  }
2113  
2114  /**
2115   * Component representing a table row.
2116   *
2117   * @copyright 2009 Nicolas Connault
2118   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2119   * @since Moodle 2.0
2120   * @package core
2121   * @category output
2122   */
2123  class html_table_row {
2124  
2125      /**
2126       * @var string Value to use for the id attribute of the row.
2127       */
2128      public $id = null;
2129  
2130      /**
2131       * @var array Array of html_table_cell objects
2132       */
2133      public $cells = array();
2134  
2135      /**
2136       * @var string Value to use for the style attribute of the table row
2137       */
2138      public $style = null;
2139  
2140      /**
2141       * @var array Attributes of additional HTML attributes for the <tr> element
2142       */
2143      public $attributes = array();
2144  
2145      /**
2146       * Constructor
2147       * @param array $cells
2148       */
2149      public function __construct(array $cells=null) {
2150          $this->attributes['class'] = '';
2151          $cells = (array)$cells;
2152          foreach ($cells as $cell) {
2153              if ($cell instanceof html_table_cell) {
2154                  $this->cells[] = $cell;
2155              } else {
2156                  $this->cells[] = new html_table_cell($cell);
2157              }
2158          }
2159      }
2160  }
2161  
2162  /**
2163   * Component representing a table cell.
2164   *
2165   * @copyright 2009 Nicolas Connault
2166   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2167   * @since Moodle 2.0
2168   * @package core
2169   * @category output
2170   */
2171  class html_table_cell {
2172  
2173      /**
2174       * @var string Value to use for the id attribute of the cell.
2175       */
2176      public $id = null;
2177  
2178      /**
2179       * @var string The contents of the cell.
2180       */
2181      public $text;
2182  
2183      /**
2184       * @var string Abbreviated version of the contents of the cell.
2185       */
2186      public $abbr = null;
2187  
2188      /**
2189       * @var int Number of columns this cell should span.
2190       */
2191      public $colspan = null;
2192  
2193      /**
2194       * @var int Number of rows this cell should span.
2195       */
2196      public $rowspan = null;
2197  
2198      /**
2199       * @var string Defines a way to associate header cells and data cells in a table.
2200       */
2201      public $scope = null;
2202  
2203      /**
2204       * @var bool Whether or not this cell is a header cell.
2205       */
2206      public $header = null;
2207  
2208      /**
2209       * @var string Value to use for the style attribute of the table cell
2210       */
2211      public $style = null;
2212  
2213      /**
2214       * @var array Attributes of additional HTML attributes for the <td> element
2215       */
2216      public $attributes = array();
2217  
2218      /**
2219       * Constructs a table cell
2220       *
2221       * @param string $text
2222       */
2223      public function __construct($text = null) {
2224          $this->text = $text;
2225          $this->attributes['class'] = '';
2226      }
2227  }
2228  
2229  /**
2230   * Component representing a paging bar.
2231   *
2232   * @copyright 2009 Nicolas Connault
2233   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2234   * @since Moodle 2.0
2235   * @package core
2236   * @category output
2237   */
2238  class paging_bar implements renderable {
2239  
2240      /**
2241       * @var int The maximum number of pagelinks to display.
2242       */
2243      public $maxdisplay = 18;
2244  
2245      /**
2246       * @var int The total number of entries to be pages through..
2247       */
2248      public $totalcount;
2249  
2250      /**
2251       * @var int The page you are currently viewing.
2252       */
2253      public $page;
2254  
2255      /**
2256       * @var int The number of entries that should be shown per page.
2257       */
2258      public $perpage;
2259  
2260      /**
2261       * @var string|moodle_url If this  is a string then it is the url which will be appended with $pagevar,
2262       * an equals sign and the page number.
2263       * If this is a moodle_url object then the pagevar param will be replaced by
2264       * the page no, for each page.
2265       */
2266      public $baseurl;
2267  
2268      /**
2269       * @var string This is the variable name that you use for the pagenumber in your
2270       * code (ie. 'tablepage', 'blogpage', etc)
2271       */
2272      public $pagevar;
2273  
2274      /**
2275       * @var string A HTML link representing the "previous" page.
2276       */
2277      public $previouslink = null;
2278  
2279      /**
2280       * @var string A HTML link representing the "next" page.
2281       */
2282      public $nextlink = null;
2283  
2284      /**
2285       * @var string A HTML link representing the first page.
2286       */
2287      public $firstlink = null;
2288  
2289      /**
2290       * @var string A HTML link representing the last page.
2291       */
2292      public $lastlink = null;
2293  
2294      /**
2295       * @var array An array of strings. One of them is just a string: the current page
2296       */
2297      public $pagelinks = array();
2298  
2299      /**
2300       * Constructor paging_bar with only the required params.
2301       *
2302       * @param int $totalcount The total number of entries available to be paged through
2303       * @param int $page The page you are currently viewing
2304       * @param int $perpage The number of entries that should be shown per page
2305       * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
2306       * @param string $pagevar name of page parameter that holds the page number
2307       */
2308      public function __construct($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
2309          $this->totalcount = $totalcount;
2310          $this->page       = $page;
2311          $this->perpage    = $perpage;
2312          $this->baseurl    = $baseurl;
2313          $this->pagevar    = $pagevar;
2314      }
2315  
2316      /**
2317       * Prepares the paging bar for output.
2318       *
2319       * This method validates the arguments set up for the paging bar and then
2320       * produces fragments of HTML to assist display later on.
2321       *
2322       * @param renderer_base $output
2323       * @param moodle_page $page
2324       * @param string $target
2325       * @throws coding_exception
2326       */
2327      public function prepare(renderer_base $output, moodle_page $page, $target) {
2328          if (!isset($this->totalcount) || is_null($this->totalcount)) {
2329              throw new coding_exception('paging_bar requires a totalcount value.');
2330          }
2331          if (!isset($this->page) || is_null($this->page)) {
2332              throw new coding_exception('paging_bar requires a page value.');
2333          }
2334          if (empty($this->perpage)) {
2335              throw new coding_exception('paging_bar requires a perpage value.');
2336          }
2337          if (empty($this->baseurl)) {
2338              throw new coding_exception('paging_bar requires a baseurl value.');
2339          }
2340  
2341          if ($this->totalcount > $this->perpage) {
2342              $pagenum = $this->page - 1;
2343  
2344              if ($this->page > 0) {
2345                  $this->previouslink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('previous'), array('class'=>'previous'));
2346              }
2347  
2348              if ($this->perpage > 0) {
2349                  $lastpage = ceil($this->totalcount / $this->perpage);
2350              } else {
2351                  $lastpage = 1;
2352              }
2353  
2354              if ($this->page > round(($this->maxdisplay/3)*2)) {
2355                  $currpage = $this->page - round($this->maxdisplay/3);
2356  
2357                  $this->firstlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>0)), '1', array('class'=>'first'));
2358              } else {
2359                  $currpage = 0;
2360              }
2361  
2362              $displaycount = $displaypage = 0;
2363  
2364              while ($displaycount < $this->maxdisplay and $currpage < $lastpage) {
2365                  $displaypage = $currpage + 1;
2366  
2367                  if ($this->page == $currpage) {
2368                      $this->pagelinks[] = html_writer::span($displaypage, 'current-page');
2369                  } else {
2370                      $pagelink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$currpage)), $displaypage);
2371                      $this->pagelinks[] = $pagelink;
2372                  }
2373  
2374                  $displaycount++;
2375                  $currpage++;
2376              }
2377  
2378              if ($currpage < $lastpage) {
2379                  $lastpageactual = $lastpage - 1;
2380                  $this->lastlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$lastpageactual)), $lastpage, array('class'=>'last'));
2381              }
2382  
2383              $pagenum = $this->page + 1;
2384  
2385              if ($pagenum != $displaypage) {
2386                  $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next'));
2387              }
2388          }
2389      }
2390  }
2391  
2392  /**
2393   * This class represents how a block appears on a page.
2394   *
2395   * During output, each block instance is asked to return a block_contents object,
2396   * those are then passed to the $OUTPUT->block function for display.
2397   *
2398   * contents should probably be generated using a moodle_block_..._renderer.
2399   *
2400   * Other block-like things that need to appear on the page, for example the
2401   * add new block UI, are also represented as block_contents objects.
2402   *
2403   * @copyright 2009 Tim Hunt
2404   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2405   * @since Moodle 2.0
2406   * @package core
2407   * @category output
2408   */
2409  class block_contents {
2410  
2411      /** Used when the block cannot be collapsed **/
2412      const NOT_HIDEABLE = 0;
2413  
2414      /** Used when the block can be collapsed but currently is not **/
2415      const VISIBLE = 1;
2416  
2417      /** Used when the block has been collapsed **/
2418      const HIDDEN = 2;
2419  
2420      /**
2421       * @var int Used to set $skipid.
2422       */
2423      protected static $idcounter = 1;
2424  
2425      /**
2426       * @var int All the blocks (or things that look like blocks) printed on
2427       * a page are given a unique number that can be used to construct id="" attributes.
2428       * This is set automatically be the {@link prepare()} method.
2429       * Do not try to set it manually.
2430       */
2431      public $skipid;
2432  
2433      /**
2434       * @var int If this is the contents of a real block, this should be set
2435       * to the block_instance.id. Otherwise this should be set to 0.
2436       */
2437      public $blockinstanceid = 0;
2438  
2439      /**
2440       * @var int If this is a real block instance, and there is a corresponding
2441       * block_position.id for the block on this page, this should be set to that id.
2442       * Otherwise it should be 0.
2443       */
2444      public $blockpositionid = 0;
2445  
2446      /**
2447       * @var array An array of attribute => value pairs that are put on the outer div of this
2448       * block. {@link $id} and {@link $classes} attributes should be set separately.
2449       */
2450      public $attributes;
2451  
2452      /**
2453       * @var string The title of this block. If this came from user input, it should already
2454       * have had format_string() processing done on it. This will be output inside
2455       * <h2> tags. Please do not cause invalid XHTML.
2456       */
2457      public $title = '';
2458  
2459      /**
2460       * @var string The label to use when the block does not, or will not have a visible title.
2461       * You should never set this as well as title... it will just be ignored.
2462       */
2463      public $arialabel = '';
2464  
2465      /**
2466       * @var string HTML for the content
2467       */
2468      public $content = '';
2469  
2470      /**
2471       * @var array An alternative to $content, it you want a list of things with optional icons.
2472       */
2473      public $footer = '';
2474  
2475      /**
2476       * @var string Any small print that should appear under the block to explain
2477       * to the teacher about the block, for example 'This is a sticky block that was
2478       * added in the system context.'
2479       */
2480      public $annotation = '';
2481  
2482      /**
2483       * @var int One of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether
2484       * the user can toggle whether this block is visible.
2485       */
2486      public $collapsible = self::NOT_HIDEABLE;
2487  
2488      /**
2489       * Set this to true if the block is dockable.
2490       * @var bool
2491       */
2492      public $dockable = false;
2493  
2494      /**
2495       * @var array A (possibly empty) array of editing controls. Each element of
2496       * this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption).
2497       * $icon is the icon name. Fed to $OUTPUT->pix_url.
2498       */
2499      public $controls = array();
2500  
2501  
2502      /**
2503       * Create new instance of block content
2504       * @param array $attributes
2505       */
2506      public function __construct(array $attributes = null) {
2507          $this->skipid = self::$idcounter;
2508          self::$idcounter += 1;
2509  
2510          if ($attributes) {
2511              // standard block
2512              $this->attributes = $attributes;
2513          } else {
2514              // simple "fake" blocks used in some modules and "Add new block" block
2515              $this->attributes = array('class'=>'block');
2516          }
2517      }
2518  
2519      /**
2520       * Add html class to block
2521       *
2522       * @param string $class
2523       */
2524      public function add_class($class) {
2525          $this->attributes['class'] .= ' '.$class;
2526      }
2527  }
2528  
2529  
2530  /**
2531   * This class represents a target for where a block can go when it is being moved.
2532   *
2533   * This needs to be rendered as a form with the given hidden from fields, and
2534   * clicking anywhere in the form should submit it. The form action should be
2535   * $PAGE->url.
2536   *
2537   * @copyright 2009 Tim Hunt
2538   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2539   * @since Moodle 2.0
2540   * @package core
2541   * @category output
2542   */
2543  class block_move_target {
2544  
2545      /**
2546       * @var moodle_url Move url
2547       */
2548      public $url;
2549  
2550      /**
2551       * Constructor
2552       * @param moodle_url $url
2553       */
2554      public function __construct(moodle_url $url) {
2555          $this->url  = $url;
2556      }
2557  }
2558  
2559  /**
2560   * Custom menu item
2561   *
2562   * This class is used to represent one item within a custom menu that may or may
2563   * not have children.
2564   *
2565   * @copyright 2010 Sam Hemelryk
2566   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2567   * @since Moodle 2.0
2568   * @package core
2569   * @category output
2570   */
2571  class custom_menu_item implements renderable {
2572  
2573      /**
2574       * @var string The text to show for the item
2575       */
2576      protected $text;
2577  
2578      /**
2579       * @var moodle_url The link to give the icon if it has no children
2580       */
2581      protected $url;
2582  
2583      /**
2584       * @var string A title to apply to the item. By default the text
2585       */
2586      protected $title;
2587  
2588      /**
2589       * @var int A sort order for the item, not necessary if you order things in
2590       * the CFG var.
2591       */
2592      protected $sort;
2593  
2594      /**
2595       * @var custom_menu_item A reference to the parent for this item or NULL if
2596       * it is a top level item
2597       */
2598      protected $parent;
2599  
2600      /**
2601       * @var array A array in which to store children this item has.
2602       */
2603      protected $children = array();
2604  
2605      /**
2606       * @var int A reference to the sort var of the last child that was added
2607       */
2608      protected $lastsort = 0;
2609  
2610      /**
2611       * Constructs the new custom menu item
2612       *
2613       * @param string $text
2614       * @param moodle_url $url A moodle url to apply as the link for this item [Optional]
2615       * @param string $title A title to apply to this item [Optional]
2616       * @param int $sort A sort or to use if we need to sort differently [Optional]
2617       * @param custom_menu_item $parent A reference to the parent custom_menu_item this child
2618       *        belongs to, only if the child has a parent. [Optional]
2619       */
2620      public function __construct($text, moodle_url $url=null, $title=null, $sort = null, custom_menu_item $parent = null) {
2621          $this->text = $text;
2622          $this->url = $url;
2623          $this->title = $title;
2624          $this->sort = (int)$sort;
2625          $this->parent = $parent;
2626      }
2627  
2628      /**
2629       * Adds a custom menu item as a child of this node given its properties.
2630       *
2631       * @param string $text
2632       * @param moodle_url $url
2633       * @param string $title
2634       * @param int $sort
2635       * @return custom_menu_item
2636       */
2637      public function add($text, moodle_url $url = null, $title = null, $sort = null) {
2638          $key = count($this->children);
2639          if (empty($sort)) {
2640              $sort = $this->lastsort + 1;
2641          }
2642          $this->children[$key] = new custom_menu_item($text, $url, $title, $sort, $this);
2643          $this->lastsort = (int)$sort;
2644          return $this->children[$key];
2645      }
2646  
2647      /**
2648       * Removes a custom menu item that is a child or descendant to the current menu.
2649       *
2650       * Returns true if child was found and removed.
2651       *
2652       * @param custom_menu_item $menuitem
2653       * @return bool
2654       */
2655      public function remove_child(custom_menu_item $menuitem) {
2656          $removed = false;
2657          if (($key = array_search($menuitem, $this->children)) !== false) {
2658              unset($this->children[$key]);
2659              $this->children = array_values($this->children);
2660              $removed = true;
2661          } else {
2662              foreach ($this->children as $child) {
2663                  if ($removed = $child->remove_child($menuitem)) {
2664                      break;
2665                  }
2666              }
2667          }
2668          return $removed;
2669      }
2670  
2671      /**
2672       * Returns the text for this item
2673       * @return string
2674       */
2675      public function get_text() {
2676          return $this->text;
2677      }
2678  
2679      /**
2680       * Returns the url for this item
2681       * @return moodle_url
2682       */
2683      public function get_url() {
2684          return $this->url;
2685      }
2686  
2687      /**
2688       * Returns the title for this item
2689       * @return string
2690       */
2691      public function get_title() {
2692          return $this->title;
2693      }
2694  
2695      /**
2696       * Sorts and returns the children for this item
2697       * @return array
2698       */
2699      public function get_children() {
2700          $this->sort();
2701          return $this->children;
2702      }
2703  
2704      /**
2705       * Gets the sort order for this child
2706       * @return int
2707       */
2708      public function get_sort_order() {
2709          return $this->sort;
2710      }
2711  
2712      /**
2713       * Gets the parent this child belong to
2714       * @return custom_menu_item
2715       */
2716      public function get_parent() {
2717          return $this->parent;
2718      }
2719  
2720      /**
2721       * Sorts the children this item has
2722       */
2723      public function sort() {
2724          usort($this->children, array('custom_menu','sort_custom_menu_items'));
2725      }
2726  
2727      /**
2728       * Returns true if this item has any children
2729       * @return bool
2730       */
2731      public function has_children() {
2732          return (count($this->children) > 0);
2733      }
2734  
2735      /**
2736       * Sets the text for the node
2737       * @param string $text
2738       */
2739      public function set_text($text) {
2740          $this->text = (string)$text;
2741      }
2742  
2743      /**
2744       * Sets the title for the node
2745       * @param string $title
2746       */
2747      public function set_title($title) {
2748          $this->title = (string)$title;
2749      }
2750  
2751      /**
2752       * Sets the url for the node
2753       * @param moodle_url $url
2754       */
2755      public function set_url(moodle_url $url) {
2756          $this->url = $url;
2757      }
2758  }
2759  
2760  /**
2761   * Custom menu class
2762   *
2763   * This class is used to operate a custom menu that can be rendered for the page.
2764   * The custom menu is built using $CFG->custommenuitems and is a structured collection
2765   * of custom_menu_item nodes that can be rendered by the core renderer.
2766   *
2767   * To configure the custom menu:
2768   *     Settings: Administration > Appearance > Themes > Theme settings
2769   *
2770   * @copyright 2010 Sam Hemelryk
2771   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2772   * @since Moodle 2.0
2773   * @package core
2774   * @category output
2775   */
2776  class custom_menu extends custom_menu_item {
2777  
2778      /**
2779       * @var string The language we should render for, null disables multilang support.
2780       */
2781      protected $currentlanguage = null;
2782  
2783      /**
2784       * Creates the custom menu
2785       *
2786       * @param string $definition the menu items definition in syntax required by {@link convert_text_to_menu_nodes()}
2787       * @param string $currentlanguage the current language code, null disables multilang support
2788       */
2789      public function __construct($definition = '', $currentlanguage = null) {
2790          $this->currentlanguage = $currentlanguage;
2791          parent::__construct('root'); // create virtual root element of the menu
2792          if (!empty($definition)) {
2793              $this->override_children(self::convert_text_to_menu_nodes($definition, $currentlanguage));
2794          }
2795      }
2796  
2797      /**
2798       * Overrides the children of this custom menu. Useful when getting children
2799       * from $CFG->custommenuitems
2800       *
2801       * @param array $children
2802       */
2803      public function override_children(array $children) {
2804          $this->children = array();
2805          foreach ($children as $child) {
2806              if ($child instanceof custom_menu_item) {
2807                  $this->children[] = $child;
2808              }
2809          }
2810      }
2811  
2812      /**
2813       * Converts a string into a structured array of custom_menu_items which can
2814       * then be added to a custom menu.
2815       *
2816       * Structure:
2817       *     text|url|title|langs
2818       * The number of hyphens at the start determines the depth of the item. The
2819       * languages are optional, comma separated list of languages the line is for.
2820       *
2821       * Example structure:
2822       *     First level first item|http://www.moodle.com/
2823       *     -Second level first item|http://www.moodle.com/partners/
2824       *     -Second level second item|http://www.moodle.com/hq/
2825       *     --Third level first item|http://www.moodle.com/jobs/
2826       *     -Second level third item|http://www.moodle.com/development/
2827       *     First level second item|http://www.moodle.com/feedback/
2828       *     First level third item
2829       *     English only|http://moodle.com|English only item|en
2830       *     German only|http://moodle.de|Deutsch|de,de_du,de_kids
2831       *
2832       *
2833       * @static
2834       * @param string $text the menu items definition
2835       * @param string $language the language code, null disables multilang support
2836       * @return array
2837       */
2838      public static function convert_text_to_menu_nodes($text, $language = null) {
2839          $root = new custom_menu();
2840          $lastitem = $root;
2841          $lastdepth = 0;
2842          $hiddenitems = array();
2843          $lines = explode("\n", $text);
2844          foreach ($lines as $linenumber => $line) {
2845              $line = trim($line);
2846              if (strlen($line) == 0) {
2847                  continue;
2848              }
2849              // Parse item settings.
2850              $itemtext = null;
2851              $itemurl = null;
2852              $itemtitle = null;
2853              $itemvisible = true;
2854              $settings = explode('|', $line);
2855              foreach ($settings as $i => $setting) {
2856                  $setting = trim($setting);
2857                  if (!empty($setting)) {
2858                      switch ($i) {
2859                          case 0:
2860                              $itemtext = ltrim($setting, '-');
2861                              $itemtitle = $itemtext;
2862                              break;
2863                          case 1:
2864                              $itemurl = new moodle_url($setting);
2865                              break;
2866                          case 2:
2867                              $itemtitle = $setting;
2868                              break;
2869                          case 3:
2870                              if (!empty($language)) {
2871                                  $itemlanguages = array_map('trim', explode(',', $setting));
2872                                  $itemvisible &= in_array($language, $itemlanguages);
2873                              }
2874                              break;
2875                      }
2876                  }
2877              }
2878              // Get depth of new item.
2879              preg_match('/^(\-*)/', $line, $match);
2880              $itemdepth = strlen($match[1]) + 1;
2881              // Find parent item for new item.
2882              while (($lastdepth - $itemdepth) >= 0) {
2883                  $lastitem = $lastitem->get_parent();
2884                  $lastdepth--;
2885              }
2886              $lastitem = $lastitem->add($itemtext, $itemurl, $itemtitle, $linenumber + 1);
2887              $lastdepth++;
2888              if (!$itemvisible) {
2889                  $hiddenitems[] = $lastitem;
2890              }
2891          }
2892          foreach ($hiddenitems as $item) {
2893              $item->parent->remove_child($item);
2894          }
2895          return $root->get_children();
2896      }
2897  
2898      /**
2899       * Sorts two custom menu items
2900       *
2901       * This function is designed to be used with the usort method
2902       *     usort($this->children, array('custom_menu','sort_custom_menu_items'));
2903       *
2904       * @static
2905       * @param custom_menu_item $itema
2906       * @param custom_menu_item $itemb
2907       * @return int
2908       */
2909      public static function sort_custom_menu_items(custom_menu_item $itema, custom_menu_item $itemb) {
2910          $itema = $itema->get_sort_order();
2911          $itemb = $itemb->get_sort_order();
2912          if ($itema == $itemb) {
2913              return 0;
2914          }
2915          return ($itema > $itemb) ? +1 : -1;
2916      }
2917  }
2918  
2919  /**
2920   * Stores one tab
2921   *
2922   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2923   * @package core
2924   */
2925  class tabobject implements renderable {
2926      /** @var string unique id of the tab in this tree, it is used to find selected and/or inactive tabs */
2927      var $id;
2928      /** @var moodle_url|string link */
2929      var $link;
2930      /** @var string text on the tab */
2931      var $text;
2932      /** @var string title under the link, by defaul equals to text */
2933      var $title;
2934      /** @var bool whether to display a link under the tab name when it's selected */
2935      var $linkedwhenselected = false;
2936      /** @var bool whether the tab is inactive */
2937      var $inactive = false;
2938      /** @var bool indicates that this tab's child is selected */
2939      var $activated = false;
2940      /** @var bool indicates that this tab is selected */
2941      var $selected = false;
2942      /** @var array stores children tabobjects */
2943      var $subtree = array();
2944      /** @var int level of tab in the tree, 0 for root (instance of tabtree), 1 for the first row of tabs */
2945      var $level = 1;
2946  
2947      /**
2948       * Constructor
2949       *
2950       * @param string $id unique id of the tab in this tree, it is used to find selected and/or inactive tabs
2951       * @param string|moodle_url $link
2952       * @param string $text text on the tab
2953       * @param string $title title under the link, by defaul equals to text
2954       * @param bool $linkedwhenselected whether to display a link under the tab name when it's selected
2955       */
2956      public function __construct($id, $link = null, $text = '', $title = '', $linkedwhenselected = false) {
2957          $this->id = $id;
2958          $this->link = $link;
2959          $this->text = $text;
2960          $this->title = $title ? $title : $text;
2961          $this->linkedwhenselected = $linkedwhenselected;
2962      }
2963  
2964      /**
2965       * Travels through tree and finds the tab to mark as selected, all parents are automatically marked as activated
2966       *
2967       * @param string $selected the id of the selected tab (whatever row it's on),
2968       *    if null marks all tabs as unselected
2969       * @return bool whether this tab is selected or contains selected tab in its subtree
2970       */
2971      protected function set_selected($selected) {
2972          if ((string)$selected === (string)$this->id) {
2973              $this->selected = true;
2974              // This tab is selected. No need to travel through subtree.
2975              return true;
2976          }
2977          foreach ($this->subtree as $subitem) {
2978              if ($subitem->set_selected($selected)) {
2979                  // This tab has child that is selected. Mark it as activated. No need to check other children.
2980                  $this->activated = true;
2981                  return true;
2982              }
2983          }
2984          return false;
2985      }
2986  
2987      /**
2988       * Travels through tree and finds a tab with specified id
2989       *
2990       * @param string $id
2991       * @return tabtree|null
2992       */
2993      public function find($id) {
2994          if ((string)$this->id === (string)$id) {
2995              return $this;
2996          }
2997          foreach ($this->subtree as $tab) {
2998              if ($obj = $tab->find($id)) {
2999                  return $obj;
3000              }
3001          }
3002          return null;
3003      }
3004  
3005      /**
3006       * Allows to mark each tab's level in the tree before rendering.
3007       *
3008       * @param int $level
3009       */
3010      protected function set_level($level) {
3011          $this->level = $level;
3012          foreach ($this->subtree as $tab) {
3013              $tab->set_level($level + 1);
3014          }
3015      }
3016  }
3017  
3018  /**
3019   * Stores tabs list
3020   *
3021   * Example how to print a single line tabs:
3022   * $rows = array(
3023   *    new tabobject(...),
3024   *    new tabobject(...)
3025   * );
3026   * echo $OUTPUT->tabtree($rows, $selectedid);
3027   *
3028   * Multiple row tabs may not look good on some devices but if you want to use them
3029   * you can specify ->subtree for the active tabobject.
3030   *
3031   * @copyright 2013 Marina Glancy
3032   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3033   * @since Moodle 2.5
3034   * @package core
3035   * @category output
3036   */
3037  class tabtree extends tabobject {
3038      /**
3039       * Constuctor
3040       *
3041       * It is highly recommended to call constructor when list of tabs is already
3042       * populated, this way you ensure that selected and inactive tabs are located
3043       * and attribute level is set correctly.
3044       *
3045       * @param array $tabs array of tabs, each of them may have it's own ->subtree
3046       * @param string|null $selected which tab to mark as selected, all parent tabs will
3047       *     automatically be marked as activated
3048       * @param array|string|null $inactive list of ids of inactive tabs, regardless of
3049       *     their level. Note that you can as weel specify tabobject::$inactive for separate instances
3050       */
3051      public function __construct($tabs, $selected = null, $inactive = null) {
3052          $this->subtree = $tabs;
3053          if ($selected !== null) {
3054              $this->set_selected($selected);
3055          }
3056          if ($inactive !== null) {
3057              if (is_array($inactive)) {
3058                  foreach ($inactive as $id) {
3059                      if ($tab = $this->find($id)) {
3060                          $tab->inactive = true;
3061                      }
3062                  }
3063              } else if ($tab = $this->find($inactive)) {
3064                  $tab->inactive = true;
3065              }
3066          }
3067          $this->set_level(0);
3068      }
3069  }
3070  
3071  /**
3072   * An action menu.
3073   *
3074   * This action menu component takes a series of primary and secondary actions.
3075   * The primary actions are displayed permanently and the secondary attributes are displayed within a drop
3076   * down menu.
3077   *
3078   * @package core
3079   * @category output
3080   * @copyright 2013 Sam Hemelryk
3081   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3082   */
3083  class action_menu implements renderable {
3084  
3085      /**
3086       * Top right alignment.
3087       */
3088      const TL = 1;
3089  
3090      /**
3091       * Top right alignment.
3092       */
3093      const TR = 2;
3094  
3095      /**
3096       * Top right alignment.
3097       */
3098      const BL = 3;
3099  
3100      /**
3101       * Top right alignment.
3102       */
3103      const BR = 4;
3104  
3105      /**
3106       * The instance number. This is unique to this instance of the action menu.
3107       * @var int
3108       */
3109      protected $instance = 0;
3110  
3111      /**
3112       * An array of primary actions. Please use {@link action_menu::add_primary_action()} to add actions.
3113       * @var array
3114       */
3115      protected $primaryactions = array();
3116  
3117      /**
3118       * An array of secondary actions. Please use {@link action_menu::add_secondary_action()} to add actions.
3119       * @var array
3120       */
3121      protected $secondaryactions = array();
3122  
3123      /**
3124       * An array of attributes added to the container of the action menu.
3125       * Initialised with defaults during construction.
3126       * @var array
3127       */
3128      public $attributes = array();
3129      /**
3130       * An array of attributes added to the container of the primary actions.
3131       * Initialised with defaults during construction.
3132       * @var array
3133       */
3134      public $attributesprimary = array();
3135      /**
3136       * An array of attributes added to the container of the secondary actions.
3137       * Initialised with defaults during construction.
3138       * @var array
3139       */
3140      public $attributessecondary = array();
3141  
3142      /**
3143       * The string to use next to the icon for the action icon relating to the secondary (dropdown) menu.
3144       * @var array
3145       */
3146      public $actiontext = null;
3147  
3148      /**
3149       * An icon to use for the toggling the secondary menu (dropdown).
3150       * @var actionicon
3151       */
3152      public $actionicon;
3153  
3154      /**
3155       * Any text to use for the toggling the secondary menu (dropdown).
3156       * @var menutrigger
3157       */
3158      public $menutrigger = '';
3159  
3160      /**
3161       * Place the action menu before all other actions.
3162       * @var prioritise
3163       */
3164      public $prioritise = false;
3165  
3166      /**
3167       * Constructs the action menu with the given items.
3168       *
3169       * @param array $actions An array of actions.
3170       */
3171      public function __construct(array $actions = array()) {
3172          static $initialised = 0;
3173          $this->instance = $initialised;
3174          $initialised++;
3175  
3176          $this->attributes = array(
3177              'id' => 'action-menu-'.$this->instance,
3178              'class' => 'moodle-actionmenu',
3179              'data-enhance' => 'moodle-core-actionmenu'
3180          );
3181          $this->attributesprimary = array(
3182              'id' => 'action-menu-'.$this->instance.'-menubar',
3183              'class' => 'menubar',
3184              'role' => 'menubar'
3185          );
3186          $this->attributessecondary = array(
3187              'id' => 'action-menu-'.$this->instance.'-menu',
3188              'class' => 'menu',
3189              'data-rel' => 'menu-content',
3190              'aria-labelledby' => 'action-menu-toggle-'.$this->instance,
3191              'role' => 'menu'
3192          );
3193          $this->set_alignment(self::TR, self::BR);
3194          foreach ($actions as $action) {
3195              $this->add($action);
3196          }
3197      }
3198  
3199      public function set_menu_trigger($trigger) {
3200          $this->menutrigger = $trigger;
3201      }
3202  
3203      /**
3204       * Initialises JS required fore the action menu.
3205       * The JS is only required once as it manages all action menu's on the page.
3206       *
3207       * @param moodle_page $page
3208       */
3209      public function initialise_js(moodle_page $page) {
3210          static $initialised = false;
3211          if (!$initialised) {
3212              $page->requires->yui_module('moodle-core-actionmenu', 'M.core.actionmenu.init');
3213              $initialised = true;
3214          }
3215      }
3216  
3217      /**
3218       * Adds an action to this action menu.
3219       *
3220       * @param action_menu_link|pix_icon|string $action
3221       */
3222      public function add($action) {
3223          if ($action instanceof action_link) {
3224              if ($action->primary) {
3225                  $this->add_primary_action($action);
3226              } else {
3227                  $this->add_secondary_action($action);
3228              }
3229          } else if ($action instanceof pix_icon) {
3230              $this->add_primary_action($action);
3231          } else {
3232              $this->add_secondary_action($action);
3233          }
3234      }
3235  
3236      /**
3237       * Adds a primary action to the action menu.
3238       *
3239       * @param action_menu_link|action_link|pix_icon|string $action
3240       */
3241      public function add_primary_action($action) {
3242          if ($action instanceof action_link || $action instanceof pix_icon) {
3243              $action->attributes['role'] = 'menuitem';
3244              if ($action instanceof action_menu_link) {
3245                  $action->actionmenu = $this;
3246              }
3247          }
3248          $this->primaryactions[] = $action;
3249      }
3250  
3251      /**
3252       * Adds a secondary action to the action menu.
3253       *
3254       * @param action_link|pix_icon|string $action
3255       */
3256      public function add_secondary_action($action) {
3257          if ($action instanceof action_link || $action instanceof pix_icon) {
3258              $action->attributes['role'] = 'menuitem';
3259              if ($action instanceof action_menu_link) {
3260                  $action->actionmenu = $this;
3261              }
3262          }
3263          $this->secondaryactions[] = $action;
3264      }
3265  
3266      /**
3267       * Returns the primary actions ready to be rendered.
3268       *
3269       * @param core_renderer $output The renderer to use for getting icons.
3270       * @return array
3271       */
3272      public function get_primary_actions(core_renderer $output = null) {
3273          global $OUTPUT;
3274          if ($output === null) {
3275              $output = $OUTPUT;
3276          }
3277          $pixicon = $this->actionicon;
3278          $linkclasses = array('toggle-display');
3279  
3280          $title = '';
3281          if (!empty($this->menutrigger)) {
3282              $pixicon = '<b class="caret"></b>';
3283              $linkclasses[] = 'textmenu';
3284          } else {
3285              $title = new lang_string('actions', 'moodle');
3286              $this->actionicon = new pix_icon(
3287                  't/edit_menu',
3288                  '',
3289                  'moodle',
3290                  array('class' => 'iconsmall actionmenu', 'title' => '')
3291              );
3292              $pixicon = $this->actionicon;
3293          }
3294          if ($pixicon instanceof renderable) {
3295              $pixicon = $output->render($pixicon);
3296              if ($pixicon instanceof pix_icon && isset($pixicon->attributes['alt'])) {
3297                  $title = $pixicon->attributes['alt'];
3298              }
3299          }
3300          $string = '';
3301          if ($this->actiontext) {
3302              $string = $this->actiontext;
3303          }
3304          $actions = $this->primaryactions;
3305          $attributes = array(
3306              'class' => implode(' ', $linkclasses),
3307              'title' => $title,
3308              'id' => 'action-menu-toggle-'.$this->instance,
3309              'role' => 'menuitem'
3310          );
3311          $link = html_writer::link('#', $string . $this->menutrigger . $pixicon, $attributes);
3312          if ($this->prioritise) {
3313              array_unshift($actions, $link);
3314          } else {
3315              $actions[] = $link;
3316          }
3317          return $actions;
3318      }
3319  
3320      /**
3321       * Returns the secondary actions ready to be rendered.
3322       * @return array
3323       */
3324      public function get_secondary_actions() {
3325          return $this->secondaryactions;
3326      }
3327  
3328      /**
3329       * Sets the selector that should be used to find the owning node of this menu.
3330       * @param string $selector A CSS/YUI selector to identify the owner of the menu.
3331       */
3332      public function set_owner_selector($selector) {
3333          $this->attributes['data-owner'] = $selector;
3334      }
3335  
3336      /**
3337       * Sets the alignment of the dialogue in relation to button used to toggle it.
3338       *
3339       * @param int $dialogue One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR.
3340       * @param int $button One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR.
3341       */
3342      public function set_alignment($dialogue, $button) {
3343          if (isset($this->attributessecondary['data-align'])) {
3344              // We've already got one set, lets remove the old class so as to avoid troubles.
3345              $class = $this->attributessecondary['class'];
3346              $search = 'align-'.$this->attributessecondary['data-align'];
3347              $this->attributessecondary['class'] = str_replace($search, '', $class);
3348          }
3349          $align = $this->get_align_string($dialogue) . '-' . $this->get_align_string($button);
3350          $this->attributessecondary['data-align'] = $align;
3351          $this->attributessecondary['class'] .= ' align-'.$align;
3352      }
3353  
3354      /**
3355       * Returns a string to describe the alignment.
3356       *
3357       * @param int $align One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR.
3358       * @return string
3359       */
3360      protected function get_align_string($align) {
3361          switch ($align) {
3362              case self::TL :
3363                  return 'tl';
3364              case self::TR :
3365                  return 'tr';
3366              case self::BL :
3367                  return 'bl';
3368              case self::BR :
3369                  return 'br';
3370              default :
3371                  return 'tl';
3372          }
3373      }
3374  
3375      /**
3376       * Sets a constraint for the dialogue.
3377       *
3378       * The constraint is applied when the dialogue is shown and limits the display of the dialogue to within the
3379       * element the constraint identifies.
3380       *
3381       * @param string $ancestorselector A snippet of CSS used to identify the ancestor to contrain the dialogue to.
3382       */
3383      public function set_constraint($ancestorselector) {
3384          $this->attributessecondary['data-constraint'] = $ancestorselector;
3385      }
3386  
3387      /**
3388       * If you call this method the action menu will be displayed but will not be enhanced.
3389       *
3390       * By not displaying the menu enhanced all items will be displayed in a single row.
3391       */
3392      public function do_not_enhance() {
3393          unset($this->attributes['data-enhance']);
3394      }
3395  
3396      /**
3397       * Returns true if this action menu will be enhanced.
3398       *
3399       * @return bool
3400       */
3401      public function will_be_enhanced() {
3402          return isset($this->attributes['data-enhance']);
3403      }
3404  
3405      /**
3406       * Sets nowrap on items. If true menu items should not wrap lines if they are longer than the available space.
3407       *
3408       * This property can be useful when the action menu is displayed within a parent element that is either floated
3409       * or relatively positioned.
3410       * In that situation the width of the menu is determined by the width of the parent element which may not be large
3411       * enough for the menu items without them wrapping.
3412       * This disables the wrapping so that the menu takes on the width of the longest item.
3413       *
3414       * @param bool $value If true nowrap gets set, if false it gets removed. Defaults to true.
3415       */
3416      public function set_nowrap_on_items($value = true) {
3417          $class = 'nowrap-items';
3418          if (!empty($this->attributes['class'])) {
3419              $pos = strpos($this->attributes['class'], $class);
3420              if ($value === true && $pos === false) {
3421                  // The value is true and the class has not been set yet. Add it.
3422                  $this->attributes['class'] .= ' '.$class;
3423              } else if ($value === false && $pos !== false) {
3424                  // The value is false and the class has been set. Remove it.
3425                  $this->attributes['class'] = substr($this->attributes['class'], $pos, strlen($class));
3426              }
3427          } else if ($value) {
3428              // The value is true and the class has not been set yet. Add it.
3429              $this->attributes['class'] = $class;
3430          }
3431      }
3432  }
3433  
3434  /**
3435   * An action menu filler
3436   *
3437   * @package core
3438   * @category output
3439   * @copyright 2013 Andrew Nicols
3440   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3441   */
3442  class action_menu_filler extends action_link implements renderable {
3443  
3444      /**
3445       * True if this is a primary action. False if not.
3446       * @var bool
3447       */
3448      public $primary = true;
3449  
3450      /**
3451       * Constructs the object.
3452       */
3453      public function __construct() {
3454      }
3455  }
3456  
3457  /**
3458   * An action menu action
3459   *
3460   * @package core
3461   * @category output
3462   * @copyright 2013 Sam Hemelryk
3463   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3464   */
3465  class action_menu_link extends action_link implements renderable {
3466  
3467      /**
3468       * True if this is a primary action. False if not.
3469       * @var bool
3470       */
3471      public $primary = true;
3472  
3473      /**
3474       * The action menu this link has been added to.
3475       * @var action_menu
3476       */
3477      public $actionmenu = null;
3478  
3479      /**
3480       * Constructs the object.
3481       *
3482       * @param moodle_url $url The URL for the action.
3483       * @param pix_icon $icon The icon to represent the action.
3484       * @param string $text The text to represent the action.
3485       * @param bool $primary Whether this is a primary action or not.
3486       * @param array $attributes Any attribtues associated with the action.
3487       */
3488      public function __construct(moodle_url $url, pix_icon $icon = null, $text, $primary = true, array $attributes = array()) {
3489          parent::__construct($url, $text, null, $attributes, $icon);
3490          $this->primary = (bool)$primary;
3491          $this->add_class('menu-action');
3492          $this->attributes['role'] = 'menuitem';
3493      }
3494  }
3495  
3496  /**
3497   * A primary action menu action
3498   *
3499   * @package core
3500   * @category output
3501   * @copyright 2013 Sam Hemelryk
3502   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3503   */
3504  class action_menu_link_primary extends action_menu_link {
3505      /**
3506       * Constructs the object.
3507       *
3508       * @param moodle_url $url
3509       * @param pix_icon $icon
3510       * @param string $text
3511       * @param array $attributes
3512       */
3513      public function __construct(moodle_url $url, pix_icon $icon = null, $text, array $attributes = array()) {
3514          parent::__construct($url, $icon, $text, true, $attributes);
3515      }
3516  }
3517  
3518  /**
3519   * A secondary action menu action
3520   *
3521   * @package core
3522   * @category output
3523   * @copyright 2013 Sam Hemelryk
3524   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3525   */
3526  class action_menu_link_secondary extends action_menu_link {
3527      /**
3528       * Constructs the object.
3529       *
3530       * @param moodle_url $url
3531       * @param pix_icon $icon
3532       * @param string $text
3533       * @param array $attributes
3534       */
3535      public function __construct(moodle_url $url, pix_icon $icon = null, $text, array $attributes = array()) {
3536          parent::__construct($url, $icon, $text, false, $attributes);
3537      }
3538  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1