See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * 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) info@skodak.org 35 * @package core 36 * @category output 37 */ 38 interface renderable { 39 // intentionally empty 40 } 41 42 /** 43 * Interface marking other classes having the ability to export their data for use by templates. 44 * 45 * @copyright 2015 Damyon Wiese 46 * @package core 47 * @category output 48 * @since 2.9 49 */ 50 interface templatable { 51 52 /** 53 * Function to export the renderer data in a format that is suitable for a 54 * mustache template. This means: 55 * 1. No complex types - only stdClass, array, int, string, float, bool 56 * 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks). 57 * 58 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 59 * @return stdClass|array 60 */ 61 public function export_for_template(renderer_base $output); 62 } 63 64 /** 65 * Data structure representing a file picker. 66 * 67 * @copyright 2010 Dongsheng Cai 68 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 69 * @since Moodle 2.0 70 * @package core 71 * @category output 72 */ 73 class file_picker implements renderable { 74 75 /** 76 * @var stdClass An object containing options for the file picker 77 */ 78 public $options; 79 80 /** 81 * Constructs a file picker object. 82 * 83 * The following are possible options for the filepicker: 84 * - accepted_types (*) 85 * - return_types (FILE_INTERNAL) 86 * - env (filepicker) 87 * - client_id (uniqid) 88 * - itemid (0) 89 * - maxbytes (-1) 90 * - maxfiles (1) 91 * - buttonname (false) 92 * 93 * @param stdClass $options An object containing options for the file picker. 94 */ 95 public function __construct(stdClass $options) { 96 global $CFG, $USER, $PAGE; 97 require_once($CFG->dirroot. '/repository/lib.php'); 98 $defaults = array( 99 'accepted_types'=>'*', 100 'return_types'=>FILE_INTERNAL, 101 'env' => 'filepicker', 102 'client_id' => uniqid(), 103 'itemid' => 0, 104 'maxbytes'=>-1, 105 'maxfiles'=>1, 106 'buttonname'=>false 107 ); 108 foreach ($defaults as $key=>$value) { 109 if (empty($options->$key)) { 110 $options->$key = $value; 111 } 112 } 113 114 $options->currentfile = ''; 115 if (!empty($options->itemid)) { 116 $fs = get_file_storage(); 117 $usercontext = context_user::instance($USER->id); 118 if (empty($options->filename)) { 119 if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id DESC', false)) { 120 $file = reset($files); 121 } 122 } else { 123 $file = $fs->get_file($usercontext->id, 'user', 'draft', $options->itemid, $options->filepath, $options->filename); 124 } 125 if (!empty($file)) { 126 $options->currentfile = html_writer::link(moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename()), $file->get_filename()); 127 } 128 } 129 130 // initilise options, getting files in root path 131 $this->options = initialise_filepicker($options); 132 133 // copying other options 134 foreach ($options as $name=>$value) { 135 if (!isset($this->options->$name)) { 136 $this->options->$name = $value; 137 } 138 } 139 } 140 } 141 142 /** 143 * Data structure representing a user picture. 144 * 145 * @copyright 2009 Nicolas Connault, 2010 Petr Skoda 146 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 147 * @since Modle 2.0 148 * @package core 149 * @category output 150 */ 151 class user_picture implements renderable { 152 /** 153 * @var stdClass A user object with at least fields all columns specified 154 * in $fields array constant set. 155 */ 156 public $user; 157 158 /** 159 * @var int The course id. Used when constructing the link to the user's 160 * profile, page course id used if not specified. 161 */ 162 public $courseid; 163 164 /** 165 * @var bool Add course profile link to image 166 */ 167 public $link = true; 168 169 /** 170 * @var int Size in pixels. Special values are (true/1 = 100px) and (false/0 = 35px) for backward compatibility. 171 * Recommended values (supporting user initials too): 16, 35, 64 and 100. 172 */ 173 public $size = 35; 174 175 /** 176 * @var bool Add non-blank alt-text to the image. 177 * Default true, set to false when image alt just duplicates text in screenreaders. 178 */ 179 public $alttext = true; 180 181 /** 182 * @var bool Whether or not to open the link in a popup window. 183 */ 184 public $popup = false; 185 186 /** 187 * @var string Image class attribute 188 */ 189 public $class = 'userpicture'; 190 191 /** 192 * @var bool Whether to be visible to screen readers. 193 */ 194 public $visibletoscreenreaders = true; 195 196 /** 197 * @var bool Whether to include the fullname in the user picture link. 198 */ 199 public $includefullname = false; 200 201 /** 202 * @var mixed Include user authentication token. True indicates to generate a token for current user, and integer value 203 * indicates to generate a token for the user whose id is the value indicated. 204 */ 205 public $includetoken = false; 206 207 /** 208 * User picture constructor. 209 * 210 * @param stdClass $user user record with at least id, picture, imagealt, firstname and lastname set. 211 * It is recommended to add also contextid of the user for performance reasons. 212 */ 213 public function __construct(stdClass $user) { 214 global $DB; 215 216 if (empty($user->id)) { 217 throw new coding_exception('User id is required when printing user avatar image.'); 218 } 219 220 // only touch the DB if we are missing data and complain loudly... 221 $needrec = false; 222 foreach (\core_user\fields::get_picture_fields() as $field) { 223 if (!property_exists($user, $field)) { 224 $needrec = true; 225 debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. ' 226 .'Please use the \core_user\fields API to get the full list of required fields.', DEBUG_DEVELOPER); 227 break; 228 } 229 } 230 231 if ($needrec) { 232 $this->user = $DB->get_record('user', array('id' => $user->id), 233 implode(',', \core_user\fields::get_picture_fields()), MUST_EXIST); 234 } else { 235 $this->user = clone($user); 236 } 237 } 238 239 /** 240 * Returns a list of required user fields, useful when fetching required user info from db. 241 * 242 * In some cases we have to fetch the user data together with some other information, 243 * the idalias is useful there because the id would otherwise override the main 244 * id of the result record. Please note it has to be converted back to id before rendering. 245 * 246 * @param string $tableprefix name of database table prefix in query 247 * @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) 248 * @param string $idalias alias of id field 249 * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id' 250 * @return string 251 * @deprecated since Moodle 3.11 MDL-45242 252 * @see \core_user\fields 253 */ 254 public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') { 255 debugging('user_picture::fields() is deprecated. Please use the \core_user\fields API instead.', DEBUG_DEVELOPER); 256 $userfields = \core_user\fields::for_userpic(); 257 if ($extrafields) { 258 $userfields->including(...$extrafields); 259 } 260 $selects = $userfields->get_sql($tableprefix, false, $fieldprefix, $idalias, false)->selects; 261 if ($tableprefix === '') { 262 // If no table alias is specified, don't add {user}. in front of fields. 263 $selects = str_replace('{user}.', '', $selects); 264 } 265 // Maintain legacy behaviour where the field list was done with 'implode' and no spaces. 266 $selects = str_replace(', ', ',', $selects); 267 return $selects; 268 } 269 270 /** 271 * Extract the aliased user fields from a given record 272 * 273 * Given a record that was previously obtained using {@link self::fields()} with aliases, 274 * this method extracts user related unaliased fields. 275 * 276 * @param stdClass $record containing user picture fields 277 * @param array $extrafields extra fields included in the $record 278 * @param string $idalias alias of the id field 279 * @param string $fieldprefix prefix added to all columns in their aliases, does not apply to 'id' 280 * @return stdClass object with unaliased user fields 281 */ 282 public static function unalias(stdClass $record, array $extrafields = null, $idalias = 'id', $fieldprefix = '') { 283 284 if (empty($idalias)) { 285 $idalias = 'id'; 286 } 287 288 $return = new stdClass(); 289 290 foreach (\core_user\fields::get_picture_fields() as $field) { 291 if ($field === 'id') { 292 if (property_exists($record, $idalias)) { 293 $return->id = $record->{$idalias}; 294 } 295 } else { 296 if (property_exists($record, $fieldprefix.$field)) { 297 $return->{$field} = $record->{$fieldprefix.$field}; 298 } 299 } 300 } 301 // add extra fields if not already there 302 if ($extrafields) { 303 foreach ($extrafields as $e) { 304 if ($e === 'id' or property_exists($return, $e)) { 305 continue; 306 } 307 $return->{$e} = $record->{$fieldprefix.$e}; 308 } 309 } 310 311 return $return; 312 } 313 314 /** 315 * Works out the URL for the users picture. 316 * 317 * This method is recommended as it avoids costly redirects of user pictures 318 * if requests are made for non-existent files etc. 319 * 320 * @param moodle_page $page 321 * @param renderer_base $renderer 322 * @return moodle_url 323 */ 324 public function get_url(moodle_page $page, renderer_base $renderer = null) { 325 global $CFG; 326 327 if (is_null($renderer)) { 328 $renderer = $page->get_renderer('core'); 329 } 330 331 // Sort out the filename and size. Size is only required for the gravatar 332 // implementation presently. 333 if (empty($this->size)) { 334 $filename = 'f2'; 335 $size = 35; 336 } else if ($this->size === true or $this->size == 1) { 337 $filename = 'f1'; 338 $size = 100; 339 } else if ($this->size > 100) { 340 $filename = 'f3'; 341 $size = (int)$this->size; 342 } else if ($this->size >= 50) { 343 $filename = 'f1'; 344 $size = (int)$this->size; 345 } else { 346 $filename = 'f2'; 347 $size = (int)$this->size; 348 } 349 350 $defaulturl = $renderer->image_url('u/'.$filename); // default image 351 352 if ((!empty($CFG->forcelogin) and !isloggedin()) || 353 (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) { 354 // Protect images if login required and not logged in; 355 // also if login is required for profile images and is not logged in or guest 356 // do not use require_login() because it is expensive and not suitable here anyway. 357 return $defaulturl; 358 } 359 360 // First try to detect deleted users - but do not read from database for performance reasons! 361 if (!empty($this->user->deleted) or strpos($this->user->email, '@') === false) { 362 // All deleted users should have email replaced by md5 hash, 363 // all active users are expected to have valid email. 364 return $defaulturl; 365 } 366 367 // Did the user upload a picture? 368 if ($this->user->picture > 0) { 369 if (!empty($this->user->contextid)) { 370 $contextid = $this->user->contextid; 371 } else { 372 $context = context_user::instance($this->user->id, IGNORE_MISSING); 373 if (!$context) { 374 // This must be an incorrectly deleted user, all other users have context. 375 return $defaulturl; 376 } 377 $contextid = $context->id; 378 } 379 380 $path = '/'; 381 if (clean_param($page->theme->name, PARAM_THEME) == $page->theme->name) { 382 // We append the theme name to the file path if we have it so that 383 // in the circumstance that the profile picture is not available 384 // when the user actually requests it they still get the profile 385 // picture for the correct theme. 386 $path .= $page->theme->name.'/'; 387 } 388 // Set the image URL to the URL for the uploaded file and return. 389 $url = moodle_url::make_pluginfile_url( 390 $contextid, 'user', 'icon', null, $path, $filename, false, $this->includetoken); 391 $url->param('rev', $this->user->picture); 392 return $url; 393 } 394 395 if ($this->user->picture == 0 and !empty($CFG->enablegravatar)) { 396 // Normalise the size variable to acceptable bounds 397 if ($size < 1 || $size > 512) { 398 $size = 35; 399 } 400 // Hash the users email address 401 $md5 = md5(strtolower(trim($this->user->email))); 402 // Build a gravatar URL with what we know. 403 404 // Find the best default image URL we can (MDL-35669) 405 if (empty($CFG->gravatardefaulturl)) { 406 $absoluteimagepath = $page->theme->resolve_image_location('u/'.$filename, 'core'); 407 if (strpos($absoluteimagepath, $CFG->dirroot) === 0) { 408 $gravatardefault = $CFG->wwwroot . substr($absoluteimagepath, strlen($CFG->dirroot)); 409 } else { 410 $gravatardefault = $CFG->wwwroot . '/pix/u/' . $filename . '.png'; 411 } 412 } else { 413 $gravatardefault = $CFG->gravatardefaulturl; 414 } 415 416 // If the currently requested page is https then we'll return an 417 // https gravatar page. 418 if (is_https()) { 419 return new moodle_url("https://secure.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault)); 420 } else { 421 return new moodle_url("http://www.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault)); 422 } 423 } 424 425 return $defaulturl; 426 } 427 } 428 429 /** 430 * Data structure representing a help icon. 431 * 432 * @copyright 2010 Petr Skoda (info@skodak.org) 433 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 434 * @since Moodle 2.0 435 * @package core 436 * @category output 437 */ 438 class help_icon implements renderable, templatable { 439 440 /** 441 * @var string lang pack identifier (without the "_help" suffix), 442 * both get_string($identifier, $component) and get_string($identifier.'_help', $component) 443 * must exist. 444 */ 445 public $identifier; 446 447 /** 448 * @var string Component name, the same as in get_string() 449 */ 450 public $component; 451 452 /** 453 * @var string Extra descriptive text next to the icon 454 */ 455 public $linktext = null; 456 457 /** 458 * Constructor 459 * 460 * @param string $identifier string for help page title, 461 * string with _help suffix is used for the actual help text. 462 * string with _link suffix is used to create a link to further info (if it exists) 463 * @param string $component 464 */ 465 public function __construct($identifier, $component) { 466 $this->identifier = $identifier; 467 $this->component = $component; 468 } 469 470 /** 471 * Verifies that both help strings exists, shows debug warnings if not 472 */ 473 public function diag_strings() { 474 $sm = get_string_manager(); 475 if (!$sm->string_exists($this->identifier, $this->component)) { 476 debugging("Help title string does not exist: [$this->identifier, $this->component]"); 477 } 478 if (!$sm->string_exists($this->identifier.'_help', $this->component)) { 479 debugging("Help contents string does not exist: [{$this->identifier}_help, $this->component]"); 480 } 481 } 482 483 /** 484 * Export this data so it can be used as the context for a mustache template. 485 * 486 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 487 * @return array 488 */ 489 public function export_for_template(renderer_base $output) { 490 global $CFG; 491 492 $title = get_string($this->identifier, $this->component); 493 494 if (empty($this->linktext)) { 495 $alt = get_string('helpprefix2', '', trim($title, ". \t")); 496 } else { 497 $alt = get_string('helpwiththis'); 498 } 499 500 $data = get_formatted_help_string($this->identifier, $this->component, false); 501 502 $data->alt = $alt; 503 $data->icon = (new pix_icon('help', $alt, 'core', ['class' => 'iconhelp']))->export_for_template($output); 504 $data->linktext = $this->linktext; 505 $data->title = get_string('helpprefix2', '', trim($title, ". \t")); 506 507 $options = [ 508 'component' => $this->component, 509 'identifier' => $this->identifier, 510 'lang' => current_language() 511 ]; 512 513 // Debugging feature lets you display string identifier and component. 514 if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) { 515 $options['strings'] = 1; 516 } 517 518 $data->url = (new moodle_url('/help.php', $options))->out(false); 519 $data->ltr = !right_to_left(); 520 return $data; 521 } 522 } 523 524 525 /** 526 * Data structure representing an icon font. 527 * 528 * @copyright 2016 Damyon Wiese 529 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 530 * @package core 531 * @category output 532 */ 533 class pix_icon_font implements templatable { 534 535 /** 536 * @var pix_icon $pixicon The original icon. 537 */ 538 private $pixicon = null; 539 540 /** 541 * @var string $key The mapped key. 542 */ 543 private $key; 544 545 /** 546 * @var bool $mapped The icon could not be mapped. 547 */ 548 private $mapped; 549 550 /** 551 * Constructor 552 * 553 * @param pix_icon $pixicon The original icon 554 */ 555 public function __construct(pix_icon $pixicon) { 556 global $PAGE; 557 558 $this->pixicon = $pixicon; 559 $this->mapped = false; 560 $iconsystem = \core\output\icon_system::instance(); 561 562 $this->key = $iconsystem->remap_icon_name($pixicon->pix, $pixicon->component); 563 if (!empty($this->key)) { 564 $this->mapped = true; 565 } 566 } 567 568 /** 569 * Return true if this pix_icon was successfully mapped to an icon font. 570 * 571 * @return bool 572 */ 573 public function is_mapped() { 574 return $this->mapped; 575 } 576 577 /** 578 * Export this data so it can be used as the context for a mustache template. 579 * 580 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 581 * @return array 582 */ 583 public function export_for_template(renderer_base $output) { 584 585 $pixdata = $this->pixicon->export_for_template($output); 586 587 $title = isset($this->pixicon->attributes['title']) ? $this->pixicon->attributes['title'] : ''; 588 $alt = isset($this->pixicon->attributes['alt']) ? $this->pixicon->attributes['alt'] : ''; 589 if (empty($title)) { 590 $title = $alt; 591 } 592 $data = array( 593 'extraclasses' => $pixdata['extraclasses'], 594 'title' => $title, 595 'alt' => $alt, 596 'key' => $this->key 597 ); 598 599 return $data; 600 } 601 } 602 603 /** 604 * Data structure representing an icon subtype. 605 * 606 * @copyright 2016 Damyon Wiese 607 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 608 * @package core 609 * @category output 610 */ 611 class pix_icon_fontawesome extends pix_icon_font { 612 613 } 614 615 /** 616 * Data structure representing an icon. 617 * 618 * @copyright 2010 Petr Skoda 619 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 620 * @since Moodle 2.0 621 * @package core 622 * @category output 623 */ 624 class pix_icon implements renderable, templatable { 625 626 /** 627 * @var string The icon name 628 */ 629 var $pix; 630 631 /** 632 * @var string The component the icon belongs to. 633 */ 634 var $component; 635 636 /** 637 * @var array An array of attributes to use on the icon 638 */ 639 var $attributes = array(); 640 641 /** 642 * Constructor 643 * 644 * @param string $pix short icon name 645 * @param string $alt The alt text to use for the icon 646 * @param string $component component name 647 * @param array $attributes html attributes 648 */ 649 public function __construct($pix, $alt, $component='moodle', array $attributes = null) { 650 global $PAGE; 651 652 $this->pix = $pix; 653 $this->component = $component; 654 $this->attributes = (array)$attributes; 655 656 if (empty($this->attributes['class'])) { 657 $this->attributes['class'] = ''; 658 } 659 660 // Set an additional class for big icons so that they can be styled properly. 661 if (substr($pix, 0, 2) === 'b/') { 662 $this->attributes['class'] .= ' iconsize-big'; 663 } 664 665 // If the alt is empty, don't place it in the attributes, otherwise it will override parent alt text. 666 if (!is_null($alt)) { 667 $this->attributes['alt'] = $alt; 668 669 // If there is no title, set it to the attribute. 670 if (!isset($this->attributes['title'])) { 671 $this->attributes['title'] = $this->attributes['alt']; 672 } 673 } else { 674 unset($this->attributes['alt']); 675 } 676 677 if (empty($this->attributes['title'])) { 678 // Remove the title attribute if empty, we probably want to use the parent node's title 679 // and some browsers might overwrite it with an empty title. 680 unset($this->attributes['title']); 681 } 682 683 // Hide icons from screen readers that have no alt. 684 if (empty($this->attributes['alt'])) { 685 $this->attributes['aria-hidden'] = 'true'; 686 } 687 } 688 689 /** 690 * Export this data so it can be used as the context for a mustache template. 691 * 692 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 693 * @return array 694 */ 695 public function export_for_template(renderer_base $output) { 696 $attributes = $this->attributes; 697 $extraclasses = ''; 698 699 foreach ($attributes as $key => $item) { 700 if ($key == 'class') { 701 $extraclasses = $item; 702 unset($attributes[$key]); 703 break; 704 } 705 } 706 707 $attributes['src'] = $output->image_url($this->pix, $this->component)->out(false); 708 $templatecontext = array(); 709 foreach ($attributes as $name => $value) { 710 $templatecontext[] = array('name' => $name, 'value' => $value); 711 } 712 $title = isset($attributes['title']) ? $attributes['title'] : ''; 713 if (empty($title)) { 714 $title = isset($attributes['alt']) ? $attributes['alt'] : ''; 715 } 716 $data = array( 717 'attributes' => $templatecontext, 718 'extraclasses' => $extraclasses 719 ); 720 721 return $data; 722 } 723 724 /** 725 * Much simpler version of export that will produce the data required to render this pix with the 726 * pix helper in a mustache tag. 727 * 728 * @return array 729 */ 730 public function export_for_pix() { 731 $title = isset($this->attributes['title']) ? $this->attributes['title'] : ''; 732 if (empty($title)) { 733 $title = isset($this->attributes['alt']) ? $this->attributes['alt'] : ''; 734 } 735 return [ 736 'key' => $this->pix, 737 'component' => $this->component, 738 'title' => (string) $title, 739 ]; 740 } 741 } 742 743 /** 744 * Data structure representing an activity icon. 745 * 746 * The difference is that activity icons will always render with the standard icon system (no font icons). 747 * 748 * @copyright 2017 Damyon Wiese 749 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 750 * @package core 751 */ 752 class image_icon extends pix_icon { 753 } 754 755 /** 756 * Data structure representing an emoticon image 757 * 758 * @copyright 2010 David Mudrak 759 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 760 * @since Moodle 2.0 761 * @package core 762 * @category output 763 */ 764 class pix_emoticon extends pix_icon implements renderable { 765 766 /** 767 * Constructor 768 * @param string $pix short icon name 769 * @param string $alt alternative text 770 * @param string $component emoticon image provider 771 * @param array $attributes explicit HTML attributes 772 */ 773 public function __construct($pix, $alt, $component = 'moodle', array $attributes = array()) { 774 if (empty($attributes['class'])) { 775 $attributes['class'] = 'emoticon'; 776 } 777 parent::__construct($pix, $alt, $component, $attributes); 778 } 779 } 780 781 /** 782 * Data structure representing a simple form with only one button. 783 * 784 * @copyright 2009 Petr Skoda 785 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 786 * @since Moodle 2.0 787 * @package core 788 * @category output 789 */ 790 class single_button implements renderable { 791 792 /** 793 * @var moodle_url Target url 794 */ 795 public $url; 796 797 /** 798 * @var string Button label 799 */ 800 public $label; 801 802 /** 803 * @var string Form submit method post or get 804 */ 805 public $method = 'post'; 806 807 /** 808 * @var string Wrapping div class 809 */ 810 public $class = 'singlebutton'; 811 812 /** 813 * @var bool True if button is primary button. Used for styling. 814 */ 815 public $primary = false; 816 817 /** 818 * @var bool True if button disabled, false if normal 819 */ 820 public $disabled = false; 821 822 /** 823 * @var string Button tooltip 824 */ 825 public $tooltip = null; 826 827 /** 828 * @var string Form id 829 */ 830 public $formid; 831 832 /** 833 * @var array List of attached actions 834 */ 835 public $actions = array(); 836 837 /** 838 * @var array $params URL Params 839 */ 840 public $params; 841 842 /** 843 * @var string Action id 844 */ 845 public $actionid; 846 847 /** 848 * @var array 849 */ 850 protected $attributes = []; 851 852 /** 853 * Constructor 854 * @param moodle_url $url 855 * @param string $label button text 856 * @param string $method get or post submit method 857 * @param bool $primary whether this is a primary button, used for styling 858 * @param array $attributes Attributes for the HTML button tag 859 */ 860 public function __construct(moodle_url $url, $label, $method='post', $primary=false, $attributes = []) { 861 $this->url = clone($url); 862 $this->label = $label; 863 $this->method = $method; 864 $this->primary = $primary; 865 $this->attributes = $attributes; 866 } 867 868 /** 869 * Shortcut for adding a JS confirm dialog when the button is clicked. 870 * The message must be a yes/no question. 871 * 872 * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur. 873 */ 874 public function add_confirm_action($confirmmessage) { 875 $this->add_action(new confirm_action($confirmmessage)); 876 } 877 878 /** 879 * Add action to the button. 880 * @param component_action $action 881 */ 882 public function add_action(component_action $action) { 883 $this->actions[] = $action; 884 } 885 886 /** 887 * Sets an attribute for the HTML button tag. 888 * 889 * @param string $name The attribute name 890 * @param mixed $value The value 891 * @return null 892 */ 893 public function set_attribute($name, $value) { 894 $this->attributes[$name] = $value; 895 } 896 897 /** 898 * Export data. 899 * 900 * @param renderer_base $output Renderer. 901 * @return stdClass 902 */ 903 public function export_for_template(renderer_base $output) { 904 $url = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring(); 905 906 $data = new stdClass(); 907 $data->id = html_writer::random_id('single_button'); 908 $data->formid = $this->formid; 909 $data->method = $this->method; 910 $data->url = $url === '' ? '#' : $url; 911 $data->label = $this->label; 912 $data->classes = $this->class; 913 $data->disabled = $this->disabled; 914 $data->tooltip = $this->tooltip; 915 $data->primary = $this->primary; 916 917 $data->attributes = []; 918 foreach ($this->attributes as $key => $value) { 919 $data->attributes[] = ['name' => $key, 'value' => $value]; 920 } 921 922 // Form parameters. 923 $actionurl = new moodle_url($this->url); 924 if ($this->method === 'post') { 925 $actionurl->param('sesskey', sesskey()); 926 } 927 $data->params = $actionurl->export_params_for_template(); 928 929 // Button actions. 930 $actions = $this->actions; 931 $data->actions = array_map(function($action) use ($output) { 932 return $action->export_for_template($output); 933 }, $actions); 934 $data->hasactions = !empty($data->actions); 935 936 return $data; 937 } 938 } 939 940 941 /** 942 * Simple form with just one select field that gets submitted automatically. 943 * 944 * If JS not enabled small go button is printed too. 945 * 946 * @copyright 2009 Petr Skoda 947 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 948 * @since Moodle 2.0 949 * @package core 950 * @category output 951 */ 952 class single_select implements renderable, templatable { 953 954 /** 955 * @var moodle_url Target url - includes hidden fields 956 */ 957 var $url; 958 959 /** 960 * @var string Name of the select element. 961 */ 962 var $name; 963 964 /** 965 * @var array $options associative array value=>label ex.: array(1=>'One, 2=>Two) 966 * it is also possible to specify optgroup as complex label array ex.: 967 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 968 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 969 */ 970 var $options; 971 972 /** 973 * @var string Selected option 974 */ 975 var $selected; 976 977 /** 978 * @var array Nothing selected 979 */ 980 var $nothing; 981 982 /** 983 * @var array Extra select field attributes 984 */ 985 var $attributes = array(); 986 987 /** 988 * @var string Button label 989 */ 990 var $label = ''; 991 992 /** 993 * @var array Button label's attributes 994 */ 995 var $labelattributes = array(); 996 997 /** 998 * @var string Form submit method post or get 999 */ 1000 var $method = 'get'; 1001 1002 /** 1003 * @var string Wrapping div class 1004 */ 1005 var $class = 'singleselect'; 1006 1007 /** 1008 * @var bool True if button disabled, false if normal 1009 */ 1010 var $disabled = false; 1011 1012 /** 1013 * @var string Button tooltip 1014 */ 1015 var $tooltip = null; 1016 1017 /** 1018 * @var string Form id 1019 */ 1020 var $formid = null; 1021 1022 /** 1023 * @var help_icon The help icon for this element. 1024 */ 1025 var $helpicon = null; 1026 1027 /** 1028 * Constructor 1029 * @param moodle_url $url form action target, includes hidden fields 1030 * @param string $name name of selection field - the changing parameter in url 1031 * @param array $options list of options 1032 * @param string $selected selected element 1033 * @param array $nothing 1034 * @param string $formid 1035 */ 1036 public function __construct(moodle_url $url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) { 1037 $this->url = $url; 1038 $this->name = $name; 1039 $this->options = $options; 1040 $this->selected = $selected; 1041 $this->nothing = $nothing; 1042 $this->formid = $formid; 1043 } 1044 1045 /** 1046 * Shortcut for adding a JS confirm dialog when the button is clicked. 1047 * The message must be a yes/no question. 1048 * 1049 * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur. 1050 */ 1051 public function add_confirm_action($confirmmessage) { 1052 $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage))); 1053 } 1054 1055 /** 1056 * Add action to the button. 1057 * 1058 * @param component_action $action 1059 */ 1060 public function add_action(component_action $action) { 1061 $this->actions[] = $action; 1062 } 1063 1064 /** 1065 * Adds help icon. 1066 * 1067 * @deprecated since Moodle 2.0 1068 */ 1069 public function set_old_help_icon($helppage, $title, $component = 'moodle') { 1070 throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); 1071 } 1072 1073 /** 1074 * Adds help icon. 1075 * 1076 * @param string $identifier The keyword that defines a help page 1077 * @param string $component 1078 */ 1079 public function set_help_icon($identifier, $component = 'moodle') { 1080 $this->helpicon = new help_icon($identifier, $component); 1081 } 1082 1083 /** 1084 * Sets select's label 1085 * 1086 * @param string $label 1087 * @param array $attributes (optional) 1088 */ 1089 public function set_label($label, $attributes = array()) { 1090 $this->label = $label; 1091 $this->labelattributes = $attributes; 1092 1093 } 1094 1095 /** 1096 * Export data. 1097 * 1098 * @param renderer_base $output Renderer. 1099 * @return stdClass 1100 */ 1101 public function export_for_template(renderer_base $output) { 1102 $attributes = $this->attributes; 1103 1104 $data = new stdClass(); 1105 $data->name = $this->name; 1106 $data->method = $this->method; 1107 $data->action = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring(); 1108 $data->classes = $this->class; 1109 $data->label = $this->label; 1110 $data->disabled = $this->disabled; 1111 $data->title = $this->tooltip; 1112 $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('single_select_f'); 1113 $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('single_select'); 1114 1115 // Select element attributes. 1116 // Unset attributes that are already predefined in the template. 1117 unset($attributes['id']); 1118 unset($attributes['class']); 1119 unset($attributes['name']); 1120 unset($attributes['title']); 1121 unset($attributes['disabled']); 1122 1123 // Map the attributes. 1124 $data->attributes = array_map(function($key) use ($attributes) { 1125 return ['name' => $key, 'value' => $attributes[$key]]; 1126 }, array_keys($attributes)); 1127 1128 // Form parameters. 1129 $actionurl = new moodle_url($this->url); 1130 if ($this->method === 'post') { 1131 $actionurl->param('sesskey', sesskey()); 1132 } 1133 $data->params = $actionurl->export_params_for_template(); 1134 1135 // Select options. 1136 $hasnothing = false; 1137 if (is_string($this->nothing) && $this->nothing !== '') { 1138 $nothing = ['' => $this->nothing]; 1139 $hasnothing = true; 1140 $nothingkey = ''; 1141 } else if (is_array($this->nothing)) { 1142 $nothingvalue = reset($this->nothing); 1143 if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { 1144 $nothing = [key($this->nothing) => get_string('choosedots')]; 1145 } else { 1146 $nothing = $this->nothing; 1147 } 1148 $hasnothing = true; 1149 $nothingkey = key($this->nothing); 1150 } 1151 if ($hasnothing) { 1152 $options = $nothing + $this->options; 1153 } else { 1154 $options = $this->options; 1155 } 1156 1157 foreach ($options as $value => $name) { 1158 if (is_array($options[$value])) { 1159 foreach ($options[$value] as $optgroupname => $optgroupvalues) { 1160 $sublist = []; 1161 foreach ($optgroupvalues as $optvalue => $optname) { 1162 $option = [ 1163 'value' => $optvalue, 1164 'name' => $optname, 1165 'selected' => strval($this->selected) === strval($optvalue), 1166 ]; 1167 1168 if ($hasnothing && $nothingkey === $optvalue) { 1169 $option['ignore'] = 'data-ignore'; 1170 } 1171 1172 $sublist[] = $option; 1173 } 1174 $data->options[] = [ 1175 'name' => $optgroupname, 1176 'optgroup' => true, 1177 'options' => $sublist 1178 ]; 1179 } 1180 } else { 1181 $option = [ 1182 'value' => $value, 1183 'name' => $options[$value], 1184 'selected' => strval($this->selected) === strval($value), 1185 'optgroup' => false 1186 ]; 1187 1188 if ($hasnothing && $nothingkey === $value) { 1189 $option['ignore'] = 'data-ignore'; 1190 } 1191 1192 $data->options[] = $option; 1193 } 1194 } 1195 1196 // Label attributes. 1197 $data->labelattributes = []; 1198 // Unset label attributes that are already in the template. 1199 unset($this->labelattributes['for']); 1200 // Map the label attributes. 1201 foreach ($this->labelattributes as $key => $value) { 1202 $data->labelattributes[] = ['name' => $key, 'value' => $value]; 1203 } 1204 1205 // Help icon. 1206 $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; 1207 1208 return $data; 1209 } 1210 } 1211 1212 /** 1213 * Simple URL selection widget description. 1214 * 1215 * @copyright 2009 Petr Skoda 1216 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1217 * @since Moodle 2.0 1218 * @package core 1219 * @category output 1220 */ 1221 class url_select implements renderable, templatable { 1222 /** 1223 * @var array $urls associative array value=>label ex.: array(1=>'One, 2=>Two) 1224 * it is also possible to specify optgroup as complex label array ex.: 1225 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 1226 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 1227 */ 1228 var $urls; 1229 1230 /** 1231 * @var string Selected option 1232 */ 1233 var $selected; 1234 1235 /** 1236 * @var array Nothing selected 1237 */ 1238 var $nothing; 1239 1240 /** 1241 * @var array Extra select field attributes 1242 */ 1243 var $attributes = array(); 1244 1245 /** 1246 * @var string Button label 1247 */ 1248 var $label = ''; 1249 1250 /** 1251 * @var array Button label's attributes 1252 */ 1253 var $labelattributes = array(); 1254 1255 /** 1256 * @var string Wrapping div class 1257 */ 1258 var $class = 'urlselect'; 1259 1260 /** 1261 * @var bool True if button disabled, false if normal 1262 */ 1263 var $disabled = false; 1264 1265 /** 1266 * @var string Button tooltip 1267 */ 1268 var $tooltip = null; 1269 1270 /** 1271 * @var string Form id 1272 */ 1273 var $formid = null; 1274 1275 /** 1276 * @var help_icon The help icon for this element. 1277 */ 1278 var $helpicon = null; 1279 1280 /** 1281 * @var string If set, makes button visible with given name for button 1282 */ 1283 var $showbutton = null; 1284 1285 /** 1286 * Constructor 1287 * @param array $urls list of options 1288 * @param string $selected selected element 1289 * @param array $nothing 1290 * @param string $formid 1291 * @param string $showbutton Set to text of button if it should be visible 1292 * or null if it should be hidden (hidden version always has text 'go') 1293 */ 1294 public function __construct(array $urls, $selected = '', $nothing = array('' => 'choosedots'), $formid = null, $showbutton = null) { 1295 $this->urls = $urls; 1296 $this->selected = $selected; 1297 $this->nothing = $nothing; 1298 $this->formid = $formid; 1299 $this->showbutton = $showbutton; 1300 } 1301 1302 /** 1303 * Adds help icon. 1304 * 1305 * @deprecated since Moodle 2.0 1306 */ 1307 public function set_old_help_icon($helppage, $title, $component = 'moodle') { 1308 throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); 1309 } 1310 1311 /** 1312 * Adds help icon. 1313 * 1314 * @param string $identifier The keyword that defines a help page 1315 * @param string $component 1316 */ 1317 public function set_help_icon($identifier, $component = 'moodle') { 1318 $this->helpicon = new help_icon($identifier, $component); 1319 } 1320 1321 /** 1322 * Sets select's label 1323 * 1324 * @param string $label 1325 * @param array $attributes (optional) 1326 */ 1327 public function set_label($label, $attributes = array()) { 1328 $this->label = $label; 1329 $this->labelattributes = $attributes; 1330 } 1331 1332 /** 1333 * Clean a URL. 1334 * 1335 * @param string $value The URL. 1336 * @return The cleaned URL. 1337 */ 1338 protected function clean_url($value) { 1339 global $CFG; 1340 1341 if (empty($value)) { 1342 // Nothing. 1343 1344 } else if (strpos($value, $CFG->wwwroot . '/') === 0) { 1345 $value = str_replace($CFG->wwwroot, '', $value); 1346 1347 } else if (strpos($value, '/') !== 0) { 1348 debugging("Invalid url_select urls parameter: url '$value' is not local relative url!", DEBUG_DEVELOPER); 1349 } 1350 1351 return $value; 1352 } 1353 1354 /** 1355 * Flatten the options for Mustache. 1356 * 1357 * This also cleans the URLs. 1358 * 1359 * @param array $options The options. 1360 * @param array $nothing The nothing option. 1361 * @return array 1362 */ 1363 protected function flatten_options($options, $nothing) { 1364 $flattened = []; 1365 1366 foreach ($options as $value => $option) { 1367 if (is_array($option)) { 1368 foreach ($option as $groupname => $optoptions) { 1369 if (!isset($flattened[$groupname])) { 1370 $flattened[$groupname] = [ 1371 'name' => $groupname, 1372 'isgroup' => true, 1373 'options' => [] 1374 ]; 1375 } 1376 foreach ($optoptions as $optvalue => $optoption) { 1377 $cleanedvalue = $this->clean_url($optvalue); 1378 $flattened[$groupname]['options'][$cleanedvalue] = [ 1379 'name' => $optoption, 1380 'value' => $cleanedvalue, 1381 'selected' => $this->selected == $optvalue, 1382 ]; 1383 } 1384 } 1385 1386 } else { 1387 $cleanedvalue = $this->clean_url($value); 1388 $flattened[$cleanedvalue] = [ 1389 'name' => $option, 1390 'value' => $cleanedvalue, 1391 'selected' => $this->selected == $value, 1392 ]; 1393 } 1394 } 1395 1396 if (!empty($nothing)) { 1397 $value = key($nothing); 1398 $name = reset($nothing); 1399 $flattened = [ 1400 $value => ['name' => $name, 'value' => $value, 'selected' => $this->selected == $value] 1401 ] + $flattened; 1402 } 1403 1404 // Make non-associative array. 1405 foreach ($flattened as $key => $value) { 1406 if (!empty($value['options'])) { 1407 $flattened[$key]['options'] = array_values($value['options']); 1408 } 1409 } 1410 $flattened = array_values($flattened); 1411 1412 return $flattened; 1413 } 1414 1415 /** 1416 * Export for template. 1417 * 1418 * @param renderer_base $output Renderer. 1419 * @return stdClass 1420 */ 1421 public function export_for_template(renderer_base $output) { 1422 $attributes = $this->attributes; 1423 1424 $data = new stdClass(); 1425 $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('url_select_f'); 1426 $data->classes = $this->class; 1427 $data->label = $this->label; 1428 $data->disabled = $this->disabled; 1429 $data->title = $this->tooltip; 1430 $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('url_select'); 1431 $data->sesskey = sesskey(); 1432 $data->action = (new moodle_url('/course/jumpto.php'))->out(false); 1433 1434 // Remove attributes passed as property directly. 1435 unset($attributes['class']); 1436 unset($attributes['id']); 1437 unset($attributes['name']); 1438 unset($attributes['title']); 1439 unset($attributes['disabled']); 1440 1441 $data->showbutton = $this->showbutton; 1442 1443 // Select options. 1444 $nothing = false; 1445 if (is_string($this->nothing) && $this->nothing !== '') { 1446 $nothing = ['' => $this->nothing]; 1447 } else if (is_array($this->nothing)) { 1448 $nothingvalue = reset($this->nothing); 1449 if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { 1450 $nothing = [key($this->nothing) => get_string('choosedots')]; 1451 } else { 1452 $nothing = $this->nothing; 1453 } 1454 } 1455 $data->options = $this->flatten_options($this->urls, $nothing); 1456 1457 // Label attributes. 1458 $data->labelattributes = []; 1459 // Unset label attributes that are already in the template. 1460 unset($this->labelattributes['for']); 1461 // Map the label attributes. 1462 foreach ($this->labelattributes as $key => $value) { 1463 $data->labelattributes[] = ['name' => $key, 'value' => $value]; 1464 } 1465 1466 // Help icon. 1467 $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; 1468 1469 // Finally all the remaining attributes. 1470 $data->attributes = []; 1471 foreach ($attributes as $key => $value) { 1472 $data->attributes[] = ['name' => $key, 'value' => $value]; 1473 } 1474 1475 return $data; 1476 } 1477 } 1478 1479 /** 1480 * Data structure describing html link with special action attached. 1481 * 1482 * @copyright 2010 Petr Skoda 1483 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1484 * @since Moodle 2.0 1485 * @package core 1486 * @category output 1487 */ 1488 class action_link implements renderable { 1489 1490 /** 1491 * @var moodle_url Href url 1492 */ 1493 public $url; 1494 1495 /** 1496 * @var string Link text HTML fragment 1497 */ 1498 public $text; 1499 1500 /** 1501 * @var array HTML attributes 1502 */ 1503 public $attributes; 1504 1505 /** 1506 * @var array List of actions attached to link 1507 */ 1508 public $actions; 1509 1510 /** 1511 * @var pix_icon Optional pix icon to render with the link 1512 */ 1513 public $icon; 1514 1515 /** 1516 * Constructor 1517 * @param moodle_url $url 1518 * @param string $text HTML fragment 1519 * @param component_action $action 1520 * @param array $attributes associative array of html link attributes + disabled 1521 * @param pix_icon $icon optional pix_icon to render with the link text 1522 */ 1523 public function __construct(moodle_url $url, 1524 $text, 1525 component_action $action=null, 1526 array $attributes=null, 1527 pix_icon $icon=null) { 1528 $this->url = clone($url); 1529 $this->text = $text; 1530 if (empty($attributes['id'])) { 1531 $attributes['id'] = html_writer::random_id('action_link'); 1532 } 1533 $this->attributes = (array)$attributes; 1534 if ($action) { 1535 $this->add_action($action); 1536 } 1537 $this->icon = $icon; 1538 } 1539 1540 /** 1541 * Add action to the link. 1542 * 1543 * @param component_action $action 1544 */ 1545 public function add_action(component_action $action) { 1546 $this->actions[] = $action; 1547 } 1548 1549 /** 1550 * Adds a CSS class to this action link object 1551 * @param string $class 1552 */ 1553 public function add_class($class) { 1554 if (empty($this->attributes['class'])) { 1555 $this->attributes['class'] = $class; 1556 } else { 1557 $this->attributes['class'] .= ' ' . $class; 1558 } 1559 } 1560 1561 /** 1562 * Returns true if the specified class has been added to this link. 1563 * @param string $class 1564 * @return bool 1565 */ 1566 public function has_class($class) { 1567 return strpos(' ' . $this->attributes['class'] . ' ', ' ' . $class . ' ') !== false; 1568 } 1569 1570 /** 1571 * Return the rendered HTML for the icon. Useful for rendering action links in a template. 1572 * @return string 1573 */ 1574 public function get_icon_html() { 1575 global $OUTPUT; 1576 if (!$this->icon) { 1577 return ''; 1578 } 1579 return $OUTPUT->render($this->icon); 1580 } 1581 1582 /** 1583 * Export for template. 1584 * 1585 * @param renderer_base $output The renderer. 1586 * @return stdClass 1587 */ 1588 public function export_for_template(renderer_base $output) { 1589 $data = new stdClass(); 1590 $attributes = $this->attributes; 1591 1592 $data->id = $attributes['id']; 1593 unset($attributes['id']); 1594 1595 $data->disabled = !empty($attributes['disabled']); 1596 unset($attributes['disabled']); 1597 1598 $data->text = $this->text instanceof renderable ? $output->render($this->text) : (string) $this->text; 1599 $data->url = $this->url ? $this->url->out(false) : ''; 1600 $data->icon = $this->icon ? $this->icon->export_for_pix() : null; 1601 $data->classes = isset($attributes['class']) ? $attributes['class'] : ''; 1602 unset($attributes['class']); 1603 1604 $data->attributes = array_map(function($key, $value) { 1605 return [ 1606 'name' => $key, 1607 'value' => $value 1608 ]; 1609 }, array_keys($attributes), $attributes); 1610 1611 $data->actions = array_map(function($action) use ($output) { 1612 return $action->export_for_template($output); 1613 }, !empty($this->actions) ? $this->actions : []); 1614 $data->hasactions = !empty($this->actions); 1615 1616 return $data; 1617 } 1618 } 1619 1620 /** 1621 * Simple html output class 1622 * 1623 * @copyright 2009 Tim Hunt, 2010 Petr Skoda 1624 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1625 * @since Moodle 2.0 1626 * @package core 1627 * @category output 1628 */ 1629 class html_writer { 1630 1631 /** 1632 * Outputs a tag with attributes and contents 1633 * 1634 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1635 * @param string $contents What goes between the opening and closing tags 1636 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1637 * @return string HTML fragment 1638 */ 1639 public static function tag($tagname, $contents, array $attributes = null) { 1640 return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname); 1641 } 1642 1643 /** 1644 * Outputs an opening tag with attributes 1645 * 1646 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1647 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1648 * @return string HTML fragment 1649 */ 1650 public static function start_tag($tagname, array $attributes = null) { 1651 return '<' . $tagname . self::attributes($attributes) . '>'; 1652 } 1653 1654 /** 1655 * Outputs a closing tag 1656 * 1657 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1658 * @return string HTML fragment 1659 */ 1660 public static function end_tag($tagname) { 1661 return '</' . $tagname . '>'; 1662 } 1663 1664 /** 1665 * Outputs an empty tag with attributes 1666 * 1667 * @param string $tagname The name of tag ('input', 'img', 'br' etc.) 1668 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1669 * @return string HTML fragment 1670 */ 1671 public static function empty_tag($tagname, array $attributes = null) { 1672 return '<' . $tagname . self::attributes($attributes) . ' />'; 1673 } 1674 1675 /** 1676 * Outputs a tag, but only if the contents are not empty 1677 * 1678 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1679 * @param string $contents What goes between the opening and closing tags 1680 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1681 * @return string HTML fragment 1682 */ 1683 public static function nonempty_tag($tagname, $contents, array $attributes = null) { 1684 if ($contents === '' || is_null($contents)) { 1685 return ''; 1686 } 1687 return self::tag($tagname, $contents, $attributes); 1688 } 1689 1690 /** 1691 * Outputs a HTML attribute and value 1692 * 1693 * @param string $name The name of the attribute ('src', 'href', 'class' etc.) 1694 * @param string $value The value of the attribute. The value will be escaped with {@link s()} 1695 * @return string HTML fragment 1696 */ 1697 public static function attribute($name, $value) { 1698 if ($value instanceof moodle_url) { 1699 return ' ' . $name . '="' . $value->out() . '"'; 1700 } 1701 1702 // special case, we do not want these in output 1703 if ($value === null) { 1704 return ''; 1705 } 1706 1707 // no sloppy trimming here! 1708 return ' ' . $name . '="' . s($value) . '"'; 1709 } 1710 1711 /** 1712 * Outputs a list of HTML attributes and values 1713 * 1714 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1715 * The values will be escaped with {@link s()} 1716 * @return string HTML fragment 1717 */ 1718 public static function attributes(array $attributes = null) { 1719 $attributes = (array)$attributes; 1720 $output = ''; 1721 foreach ($attributes as $name => $value) { 1722 $output .= self::attribute($name, $value); 1723 } 1724 return $output; 1725 } 1726 1727 /** 1728 * Generates a simple image tag with attributes. 1729 * 1730 * @param string $src The source of image 1731 * @param string $alt The alternate text for image 1732 * @param array $attributes The tag attributes (array('height' => $max_height, 'class' => 'class1') etc.) 1733 * @return string HTML fragment 1734 */ 1735 public static function img($src, $alt, array $attributes = null) { 1736 $attributes = (array)$attributes; 1737 $attributes['src'] = $src; 1738 $attributes['alt'] = $alt; 1739 1740 return self::empty_tag('img', $attributes); 1741 } 1742 1743 /** 1744 * Generates random html element id. 1745 * 1746 * @staticvar int $counter 1747 * @staticvar type $uniq 1748 * @param string $base A string fragment that will be included in the random ID. 1749 * @return string A unique ID 1750 */ 1751 public static function random_id($base='random') { 1752 static $counter = 0; 1753 static $uniq; 1754 1755 if (!isset($uniq)) { 1756 $uniq = uniqid(); 1757 } 1758 1759 $counter++; 1760 return $base.$uniq.$counter; 1761 } 1762 1763 /** 1764 * Generates a simple html link 1765 * 1766 * @param string|moodle_url $url The URL 1767 * @param string $text The text 1768 * @param array $attributes HTML attributes 1769 * @return string HTML fragment 1770 */ 1771 public static function link($url, $text, array $attributes = null) { 1772 $attributes = (array)$attributes; 1773 $attributes['href'] = $url; 1774 return self::tag('a', $text, $attributes); 1775 } 1776 1777 /** 1778 * Generates a simple checkbox with optional label 1779 * 1780 * @param string $name The name of the checkbox 1781 * @param string $value The value of the checkbox 1782 * @param bool $checked Whether the checkbox is checked 1783 * @param string $label The label for the checkbox 1784 * @param array $attributes Any attributes to apply to the checkbox 1785 * @param array $labelattributes Any attributes to apply to the label, if present 1786 * @return string html fragment 1787 */ 1788 public static function checkbox($name, $value, $checked = true, $label = '', 1789 array $attributes = null, array $labelattributes = null) { 1790 $attributes = (array) $attributes; 1791 $output = ''; 1792 1793 if ($label !== '' and !is_null($label)) { 1794 if (empty($attributes['id'])) { 1795 $attributes['id'] = self::random_id('checkbox_'); 1796 } 1797 } 1798 $attributes['type'] = 'checkbox'; 1799 $attributes['value'] = $value; 1800 $attributes['name'] = $name; 1801 $attributes['checked'] = $checked ? 'checked' : null; 1802 1803 $output .= self::empty_tag('input', $attributes); 1804 1805 if ($label !== '' and !is_null($label)) { 1806 $labelattributes = (array) $labelattributes; 1807 $labelattributes['for'] = $attributes['id']; 1808 $output .= self::tag('label', $label, $labelattributes); 1809 } 1810 1811 return $output; 1812 } 1813 1814 /** 1815 * Generates a simple select yes/no form field 1816 * 1817 * @param string $name name of select element 1818 * @param bool $selected 1819 * @param array $attributes - html select element attributes 1820 * @return string HTML fragment 1821 */ 1822 public static function select_yes_no($name, $selected=true, array $attributes = null) { 1823 $options = array('1'=>get_string('yes'), '0'=>get_string('no')); 1824 return self::select($options, $name, $selected, null, $attributes); 1825 } 1826 1827 /** 1828 * Generates a simple select form field 1829 * 1830 * Note this function does HTML escaping on the optgroup labels, but not on the choice labels. 1831 * 1832 * @param array $options associative array value=>label ex.: 1833 * array(1=>'One, 2=>Two) 1834 * it is also possible to specify optgroup as complex label array ex.: 1835 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 1836 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 1837 * @param string $name name of select element 1838 * @param string|array $selected value or array of values depending on multiple attribute 1839 * @param array|bool $nothing add nothing selected option, or false of not added 1840 * @param array $attributes html select element attributes 1841 * @return string HTML fragment 1842 */ 1843 public static function select(array $options, $name, $selected = '', $nothing = array('' => 'choosedots'), array $attributes = null) { 1844 $attributes = (array)$attributes; 1845 if (is_array($nothing)) { 1846 foreach ($nothing as $k=>$v) { 1847 if ($v === 'choose' or $v === 'choosedots') { 1848 $nothing[$k] = get_string('choosedots'); 1849 } 1850 } 1851 $options = $nothing + $options; // keep keys, do not override 1852 1853 } else if (is_string($nothing) and $nothing !== '') { 1854 // BC 1855 $options = array(''=>$nothing) + $options; 1856 } 1857 1858 // we may accept more values if multiple attribute specified 1859 $selected = (array)$selected; 1860 foreach ($selected as $k=>$v) { 1861 $selected[$k] = (string)$v; 1862 } 1863 1864 if (!isset($attributes['id'])) { 1865 $id = 'menu'.$name; 1866 // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading 1867 $id = str_replace('[', '', $id); 1868 $id = str_replace(']', '', $id); 1869 $attributes['id'] = $id; 1870 } 1871 1872 if (!isset($attributes['class'])) { 1873 $class = 'menu'.$name; 1874 // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading 1875 $class = str_replace('[', '', $class); 1876 $class = str_replace(']', '', $class); 1877 $attributes['class'] = $class; 1878 } 1879 $attributes['class'] = 'select custom-select ' . $attributes['class']; // Add 'select' selector always. 1880 1881 $attributes['name'] = $name; 1882 1883 if (!empty($attributes['disabled'])) { 1884 $attributes['disabled'] = 'disabled'; 1885 } else { 1886 unset($attributes['disabled']); 1887 } 1888 1889 $output = ''; 1890 foreach ($options as $value=>$label) { 1891 if (is_array($label)) { 1892 // ignore key, it just has to be unique 1893 $output .= self::select_optgroup(key($label), current($label), $selected); 1894 } else { 1895 $output .= self::select_option($label, $value, $selected); 1896 } 1897 } 1898 return self::tag('select', $output, $attributes); 1899 } 1900 1901 /** 1902 * Returns HTML to display a select box option. 1903 * 1904 * @param string $label The label to display as the option. 1905 * @param string|int $value The value the option represents 1906 * @param array $selected An array of selected options 1907 * @return string HTML fragment 1908 */ 1909 private static function select_option($label, $value, array $selected) { 1910 $attributes = array(); 1911 $value = (string)$value; 1912 if (in_array($value, $selected, true)) { 1913 $attributes['selected'] = 'selected'; 1914 } 1915 $attributes['value'] = $value; 1916 return self::tag('option', $label, $attributes); 1917 } 1918 1919 /** 1920 * Returns HTML to display a select box option group. 1921 * 1922 * @param string $groupname The label to use for the group 1923 * @param array $options The options in the group 1924 * @param array $selected An array of selected values. 1925 * @return string HTML fragment. 1926 */ 1927 private static function select_optgroup($groupname, $options, array $selected) { 1928 if (empty($options)) { 1929 return ''; 1930 } 1931 $attributes = array('label'=>$groupname); 1932 $output = ''; 1933 foreach ($options as $value=>$label) { 1934 $output .= self::select_option($label, $value, $selected); 1935 } 1936 return self::tag('optgroup', $output, $attributes); 1937 } 1938 1939 /** 1940 * This is a shortcut for making an hour selector menu. 1941 * 1942 * @param string $type The type of selector (years, months, days, hours, minutes) 1943 * @param string $name fieldname 1944 * @param int $currenttime A default timestamp in GMT 1945 * @param int $step minute spacing 1946 * @param array $attributes - html select element attributes 1947 * @return HTML fragment 1948 */ 1949 public static function select_time($type, $name, $currenttime = 0, $step = 5, array $attributes = null) { 1950 global $OUTPUT; 1951 1952 if (!$currenttime) { 1953 $currenttime = time(); 1954 } 1955 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1956 $currentdate = $calendartype->timestamp_to_date_array($currenttime); 1957 $userdatetype = $type; 1958 $timeunits = array(); 1959 1960 switch ($type) { 1961 case 'years': 1962 $timeunits = $calendartype->get_years(); 1963 $userdatetype = 'year'; 1964 break; 1965 case 'months': 1966 $timeunits = $calendartype->get_months(); 1967 $userdatetype = 'month'; 1968 $currentdate['month'] = (int)$currentdate['mon']; 1969 break; 1970 case 'days': 1971 $timeunits = $calendartype->get_days(); 1972 $userdatetype = 'mday'; 1973 break; 1974 case 'hours': 1975 for ($i=0; $i<=23; $i++) { 1976 $timeunits[$i] = sprintf("%02d",$i); 1977 } 1978 break; 1979 case 'minutes': 1980 if ($step != 1) { 1981 $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step; 1982 } 1983 1984 for ($i=0; $i<=59; $i+=$step) { 1985 $timeunits[$i] = sprintf("%02d",$i); 1986 } 1987 break; 1988 default: 1989 throw new coding_exception("Time type $type is not supported by html_writer::select_time()."); 1990 } 1991 1992 $attributes = (array) $attributes; 1993 $data = (object) [ 1994 'name' => $name, 1995 'id' => !empty($attributes['id']) ? $attributes['id'] : self::random_id('ts_'), 1996 'label' => get_string(substr($type, 0, -1), 'form'), 1997 'options' => array_map(function($value) use ($timeunits, $currentdate, $userdatetype) { 1998 return [ 1999 'name' => $timeunits[$value], 2000 'value' => $value, 2001 'selected' => $currentdate[$userdatetype] == $value 2002 ]; 2003 }, array_keys($timeunits)), 2004 ]; 2005 2006 unset($attributes['id']); 2007 unset($attributes['name']); 2008 $data->attributes = array_map(function($name) use ($attributes) { 2009 return [ 2010 'name' => $name, 2011 'value' => $attributes[$name] 2012 ]; 2013 }, array_keys($attributes)); 2014 2015 return $OUTPUT->render_from_template('core/select_time', $data); 2016 } 2017 2018 /** 2019 * Shortcut for quick making of lists 2020 * 2021 * Note: 'list' is a reserved keyword ;-) 2022 * 2023 * @param array $items 2024 * @param array $attributes 2025 * @param string $tag ul or ol 2026 * @return string 2027 */ 2028 public static function alist(array $items, array $attributes = null, $tag = 'ul') { 2029 $output = html_writer::start_tag($tag, $attributes)."\n"; 2030 foreach ($items as $item) { 2031 $output .= html_writer::tag('li', $item)."\n"; 2032 } 2033 $output .= html_writer::end_tag($tag); 2034 return $output; 2035 } 2036 2037 /** 2038 * Returns hidden input fields created from url parameters. 2039 * 2040 * @param moodle_url $url 2041 * @param array $exclude list of excluded parameters 2042 * @return string HTML fragment 2043 */ 2044 public static function input_hidden_params(moodle_url $url, array $exclude = null) { 2045 $exclude = (array)$exclude; 2046 $params = $url->params(); 2047 foreach ($exclude as $key) { 2048 unset($params[$key]); 2049 } 2050 2051 $output = ''; 2052 foreach ($params as $key => $value) { 2053 $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value); 2054 $output .= self::empty_tag('input', $attributes)."\n"; 2055 } 2056 return $output; 2057 } 2058 2059 /** 2060 * Generate a script tag containing the the specified code. 2061 * 2062 * @param string $jscode the JavaScript code 2063 * @param moodle_url|string $url optional url of the external script, $code ignored if specified 2064 * @return string HTML, the code wrapped in <script> tags. 2065 */ 2066 public static function script($jscode, $url=null) { 2067 if ($jscode) { 2068 return self::tag('script', "\n//<![CDATA[\n$jscode\n//]]>\n") . "\n"; 2069 2070 } else if ($url) { 2071 return self::tag('script', '', ['src' => $url]) . "\n"; 2072 2073 } else { 2074 return ''; 2075 } 2076 } 2077 2078 /** 2079 * Renders HTML table 2080 * 2081 * This method may modify the passed instance by adding some default properties if they are not set yet. 2082 * If this is not what you want, you should make a full clone of your data before passing them to this 2083 * method. In most cases this is not an issue at all so we do not clone by default for performance 2084 * and memory consumption reasons. 2085 * 2086 * @param html_table $table data to be rendered 2087 * @return string HTML code 2088 */ 2089 public static function table(html_table $table) { 2090 // prepare table data and populate missing properties with reasonable defaults 2091 if (!empty($table->align)) { 2092 foreach ($table->align as $key => $aa) { 2093 if ($aa) { 2094 $table->align[$key] = 'text-align:'. fix_align_rtl($aa) .';'; // Fix for RTL languages 2095 } else { 2096 $table->align[$key] = null; 2097 } 2098 } 2099 } 2100 if (!empty($table->size)) { 2101 foreach ($table->size as $key => $ss) { 2102 if ($ss) { 2103 $table->size[$key] = 'width:'. $ss .';'; 2104 } else { 2105 $table->size[$key] = null; 2106 } 2107 } 2108 } 2109 if (!empty($table->wrap)) { 2110 foreach ($table->wrap as $key => $ww) { 2111 if ($ww) { 2112 $table->wrap[$key] = 'white-space:nowrap;'; 2113 } else { 2114 $table->wrap[$key] = ''; 2115 } 2116 } 2117 } 2118 if (!empty($table->head)) { 2119 foreach ($table->head as $key => $val) { 2120 if (!isset($table->align[$key])) { 2121 $table->align[$key] = null; 2122 } 2123 if (!isset($table->size[$key])) { 2124 $table->size[$key] = null; 2125 } 2126 if (!isset($table->wrap[$key])) { 2127 $table->wrap[$key] = null; 2128 } 2129 2130 } 2131 } 2132 if (empty($table->attributes['class'])) { 2133 $table->attributes['class'] = 'generaltable'; 2134 } 2135 if (!empty($table->tablealign)) { 2136 $table->attributes['class'] .= ' boxalign' . $table->tablealign; 2137 } 2138 2139 // explicitly assigned properties override those defined via $table->attributes 2140 $table->attributes['class'] = trim($table->attributes['class']); 2141 $attributes = array_merge($table->attributes, array( 2142 'id' => $table->id, 2143 'width' => $table->width, 2144 'summary' => $table->summary, 2145 'cellpadding' => $table->cellpadding, 2146 'cellspacing' => $table->cellspacing, 2147 )); 2148 $output = html_writer::start_tag('table', $attributes) . "\n"; 2149 2150 $countcols = 0; 2151 2152 // Output a caption if present. 2153 if (!empty($table->caption)) { 2154 $captionattributes = array(); 2155 if ($table->captionhide) { 2156 $captionattributes['class'] = 'accesshide'; 2157 } 2158 $output .= html_writer::tag( 2159 'caption', 2160 $table->caption, 2161 $captionattributes 2162 ); 2163 } 2164 2165 if (!empty($table->head)) { 2166 $countcols = count($table->head); 2167 2168 $output .= html_writer::start_tag('thead', array()) . "\n"; 2169 $output .= html_writer::start_tag('tr', array()) . "\n"; 2170 $keys = array_keys($table->head); 2171 $lastkey = end($keys); 2172 2173 foreach ($table->head as $key => $heading) { 2174 // Convert plain string headings into html_table_cell objects 2175 if (!($heading instanceof html_table_cell)) { 2176 $headingtext = $heading; 2177 $heading = new html_table_cell(); 2178 $heading->text = $headingtext; 2179 $heading->header = true; 2180 } 2181 2182 if ($heading->header !== false) { 2183 $heading->header = true; 2184 } 2185 2186 $tagtype = 'td'; 2187 if ($heading->header && (string)$heading->text != '') { 2188 $tagtype = 'th'; 2189 } 2190 2191 $heading->attributes['class'] .= ' header c' . $key; 2192 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) { 2193 $heading->colspan = $table->headspan[$key]; 2194 $countcols += $table->headspan[$key] - 1; 2195 } 2196 2197 if ($key == $lastkey) { 2198 $heading->attributes['class'] .= ' lastcol'; 2199 } 2200 if (isset($table->colclasses[$key])) { 2201 $heading->attributes['class'] .= ' ' . $table->colclasses[$key]; 2202 } 2203 $heading->attributes['class'] = trim($heading->attributes['class']); 2204 $attributes = array_merge($heading->attributes, [ 2205 'style' => $table->align[$key] . $table->size[$key] . $heading->style, 2206 'colspan' => $heading->colspan, 2207 ]); 2208 2209 if ($tagtype == 'th') { 2210 $attributes['scope'] = !empty($heading->scope) ? $heading->scope : 'col'; 2211 } 2212 2213 $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n"; 2214 } 2215 $output .= html_writer::end_tag('tr') . "\n"; 2216 $output .= html_writer::end_tag('thead') . "\n"; 2217 2218 if (empty($table->data)) { 2219 // For valid XHTML strict every table must contain either a valid tr 2220 // or a valid tbody... both of which must contain a valid td 2221 $output .= html_writer::start_tag('tbody', array('class' => 'empty')); 2222 $output .= html_writer::tag('tr', html_writer::tag('td', '', array('colspan'=>count($table->head)))); 2223 $output .= html_writer::end_tag('tbody'); 2224 } 2225 } 2226 2227 if (!empty($table->data)) { 2228 $keys = array_keys($table->data); 2229 $lastrowkey = end($keys); 2230 $output .= html_writer::start_tag('tbody', array()); 2231 2232 foreach ($table->data as $key => $row) { 2233 if (($row === 'hr') && ($countcols)) { 2234 $output .= html_writer::tag('td', html_writer::tag('div', '', array('class' => 'tabledivider')), array('colspan' => $countcols)); 2235 } else { 2236 // Convert array rows to html_table_rows and cell strings to html_table_cell objects 2237 if (!($row instanceof html_table_row)) { 2238 $newrow = new html_table_row(); 2239 2240 foreach ($row as $cell) { 2241 if (!($cell instanceof html_table_cell)) { 2242 $cell = new html_table_cell($cell); 2243 } 2244 $newrow->cells[] = $cell; 2245 } 2246 $row = $newrow; 2247 } 2248 2249 if (isset($table->rowclasses[$key])) { 2250 $row->attributes['class'] .= ' ' . $table->rowclasses[$key]; 2251 } 2252 2253 if ($key == $lastrowkey) { 2254 $row->attributes['class'] .= ' lastrow'; 2255 } 2256 2257 // Explicitly assigned properties should override those defined in the attributes. 2258 $row->attributes['class'] = trim($row->attributes['class']); 2259 $trattributes = array_merge($row->attributes, array( 2260 'id' => $row->id, 2261 'style' => $row->style, 2262 )); 2263 $output .= html_writer::start_tag('tr', $trattributes) . "\n"; 2264 $keys2 = array_keys($row->cells); 2265 $lastkey = end($keys2); 2266 2267 $gotlastkey = false; //flag for sanity checking 2268 foreach ($row->cells as $key => $cell) { 2269 if ($gotlastkey) { 2270 //This should never happen. Why do we have a cell after the last cell? 2271 mtrace("A cell with key ($key) was found after the last key ($lastkey)"); 2272 } 2273 2274 if (!($cell instanceof html_table_cell)) { 2275 $mycell = new html_table_cell(); 2276 $mycell->text = $cell; 2277 $cell = $mycell; 2278 } 2279 2280 if (($cell->header === true) && empty($cell->scope)) { 2281 $cell->scope = 'row'; 2282 } 2283 2284 if (isset($table->colclasses[$key])) { 2285 $cell->attributes['class'] .= ' ' . $table->colclasses[$key]; 2286 } 2287 2288 $cell->attributes['class'] .= ' cell c' . $key; 2289 if ($key == $lastkey) { 2290 $cell->attributes['class'] .= ' lastcol'; 2291 $gotlastkey = true; 2292 } 2293 $tdstyle = ''; 2294 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : ''; 2295 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : ''; 2296 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : ''; 2297 $cell->attributes['class'] = trim($cell->attributes['class']); 2298 $tdattributes = array_merge($cell->attributes, array( 2299 'style' => $tdstyle . $cell->style, 2300 'colspan' => $cell->colspan, 2301 'rowspan' => $cell->rowspan, 2302 'id' => $cell->id, 2303 'abbr' => $cell->abbr, 2304 'scope' => $cell->scope, 2305 )); 2306 $tagtype = 'td'; 2307 if ($cell->header === true) { 2308 $tagtype = 'th'; 2309 } 2310 $output .= html_writer::tag($tagtype, $cell->text, $tdattributes) . "\n"; 2311 } 2312 } 2313 $output .= html_writer::end_tag('tr') . "\n"; 2314 } 2315 $output .= html_writer::end_tag('tbody') . "\n"; 2316 } 2317 $output .= html_writer::end_tag('table') . "\n"; 2318 2319 if ($table->responsive) { 2320 return self::div($output, 'table-responsive'); 2321 } 2322 2323 return $output; 2324 } 2325 2326 /** 2327 * Renders form element label 2328 * 2329 * By default, the label is suffixed with a label separator defined in the 2330 * current language pack (colon by default in the English lang pack). 2331 * Adding the colon can be explicitly disabled if needed. Label separators 2332 * are put outside the label tag itself so they are not read by 2333 * screenreaders (accessibility). 2334 * 2335 * Parameter $for explicitly associates the label with a form control. When 2336 * set, the value of this attribute must be the same as the value of 2337 * the id attribute of the form control in the same document. When null, 2338 * the label being defined is associated with the control inside the label 2339 * element. 2340 * 2341 * @param string $text content of the label tag 2342 * @param string|null $for id of the element this label is associated with, null for no association 2343 * @param bool $colonize add label separator (colon) to the label text, if it is not there yet 2344 * @param array $attributes to be inserted in the tab, for example array('accesskey' => 'a') 2345 * @return string HTML of the label element 2346 */ 2347 public static function label($text, $for, $colonize = true, array $attributes=array()) { 2348 if (!is_null($for)) { 2349 $attributes = array_merge($attributes, array('for' => $for)); 2350 } 2351 $text = trim($text ?? ''); 2352 $label = self::tag('label', $text, $attributes); 2353 2354 // TODO MDL-12192 $colonize disabled for now yet 2355 // if (!empty($text) and $colonize) { 2356 // // the $text may end with the colon already, though it is bad string definition style 2357 // $colon = get_string('labelsep', 'langconfig'); 2358 // if (!empty($colon)) { 2359 // $trimmed = trim($colon); 2360 // if ((substr($text, -strlen($trimmed)) == $trimmed) or (substr($text, -1) == ':')) { 2361 // //debugging('The label text should not end with colon or other label separator, 2362 // // please fix the string definition.', DEBUG_DEVELOPER); 2363 // } else { 2364 // $label .= $colon; 2365 // } 2366 // } 2367 // } 2368 2369 return $label; 2370 } 2371 2372 /** 2373 * Combines a class parameter with other attributes. Aids in code reduction 2374 * because the class parameter is very frequently used. 2375 * 2376 * If the class attribute is specified both in the attributes and in the 2377 * class parameter, the two values are combined with a space between. 2378 * 2379 * @param string $class Optional CSS class (or classes as space-separated list) 2380 * @param array $attributes Optional other attributes as array 2381 * @return array Attributes (or null if still none) 2382 */ 2383 private static function add_class($class = '', array $attributes = null) { 2384 if ($class !== '') { 2385 $classattribute = array('class' => $class); 2386 if ($attributes) { 2387 if (array_key_exists('class', $attributes)) { 2388 $attributes['class'] = trim($attributes['class'] . ' ' . $class); 2389 } else { 2390 $attributes = $classattribute + $attributes; 2391 } 2392 } else { 2393 $attributes = $classattribute; 2394 } 2395 } 2396 return $attributes; 2397 } 2398 2399 /** 2400 * Creates a <div> tag. (Shortcut function.) 2401 * 2402 * @param string $content HTML content of tag 2403 * @param string $class Optional CSS class (or classes as space-separated list) 2404 * @param array $attributes Optional other attributes as array 2405 * @return string HTML code for div 2406 */ 2407 public static function div($content, $class = '', array $attributes = null) { 2408 return self::tag('div', $content, self::add_class($class, $attributes)); 2409 } 2410 2411 /** 2412 * Starts a <div> tag. (Shortcut function.) 2413 * 2414 * @param string $class Optional CSS class (or classes as space-separated list) 2415 * @param array $attributes Optional other attributes as array 2416 * @return string HTML code for open div tag 2417 */ 2418 public static function start_div($class = '', array $attributes = null) { 2419 return self::start_tag('div', self::add_class($class, $attributes)); 2420 } 2421 2422 /** 2423 * Ends a <div> tag. (Shortcut function.) 2424 * 2425 * @return string HTML code for close div tag 2426 */ 2427 public static function end_div() { 2428 return self::end_tag('div'); 2429 } 2430 2431 /** 2432 * Creates a <span> tag. (Shortcut function.) 2433 * 2434 * @param string $content HTML content of tag 2435 * @param string $class Optional CSS class (or classes as space-separated list) 2436 * @param array $attributes Optional other attributes as array 2437 * @return string HTML code for span 2438 */ 2439 public static function span($content, $class = '', array $attributes = null) { 2440 return self::tag('span', $content, self::add_class($class, $attributes)); 2441 } 2442 2443 /** 2444 * Starts a <span> tag. (Shortcut function.) 2445 * 2446 * @param string $class Optional CSS class (or classes as space-separated list) 2447 * @param array $attributes Optional other attributes as array 2448 * @return string HTML code for open span tag 2449 */ 2450 public static function start_span($class = '', array $attributes = null) { 2451 return self::start_tag('span', self::add_class($class, $attributes)); 2452 } 2453 2454 /** 2455 * Ends a <span> tag. (Shortcut function.) 2456 * 2457 * @return string HTML code for close span tag 2458 */ 2459 public static function end_span() { 2460 return self::end_tag('span'); 2461 } 2462 } 2463 2464 /** 2465 * Simple javascript output class 2466 * 2467 * @copyright 2010 Petr Skoda 2468 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2469 * @since Moodle 2.0 2470 * @package core 2471 * @category output 2472 */ 2473 class js_writer { 2474 2475 /** 2476 * Returns javascript code calling the function 2477 * 2478 * @param string $function function name, can be complex like Y.Event.purgeElement 2479 * @param array $arguments parameters 2480 * @param int $delay execution delay in seconds 2481 * @return string JS code fragment 2482 */ 2483 public static function function_call($function, array $arguments = null, $delay=0) { 2484 if ($arguments) { 2485 $arguments = array_map('json_encode', convert_to_array($arguments)); 2486 $arguments = implode(', ', $arguments); 2487 } else { 2488 $arguments = ''; 2489 } 2490 $js = "$function($arguments);"; 2491 2492 if ($delay) { 2493 $delay = $delay * 1000; // in miliseconds 2494 $js = "setTimeout(function() { $js }, $delay);"; 2495 } 2496 return $js . "\n"; 2497 } 2498 2499 /** 2500 * Special function which adds Y as first argument of function call. 2501 * 2502 * @param string $function The function to call 2503 * @param array $extraarguments Any arguments to pass to it 2504 * @return string Some JS code 2505 */ 2506 public static function function_call_with_Y($function, array $extraarguments = null) { 2507 if ($extraarguments) { 2508 $extraarguments = array_map('json_encode', convert_to_array($extraarguments)); 2509 $arguments = 'Y, ' . implode(', ', $extraarguments); 2510 } else { 2511 $arguments = 'Y'; 2512 } 2513 return "$function($arguments);\n"; 2514 } 2515 2516 /** 2517 * Returns JavaScript code to initialise a new object 2518 * 2519 * @param string $var If it is null then no var is assigned the new object. 2520 * @param string $class The class to initialise an object for. 2521 * @param array $arguments An array of args to pass to the init method. 2522 * @param array $requirements Any modules required for this class. 2523 * @param int $delay The delay before initialisation. 0 = no delay. 2524 * @return string Some JS code 2525 */ 2526 public static function object_init($var, $class, array $arguments = null, array $requirements = null, $delay=0) { 2527 if (is_array($arguments)) { 2528 $arguments = array_map('json_encode', convert_to_array($arguments)); 2529 $arguments = implode(', ', $arguments); 2530 } 2531 2532 if ($var === null) { 2533 $js = "new $class(Y, $arguments);"; 2534 } else if (strpos($var, '.')!==false) { 2535 $js = "$var = new $class(Y, $arguments);"; 2536 } else { 2537 $js = "var $var = new $class(Y, $arguments);"; 2538 } 2539 2540 if ($delay) { 2541 $delay = $delay * 1000; // in miliseconds 2542 $js = "setTimeout(function() { $js }, $delay);"; 2543 } 2544 2545 if (count($requirements) > 0) { 2546 $requirements = implode("', '", $requirements); 2547 $js = "Y.use('$requirements', function(Y){ $js });"; 2548 } 2549 return $js."\n"; 2550 } 2551 2552 /** 2553 * Returns code setting value to variable 2554 * 2555 * @param string $name 2556 * @param mixed $value json serialised value 2557 * @param bool $usevar add var definition, ignored for nested properties 2558 * @return string JS code fragment 2559 */ 2560 public static function set_variable($name, $value, $usevar = true) { 2561 $output = ''; 2562 2563 if ($usevar) { 2564 if (strpos($name, '.')) { 2565 $output .= ''; 2566 } else { 2567 $output .= 'var '; 2568 } 2569 } 2570 2571 $output .= "$name = ".json_encode($value).";"; 2572 2573 return $output; 2574 } 2575 2576 /** 2577 * Writes event handler attaching code 2578 * 2579 * @param array|string $selector standard YUI selector for elements, may be 2580 * array or string, element id is in the form "#idvalue" 2581 * @param string $event A valid DOM event (click, mousedown, change etc.) 2582 * @param string $function The name of the function to call 2583 * @param array $arguments An optional array of argument parameters to pass to the function 2584 * @return string JS code fragment 2585 */ 2586 public static function event_handler($selector, $event, $function, array $arguments = null) { 2587 $selector = json_encode($selector); 2588 $output = "Y.on('$event', $function, $selector, null"; 2589 if (!empty($arguments)) { 2590 $output .= ', ' . json_encode($arguments); 2591 } 2592 return $output . ");\n"; 2593 } 2594 } 2595 2596 /** 2597 * Holds all the information required to render a <table> by {@link core_renderer::table()} 2598 * 2599 * Example of usage: 2600 * $t = new html_table(); 2601 * ... // set various properties of the object $t as described below 2602 * echo html_writer::table($t); 2603 * 2604 * @copyright 2009 David Mudrak <david.mudrak@gmail.com> 2605 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2606 * @since Moodle 2.0 2607 * @package core 2608 * @category output 2609 */ 2610 class html_table { 2611 2612 /** 2613 * @var string Value to use for the id attribute of the table 2614 */ 2615 public $id = null; 2616 2617 /** 2618 * @var array Attributes of HTML attributes for the <table> element 2619 */ 2620 public $attributes = array(); 2621 2622 /** 2623 * @var array An array of headings. The n-th array item is used as a heading of the n-th column. 2624 * For more control over the rendering of the headers, an array of html_table_cell objects 2625 * can be passed instead of an array of strings. 2626 * 2627 * Example of usage: 2628 * $t->head = array('Student', 'Grade'); 2629 */ 2630 public $head; 2631 2632 /** 2633 * @var array An array that can be used to make a heading span multiple columns. 2634 * In this example, {@link html_table:$data} is supposed to have three columns. For the first two columns, 2635 * the same heading is used. Therefore, {@link html_table::$head} should consist of two items. 2636 * 2637 * Example of usage: 2638 * $t->headspan = array(2,1); 2639 */ 2640 public $headspan; 2641 2642 /** 2643 * @var array An array of column alignments. 2644 * The value is used as CSS 'text-align' property. Therefore, possible 2645 * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective 2646 * of a left-to-right (LTR) language. For RTL, the values are flipped automatically. 2647 * 2648 * Examples of usage: 2649 * $t->align = array(null, 'right'); 2650 * or 2651 * $t->align[1] = 'right'; 2652 */ 2653 public $align; 2654 2655 /** 2656 * @var array The value is used as CSS 'size' property. 2657 * 2658 * Examples of usage: 2659 * $t->size = array('50%', '50%'); 2660 * or 2661 * $t->size[1] = '120px'; 2662 */ 2663 public $size; 2664 2665 /** 2666 * @var array An array of wrapping information. 2667 * The only possible value is 'nowrap' that sets the 2668 * CSS property 'white-space' to the value 'nowrap' in the given column. 2669 * 2670 * Example of usage: 2671 * $t->wrap = array(null, 'nowrap'); 2672 */ 2673 public $wrap; 2674 2675 /** 2676 * @var array Array of arrays or html_table_row objects containing the data. Alternatively, if you have 2677 * $head specified, the string 'hr' (for horizontal ruler) can be used 2678 * instead of an array of cells data resulting in a divider rendered. 2679 * 2680 * Example of usage with array of arrays: 2681 * $row1 = array('Harry Potter', '76 %'); 2682 * $row2 = array('Hermione Granger', '100 %'); 2683 * $t->data = array($row1, $row2); 2684 * 2685 * Example with array of html_table_row objects: (used for more fine-grained control) 2686 * $cell1 = new html_table_cell(); 2687 * $cell1->text = 'Harry Potter'; 2688 * $cell1->colspan = 2; 2689 * $row1 = new html_table_row(); 2690 * $row1->cells[] = $cell1; 2691 * $cell2 = new html_table_cell(); 2692 * $cell2->text = 'Hermione Granger'; 2693 * $cell3 = new html_table_cell(); 2694 * $cell3->text = '100 %'; 2695 * $row2 = new html_table_row(); 2696 * $row2->cells = array($cell2, $cell3); 2697 * $t->data = array($row1, $row2); 2698 */ 2699 public $data = []; 2700 2701 /** 2702 * @deprecated since Moodle 2.0. Styling should be in the CSS. 2703 * @var string Width of the table, percentage of the page preferred. 2704 */ 2705 public $width = null; 2706 2707 /** 2708 * @deprecated since Moodle 2.0. Styling should be in the CSS. 2709 * @var string Alignment for the whole table. Can be 'right', 'left' or 'center' (default). 2710 */ 2711 public $tablealign = null; 2712 2713 /** 2714 * @deprecated since Moodle 2.0. Styling should be in the CSS. 2715 * @var int Padding on each cell, in pixels 2716 */ 2717 public $cellpadding = null; 2718 2719 /** 2720 * @var int Spacing between cells, in pixels 2721 * @deprecated since Moodle 2.0. Styling should be in the CSS. 2722 */ 2723 public $cellspacing = null; 2724 2725 /** 2726 * @var array Array of classes to add to particular rows, space-separated string. 2727 * Class 'lastrow' is added automatically for the last row in the table. 2728 * 2729 * Example of usage: 2730 * $t->rowclasses[9] = 'tenth' 2731 */ 2732 public $rowclasses; 2733 2734 /** 2735 * @var array An array of classes to add to every cell in a particular column, 2736 * space-separated string. Class 'cell' is added automatically by the renderer. 2737 * Classes 'c0' or 'c1' are added automatically for every odd or even column, 2738 * respectively. Class 'lastcol' is added automatically for all last cells 2739 * in a row. 2740 * 2741 * Example of usage: 2742 * $t->colclasses = array(null, 'grade'); 2743 */ 2744 public $colclasses; 2745 2746 /** 2747 * @var string Description of the contents for screen readers. 2748 * 2749 * The "summary" attribute on the "table" element is not supported in HTML5. 2750 * Consider describing the structure of the table in a "caption" element or in a "figure" element containing the table; 2751 * or, simplify the structure of the table so that no description is needed. 2752 * 2753 * @deprecated since Moodle 3.9. 2754 */ 2755 public $summary; 2756 2757 /** 2758 * @var string Caption for the table, typically a title. 2759 * 2760 * Example of usage: 2761 * $t->caption = "TV Guide"; 2762 */ 2763 public $caption; 2764 2765 /** 2766 * @var bool Whether to hide the table's caption from sighted users. 2767 * 2768 * Example of usage: 2769 * $t->caption = "TV Guide"; 2770 * $t->captionhide = true; 2771 */ 2772 public $captionhide = false; 2773 2774 /** @var bool Whether to make the table to be scrolled horizontally with ease. Make table responsive across all viewports. */ 2775 public $responsive = true; 2776 2777 /** 2778 * Constructor 2779 */ 2780 public function __construct() { 2781 $this->attributes['class'] = ''; 2782 } 2783 } 2784 2785 /** 2786 * Component representing a table row. 2787 * 2788 * @copyright 2009 Nicolas Connault 2789 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2790 * @since Moodle 2.0 2791 * @package core 2792 * @category output 2793 */ 2794 class html_table_row { 2795 2796 /** 2797 * @var string Value to use for the id attribute of the row. 2798 */ 2799 public $id = null; 2800 2801 /** 2802 * @var array Array of html_table_cell objects 2803 */ 2804 public $cells = array(); 2805 2806 /** 2807 * @var string Value to use for the style attribute of the table row 2808 */ 2809 public $style = null; 2810 2811 /** 2812 * @var array Attributes of additional HTML attributes for the <tr> element 2813 */ 2814 public $attributes = array(); 2815 2816 /** 2817 * Constructor 2818 * @param array $cells 2819 */ 2820 public function __construct(array $cells=null) { 2821 $this->attributes['class'] = ''; 2822 $cells = (array)$cells; 2823 foreach ($cells as $cell) { 2824 if ($cell instanceof html_table_cell) { 2825 $this->cells[] = $cell; 2826 } else { 2827 $this->cells[] = new html_table_cell($cell); 2828 } 2829 } 2830 } 2831 } 2832 2833 /** 2834 * Component representing a table cell. 2835 * 2836 * @copyright 2009 Nicolas Connault 2837 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2838 * @since Moodle 2.0 2839 * @package core 2840 * @category output 2841 */ 2842 class html_table_cell { 2843 2844 /** 2845 * @var string Value to use for the id attribute of the cell. 2846 */ 2847 public $id = null; 2848 2849 /** 2850 * @var string The contents of the cell. 2851 */ 2852 public $text; 2853 2854 /** 2855 * @var string Abbreviated version of the contents of the cell. 2856 */ 2857 public $abbr = null; 2858 2859 /** 2860 * @var int Number of columns this cell should span. 2861 */ 2862 public $colspan = null; 2863 2864 /** 2865 * @var int Number of rows this cell should span. 2866 */ 2867 public $rowspan = null; 2868 2869 /** 2870 * @var string Defines a way to associate header cells and data cells in a table. 2871 */ 2872 public $scope = null; 2873 2874 /** 2875 * @var bool Whether or not this cell is a header cell. 2876 */ 2877 public $header = null; 2878 2879 /** 2880 * @var string Value to use for the style attribute of the table cell 2881 */ 2882 public $style = null; 2883 2884 /** 2885 * @var array Attributes of additional HTML attributes for the <td> element 2886 */ 2887 public $attributes = array(); 2888 2889 /** 2890 * Constructs a table cell 2891 * 2892 * @param string $text 2893 */ 2894 public function __construct($text = null) { 2895 $this->text = $text; 2896 $this->attributes['class'] = ''; 2897 } 2898 } 2899 2900 /** 2901 * Component representing a paging bar. 2902 * 2903 * @copyright 2009 Nicolas Connault 2904 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2905 * @since Moodle 2.0 2906 * @package core 2907 * @category output 2908 */ 2909 class paging_bar implements renderable, templatable { 2910 2911 /** 2912 * @var int The maximum number of pagelinks to display. 2913 */ 2914 public $maxdisplay = 18; 2915 2916 /** 2917 * @var int The total number of entries to be pages through.. 2918 */ 2919 public $totalcount; 2920 2921 /** 2922 * @var int The page you are currently viewing. 2923 */ 2924 public $page; 2925 2926 /** 2927 * @var int The number of entries that should be shown per page. 2928 */ 2929 public $perpage; 2930 2931 /** 2932 * @var string|moodle_url If this is a string then it is the url which will be appended with $pagevar, 2933 * an equals sign and the page number. 2934 * If this is a moodle_url object then the pagevar param will be replaced by 2935 * the page no, for each page. 2936 */ 2937 public $baseurl; 2938 2939 /** 2940 * @var string This is the variable name that you use for the pagenumber in your 2941 * code (ie. 'tablepage', 'blogpage', etc) 2942 */ 2943 public $pagevar; 2944 2945 /** 2946 * @var string A HTML link representing the "previous" page. 2947 */ 2948 public $previouslink = null; 2949 2950 /** 2951 * @var string A HTML link representing the "next" page. 2952 */ 2953 public $nextlink = null; 2954 2955 /** 2956 * @var string A HTML link representing the first page. 2957 */ 2958 public $firstlink = null; 2959 2960 /** 2961 * @var string A HTML link representing the last page. 2962 */ 2963 public $lastlink = null; 2964 2965 /** 2966 * @var array An array of strings. One of them is just a string: the current page 2967 */ 2968 public $pagelinks = array(); 2969 2970 /** 2971 * Constructor paging_bar with only the required params. 2972 * 2973 * @param int $totalcount The total number of entries available to be paged through 2974 * @param int $page The page you are currently viewing 2975 * @param int $perpage The number of entries that should be shown per page 2976 * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added 2977 * @param string $pagevar name of page parameter that holds the page number 2978 */ 2979 public function __construct($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') { 2980 $this->totalcount = $totalcount; 2981 $this->page = $page; 2982 $this->perpage = $perpage; 2983 $this->baseurl = $baseurl; 2984 $this->pagevar = $pagevar; 2985 } 2986 2987 /** 2988 * Prepares the paging bar for output. 2989 * 2990 * This method validates the arguments set up for the paging bar and then 2991 * produces fragments of HTML to assist display later on. 2992 * 2993 * @param renderer_base $output 2994 * @param moodle_page $page 2995 * @param string $target 2996 * @throws coding_exception 2997 */ 2998 public function prepare(renderer_base $output, moodle_page $page, $target) { 2999 if (!isset($this->totalcount) || is_null($this->totalcount)) { 3000 throw new coding_exception('paging_bar requires a totalcount value.'); 3001 } 3002 if (!isset($this->page) || is_null($this->page)) { 3003 throw new coding_exception('paging_bar requires a page value.'); 3004 } 3005 if (empty($this->perpage)) { 3006 throw new coding_exception('paging_bar requires a perpage value.'); 3007 } 3008 if (empty($this->baseurl)) { 3009 throw new coding_exception('paging_bar requires a baseurl value.'); 3010 } 3011 3012 if ($this->totalcount > $this->perpage) { 3013 $pagenum = $this->page - 1; 3014 3015 if ($this->page > 0) { 3016 $this->previouslink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('previous'), array('class'=>'previous')); 3017 } 3018 3019 if ($this->perpage > 0) { 3020 $lastpage = ceil($this->totalcount / $this->perpage); 3021 } else { 3022 $lastpage = 1; 3023 } 3024 3025 if ($this->page > round(($this->maxdisplay/3)*2)) { 3026 $currpage = $this->page - round($this->maxdisplay/3); 3027 3028 $this->firstlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>0)), '1', array('class'=>'first')); 3029 } else { 3030 $currpage = 0; 3031 } 3032 3033 $displaycount = $displaypage = 0; 3034 3035 while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { 3036 $displaypage = $currpage + 1; 3037 3038 if ($this->page == $currpage) { 3039 $this->pagelinks[] = html_writer::span($displaypage, 'current-page'); 3040 } else { 3041 $pagelink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$currpage)), $displaypage); 3042 $this->pagelinks[] = $pagelink; 3043 } 3044 3045 $displaycount++; 3046 $currpage++; 3047 } 3048 3049 if ($currpage < $lastpage) { 3050 $lastpageactual = $lastpage - 1; 3051 $this->lastlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$lastpageactual)), $lastpage, array('class'=>'last')); 3052 } 3053 3054 $pagenum = $this->page + 1; 3055 3056 if ($pagenum != $lastpage) { 3057 $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next')); 3058 } 3059 } 3060 } 3061 3062 /** 3063 * Export for template. 3064 * 3065 * @param renderer_base $output The renderer. 3066 * @return stdClass 3067 */ 3068 public function export_for_template(renderer_base $output) { 3069 $data = new stdClass(); 3070 $data->previous = null; 3071 $data->next = null; 3072 $data->first = null; 3073 $data->last = null; 3074 $data->label = get_string('page'); 3075 $data->pages = []; 3076 $data->haspages = $this->totalcount > $this->perpage; 3077 $data->pagesize = $this->perpage; 3078 3079 if (!$data->haspages) { 3080 return $data; 3081 } 3082 3083 if ($this->page > 0) { 3084 $data->previous = [ 3085 'page' => $this->page, 3086 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page - 1]))->out(false) 3087 ]; 3088 } 3089 3090 $currpage = 0; 3091 if ($this->page > round(($this->maxdisplay / 3) * 2)) { 3092 $currpage = $this->page - round($this->maxdisplay / 3); 3093 $data->first = [ 3094 'page' => 1, 3095 'url' => (new moodle_url($this->baseurl, [$this->pagevar => 0]))->out(false) 3096 ]; 3097 } 3098 3099 $lastpage = 1; 3100 if ($this->perpage > 0) { 3101 $lastpage = ceil($this->totalcount / $this->perpage); 3102 } 3103 3104 $displaycount = 0; 3105 $displaypage = 0; 3106 while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { 3107 $displaypage = $currpage + 1; 3108 3109 $iscurrent = $this->page == $currpage; 3110 $link = new moodle_url($this->baseurl, [$this->pagevar => $currpage]); 3111 3112 $data->pages[] = [ 3113 'page' => $displaypage, 3114 'active' => $iscurrent, 3115 'url' => $iscurrent ? null : $link->out(false) 3116 ]; 3117 3118 $displaycount++; 3119 $currpage++; 3120 } 3121 3122 if ($currpage < $lastpage) { 3123 $data->last = [ 3124 'page' => $lastpage, 3125 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $lastpage - 1]))->out(false) 3126 ]; 3127 } 3128 3129 if ($this->page + 1 != $lastpage) { 3130 $data->next = [ 3131 'page' => $this->page + 2, 3132 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page + 1]))->out(false) 3133 ]; 3134 } 3135 3136 return $data; 3137 } 3138 } 3139 3140 /** 3141 * Component representing initials bar. 3142 * 3143 * @copyright 2017 Ilya Tregubov 3144 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3145 * @since Moodle 3.3 3146 * @package core 3147 * @category output 3148 */ 3149 class initials_bar implements renderable, templatable { 3150 3151 /** 3152 * @var string Currently selected letter. 3153 */ 3154 public $current; 3155 3156 /** 3157 * @var string Class name to add to this initial bar. 3158 */ 3159 public $class; 3160 3161 /** 3162 * @var string The name to put in front of this initial bar. 3163 */ 3164 public $title; 3165 3166 /** 3167 * @var string URL parameter name for this initial. 3168 */ 3169 public $urlvar; 3170 3171 /** 3172 * @var string URL object. 3173 */ 3174 public $url; 3175 3176 /** 3177 * @var array An array of letters in the alphabet. 3178 */ 3179 public $alpha; 3180 3181 /** 3182 * Constructor initials_bar with only the required params. 3183 * 3184 * @param string $current the currently selected letter. 3185 * @param string $class class name to add to this initial bar. 3186 * @param string $title the name to put in front of this initial bar. 3187 * @param string $urlvar URL parameter name for this initial. 3188 * @param string $url URL object. 3189 * @param array $alpha of letters in the alphabet. 3190 */ 3191 public function __construct($current, $class, $title, $urlvar, $url, $alpha = null) { 3192 $this->current = $current; 3193 $this->class = $class; 3194 $this->title = $title; 3195 $this->urlvar = $urlvar; 3196 $this->url = $url; 3197 $this->alpha = $alpha; 3198 } 3199 3200 /** 3201 * Export for template. 3202 * 3203 * @param renderer_base $output The renderer. 3204 * @return stdClass 3205 */ 3206 public function export_for_template(renderer_base $output) { 3207 $data = new stdClass(); 3208 3209 if ($this->alpha == null) { 3210 $this->alpha = explode(',', get_string('alphabet', 'langconfig')); 3211 } 3212 3213 if ($this->current == 'all') { 3214 $this->current = ''; 3215 } 3216 3217 // We want to find a letter grouping size which suits the language so 3218 // find the largest group size which is less than 15 chars. 3219 // The choice of 15 chars is the largest number of chars that reasonably 3220 // fits on the smallest supported screen size. By always using a max number 3221 // of groups which is a factor of 2, we always get nice wrapping, and the 3222 // last row is always the shortest. 3223 $groupsize = count($this->alpha); 3224 $groups = 1; 3225 while ($groupsize > 15) { 3226 $groups *= 2; 3227 $groupsize = ceil(count($this->alpha) / $groups); 3228 } 3229 3230 $groupsizelimit = 0; 3231 $groupnumber = 0; 3232 foreach ($this->alpha as $letter) { 3233 if ($groupsizelimit++ > 0 && $groupsizelimit % $groupsize == 1) { 3234 $groupnumber++; 3235 } 3236 $groupletter = new stdClass(); 3237 $groupletter->name = $letter; 3238 $groupletter->url = $this->url->out(false, array($this->urlvar => $letter)); 3239 if ($letter == $this->current) { 3240 $groupletter->selected = $this->current; 3241 } 3242 if (!isset($data->group[$groupnumber])) { 3243 $data->group[$groupnumber] = new stdClass(); 3244 } 3245 $data->group[$groupnumber]->letter[] = $groupletter; 3246 } 3247 3248 $data->class = $this->class; 3249 $data->title = $this->title; 3250 $data->url = $this->url->out(false, array($this->urlvar => '')); 3251 $data->current = $this->current; 3252 $data->all = get_string('all'); 3253 3254 return $data; 3255 } 3256 } 3257 3258 /** 3259 * This class represents how a block appears on a page. 3260 * 3261 * During output, each block instance is asked to return a block_contents object, 3262 * those are then passed to the $OUTPUT->block function for display. 3263 * 3264 * contents should probably be generated using a moodle_block_..._renderer. 3265 * 3266 * Other block-like things that need to appear on the page, for example the 3267 * add new block UI, are also represented as block_contents objects. 3268 * 3269 * @copyright 2009 Tim Hunt 3270 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3271 * @since Moodle 2.0 3272 * @package core 3273 * @category output 3274 */ 3275 class block_contents { 3276 3277 /** Used when the block cannot be collapsed **/ 3278 const NOT_HIDEABLE = 0; 3279 3280 /** Used when the block can be collapsed but currently is not **/ 3281 const VISIBLE = 1; 3282 3283 /** Used when the block has been collapsed **/ 3284 const HIDDEN = 2; 3285 3286 /** 3287 * @var int Used to set $skipid. 3288 */ 3289 protected static $idcounter = 1; 3290 3291 /** 3292 * @var int All the blocks (or things that look like blocks) printed on 3293 * a page are given a unique number that can be used to construct id="" attributes. 3294 * This is set automatically be the {@link prepare()} method. 3295 * Do not try to set it manually. 3296 */ 3297 public $skipid; 3298 3299 /** 3300 * @var int If this is the contents of a real block, this should be set 3301 * to the block_instance.id. Otherwise this should be set to 0. 3302 */ 3303 public $blockinstanceid = 0; 3304 3305 /** 3306 * @var int If this is a real block instance, and there is a corresponding 3307 * block_position.id for the block on this page, this should be set to that id. 3308 * Otherwise it should be 0. 3309 */ 3310 public $blockpositionid = 0; 3311 3312 /** 3313 * @var array An array of attribute => value pairs that are put on the outer div of this 3314 * block. {@link $id} and {@link $classes} attributes should be set separately. 3315 */ 3316 public $attributes; 3317 3318 /** 3319 * @var string The title of this block. If this came from user input, it should already 3320 * have had format_string() processing done on it. This will be output inside 3321 * <h2> tags. Please do not cause invalid XHTML. 3322 */ 3323 public $title = ''; 3324 3325 /** 3326 * @var string The label to use when the block does not, or will not have a visible title. 3327 * You should never set this as well as title... it will just be ignored. 3328 */ 3329 public $arialabel = ''; 3330 3331 /** 3332 * @var string HTML for the content 3333 */ 3334 public $content = ''; 3335 3336 /** 3337 * @var array An alternative to $content, it you want a list of things with optional icons. 3338 */ 3339 public $footer = ''; 3340 3341 /** 3342 * @var string Any small print that should appear under the block to explain 3343 * to the teacher about the block, for example 'This is a sticky block that was 3344 * added in the system context.' 3345 */ 3346 public $annotation = ''; 3347 3348 /** 3349 * @var int One of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether 3350 * the user can toggle whether this block is visible. 3351 */ 3352 public $collapsible = self::NOT_HIDEABLE; 3353 3354 /** 3355 * Set this to true if the block is dockable. 3356 * @var bool 3357 */ 3358 public $dockable = false; 3359 3360 /** 3361 * @var array A (possibly empty) array of editing controls. Each element of 3362 * this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption). 3363 * $icon is the icon name. Fed to $OUTPUT->image_url. 3364 */ 3365 public $controls = array(); 3366 3367 3368 /** 3369 * Create new instance of block content 3370 * @param array $attributes 3371 */ 3372 public function __construct(array $attributes = null) { 3373 $this->skipid = self::$idcounter; 3374 self::$idcounter += 1; 3375 3376 if ($attributes) { 3377 // standard block 3378 $this->attributes = $attributes; 3379 } else { 3380 // simple "fake" blocks used in some modules and "Add new block" block 3381 $this->attributes = array('class'=>'block'); 3382 } 3383 } 3384 3385 /** 3386 * Add html class to block 3387 * 3388 * @param string $class 3389 */ 3390 public function add_class($class) { 3391 $this->attributes['class'] .= ' '.$class; 3392 } 3393 3394 /** 3395 * Check if the block is a fake block. 3396 * 3397 * @return boolean 3398 */ 3399 public function is_fake() { 3400 return isset($this->attributes['data-block']) && $this->attributes['data-block'] == '_fake'; 3401 } 3402 } 3403 3404 3405 /** 3406 * This class represents a target for where a block can go when it is being moved. 3407 * 3408 * This needs to be rendered as a form with the given hidden from fields, and 3409 * clicking anywhere in the form should submit it. The form action should be 3410 * $PAGE->url. 3411 * 3412 * @copyright 2009 Tim Hunt 3413 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3414 * @since Moodle 2.0 3415 * @package core 3416 * @category output 3417 */ 3418 class block_move_target { 3419 3420 /** 3421 * @var moodle_url Move url 3422 */ 3423 public $url; 3424 3425 /** 3426 * Constructor 3427 * @param moodle_url $url 3428 */ 3429 public function __construct(moodle_url $url) { 3430 $this->url = $url; 3431 } 3432 } 3433 3434 /** 3435 * Custom menu item 3436 * 3437 * This class is used to represent one item within a custom menu that may or may 3438 * not have children. 3439 * 3440 * @copyright 2010 Sam Hemelryk 3441 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3442 * @since Moodle 2.0 3443 * @package core 3444 * @category output 3445 */ 3446 class custom_menu_item implements renderable, templatable { 3447 3448 /** 3449 * @var string The text to show for the item 3450 */ 3451 protected $text; 3452 3453 /** 3454 * @var moodle_url The link to give the icon if it has no children 3455 */ 3456 protected $url; 3457 3458 /** 3459 * @var string A title to apply to the item. By default the text 3460 */ 3461 protected $title; 3462 3463 /** 3464 * @var int A sort order for the item, not necessary if you order things in 3465 * the CFG var. 3466 */ 3467 protected $sort; 3468 3469 /** 3470 * @var custom_menu_item A reference to the parent for this item or NULL if 3471 * it is a top level item 3472 */ 3473 protected $parent; 3474 3475 /** 3476 * @var array A array in which to store children this item has. 3477 */ 3478 protected $children = array(); 3479 3480 /** 3481 * @var int A reference to the sort var of the last child that was added 3482 */ 3483 protected $lastsort = 0; 3484 3485 /** @var array Array of other HTML attributes for the custom menu item. */ 3486 protected $attributes = []; 3487 3488 /** 3489 * Constructs the new custom menu item 3490 * 3491 * @param string $text 3492 * @param moodle_url $url A moodle url to apply as the link for this item [Optional] 3493 * @param string $title A title to apply to this item [Optional] 3494 * @param int $sort A sort or to use if we need to sort differently [Optional] 3495 * @param custom_menu_item $parent A reference to the parent custom_menu_item this child 3496 * belongs to, only if the child has a parent. [Optional] 3497 * @param array $attributes Array of other HTML attributes for the custom menu item. 3498 */ 3499 public function __construct($text, moodle_url $url = null, $title = null, $sort = null, custom_menu_item $parent = null, 3500 array $attributes = []) { 3501 3502 // Use class setter method for text to ensure it's always a string type. 3503 $this->set_text($text); 3504 3505 $this->url = $url; 3506 $this->title = $title; 3507 $this->sort = (int)$sort; 3508 $this->parent = $parent; 3509 $this->attributes = $attributes; 3510 } 3511 3512 /** 3513 * Adds a custom menu item as a child of this node given its properties. 3514 * 3515 * @param string $text 3516 * @param moodle_url $url 3517 * @param string $title 3518 * @param int $sort 3519 * @param array $attributes Array of other HTML attributes for the custom menu item. 3520 * @return custom_menu_item 3521 */ 3522 public function add($text, moodle_url $url = null, $title = null, $sort = null, $attributes = []) { 3523 $key = count($this->children); 3524 if (empty($sort)) { 3525 $sort = $this->lastsort + 1; 3526 } 3527 $this->children[$key] = new custom_menu_item($text, $url, $title, $sort, $this, $attributes); 3528 $this->lastsort = (int)$sort; 3529 return $this->children[$key]; 3530 } 3531 3532 /** 3533 * Removes a custom menu item that is a child or descendant to the current menu. 3534 * 3535 * Returns true if child was found and removed. 3536 * 3537 * @param custom_menu_item $menuitem 3538 * @return bool 3539 */ 3540 public function remove_child(custom_menu_item $menuitem) { 3541 $removed = false; 3542 if (($key = array_search($menuitem, $this->children)) !== false) { 3543 unset($this->children[$key]); 3544 $this->children = array_values($this->children); 3545 $removed = true; 3546 } else { 3547 foreach ($this->children as $child) { 3548 if ($removed = $child->remove_child($menuitem)) { 3549 break; 3550 } 3551 } 3552 } 3553 return $removed; 3554 } 3555 3556 /** 3557 * Returns the text for this item 3558 * @return string 3559 */ 3560 public function get_text() { 3561 return $this->text; 3562 } 3563 3564 /** 3565 * Returns the url for this item 3566 * @return moodle_url 3567 */ 3568 public function get_url() { 3569 return $this->url; 3570 } 3571 3572 /** 3573 * Returns the title for this item 3574 * @return string 3575 */ 3576 public function get_title() { 3577 return $this->title; 3578 } 3579 3580 /** 3581 * Sorts and returns the children for this item 3582 * @return array 3583 */ 3584 public function get_children() { 3585 $this->sort(); 3586 return $this->children; 3587 } 3588 3589 /** 3590 * Gets the sort order for this child 3591 * @return int 3592 */ 3593 public function get_sort_order() { 3594 return $this->sort; 3595 } 3596 3597 /** 3598 * Gets the parent this child belong to 3599 * @return custom_menu_item 3600 */ 3601 public function get_parent() { 3602 return $this->parent; 3603 } 3604 3605 /** 3606 * Sorts the children this item has 3607 */ 3608 public function sort() { 3609 usort($this->children, array('custom_menu','sort_custom_menu_items')); 3610 } 3611 3612 /** 3613 * Returns true if this item has any children 3614 * @return bool 3615 */ 3616 public function has_children() { 3617 return (count($this->children) > 0); 3618 } 3619 3620 /** 3621 * Sets the text for the node 3622 * @param string $text 3623 */ 3624 public function set_text($text) { 3625 $this->text = (string)$text; 3626 } 3627 3628 /** 3629 * Sets the title for the node 3630 * @param string $title 3631 */ 3632 public function set_title($title) { 3633 $this->title = (string)$title; 3634 } 3635 3636 /** 3637 * Sets the url for the node 3638 * @param moodle_url $url 3639 */ 3640 public function set_url(moodle_url $url) { 3641 $this->url = $url; 3642 } 3643 3644 /** 3645 * Export this data so it can be used as the context for a mustache template. 3646 * 3647 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 3648 * @return array 3649 */ 3650 public function export_for_template(renderer_base $output) { 3651 global $CFG; 3652 3653 require_once($CFG->libdir . '/externallib.php'); 3654 3655 $syscontext = context_system::instance(); 3656 3657 $context = new stdClass(); 3658 $context->moremenuid = uniqid(); 3659 $context->text = external_format_string($this->text, $syscontext->id); 3660 $context->url = $this->url ? $this->url->out() : null; 3661 // No need for the title if it's the same with text. 3662 if ($this->text !== $this->title) { 3663 // Show the title attribute only if it's different from the text. 3664 $context->title = external_format_string($this->title, $syscontext->id); 3665 } 3666 $context->sort = $this->sort; 3667 if (!empty($this->attributes)) { 3668 $context->attributes = $this->attributes; 3669 } 3670 $context->children = array(); 3671 if (preg_match("/^#+$/", $this->text)) { 3672 $context->divider = true; 3673 } 3674 $context->haschildren = !empty($this->children) && (count($this->children) > 0); 3675 foreach ($this->children as $child) { 3676 $child = $child->export_for_template($output); 3677 array_push($context->children, $child); 3678 } 3679 3680 return $context; 3681 } 3682 } 3683 3684 /** 3685 * Custom menu class 3686 * 3687 * This class is used to operate a custom menu that can be rendered for the page. 3688 * The custom menu is built using $CFG->custommenuitems and is a structured collection 3689 * of custom_menu_item nodes that can be rendered by the core renderer. 3690 * 3691 * To configure the custom menu: 3692 * Settings: Administration > Appearance > Themes > Theme settings 3693 * 3694 * @copyright 2010 Sam Hemelryk 3695 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3696 * @since Moodle 2.0 3697 * @package core 3698 * @category output 3699 */ 3700 class custom_menu extends custom_menu_item { 3701 3702 /** 3703 * @var string The language we should render for, null disables multilang support. 3704 */ 3705 protected $currentlanguage = null; 3706 3707 /** 3708 * Creates the custom menu 3709 * 3710 * @param string $definition the menu items definition in syntax required by {@link convert_text_to_menu_nodes()} 3711 * @param string $currentlanguage the current language code, null disables multilang support 3712 */ 3713 public function __construct($definition = '', $currentlanguage = null) { 3714 $this->currentlanguage = $currentlanguage; 3715 parent::__construct('root'); // create virtual root element of the menu 3716 if (!empty($definition)) { 3717 $this->override_children(self::convert_text_to_menu_nodes($definition, $currentlanguage)); 3718 } 3719 } 3720 3721 /** 3722 * Overrides the children of this custom menu. Useful when getting children 3723 * from $CFG->custommenuitems 3724 * 3725 * @param array $children 3726 */ 3727 public function override_children(array $children) { 3728 $this->children = array(); 3729 foreach ($children as $child) { 3730 if ($child instanceof custom_menu_item) { 3731 $this->children[] = $child; 3732 } 3733 } 3734 } 3735 3736 /** 3737 * Converts a string into a structured array of custom_menu_items which can 3738 * then be added to a custom menu. 3739 * 3740 * Structure: 3741 * text|url|title|langs 3742 * The number of hyphens at the start determines the depth of the item. The 3743 * languages are optional, comma separated list of languages the line is for. 3744 * 3745 * Example structure: 3746 * First level first item|http://www.moodle.com/ 3747 * -Second level first item|http://www.moodle.com/partners/ 3748 * -Second level second item|http://www.moodle.com/hq/ 3749 * --Third level first item|http://www.moodle.com/jobs/ 3750 * -Second level third item|http://www.moodle.com/development/ 3751 * First level second item|http://www.moodle.com/feedback/ 3752 * First level third item 3753 * English only|http://moodle.com|English only item|en 3754 * German only|http://moodle.de|Deutsch|de,de_du,de_kids 3755 * 3756 * 3757 * @static 3758 * @param string $text the menu items definition 3759 * @param string $language the language code, null disables multilang support 3760 * @return array 3761 */ 3762 public static function convert_text_to_menu_nodes($text, $language = null) { 3763 $root = new custom_menu(); 3764 $lastitem = $root; 3765 $lastdepth = 0; 3766 $hiddenitems = array(); 3767 $lines = explode("\n", $text); 3768 foreach ($lines as $linenumber => $line) { 3769 $line = trim($line); 3770 if (strlen($line) == 0) { 3771 continue; 3772 } 3773 // Parse item settings. 3774 $itemtext = null; 3775 $itemurl = null; 3776 $itemtitle = null; 3777 $itemvisible = true; 3778 $settings = explode('|', $line); 3779 foreach ($settings as $i => $setting) { 3780 $setting = trim($setting); 3781 if ($setting !== '') { 3782 switch ($i) { 3783 case 0: // Menu text. 3784 $itemtext = ltrim($setting, '-'); 3785 break; 3786 case 1: // URL. 3787 try { 3788 $itemurl = new moodle_url($setting); 3789 } catch (moodle_exception $exception) { 3790 // We're not actually worried about this, we don't want to mess up the display 3791 // just for a wrongly entered URL. 3792 $itemurl = null; 3793 } 3794 break; 3795 case 2: // Title attribute. 3796 $itemtitle = $setting; 3797 break; 3798 case 3: // Language. 3799 if (!empty($language)) { 3800 $itemlanguages = array_map('trim', explode(',', $setting)); 3801 $itemvisible &= in_array($language, $itemlanguages); 3802 } 3803 break; 3804 } 3805 } 3806 } 3807 // Get depth of new item. 3808 preg_match('/^(\-*)/', $line, $match); 3809 $itemdepth = strlen($match[1]) + 1; 3810 // Find parent item for new item. 3811 while (($lastdepth - $itemdepth) >= 0) { 3812 $lastitem = $lastitem->get_parent(); 3813 $lastdepth--; 3814 } 3815 $lastitem = $lastitem->add($itemtext, $itemurl, $itemtitle, $linenumber + 1); 3816 $lastdepth++; 3817 if (!$itemvisible) { 3818 $hiddenitems[] = $lastitem; 3819 } 3820 } 3821 foreach ($hiddenitems as $item) { 3822 $item->parent->remove_child($item); 3823 } 3824 return $root->get_children(); 3825 } 3826 3827 /** 3828 * Sorts two custom menu items 3829 * 3830 * This function is designed to be used with the usort method 3831 * usort($this->children, array('custom_menu','sort_custom_menu_items')); 3832 * 3833 * @static 3834 * @param custom_menu_item $itema 3835 * @param custom_menu_item $itemb 3836 * @return int 3837 */ 3838 public static function sort_custom_menu_items(custom_menu_item $itema, custom_menu_item $itemb) { 3839 $itema = $itema->get_sort_order(); 3840 $itemb = $itemb->get_sort_order(); 3841 if ($itema == $itemb) { 3842 return 0; 3843 } 3844 return ($itema > $itemb) ? +1 : -1; 3845 } 3846 } 3847 3848 /** 3849 * Stores one tab 3850 * 3851 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3852 * @package core 3853 */ 3854 class tabobject implements renderable, templatable { 3855 /** @var string unique id of the tab in this tree, it is used to find selected and/or inactive tabs */ 3856 var $id; 3857 /** @var moodle_url|string link */ 3858 var $link; 3859 /** @var string text on the tab */ 3860 var $text; 3861 /** @var string title under the link, by defaul equals to text */ 3862 var $title; 3863 /** @var bool whether to display a link under the tab name when it's selected */ 3864 var $linkedwhenselected = false; 3865 /** @var bool whether the tab is inactive */ 3866 var $inactive = false; 3867 /** @var bool indicates that this tab's child is selected */ 3868 var $activated = false; 3869 /** @var bool indicates that this tab is selected */ 3870 var $selected = false; 3871 /** @var array stores children tabobjects */ 3872 var $subtree = array(); 3873 /** @var int level of tab in the tree, 0 for root (instance of tabtree), 1 for the first row of tabs */ 3874 var $level = 1; 3875 3876 /** 3877 * Constructor 3878 * 3879 * @param string $id unique id of the tab in this tree, it is used to find selected and/or inactive tabs 3880 * @param string|moodle_url $link 3881 * @param string $text text on the tab 3882 * @param string $title title under the link, by defaul equals to text 3883 * @param bool $linkedwhenselected whether to display a link under the tab name when it's selected 3884 */ 3885 public function __construct($id, $link = null, $text = '', $title = '', $linkedwhenselected = false) { 3886 $this->id = $id; 3887 $this->link = $link; 3888 $this->text = $text; 3889 $this->title = $title ? $title : $text; 3890 $this->linkedwhenselected = $linkedwhenselected; 3891 } 3892 3893 /** 3894 * Travels through tree and finds the tab to mark as selected, all parents are automatically marked as activated 3895 * 3896 * @param string $selected the id of the selected tab (whatever row it's on), 3897 * if null marks all tabs as unselected 3898 * @return bool whether this tab is selected or contains selected tab in its subtree 3899 */ 3900 protected function set_selected($selected) { 3901 if ((string)$selected === (string)$this->id) { 3902 $this->selected = true; 3903 // This tab is selected. No need to travel through subtree. 3904 return true; 3905 } 3906 foreach ($this->subtree as $subitem) { 3907 if ($subitem->set_selected($selected)) { 3908 // This tab has child that is selected. Mark it as activated. No need to check other children. 3909 $this->activated = true; 3910 return true; 3911 } 3912 } 3913 return false; 3914 } 3915 3916 /** 3917 * Travels through tree and finds a tab with specified id 3918 * 3919 * @param string $id 3920 * @return tabtree|null 3921 */ 3922 public function find($id) { 3923 if ((string)$this->id === (string)$id) { 3924 return $this; 3925 } 3926 foreach ($this->subtree as $tab) { 3927 if ($obj = $tab->find($id)) { 3928 return $obj; 3929 } 3930 } 3931 return null; 3932 } 3933 3934 /** 3935 * Allows to mark each tab's level in the tree before rendering. 3936 * 3937 * @param int $level 3938 */ 3939 protected function set_level($level) { 3940 $this->level = $level; 3941 foreach ($this->subtree as $tab) { 3942 $tab->set_level($level + 1); 3943 } 3944 } 3945 3946 /** 3947 * Export for template. 3948 * 3949 * @param renderer_base $output Renderer. 3950 * @return object 3951 */ 3952 public function export_for_template(renderer_base $output) { 3953 if ($this->inactive || ($this->selected && !$this->linkedwhenselected) || $this->activated) { 3954 $link = null; 3955 } else { 3956 $link = $this->link; 3957 } 3958 $active = $this->activated || $this->selected; 3959 3960 return (object) [ 3961 'id' => $this->id, 3962 'link' => is_object($link) ? $link->out(false) : $link, 3963 'text' => $this->text, 3964 'title' => $this->title, 3965 'inactive' => !$active && $this->inactive, 3966 'active' => $active, 3967 'level' => $this->level, 3968 ]; 3969 } 3970 3971 } 3972 3973 /** 3974 * Renderable for the main page header. 3975 * 3976 * @package core 3977 * @category output 3978 * @since 2.9 3979 * @copyright 2015 Adrian Greeve <adrian@moodle.com> 3980 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3981 */ 3982 class context_header implements renderable { 3983 3984 /** 3985 * @var string $heading Main heading. 3986 */ 3987 public $heading; 3988 /** 3989 * @var int $headinglevel Main heading 'h' tag level. 3990 */ 3991 public $headinglevel; 3992 /** 3993 * @var string|null $imagedata HTML code for the picture in the page header. 3994 */ 3995 public $imagedata; 3996 /** 3997 * @var array $additionalbuttons Additional buttons for the header e.g. Messaging button for the user header. 3998 * array elements - title => alternate text for the image, or if no image is available the button text. 3999 * url => Link for the button to head to. Should be a moodle_url. 4000 * image => location to the image, or name of the image in /pix/t/{image name}. 4001 * linkattributes => additional attributes for the <a href> element. 4002 * page => page object. Don't include if the image is an external image. 4003 */ 4004 public $additionalbuttons; 4005 /** 4006 * @var string $prefix A string that is before the title. 4007 */ 4008 public $prefix; 4009 4010 /** 4011 * Constructor. 4012 * 4013 * @param string $heading Main heading data. 4014 * @param int $headinglevel Main heading 'h' tag level. 4015 * @param string|null $imagedata HTML code for the picture in the page header. 4016 * @param string $additionalbuttons Buttons for the header e.g. Messaging button for the user header. 4017 * @param string $prefix Text that precedes the heading. 4018 */ 4019 public function __construct($heading = null, $headinglevel = 1, $imagedata = null, $additionalbuttons = null, $prefix = null) { 4020 4021 $this->heading = $heading; 4022 $this->headinglevel = $headinglevel; 4023 $this->imagedata = $imagedata; 4024 $this->additionalbuttons = $additionalbuttons; 4025 // If we have buttons then format them. 4026 if (isset($this->additionalbuttons)) { 4027 $this->format_button_images(); 4028 } 4029 $this->prefix = $prefix; 4030 } 4031 4032 /** 4033 * Adds an array element for a formatted image. 4034 */ 4035 protected function format_button_images() { 4036 4037 foreach ($this->additionalbuttons as $buttontype => $button) { 4038 $page = $button['page']; 4039 // If no image is provided then just use the title. 4040 if (!isset($button['image'])) { 4041 $this->additionalbuttons[$buttontype]['formattedimage'] = $button['title']; 4042 } else { 4043 // Check to see if this is an internal Moodle icon. 4044 $internalimage = $page->theme->resolve_image_location('t/' . $button['image'], 'moodle'); 4045 if ($internalimage) { 4046 $this->additionalbuttons[$buttontype]['formattedimage'] = 't/' . $button['image']; 4047 } else { 4048 // Treat as an external image. 4049 $this->additionalbuttons[$buttontype]['formattedimage'] = $button['image']; 4050 } 4051 } 4052 4053 if (isset($button['linkattributes']['class'])) { 4054 $class = $button['linkattributes']['class'] . ' btn'; 4055 } else { 4056 $class = 'btn'; 4057 } 4058 // Add the bootstrap 'btn' class for formatting. 4059 $this->additionalbuttons[$buttontype]['linkattributes'] = array_merge($button['linkattributes'], 4060 array('class' => $class)); 4061 } 4062 } 4063 } 4064 4065 /** 4066 * Stores tabs list 4067 * 4068 * Example how to print a single line tabs: 4069 * $rows = array( 4070 * new tabobject(...), 4071 * new tabobject(...) 4072 * ); 4073 * echo $OUTPUT->tabtree($rows, $selectedid); 4074 * 4075 * Multiple row tabs may not look good on some devices but if you want to use them 4076 * you can specify ->subtree for the active tabobject. 4077 * 4078 * @copyright 2013 Marina Glancy 4079 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4080 * @since Moodle 2.5 4081 * @package core 4082 * @category output 4083 */ 4084 class tabtree extends tabobject { 4085 /** 4086 * Constuctor 4087 * 4088 * It is highly recommended to call constructor when list of tabs is already 4089 * populated, this way you ensure that selected and inactive tabs are located 4090 * and attribute level is set correctly. 4091 * 4092 * @param array $tabs array of tabs, each of them may have it's own ->subtree 4093 * @param string|null $selected which tab to mark as selected, all parent tabs will 4094 * automatically be marked as activated 4095 * @param array|string|null $inactive list of ids of inactive tabs, regardless of 4096 * their level. Note that you can as weel specify tabobject::$inactive for separate instances 4097 */ 4098 public function __construct($tabs, $selected = null, $inactive = null) { 4099 $this->subtree = $tabs; 4100 if ($selected !== null) { 4101 $this->set_selected($selected); 4102 } 4103 if ($inactive !== null) { 4104 if (is_array($inactive)) { 4105 foreach ($inactive as $id) { 4106 if ($tab = $this->find($id)) { 4107 $tab->inactive = true; 4108 } 4109 } 4110 } else if ($tab = $this->find($inactive)) { 4111 $tab->inactive = true; 4112 } 4113 } 4114 $this->set_level(0); 4115 } 4116 4117 /** 4118 * Export for template. 4119 * 4120 * @param renderer_base $output Renderer. 4121 * @return object 4122 */ 4123 public function export_for_template(renderer_base $output) { 4124 $tabs = []; 4125 $secondrow = false; 4126 4127 foreach ($this->subtree as $tab) { 4128 $tabs[] = $tab->export_for_template($output); 4129 if (!empty($tab->subtree) && ($tab->level == 0 || $tab->selected || $tab->activated)) { 4130 $secondrow = new tabtree($tab->subtree); 4131 } 4132 } 4133 4134 return (object) [ 4135 'tabs' => $tabs, 4136 'secondrow' => $secondrow ? $secondrow->export_for_template($output) : false 4137 ]; 4138 } 4139 } 4140 4141 /** 4142 * An action menu. 4143 * 4144 * This action menu component takes a series of primary and secondary actions. 4145 * The primary actions are displayed permanently and the secondary attributes are displayed within a drop 4146 * down menu. 4147 * 4148 * @package core 4149 * @category output 4150 * @copyright 2013 Sam Hemelryk 4151 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4152 */ 4153 class action_menu implements renderable, templatable { 4154 4155 /** 4156 * Top right alignment. 4157 */ 4158 const TL = 1; 4159 4160 /** 4161 * Top right alignment. 4162 */ 4163 const TR = 2; 4164 4165 /** 4166 * Top right alignment. 4167 */ 4168 const BL = 3; 4169 4170 /** 4171 * Top right alignment. 4172 */ 4173 const BR = 4; 4174 4175 /** 4176 * The instance number. This is unique to this instance of the action menu. 4177 * @var int 4178 */ 4179 protected $instance = 0; 4180 4181 /** 4182 * An array of primary actions. Please use {@link action_menu::add_primary_action()} to add actions. 4183 * @var array 4184 */ 4185 protected $primaryactions = array(); 4186 4187 /** 4188 * An array of secondary actions. Please use {@link action_menu::add_secondary_action()} to add actions. 4189 * @var array 4190 */ 4191 protected $secondaryactions = array(); 4192 4193 /** 4194 * An array of attributes added to the container of the action menu. 4195 * Initialised with defaults during construction. 4196 * @var array 4197 */ 4198 public $attributes = array(); 4199 /** 4200 * An array of attributes added to the container of the primary actions. 4201 * Initialised with defaults during construction. 4202 * @var array 4203 */ 4204 public $attributesprimary = array(); 4205 /** 4206 * An array of attributes added to the container of the secondary actions. 4207 * Initialised with defaults during construction. 4208 * @var array 4209 */ 4210 public $attributessecondary = array(); 4211 4212 /** 4213 * The string to use next to the icon for the action icon relating to the secondary (dropdown) menu. 4214 * @var array 4215 */ 4216 public $actiontext = null; 4217 4218 /** 4219 * The string to use for the accessible label for the menu. 4220 * @var array 4221 */ 4222 public $actionlabel = null; 4223 4224 /** 4225 * An icon to use for the toggling the secondary menu (dropdown). 4226 * @var pix_icon 4227 */ 4228 public $actionicon; 4229 4230 /** 4231 * Any text to use for the toggling the secondary menu (dropdown). 4232 * @var string 4233 */ 4234 public $menutrigger = ''; 4235 4236 /** 4237 * An array of attributes added to the trigger element of the secondary menu. 4238 * @var array 4239 */ 4240 public $triggerattributes = []; 4241 4242 /** 4243 * Any extra classes for toggling to the secondary menu. 4244 * @var string 4245 */ 4246 public $triggerextraclasses = ''; 4247 4248 /** 4249 * Place the action menu before all other actions. 4250 * @var bool 4251 */ 4252 public $prioritise = false; 4253 4254 /** 4255 * Dropdown menu alignment class. 4256 * @var string 4257 */ 4258 public $dropdownalignment = ''; 4259 4260 /** 4261 * Constructs the action menu with the given items. 4262 * 4263 * @param array $actions An array of actions (action_menu_link|pix_icon|string). 4264 */ 4265 public function __construct(array $actions = array()) { 4266 static $initialised = 0; 4267 $this->instance = $initialised; 4268 $initialised++; 4269 4270 $this->attributes = array( 4271 'id' => 'action-menu-'.$this->instance, 4272 'class' => 'moodle-actionmenu', 4273 'data-enhance' => 'moodle-core-actionmenu' 4274 ); 4275 $this->attributesprimary = array( 4276 'id' => 'action-menu-'.$this->instance.'-menubar', 4277 'class' => 'menubar', 4278 ); 4279 $this->attributessecondary = array( 4280 'id' => 'action-menu-'.$this->instance.'-menu', 4281 'class' => 'menu', 4282 'data-rel' => 'menu-content', 4283 'aria-labelledby' => 'action-menu-toggle-'.$this->instance, 4284 'role' => 'menu' 4285 ); 4286 $this->dropdownalignment = 'dropdown-menu-right'; 4287 foreach ($actions as $action) { 4288 $this->add($action); 4289 } 4290 } 4291 4292 /** 4293 * Sets the label for the menu trigger. 4294 * 4295 * @param string $label The text 4296 */ 4297 public function set_action_label($label) { 4298 $this->actionlabel = $label; 4299 } 4300 4301 /** 4302 * Sets the menu trigger text. 4303 * 4304 * @param string $trigger The text 4305 * @param string $extraclasses Extra classes to style the secondary menu toggle. 4306 */ 4307 public function set_menu_trigger($trigger, $extraclasses = '') { 4308 $this->menutrigger = $trigger; 4309 $this->triggerextraclasses = $extraclasses; 4310 } 4311 4312 /** 4313 * Return true if there is at least one visible link in the menu. 4314 * 4315 * @return bool 4316 */ 4317 public function is_empty() { 4318 return !count($this->primaryactions) && !count($this->secondaryactions); 4319 } 4320 4321 /** 4322 * Initialises JS required fore the action menu. 4323 * The JS is only required once as it manages all action menu's on the page. 4324 * 4325 * @param moodle_page $page 4326 */ 4327 public function initialise_js(moodle_page $page) { 4328 static $initialised = false; 4329 if (!$initialised) { 4330 $page->requires->yui_module('moodle-core-actionmenu', 'M.core.actionmenu.init'); 4331 $initialised = true; 4332 } 4333 } 4334 4335 /** 4336 * Adds an action to this action menu. 4337 * 4338 * @param action_menu_link|pix_icon|string $action 4339 */ 4340 public function add($action) { 4341 if ($action instanceof action_link) { 4342 if ($action->primary) { 4343 $this->add_primary_action($action); 4344 } else { 4345 $this->add_secondary_action($action); 4346 } 4347 } else if ($action instanceof pix_icon) { 4348 $this->add_primary_action($action); 4349 } else { 4350 $this->add_secondary_action($action); 4351 } 4352 } 4353 4354 /** 4355 * Adds a primary action to the action menu. 4356 * 4357 * @param action_menu_link|action_link|pix_icon|string $action 4358 */ 4359 public function add_primary_action($action) { 4360 if ($action instanceof action_link || $action instanceof pix_icon) { 4361 $action->attributes['role'] = 'menuitem'; 4362 $action->attributes['tabindex'] = '-1'; 4363 if ($action instanceof action_menu_link) { 4364 $action->actionmenu = $this; 4365 } 4366 } 4367 $this->primaryactions[] = $action; 4368 } 4369 4370 /** 4371 * Adds a secondary action to the action menu. 4372 * 4373 * @param action_link|pix_icon|string $action 4374 */ 4375 public function add_secondary_action($action) { 4376 if ($action instanceof action_link || $action instanceof pix_icon) { 4377 $action->attributes['role'] = 'menuitem'; 4378 $action->attributes['tabindex'] = '-1'; 4379 if ($action instanceof action_menu_link) { 4380 $action->actionmenu = $this; 4381 } 4382 } 4383 $this->secondaryactions[] = $action; 4384 } 4385 4386 /** 4387 * Returns the primary actions ready to be rendered. 4388 * 4389 * @param core_renderer $output The renderer to use for getting icons. 4390 * @return array 4391 */ 4392 public function get_primary_actions(core_renderer $output = null) { 4393 global $OUTPUT; 4394 if ($output === null) { 4395 $output = $OUTPUT; 4396 } 4397 $pixicon = $this->actionicon; 4398 $linkclasses = array('toggle-display'); 4399 4400 $title = ''; 4401 if (!empty($this->menutrigger)) { 4402 $pixicon = '<b class="caret"></b>'; 4403 $linkclasses[] = 'textmenu'; 4404 } else { 4405 $title = new lang_string('actionsmenu', 'moodle'); 4406 $this->actionicon = new pix_icon( 4407 't/edit_menu', 4408 '', 4409 'moodle', 4410 array('class' => 'iconsmall actionmenu', 'title' => '') 4411 ); 4412 $pixicon = $this->actionicon; 4413 } 4414 if ($pixicon instanceof renderable) { 4415 $pixicon = $output->render($pixicon); 4416 if ($pixicon instanceof pix_icon && isset($pixicon->attributes['alt'])) { 4417 $title = $pixicon->attributes['alt']; 4418 } 4419 } 4420 $string = ''; 4421 if ($this->actiontext) { 4422 $string = $this->actiontext; 4423 } 4424 $label = ''; 4425 if ($this->actionlabel) { 4426 $label = $this->actionlabel; 4427 } else { 4428 $label = $title; 4429 } 4430 $actions = $this->primaryactions; 4431 $attributes = array( 4432 'class' => implode(' ', $linkclasses), 4433 'title' => $title, 4434 'aria-label' => $label, 4435 'id' => 'action-menu-toggle-'.$this->instance, 4436 'role' => 'menuitem', 4437 'tabindex' => '-1', 4438 ); 4439 $link = html_writer::link('#', $string . $this->menutrigger . $pixicon, $attributes); 4440 if ($this->prioritise) { 4441 array_unshift($actions, $link); 4442 } else { 4443 $actions[] = $link; 4444 } 4445 return $actions; 4446 } 4447 4448 /** 4449 * Returns the secondary actions ready to be rendered. 4450 * @return array 4451 */ 4452 public function get_secondary_actions() { 4453 return $this->secondaryactions; 4454 } 4455 4456 /** 4457 * Sets the selector that should be used to find the owning node of this menu. 4458 * @param string $selector A CSS/YUI selector to identify the owner of the menu. 4459 */ 4460 public function set_owner_selector($selector) { 4461 $this->attributes['data-owner'] = $selector; 4462 } 4463 4464 /** 4465 * Sets the alignment of the dialogue in relation to button used to toggle it. 4466 * 4467 * @deprecated since Moodle 4.0 4468 * 4469 * @param int $dialogue One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4470 * @param int $button One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4471 */ 4472 public function set_alignment($dialogue, $button) { 4473 debugging('The method action_menu::set_alignment() is deprecated, use action_menu::set_menu_left()', DEBUG_DEVELOPER); 4474 if (isset($this->attributessecondary['data-align'])) { 4475 // We've already got one set, lets remove the old class so as to avoid troubles. 4476 $class = $this->attributessecondary['class']; 4477 $search = 'align-'.$this->attributessecondary['data-align']; 4478 $this->attributessecondary['class'] = str_replace($search, '', $class); 4479 } 4480 $align = $this->get_align_string($dialogue) . '-' . $this->get_align_string($button); 4481 $this->attributessecondary['data-align'] = $align; 4482 $this->attributessecondary['class'] .= ' align-'.$align; 4483 } 4484 4485 /** 4486 * Returns a string to describe the alignment. 4487 * 4488 * @param int $align One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4489 * @return string 4490 */ 4491 protected function get_align_string($align) { 4492 switch ($align) { 4493 case self::TL : 4494 return 'tl'; 4495 case self::TR : 4496 return 'tr'; 4497 case self::BL : 4498 return 'bl'; 4499 case self::BR : 4500 return 'br'; 4501 default : 4502 return 'tl'; 4503 } 4504 } 4505 4506 /** 4507 * Aligns the left corner of the dropdown. 4508 * 4509 */ 4510 public function set_menu_left() { 4511 $this->dropdownalignment = 'dropdown-menu-left'; 4512 } 4513 4514 /** 4515 * Sets a constraint for the dialogue. 4516 * 4517 * The constraint is applied when the dialogue is shown and limits the display of the dialogue to within the 4518 * element the constraint identifies. 4519 * 4520 * This is required whenever the action menu is displayed inside any CSS element with the .no-overflow class 4521 * (flexible_table and any of it's child classes are a likely candidate). 4522 * 4523 * @param string $ancestorselector A snippet of CSS used to identify the ancestor to contrain the dialogue to. 4524 */ 4525 public function set_constraint($ancestorselector) { 4526 $this->attributessecondary['data-constraint'] = $ancestorselector; 4527 } 4528 4529 /** 4530 * Set the overflow constraint boundary of the dropdown menu. 4531 * @see https://getbootstrap.com/docs/4.6/components/dropdowns/#options The 'boundary' option in the Bootstrap documentation 4532 * 4533 * @param string $boundary Accepts the values of 'viewport', 'window', or 'scrollParent'. 4534 * @throws coding_exception 4535 */ 4536 public function set_boundary(string $boundary) { 4537 if (!in_array($boundary, ['viewport', 'window', 'scrollParent'])) { 4538 throw new coding_exception("HTMLElement reference boundaries are not supported." . 4539 "Accepted boundaries are 'viewport', 'window', or 'scrollParent'.", DEBUG_DEVELOPER); 4540 } 4541 4542 $this->triggerattributes['data-boundary'] = $boundary; 4543 } 4544 4545 /** 4546 * If you call this method the action menu will be displayed but will not be enhanced. 4547 * 4548 * By not displaying the menu enhanced all items will be displayed in a single row. 4549 * 4550 * @deprecated since Moodle 3.2 4551 */ 4552 public function do_not_enhance() { 4553 debugging('The method action_menu::do_not_enhance() is deprecated, use a list of action_icon instead.', DEBUG_DEVELOPER); 4554 } 4555 4556 /** 4557 * Returns true if this action menu will be enhanced. 4558 * 4559 * @return bool 4560 */ 4561 public function will_be_enhanced() { 4562 return isset($this->attributes['data-enhance']); 4563 } 4564 4565 /** 4566 * Sets nowrap on items. If true menu items should not wrap lines if they are longer than the available space. 4567 * 4568 * This property can be useful when the action menu is displayed within a parent element that is either floated 4569 * or relatively positioned. 4570 * In that situation the width of the menu is determined by the width of the parent element which may not be large 4571 * enough for the menu items without them wrapping. 4572 * This disables the wrapping so that the menu takes on the width of the longest item. 4573 * 4574 * @param bool $value If true nowrap gets set, if false it gets removed. Defaults to true. 4575 */ 4576 public function set_nowrap_on_items($value = true) { 4577 $class = 'nowrap-items'; 4578 if (!empty($this->attributes['class'])) { 4579 $pos = strpos($this->attributes['class'], $class); 4580 if ($value === true && $pos === false) { 4581 // The value is true and the class has not been set yet. Add it. 4582 $this->attributes['class'] .= ' '.$class; 4583 } else if ($value === false && $pos !== false) { 4584 // The value is false and the class has been set. Remove it. 4585 $this->attributes['class'] = substr($this->attributes['class'], $pos, strlen($class)); 4586 } 4587 } else if ($value) { 4588 // The value is true and the class has not been set yet. Add it. 4589 $this->attributes['class'] = $class; 4590 } 4591 } 4592 4593 /** 4594 * Add classes to the action menu for an easier styling. 4595 * 4596 * @param string $class The class to add to attributes. 4597 */ 4598 public function set_additional_classes(string $class = '') { 4599 if (!empty($this->attributes['class'])) { 4600 $this->attributes['class'] .= " ".$class; 4601 } else { 4602 $this->attributes['class'] = $class; 4603 } 4604 } 4605 4606 /** 4607 * Export for template. 4608 * 4609 * @param renderer_base $output The renderer. 4610 * @return stdClass 4611 */ 4612 public function export_for_template(renderer_base $output) { 4613 $data = new stdClass(); 4614 // Assign a role of menubar to this action menu when: 4615 // - it contains 2 or more primary actions; or 4616 // - if it contains a primary action and secondary actions. 4617 if (count($this->primaryactions) > 1 || (!empty($this->primaryactions) && !empty($this->secondaryactions))) { 4618 $this->attributes['role'] = 'menubar'; 4619 } 4620 $attributes = $this->attributes; 4621 $attributesprimary = $this->attributesprimary; 4622 $attributessecondary = $this->attributessecondary; 4623 4624 $data->instance = $this->instance; 4625 4626 $data->classes = isset($attributes['class']) ? $attributes['class'] : ''; 4627 unset($attributes['class']); 4628 4629 $data->attributes = array_map(function($key, $value) { 4630 return [ 'name' => $key, 'value' => $value ]; 4631 }, array_keys($attributes), $attributes); 4632 4633 $primary = new stdClass(); 4634 $primary->title = ''; 4635 $primary->prioritise = $this->prioritise; 4636 4637 $primary->classes = isset($attributesprimary['class']) ? $attributesprimary['class'] : ''; 4638 unset($attributesprimary['class']); 4639 $primary->attributes = array_map(function($key, $value) { 4640 return [ 'name' => $key, 'value' => $value ]; 4641 }, array_keys($attributesprimary), $attributesprimary); 4642 $primary->triggerattributes = array_map(function($key, $value) { 4643 return [ 'name' => $key, 'value' => $value ]; 4644 }, array_keys($this->triggerattributes), $this->triggerattributes); 4645 4646 $actionicon = $this->actionicon; 4647 if (!empty($this->menutrigger)) { 4648 $primary->menutrigger = $this->menutrigger; 4649 $primary->triggerextraclasses = $this->triggerextraclasses; 4650 if ($this->actionlabel) { 4651 $primary->title = $this->actionlabel; 4652 } else if ($this->actiontext) { 4653 $primary->title = $this->actiontext; 4654 } else { 4655 $primary->title = strip_tags($this->menutrigger); 4656 } 4657 } else { 4658 $primary->title = get_string('actionsmenu'); 4659 $iconattributes = ['class' => 'iconsmall actionmenu', 'title' => $primary->title]; 4660 $actionicon = new pix_icon('t/edit_menu', '', 'moodle', $iconattributes); 4661 } 4662 4663 // If the menu trigger is within the menubar, assign a role of menuitem. Otherwise, assign as a button. 4664 $primary->triggerrole = 'button'; 4665 if (isset($attributes['role']) && $attributes['role'] === 'menubar') { 4666 $primary->triggerrole = 'menuitem'; 4667 } 4668 4669 if ($actionicon instanceof pix_icon) { 4670 $primary->icon = $actionicon->export_for_pix(); 4671 if (!empty($actionicon->attributes['alt'])) { 4672 $primary->title = $actionicon->attributes['alt']; 4673 } 4674 } else { 4675 $primary->iconraw = $actionicon ? $output->render($actionicon) : ''; 4676 } 4677 4678 $primary->actiontext = $this->actiontext ? (string) $this->actiontext : ''; 4679 $primary->items = array_map(function($item) use ($output) { 4680 $data = (object) []; 4681 if ($item instanceof action_menu_link) { 4682 $data->actionmenulink = $item->export_for_template($output); 4683 } else if ($item instanceof action_menu_filler) { 4684 $data->actionmenufiller = $item->export_for_template($output); 4685 } else if ($item instanceof action_link) { 4686 $data->actionlink = $item->export_for_template($output); 4687 } else if ($item instanceof pix_icon) { 4688 $data->pixicon = $item->export_for_template($output); 4689 } else { 4690 $data->rawhtml = ($item instanceof renderable) ? $output->render($item) : $item; 4691 } 4692 return $data; 4693 }, $this->primaryactions); 4694 4695 $secondary = new stdClass(); 4696 $secondary->classes = isset($attributessecondary['class']) ? $attributessecondary['class'] : ''; 4697 unset($attributessecondary['class']); 4698 $secondary->attributes = array_map(function($key, $value) { 4699 return [ 'name' => $key, 'value' => $value ]; 4700 }, array_keys($attributessecondary), $attributessecondary); 4701 $secondary->items = array_map(function($item) use ($output) { 4702 $data = (object) []; 4703 if ($item instanceof action_menu_link) { 4704 $data->actionmenulink = $item->export_for_template($output); 4705 } else if ($item instanceof action_menu_filler) { 4706 $data->actionmenufiller = $item->export_for_template($output); 4707 } else if ($item instanceof action_link) { 4708 $data->actionlink = $item->export_for_template($output); 4709 } else if ($item instanceof pix_icon) { 4710 $data->pixicon = $item->export_for_template($output); 4711 } else { 4712 $data->rawhtml = ($item instanceof renderable) ? $output->render($item) : $item; 4713 } 4714 return $data; 4715 }, $this->secondaryactions); 4716 4717 $data->primary = $primary; 4718 $data->secondary = $secondary; 4719 $data->dropdownalignment = $this->dropdownalignment; 4720 4721 return $data; 4722 } 4723 4724 } 4725 4726 /** 4727 * An action menu filler 4728 * 4729 * @package core 4730 * @category output 4731 * @copyright 2013 Andrew Nicols 4732 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4733 */ 4734 class action_menu_filler extends action_link implements renderable { 4735 4736 /** 4737 * True if this is a primary action. False if not. 4738 * @var bool 4739 */ 4740 public $primary = true; 4741 4742 /** 4743 * Constructs the object. 4744 */ 4745 public function __construct() { 4746 $this->attributes['id'] = html_writer::random_id('action_link'); 4747 } 4748 } 4749 4750 /** 4751 * An action menu action 4752 * 4753 * @package core 4754 * @category output 4755 * @copyright 2013 Sam Hemelryk 4756 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4757 */ 4758 class action_menu_link extends action_link implements renderable { 4759 4760 /** 4761 * True if this is a primary action. False if not. 4762 * @var bool 4763 */ 4764 public $primary = true; 4765 4766 /** 4767 * The action menu this link has been added to. 4768 * @var action_menu 4769 */ 4770 public $actionmenu = null; 4771 4772 /** 4773 * The number of instances of this action menu link (and its subclasses). 4774 * @var int 4775 */ 4776 protected static $instance = 1; 4777 4778 /** 4779 * Constructs the object. 4780 * 4781 * @param moodle_url $url The URL for the action. 4782 * @param pix_icon|null $icon The icon to represent the action. 4783 * @param string $text The text to represent the action. 4784 * @param bool $primary Whether this is a primary action or not. 4785 * @param array $attributes Any attribtues associated with the action. 4786 */ 4787 public function __construct(moodle_url $url, ?pix_icon $icon, $text, $primary = true, array $attributes = array()) { 4788 parent::__construct($url, $text, null, $attributes, $icon); 4789 $this->primary = (bool)$primary; 4790 $this->add_class('menu-action'); 4791 $this->attributes['role'] = 'menuitem'; 4792 } 4793 4794 /** 4795 * Export for template. 4796 * 4797 * @param renderer_base $output The renderer. 4798 * @return stdClass 4799 */ 4800 public function export_for_template(renderer_base $output) { 4801 $data = parent::export_for_template($output); 4802 $data->instance = self::$instance++; 4803 4804 // Ignore what the parent did with the attributes, except for ID and class. 4805 $data->attributes = []; 4806 $attributes = $this->attributes; 4807 unset($attributes['id']); 4808 unset($attributes['class']); 4809 4810 // Handle text being a renderable. 4811 if ($this->text instanceof renderable) { 4812 $data->text = $this->render($this->text); 4813 } 4814 4815 $data->showtext = (!$this->icon || $this->primary === false); 4816 4817 $data->icon = null; 4818 if ($this->icon) { 4819 $icon = $this->icon; 4820 if ($this->primary || !$this->actionmenu->will_be_enhanced()) { 4821 $attributes['title'] = $data->text; 4822 } 4823 $data->icon = $icon ? $icon->export_for_pix() : null; 4824 } 4825 4826 $data->disabled = !empty($attributes['disabled']); 4827 unset($attributes['disabled']); 4828 4829 $data->attributes = array_map(function($key, $value) { 4830 return [ 4831 'name' => $key, 4832 'value' => $value 4833 ]; 4834 }, array_keys($attributes), $attributes); 4835 4836 return $data; 4837 } 4838 } 4839 4840 /** 4841 * A primary action menu action 4842 * 4843 * @package core 4844 * @category output 4845 * @copyright 2013 Sam Hemelryk 4846 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4847 */ 4848 class action_menu_link_primary extends action_menu_link { 4849 /** 4850 * Constructs the object. 4851 * 4852 * @param moodle_url $url 4853 * @param pix_icon|null $icon 4854 * @param string $text 4855 * @param array $attributes 4856 */ 4857 public function __construct(moodle_url $url, ?pix_icon $icon, $text, array $attributes = array()) { 4858 parent::__construct($url, $icon, $text, true, $attributes); 4859 } 4860 } 4861 4862 /** 4863 * A secondary action menu action 4864 * 4865 * @package core 4866 * @category output 4867 * @copyright 2013 Sam Hemelryk 4868 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4869 */ 4870 class action_menu_link_secondary extends action_menu_link { 4871 /** 4872 * Constructs the object. 4873 * 4874 * @param moodle_url $url 4875 * @param pix_icon|null $icon 4876 * @param string $text 4877 * @param array $attributes 4878 */ 4879 public function __construct(moodle_url $url, ?pix_icon $icon, $text, array $attributes = array()) { 4880 parent::__construct($url, $icon, $text, false, $attributes); 4881 } 4882 } 4883 4884 /** 4885 * Represents a set of preferences groups. 4886 * 4887 * @package core 4888 * @category output 4889 * @copyright 2015 Frédéric Massart - FMCorz.net 4890 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4891 */ 4892 class preferences_groups implements renderable { 4893 4894 /** 4895 * Array of preferences_group. 4896 * @var array 4897 */ 4898 public $groups; 4899 4900 /** 4901 * Constructor. 4902 * @param array $groups of preferences_group 4903 */ 4904 public function __construct($groups) { 4905 $this->groups = $groups; 4906 } 4907 4908 } 4909 4910 /** 4911 * Represents a group of preferences page link. 4912 * 4913 * @package core 4914 * @category output 4915 * @copyright 2015 Frédéric Massart - FMCorz.net 4916 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4917 */ 4918 class preferences_group implements renderable { 4919 4920 /** 4921 * Title of the group. 4922 * @var string 4923 */ 4924 public $title; 4925 4926 /** 4927 * Array of navigation_node. 4928 * @var array 4929 */ 4930 public $nodes; 4931 4932 /** 4933 * Constructor. 4934 * @param string $title The title. 4935 * @param array $nodes of navigation_node. 4936 */ 4937 public function __construct($title, $nodes) { 4938 $this->title = $title; 4939 $this->nodes = $nodes; 4940 } 4941 } 4942 4943 /** 4944 * Progress bar class. 4945 * 4946 * Manages the display of a progress bar. 4947 * 4948 * To use this class. 4949 * - construct 4950 * - call create (or use the 3rd param to the constructor) 4951 * - call update or update_full() or update() repeatedly 4952 * 4953 * @copyright 2008 jamiesensei 4954 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4955 * @package core 4956 * @category output 4957 */ 4958 class progress_bar implements renderable, templatable { 4959 /** @var string html id */ 4960 private $html_id; 4961 /** @var int total width */ 4962 private $width; 4963 /** @var int last percentage printed */ 4964 private $percent = 0; 4965 /** @var int time when last printed */ 4966 private $lastupdate = 0; 4967 /** @var int when did we start printing this */ 4968 private $time_start = 0; 4969 4970 /** 4971 * Constructor 4972 * 4973 * Prints JS code if $autostart true. 4974 * 4975 * @param string $htmlid The container ID. 4976 * @param int $width The suggested width. 4977 * @param bool $autostart Whether to start the progress bar right away. 4978 */ 4979 public function __construct($htmlid = '', $width = 500, $autostart = false) { 4980 if (!CLI_SCRIPT && !NO_OUTPUT_BUFFERING) { 4981 debugging('progress_bar used in a non-CLI script without setting NO_OUTPUT_BUFFERING.', DEBUG_DEVELOPER); 4982 } 4983 4984 if (!empty($htmlid)) { 4985 $this->html_id = $htmlid; 4986 } else { 4987 $this->html_id = 'pbar_'.uniqid(); 4988 } 4989 4990 $this->width = $width; 4991 4992 if ($autostart) { 4993 $this->create(); 4994 } 4995 } 4996 4997 /** 4998 * Getter for ID 4999 * @return string id 5000 */ 5001 public function get_id() : string { 5002 return $this->html_id; 5003 } 5004 5005 /** 5006 * Create a new progress bar, this function will output html. 5007 * 5008 * @return void Echo's output 5009 */ 5010 public function create() { 5011 global $OUTPUT; 5012 5013 $this->time_start = microtime(true); 5014 5015 flush(); 5016 echo $OUTPUT->render($this); 5017 flush(); 5018 } 5019 5020 /** 5021 * Update the progress bar. 5022 * 5023 * @param int $percent From 1-100. 5024 * @param string $msg The message. 5025 * @return void Echo's output 5026 * @throws coding_exception 5027 */ 5028 private function _update($percent, $msg) { 5029 global $OUTPUT; 5030 5031 if (empty($this->time_start)) { 5032 throw new coding_exception('You must call create() (or use the $autostart ' . 5033 'argument to the constructor) before you try updating the progress bar.'); 5034 } 5035 5036 $estimate = $this->estimate($percent); 5037 5038 if ($estimate === null) { 5039 // Always do the first and last updates. 5040 } else if ($estimate == 0) { 5041 // Always do the last updates. 5042 } else if ($this->lastupdate + 20 < time()) { 5043 // We must update otherwise browser would time out. 5044 } else if (round($this->percent, 2) === round($percent, 2)) { 5045 // No significant change, no need to update anything. 5046 return; 5047 } 5048 5049 $estimatemsg = ''; 5050 if ($estimate != 0 && is_numeric($estimate)) { 5051 $estimatemsg = format_time(round($estimate)); 5052 } 5053 5054 $this->percent = $percent; 5055 $this->lastupdate = microtime(true); 5056 5057 echo $OUTPUT->render_progress_bar_update($this->html_id, sprintf("%.1f", $this->percent), $msg, $estimatemsg); 5058 flush(); 5059 } 5060 5061 /** 5062 * Estimate how much time it is going to take. 5063 * 5064 * @param int $pt From 1-100. 5065 * @return mixed Null (unknown), or int. 5066 */ 5067 private function estimate($pt) { 5068 if ($this->lastupdate == 0) { 5069 return null; 5070 } 5071 if ($pt < 0.00001) { 5072 return null; // We do not know yet how long it will take. 5073 } 5074 if ($pt > 99.99999) { 5075 return 0; // Nearly done, right? 5076 } 5077 $consumed = microtime(true) - $this->time_start; 5078 if ($consumed < 0.001) { 5079 return null; 5080 } 5081 5082 return (100 - $pt) * ($consumed / $pt); 5083 } 5084 5085 /** 5086 * Update progress bar according percent. 5087 * 5088 * @param int $percent From 1-100. 5089 * @param string $msg The message needed to be shown. 5090 */ 5091 public function update_full($percent, $msg) { 5092 $percent = max(min($percent, 100), 0); 5093 $this->_update($percent, $msg); 5094 } 5095 5096 /** 5097 * Update progress bar according the number of tasks. 5098 * 5099 * @param int $cur Current task number. 5100 * @param int $total Total task number. 5101 * @param string $msg The message needed to be shown. 5102 */ 5103 public function update($cur, $total, $msg) { 5104 $percent = ($cur / $total) * 100; 5105 $this->update_full($percent, $msg); 5106 } 5107 5108 /** 5109 * Restart the progress bar. 5110 */ 5111 public function restart() { 5112 $this->percent = 0; 5113 $this->lastupdate = 0; 5114 $this->time_start = 0; 5115 } 5116 5117 /** 5118 * Export for template. 5119 * 5120 * @param renderer_base $output The renderer. 5121 * @return array 5122 */ 5123 public function export_for_template(renderer_base $output) { 5124 return [ 5125 'id' => $this->html_id, 5126 'width' => $this->width, 5127 ]; 5128 } 5129 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body