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