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