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