Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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' => $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 $params = $this->url->params(); 925 if ($this->method === 'post') { 926 $params['sesskey'] = sesskey(); 927 } 928 $data->params = array_map(function($key) use ($params) { 929 return ['name' => $key, 'value' => $params[$key]]; 930 }, array_keys($params)); 931 932 // Button actions. 933 $actions = $this->actions; 934 $data->actions = array_map(function($action) use ($output) { 935 return $action->export_for_template($output); 936 }, $actions); 937 $data->hasactions = !empty($data->actions); 938 939 return $data; 940 } 941 } 942 943 944 /** 945 * Simple form with just one select field that gets submitted automatically. 946 * 947 * If JS not enabled small go button is printed too. 948 * 949 * @copyright 2009 Petr Skoda 950 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 951 * @since Moodle 2.0 952 * @package core 953 * @category output 954 */ 955 class single_select implements renderable, templatable { 956 957 /** 958 * @var moodle_url Target url - includes hidden fields 959 */ 960 var $url; 961 962 /** 963 * @var string Name of the select element. 964 */ 965 var $name; 966 967 /** 968 * @var array $options associative array value=>label ex.: array(1=>'One, 2=>Two) 969 * it is also possible to specify optgroup as complex label array ex.: 970 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 971 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 972 */ 973 var $options; 974 975 /** 976 * @var string Selected option 977 */ 978 var $selected; 979 980 /** 981 * @var array Nothing selected 982 */ 983 var $nothing; 984 985 /** 986 * @var array Extra select field attributes 987 */ 988 var $attributes = array(); 989 990 /** 991 * @var string Button label 992 */ 993 var $label = ''; 994 995 /** 996 * @var array Button label's attributes 997 */ 998 var $labelattributes = array(); 999 1000 /** 1001 * @var string Form submit method post or get 1002 */ 1003 var $method = 'get'; 1004 1005 /** 1006 * @var string Wrapping div class 1007 */ 1008 var $class = 'singleselect'; 1009 1010 /** 1011 * @var bool True if button disabled, false if normal 1012 */ 1013 var $disabled = false; 1014 1015 /** 1016 * @var string Button tooltip 1017 */ 1018 var $tooltip = null; 1019 1020 /** 1021 * @var string Form id 1022 */ 1023 var $formid = null; 1024 1025 /** 1026 * @var help_icon The help icon for this element. 1027 */ 1028 var $helpicon = null; 1029 1030 /** 1031 * Constructor 1032 * @param moodle_url $url form action target, includes hidden fields 1033 * @param string $name name of selection field - the changing parameter in url 1034 * @param array $options list of options 1035 * @param string $selected selected element 1036 * @param array $nothing 1037 * @param string $formid 1038 */ 1039 public function __construct(moodle_url $url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) { 1040 $this->url = $url; 1041 $this->name = $name; 1042 $this->options = $options; 1043 $this->selected = $selected; 1044 $this->nothing = $nothing; 1045 $this->formid = $formid; 1046 } 1047 1048 /** 1049 * Shortcut for adding a JS confirm dialog when the button is clicked. 1050 * The message must be a yes/no question. 1051 * 1052 * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur. 1053 */ 1054 public function add_confirm_action($confirmmessage) { 1055 $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage))); 1056 } 1057 1058 /** 1059 * Add action to the button. 1060 * 1061 * @param component_action $action 1062 */ 1063 public function add_action(component_action $action) { 1064 $this->actions[] = $action; 1065 } 1066 1067 /** 1068 * Adds help icon. 1069 * 1070 * @deprecated since Moodle 2.0 1071 */ 1072 public function set_old_help_icon($helppage, $title, $component = 'moodle') { 1073 throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); 1074 } 1075 1076 /** 1077 * Adds help icon. 1078 * 1079 * @param string $identifier The keyword that defines a help page 1080 * @param string $component 1081 */ 1082 public function set_help_icon($identifier, $component = 'moodle') { 1083 $this->helpicon = new help_icon($identifier, $component); 1084 } 1085 1086 /** 1087 * Sets select's label 1088 * 1089 * @param string $label 1090 * @param array $attributes (optional) 1091 */ 1092 public function set_label($label, $attributes = array()) { 1093 $this->label = $label; 1094 $this->labelattributes = $attributes; 1095 1096 } 1097 1098 /** 1099 * Export data. 1100 * 1101 * @param renderer_base $output Renderer. 1102 * @return stdClass 1103 */ 1104 public function export_for_template(renderer_base $output) { 1105 $attributes = $this->attributes; 1106 1107 $data = new stdClass(); 1108 $data->name = $this->name; 1109 $data->method = $this->method; 1110 $data->action = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring(); 1111 $data->classes = $this->class; 1112 $data->label = $this->label; 1113 $data->disabled = $this->disabled; 1114 $data->title = $this->tooltip; 1115 $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('single_select_f'); 1116 $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('single_select'); 1117 1118 // Select element attributes. 1119 // Unset attributes that are already predefined in the template. 1120 unset($attributes['id']); 1121 unset($attributes['class']); 1122 unset($attributes['name']); 1123 unset($attributes['title']); 1124 unset($attributes['disabled']); 1125 1126 // Map the attributes. 1127 $data->attributes = array_map(function($key) use ($attributes) { 1128 return ['name' => $key, 'value' => $attributes[$key]]; 1129 }, array_keys($attributes)); 1130 1131 // Form parameters. 1132 $params = $this->url->params(); 1133 if ($this->method === 'post') { 1134 $params['sesskey'] = sesskey(); 1135 } 1136 $data->params = array_map(function($key) use ($params) { 1137 return ['name' => $key, 'value' => $params[$key]]; 1138 }, array_keys($params)); 1139 1140 // Select options. 1141 $hasnothing = false; 1142 if (is_string($this->nothing) && $this->nothing !== '') { 1143 $nothing = ['' => $this->nothing]; 1144 $hasnothing = true; 1145 $nothingkey = ''; 1146 } else if (is_array($this->nothing)) { 1147 $nothingvalue = reset($this->nothing); 1148 if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { 1149 $nothing = [key($this->nothing) => get_string('choosedots')]; 1150 } else { 1151 $nothing = $this->nothing; 1152 } 1153 $hasnothing = true; 1154 $nothingkey = key($this->nothing); 1155 } 1156 if ($hasnothing) { 1157 $options = $nothing + $this->options; 1158 } else { 1159 $options = $this->options; 1160 } 1161 1162 foreach ($options as $value => $name) { 1163 if (is_array($options[$value])) { 1164 foreach ($options[$value] as $optgroupname => $optgroupvalues) { 1165 $sublist = []; 1166 foreach ($optgroupvalues as $optvalue => $optname) { 1167 $option = [ 1168 'value' => $optvalue, 1169 'name' => $optname, 1170 'selected' => strval($this->selected) === strval($optvalue), 1171 ]; 1172 1173 if ($hasnothing && $nothingkey === $optvalue) { 1174 $option['ignore'] = 'data-ignore'; 1175 } 1176 1177 $sublist[] = $option; 1178 } 1179 $data->options[] = [ 1180 'name' => $optgroupname, 1181 'optgroup' => true, 1182 'options' => $sublist 1183 ]; 1184 } 1185 } else { 1186 $option = [ 1187 'value' => $value, 1188 'name' => $options[$value], 1189 'selected' => strval($this->selected) === strval($value), 1190 'optgroup' => false 1191 ]; 1192 1193 if ($hasnothing && $nothingkey === $value) { 1194 $option['ignore'] = 'data-ignore'; 1195 } 1196 1197 $data->options[] = $option; 1198 } 1199 } 1200 1201 // Label attributes. 1202 $data->labelattributes = []; 1203 // Unset label attributes that are already in the template. 1204 unset($this->labelattributes['for']); 1205 // Map the label attributes. 1206 foreach ($this->labelattributes as $key => $value) { 1207 $data->labelattributes[] = ['name' => $key, 'value' => $value]; 1208 } 1209 1210 // Help icon. 1211 $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; 1212 1213 return $data; 1214 } 1215 } 1216 1217 /** 1218 * Simple URL selection widget description. 1219 * 1220 * @copyright 2009 Petr Skoda 1221 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1222 * @since Moodle 2.0 1223 * @package core 1224 * @category output 1225 */ 1226 class url_select implements renderable, templatable { 1227 /** 1228 * @var array $urls associative array value=>label ex.: array(1=>'One, 2=>Two) 1229 * it is also possible to specify optgroup as complex label array ex.: 1230 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 1231 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 1232 */ 1233 var $urls; 1234 1235 /** 1236 * @var string Selected option 1237 */ 1238 var $selected; 1239 1240 /** 1241 * @var array Nothing selected 1242 */ 1243 var $nothing; 1244 1245 /** 1246 * @var array Extra select field attributes 1247 */ 1248 var $attributes = array(); 1249 1250 /** 1251 * @var string Button label 1252 */ 1253 var $label = ''; 1254 1255 /** 1256 * @var array Button label's attributes 1257 */ 1258 var $labelattributes = array(); 1259 1260 /** 1261 * @var string Wrapping div class 1262 */ 1263 var $class = 'urlselect'; 1264 1265 /** 1266 * @var bool True if button disabled, false if normal 1267 */ 1268 var $disabled = false; 1269 1270 /** 1271 * @var string Button tooltip 1272 */ 1273 var $tooltip = null; 1274 1275 /** 1276 * @var string Form id 1277 */ 1278 var $formid = null; 1279 1280 /** 1281 * @var help_icon The help icon for this element. 1282 */ 1283 var $helpicon = null; 1284 1285 /** 1286 * @var string If set, makes button visible with given name for button 1287 */ 1288 var $showbutton = null; 1289 1290 /** 1291 * Constructor 1292 * @param array $urls list of options 1293 * @param string $selected selected element 1294 * @param array $nothing 1295 * @param string $formid 1296 * @param string $showbutton Set to text of button if it should be visible 1297 * or null if it should be hidden (hidden version always has text 'go') 1298 */ 1299 public function __construct(array $urls, $selected = '', $nothing = array('' => 'choosedots'), $formid = null, $showbutton = null) { 1300 $this->urls = $urls; 1301 $this->selected = $selected; 1302 $this->nothing = $nothing; 1303 $this->formid = $formid; 1304 $this->showbutton = $showbutton; 1305 } 1306 1307 /** 1308 * Adds help icon. 1309 * 1310 * @deprecated since Moodle 2.0 1311 */ 1312 public function set_old_help_icon($helppage, $title, $component = 'moodle') { 1313 throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); 1314 } 1315 1316 /** 1317 * Adds help icon. 1318 * 1319 * @param string $identifier The keyword that defines a help page 1320 * @param string $component 1321 */ 1322 public function set_help_icon($identifier, $component = 'moodle') { 1323 $this->helpicon = new help_icon($identifier, $component); 1324 } 1325 1326 /** 1327 * Sets select's label 1328 * 1329 * @param string $label 1330 * @param array $attributes (optional) 1331 */ 1332 public function set_label($label, $attributes = array()) { 1333 $this->label = $label; 1334 $this->labelattributes = $attributes; 1335 } 1336 1337 /** 1338 * Clean a URL. 1339 * 1340 * @param string $value The URL. 1341 * @return The cleaned URL. 1342 */ 1343 protected function clean_url($value) { 1344 global $CFG; 1345 1346 if (empty($value)) { 1347 // Nothing. 1348 1349 } else if (strpos($value, $CFG->wwwroot . '/') === 0) { 1350 $value = str_replace($CFG->wwwroot, '', $value); 1351 1352 } else if (strpos($value, '/') !== 0) { 1353 debugging("Invalid url_select urls parameter: url '$value' is not local relative url!", DEBUG_DEVELOPER); 1354 } 1355 1356 return $value; 1357 } 1358 1359 /** 1360 * Flatten the options for Mustache. 1361 * 1362 * This also cleans the URLs. 1363 * 1364 * @param array $options The options. 1365 * @param array $nothing The nothing option. 1366 * @return array 1367 */ 1368 protected function flatten_options($options, $nothing) { 1369 $flattened = []; 1370 1371 foreach ($options as $value => $option) { 1372 if (is_array($option)) { 1373 foreach ($option as $groupname => $optoptions) { 1374 if (!isset($flattened[$groupname])) { 1375 $flattened[$groupname] = [ 1376 'name' => $groupname, 1377 'isgroup' => true, 1378 'options' => [] 1379 ]; 1380 } 1381 foreach ($optoptions as $optvalue => $optoption) { 1382 $cleanedvalue = $this->clean_url($optvalue); 1383 $flattened[$groupname]['options'][$cleanedvalue] = [ 1384 'name' => $optoption, 1385 'value' => $cleanedvalue, 1386 'selected' => $this->selected == $optvalue, 1387 ]; 1388 } 1389 } 1390 1391 } else { 1392 $cleanedvalue = $this->clean_url($value); 1393 $flattened[$cleanedvalue] = [ 1394 'name' => $option, 1395 'value' => $cleanedvalue, 1396 'selected' => $this->selected == $value, 1397 ]; 1398 } 1399 } 1400 1401 if (!empty($nothing)) { 1402 $value = key($nothing); 1403 $name = reset($nothing); 1404 $flattened = [ 1405 $value => ['name' => $name, 'value' => $value, 'selected' => $this->selected == $value] 1406 ] + $flattened; 1407 } 1408 1409 // Make non-associative array. 1410 foreach ($flattened as $key => $value) { 1411 if (!empty($value['options'])) { 1412 $flattened[$key]['options'] = array_values($value['options']); 1413 } 1414 } 1415 $flattened = array_values($flattened); 1416 1417 return $flattened; 1418 } 1419 1420 /** 1421 * Export for template. 1422 * 1423 * @param renderer_base $output Renderer. 1424 * @return stdClass 1425 */ 1426 public function export_for_template(renderer_base $output) { 1427 $attributes = $this->attributes; 1428 1429 $data = new stdClass(); 1430 $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('url_select_f'); 1431 $data->classes = $this->class; 1432 $data->label = $this->label; 1433 $data->disabled = $this->disabled; 1434 $data->title = $this->tooltip; 1435 $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('url_select'); 1436 $data->sesskey = sesskey(); 1437 $data->action = (new moodle_url('/course/jumpto.php'))->out(false); 1438 1439 // Remove attributes passed as property directly. 1440 unset($attributes['class']); 1441 unset($attributes['id']); 1442 unset($attributes['name']); 1443 unset($attributes['title']); 1444 unset($attributes['disabled']); 1445 1446 $data->showbutton = $this->showbutton; 1447 1448 // Select options. 1449 $nothing = false; 1450 if (is_string($this->nothing) && $this->nothing !== '') { 1451 $nothing = ['' => $this->nothing]; 1452 } else if (is_array($this->nothing)) { 1453 $nothingvalue = reset($this->nothing); 1454 if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { 1455 $nothing = [key($this->nothing) => get_string('choosedots')]; 1456 } else { 1457 $nothing = $this->nothing; 1458 } 1459 } 1460 $data->options = $this->flatten_options($this->urls, $nothing); 1461 1462 // Label attributes. 1463 $data->labelattributes = []; 1464 // Unset label attributes that are already in the template. 1465 unset($this->labelattributes['for']); 1466 // Map the label attributes. 1467 foreach ($this->labelattributes as $key => $value) { 1468 $data->labelattributes[] = ['name' => $key, 'value' => $value]; 1469 } 1470 1471 // Help icon. 1472 $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; 1473 1474 // Finally all the remaining attributes. 1475 $data->attributes = []; 1476 foreach ($attributes as $key => $value) { 1477 $data->attributes[] = ['name' => $key, 'value' => $value]; 1478 } 1479 1480 return $data; 1481 } 1482 } 1483 1484 /** 1485 * Data structure describing html link with special action attached. 1486 * 1487 * @copyright 2010 Petr Skoda 1488 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1489 * @since Moodle 2.0 1490 * @package core 1491 * @category output 1492 */ 1493 class action_link implements renderable { 1494 1495 /** 1496 * @var moodle_url Href url 1497 */ 1498 public $url; 1499 1500 /** 1501 * @var string Link text HTML fragment 1502 */ 1503 public $text; 1504 1505 /** 1506 * @var array HTML attributes 1507 */ 1508 public $attributes; 1509 1510 /** 1511 * @var array List of actions attached to link 1512 */ 1513 public $actions; 1514 1515 /** 1516 * @var pix_icon Optional pix icon to render with the link 1517 */ 1518 public $icon; 1519 1520 /** 1521 * Constructor 1522 * @param moodle_url $url 1523 * @param string $text HTML fragment 1524 * @param component_action $action 1525 * @param array $attributes associative array of html link attributes + disabled 1526 * @param pix_icon $icon optional pix_icon to render with the link text 1527 */ 1528 public function __construct(moodle_url $url, 1529 $text, 1530 component_action $action=null, 1531 array $attributes=null, 1532 pix_icon $icon=null) { 1533 $this->url = clone($url); 1534 $this->text = $text; 1535 $this->attributes = (array)$attributes; 1536 if ($action) { 1537 $this->add_action($action); 1538 } 1539 $this->icon = $icon; 1540 } 1541 1542 /** 1543 * Add action to the link. 1544 * 1545 * @param component_action $action 1546 */ 1547 public function add_action(component_action $action) { 1548 $this->actions[] = $action; 1549 } 1550 1551 /** 1552 * Adds a CSS class to this action link object 1553 * @param string $class 1554 */ 1555 public function add_class($class) { 1556 if (empty($this->attributes['class'])) { 1557 $this->attributes['class'] = $class; 1558 } else { 1559 $this->attributes['class'] .= ' ' . $class; 1560 } 1561 } 1562 1563 /** 1564 * Returns true if the specified class has been added to this link. 1565 * @param string $class 1566 * @return bool 1567 */ 1568 public function has_class($class) { 1569 return strpos(' ' . $this->attributes['class'] . ' ', ' ' . $class . ' ') !== false; 1570 } 1571 1572 /** 1573 * Return the rendered HTML for the icon. Useful for rendering action links in a template. 1574 * @return string 1575 */ 1576 public function get_icon_html() { 1577 global $OUTPUT; 1578 if (!$this->icon) { 1579 return ''; 1580 } 1581 return $OUTPUT->render($this->icon); 1582 } 1583 1584 /** 1585 * Export for template. 1586 * 1587 * @param renderer_base $output The renderer. 1588 * @return stdClass 1589 */ 1590 public function export_for_template(renderer_base $output) { 1591 $data = new stdClass(); 1592 $attributes = $this->attributes; 1593 1594 if (empty($attributes['id'])) { 1595 $attributes['id'] = html_writer::random_id('action_link'); 1596 } 1597 $data->id = $attributes['id']; 1598 unset($attributes['id']); 1599 1600 $data->disabled = !empty($attributes['disabled']); 1601 unset($attributes['disabled']); 1602 1603 $data->text = $this->text instanceof renderable ? $output->render($this->text) : (string) $this->text; 1604 $data->url = $this->url ? $this->url->out(false) : ''; 1605 $data->icon = $this->icon ? $this->icon->export_for_pix() : null; 1606 $data->classes = isset($attributes['class']) ? $attributes['class'] : ''; 1607 unset($attributes['class']); 1608 1609 $data->attributes = array_map(function($key, $value) { 1610 return [ 1611 'name' => $key, 1612 'value' => $value 1613 ]; 1614 }, array_keys($attributes), $attributes); 1615 1616 $data->actions = array_map(function($action) use ($output) { 1617 return $action->export_for_template($output); 1618 }, !empty($this->actions) ? $this->actions : []); 1619 $data->hasactions = !empty($this->actions); 1620 1621 return $data; 1622 } 1623 } 1624 1625 /** 1626 * Simple html output class 1627 * 1628 * @copyright 2009 Tim Hunt, 2010 Petr Skoda 1629 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1630 * @since Moodle 2.0 1631 * @package core 1632 * @category output 1633 */ 1634 class html_writer { 1635 1636 /** 1637 * Outputs a tag with attributes and contents 1638 * 1639 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1640 * @param string $contents What goes between the opening and closing tags 1641 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1642 * @return string HTML fragment 1643 */ 1644 public static function tag($tagname, $contents, array $attributes = null) { 1645 return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname); 1646 } 1647 1648 /** 1649 * Outputs an opening tag with attributes 1650 * 1651 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1652 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1653 * @return string HTML fragment 1654 */ 1655 public static function start_tag($tagname, array $attributes = null) { 1656 return '<' . $tagname . self::attributes($attributes) . '>'; 1657 } 1658 1659 /** 1660 * Outputs a closing tag 1661 * 1662 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1663 * @return string HTML fragment 1664 */ 1665 public static function end_tag($tagname) { 1666 return '</' . $tagname . '>'; 1667 } 1668 1669 /** 1670 * Outputs an empty tag with attributes 1671 * 1672 * @param string $tagname The name of tag ('input', 'img', 'br' etc.) 1673 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1674 * @return string HTML fragment 1675 */ 1676 public static function empty_tag($tagname, array $attributes = null) { 1677 return '<' . $tagname . self::attributes($attributes) . ' />'; 1678 } 1679 1680 /** 1681 * Outputs a tag, but only if the contents are not empty 1682 * 1683 * @param string $tagname The name of tag ('a', 'img', 'span' etc.) 1684 * @param string $contents What goes between the opening and closing tags 1685 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1686 * @return string HTML fragment 1687 */ 1688 public static function nonempty_tag($tagname, $contents, array $attributes = null) { 1689 if ($contents === '' || is_null($contents)) { 1690 return ''; 1691 } 1692 return self::tag($tagname, $contents, $attributes); 1693 } 1694 1695 /** 1696 * Outputs a HTML attribute and value 1697 * 1698 * @param string $name The name of the attribute ('src', 'href', 'class' etc.) 1699 * @param string $value The value of the attribute. The value will be escaped with {@link s()} 1700 * @return string HTML fragment 1701 */ 1702 public static function attribute($name, $value) { 1703 if ($value instanceof moodle_url) { 1704 return ' ' . $name . '="' . $value->out() . '"'; 1705 } 1706 1707 // special case, we do not want these in output 1708 if ($value === null) { 1709 return ''; 1710 } 1711 1712 // no sloppy trimming here! 1713 return ' ' . $name . '="' . s($value) . '"'; 1714 } 1715 1716 /** 1717 * Outputs a list of HTML attributes and values 1718 * 1719 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) 1720 * The values will be escaped with {@link s()} 1721 * @return string HTML fragment 1722 */ 1723 public static function attributes(array $attributes = null) { 1724 $attributes = (array)$attributes; 1725 $output = ''; 1726 foreach ($attributes as $name => $value) { 1727 $output .= self::attribute($name, $value); 1728 } 1729 return $output; 1730 } 1731 1732 /** 1733 * Generates a simple image tag with attributes. 1734 * 1735 * @param string $src The source of image 1736 * @param string $alt The alternate text for image 1737 * @param array $attributes The tag attributes (array('height' => $max_height, 'class' => 'class1') etc.) 1738 * @return string HTML fragment 1739 */ 1740 public static function img($src, $alt, array $attributes = null) { 1741 $attributes = (array)$attributes; 1742 $attributes['src'] = $src; 1743 $attributes['alt'] = $alt; 1744 1745 return self::empty_tag('img', $attributes); 1746 } 1747 1748 /** 1749 * Generates random html element id. 1750 * 1751 * @staticvar int $counter 1752 * @staticvar type $uniq 1753 * @param string $base A string fragment that will be included in the random ID. 1754 * @return string A unique ID 1755 */ 1756 public static function random_id($base='random') { 1757 static $counter = 0; 1758 static $uniq; 1759 1760 if (!isset($uniq)) { 1761 $uniq = uniqid(); 1762 } 1763 1764 $counter++; 1765 return $base.$uniq.$counter; 1766 } 1767 1768 /** 1769 * Generates a simple html link 1770 * 1771 * @param string|moodle_url $url The URL 1772 * @param string $text The text 1773 * @param array $attributes HTML attributes 1774 * @return string HTML fragment 1775 */ 1776 public static function link($url, $text, array $attributes = null) { 1777 $attributes = (array)$attributes; 1778 $attributes['href'] = $url; 1779 return self::tag('a', $text, $attributes); 1780 } 1781 1782 /** 1783 * Generates a simple checkbox with optional label 1784 * 1785 * @param string $name The name of the checkbox 1786 * @param string $value The value of the checkbox 1787 * @param bool $checked Whether the checkbox is checked 1788 * @param string $label The label for the checkbox 1789 * @param array $attributes Any attributes to apply to the checkbox 1790 * @param array $labelattributes Any attributes to apply to the label, if present 1791 * @return string html fragment 1792 */ 1793 public static function checkbox($name, $value, $checked = true, $label = '', 1794 array $attributes = null, array $labelattributes = null) { 1795 $attributes = (array) $attributes; 1796 $output = ''; 1797 1798 if ($label !== '' and !is_null($label)) { 1799 if (empty($attributes['id'])) { 1800 $attributes['id'] = self::random_id('checkbox_'); 1801 } 1802 } 1803 $attributes['type'] = 'checkbox'; 1804 $attributes['value'] = $value; 1805 $attributes['name'] = $name; 1806 $attributes['checked'] = $checked ? 'checked' : null; 1807 1808 $output .= self::empty_tag('input', $attributes); 1809 1810 if ($label !== '' and !is_null($label)) { 1811 $labelattributes = (array) $labelattributes; 1812 $labelattributes['for'] = $attributes['id']; 1813 $output .= self::tag('label', $label, $labelattributes); 1814 } 1815 1816 return $output; 1817 } 1818 1819 /** 1820 * Generates a simple select yes/no form field 1821 * 1822 * @param string $name name of select element 1823 * @param bool $selected 1824 * @param array $attributes - html select element attributes 1825 * @return string HTML fragment 1826 */ 1827 public static function select_yes_no($name, $selected=true, array $attributes = null) { 1828 $options = array('1'=>get_string('yes'), '0'=>get_string('no')); 1829 return self::select($options, $name, $selected, null, $attributes); 1830 } 1831 1832 /** 1833 * Generates a simple select form field 1834 * 1835 * Note this function does HTML escaping on the optgroup labels, but not on the choice labels. 1836 * 1837 * @param array $options associative array value=>label ex.: 1838 * array(1=>'One, 2=>Two) 1839 * it is also possible to specify optgroup as complex label array ex.: 1840 * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) 1841 * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) 1842 * @param string $name name of select element 1843 * @param string|array $selected value or array of values depending on multiple attribute 1844 * @param array|bool $nothing add nothing selected option, or false of not added 1845 * @param array $attributes html select element attributes 1846 * @return string HTML fragment 1847 */ 1848 public static function select(array $options, $name, $selected = '', $nothing = array('' => 'choosedots'), array $attributes = null) { 1849 $attributes = (array)$attributes; 1850 if (is_array($nothing)) { 1851 foreach ($nothing as $k=>$v) { 1852 if ($v === 'choose' or $v === 'choosedots') { 1853 $nothing[$k] = get_string('choosedots'); 1854 } 1855 } 1856 $options = $nothing + $options; // keep keys, do not override 1857 1858 } else if (is_string($nothing) and $nothing !== '') { 1859 // BC 1860 $options = array(''=>$nothing) + $options; 1861 } 1862 1863 // we may accept more values if multiple attribute specified 1864 $selected = (array)$selected; 1865 foreach ($selected as $k=>$v) { 1866 $selected[$k] = (string)$v; 1867 } 1868 1869 if (!isset($attributes['id'])) { 1870 $id = 'menu'.$name; 1871 // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading 1872 $id = str_replace('[', '', $id); 1873 $id = str_replace(']', '', $id); 1874 $attributes['id'] = $id; 1875 } 1876 1877 if (!isset($attributes['class'])) { 1878 $class = 'menu'.$name; 1879 // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading 1880 $class = str_replace('[', '', $class); 1881 $class = str_replace(']', '', $class); 1882 $attributes['class'] = $class; 1883 } 1884 $attributes['class'] = 'select custom-select ' . $attributes['class']; // Add 'select' selector always. 1885 1886 $attributes['name'] = $name; 1887 1888 if (!empty($attributes['disabled'])) { 1889 $attributes['disabled'] = 'disabled'; 1890 } else { 1891 unset($attributes['disabled']); 1892 } 1893 1894 $output = ''; 1895 foreach ($options as $value=>$label) { 1896 if (is_array($label)) { 1897 // ignore key, it just has to be unique 1898 $output .= self::select_optgroup(key($label), current($label), $selected); 1899 } else { 1900 $output .= self::select_option($label, $value, $selected); 1901 } 1902 } 1903 return self::tag('select', $output, $attributes); 1904 } 1905 1906 /** 1907 * Returns HTML to display a select box option. 1908 * 1909 * @param string $label The label to display as the option. 1910 * @param string|int $value The value the option represents 1911 * @param array $selected An array of selected options 1912 * @return string HTML fragment 1913 */ 1914 private static function select_option($label, $value, array $selected) { 1915 $attributes = array(); 1916 $value = (string)$value; 1917 if (in_array($value, $selected, true)) { 1918 $attributes['selected'] = 'selected'; 1919 } 1920 $attributes['value'] = $value; 1921 return self::tag('option', $label, $attributes); 1922 } 1923 1924 /** 1925 * Returns HTML to display a select box option group. 1926 * 1927 * @param string $groupname The label to use for the group 1928 * @param array $options The options in the group 1929 * @param array $selected An array of selected values. 1930 * @return string HTML fragment. 1931 */ 1932 private static function select_optgroup($groupname, $options, array $selected) { 1933 if (empty($options)) { 1934 return ''; 1935 } 1936 $attributes = array('label'=>$groupname); 1937 $output = ''; 1938 foreach ($options as $value=>$label) { 1939 $output .= self::select_option($label, $value, $selected); 1940 } 1941 return self::tag('optgroup', $output, $attributes); 1942 } 1943 1944 /** 1945 * This is a shortcut for making an hour selector menu. 1946 * 1947 * @param string $type The type of selector (years, months, days, hours, minutes) 1948 * @param string $name fieldname 1949 * @param int $currenttime A default timestamp in GMT 1950 * @param int $step minute spacing 1951 * @param array $attributes - html select element attributes 1952 * @return HTML fragment 1953 */ 1954 public static function select_time($type, $name, $currenttime = 0, $step = 5, array $attributes = null) { 1955 global $OUTPUT; 1956 1957 if (!$currenttime) { 1958 $currenttime = time(); 1959 } 1960 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1961 $currentdate = $calendartype->timestamp_to_date_array($currenttime); 1962 $userdatetype = $type; 1963 $timeunits = array(); 1964 1965 switch ($type) { 1966 case 'years': 1967 $timeunits = $calendartype->get_years(); 1968 $userdatetype = 'year'; 1969 break; 1970 case 'months': 1971 $timeunits = $calendartype->get_months(); 1972 $userdatetype = 'month'; 1973 $currentdate['month'] = (int)$currentdate['mon']; 1974 break; 1975 case 'days': 1976 $timeunits = $calendartype->get_days(); 1977 $userdatetype = 'mday'; 1978 break; 1979 case 'hours': 1980 for ($i=0; $i<=23; $i++) { 1981 $timeunits[$i] = sprintf("%02d",$i); 1982 } 1983 break; 1984 case 'minutes': 1985 if ($step != 1) { 1986 $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step; 1987 } 1988 1989 for ($i=0; $i<=59; $i+=$step) { 1990 $timeunits[$i] = sprintf("%02d",$i); 1991 } 1992 break; 1993 default: 1994 throw new coding_exception("Time type $type is not supported by html_writer::select_time()."); 1995 } 1996 1997 $attributes = (array) $attributes; 1998 $data = (object) [ 1999 'name' => $name, 2000 'id' => !empty($attributes['id']) ? $attributes['id'] : self::random_id('ts_'), 2001 'label' => get_string(substr($type, 0, -1), 'form'), 2002 'options' => array_map(function($value) use ($timeunits, $currentdate, $userdatetype) { 2003 return [ 2004 'name' => $timeunits[$value], 2005 'value' => $value, 2006 'selected' => $currentdate[$userdatetype] == $value 2007 ]; 2008 }, array_keys($timeunits)), 2009 ]; 2010 2011 unset($attributes['id']); 2012 unset($attributes['name']); 2013 $data->attributes = array_map(function($name) use ($attributes) { 2014 return [ 2015 'name' => $name, 2016 'value' => $attributes[$name] 2017 ]; 2018 }, array_keys($attributes)); 2019 2020 return $OUTPUT->render_from_template('core/select_time', $data); 2021 } 2022 2023 /** 2024 * Shortcut for quick making of lists 2025 * 2026 * Note: 'list' is a reserved keyword ;-) 2027 * 2028 * @param array $items 2029 * @param array $attributes 2030 * @param string $tag ul or ol 2031 * @return string 2032 */ 2033 public static function alist(array $items, array $attributes = null, $tag = 'ul') { 2034 $output = html_writer::start_tag($tag, $attributes)."\n"; 2035 foreach ($items as $item) { 2036 $output .= html_writer::tag('li', $item)."\n"; 2037 } 2038 $output .= html_writer::end_tag($tag); 2039 return $output; 2040 } 2041 2042 /** 2043 * Returns hidden input fields created from url parameters. 2044 * 2045 * @param moodle_url $url 2046 * @param array $exclude list of excluded parameters 2047 * @return string HTML fragment 2048 */ 2049 public static function input_hidden_params(moodle_url $url, array $exclude = null) { 2050 $exclude = (array)$exclude; 2051 $params = $url->params(); 2052 foreach ($exclude as $key) { 2053 unset($params[$key]); 2054 } 2055 2056 $output = ''; 2057 foreach ($params as $key => $value) { 2058 $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value); 2059 $output .= self::empty_tag('input', $attributes)."\n"; 2060 } 2061 return $output; 2062 } 2063 2064 /** 2065 * Generate a script tag containing the the specified code. 2066 * 2067 * @param string $jscode the JavaScript code 2068 * @param moodle_url|string $url optional url of the external script, $code ignored if specified 2069 * @return string HTML, the code wrapped in <script> tags. 2070 */ 2071 public static function script($jscode, $url=null) { 2072 if ($jscode) { 2073 return self::tag('script', "\n//<![CDATA[\n$jscode\n//]]>\n") . "\n"; 2074 2075 } else if ($url) { 2076 return self::tag('script', '', ['src' => $url]) . "\n"; 2077 2078 } else { 2079 return ''; 2080 } 2081 } 2082 2083 /** 2084 * Renders HTML table 2085 * 2086 * This method may modify the passed instance by adding some default properties if they are not set yet. 2087 * If this is not what you want, you should make a full clone of your data before passing them to this 2088 * method. In most cases this is not an issue at all so we do not clone by default for performance 2089 * and memory consumption reasons. 2090 * 2091 * @param html_table $table data to be rendered 2092 * @return string HTML code 2093 */ 2094 public static function table(html_table $table) { 2095 // prepare table data and populate missing properties with reasonable defaults 2096 if (!empty($table->align)) { 2097 foreach ($table->align as $key => $aa) { 2098 if ($aa) { 2099 $table->align[$key] = 'text-align:'. fix_align_rtl($aa) .';'; // Fix for RTL languages 2100 } else { 2101 $table->align[$key] = null; 2102 } 2103 } 2104 } 2105 if (!empty($table->size)) { 2106 foreach ($table->size as $key => $ss) { 2107 if ($ss) { 2108 $table->size[$key] = 'width:'. $ss .';'; 2109 } else { 2110 $table->size[$key] = null; 2111 } 2112 } 2113 } 2114 if (!empty($table->wrap)) { 2115 foreach ($table->wrap as $key => $ww) { 2116 if ($ww) { 2117 $table->wrap[$key] = 'white-space:nowrap;'; 2118 } else { 2119 $table->wrap[$key] = ''; 2120 } 2121 } 2122 } 2123 if (!empty($table->head)) { 2124 foreach ($table->head as $key => $val) { 2125 if (!isset($table->align[$key])) { 2126 $table->align[$key] = null; 2127 } 2128 if (!isset($table->size[$key])) { 2129 $table->size[$key] = null; 2130 } 2131 if (!isset($table->wrap[$key])) { 2132 $table->wrap[$key] = null; 2133 } 2134 2135 } 2136 } 2137 if (empty($table->attributes['class'])) { 2138 $table->attributes['class'] = 'generaltable'; 2139 } 2140 if (!empty($table->tablealign)) { 2141 $table->attributes['class'] .= ' boxalign' . $table->tablealign; 2142 } 2143 2144 // explicitly assigned properties override those defined via $table->attributes 2145 $table->attributes['class'] = trim($table->attributes['class']); 2146 $attributes = array_merge($table->attributes, array( 2147 'id' => $table->id, 2148 'width' => $table->width, 2149 'summary' => $table->summary, 2150 'cellpadding' => $table->cellpadding, 2151 'cellspacing' => $table->cellspacing, 2152 )); 2153 $output = html_writer::start_tag('table', $attributes) . "\n"; 2154 2155 $countcols = 0; 2156 2157 // Output a caption if present. 2158 if (!empty($table->caption)) { 2159 $captionattributes = array(); 2160 if ($table->captionhide) { 2161 $captionattributes['class'] = 'accesshide'; 2162 } 2163 $output .= html_writer::tag( 2164 'caption', 2165 $table->caption, 2166 $captionattributes 2167 ); 2168 } 2169 2170 if (!empty($table->head)) { 2171 $countcols = count($table->head); 2172 2173 $output .= html_writer::start_tag('thead', array()) . "\n"; 2174 $output .= html_writer::start_tag('tr', array()) . "\n"; 2175 $keys = array_keys($table->head); 2176 $lastkey = end($keys); 2177 2178 foreach ($table->head as $key => $heading) { 2179 // Convert plain string headings into html_table_cell objects 2180 if (!($heading instanceof html_table_cell)) { 2181 $headingtext = $heading; 2182 $heading = new html_table_cell(); 2183 $heading->text = $headingtext; 2184 $heading->header = true; 2185 } 2186 2187 if ($heading->header !== false) { 2188 $heading->header = true; 2189 } 2190 2191 $tagtype = 'td'; 2192 if ($heading->header && (string)$heading->text != '') { 2193 $tagtype = 'th'; 2194 } 2195 2196 $heading->attributes['class'] .= ' header c' . $key; 2197 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) { 2198 $heading->colspan = $table->headspan[$key]; 2199 $countcols += $table->headspan[$key] - 1; 2200 } 2201 2202 if ($key == $lastkey) { 2203 $heading->attributes['class'] .= ' lastcol'; 2204 } 2205 if (isset($table->colclasses[$key])) { 2206 $heading->attributes['class'] .= ' ' . $table->colclasses[$key]; 2207 } 2208 $heading->attributes['class'] = trim($heading->attributes['class']); 2209 $attributes = array_merge($heading->attributes, [ 2210 'style' => $table->align[$key] . $table->size[$key] . $heading->style, 2211 'colspan' => $heading->colspan, 2212 ]); 2213 2214 if ($tagtype == 'th') { 2215 $attributes['scope'] = !empty($heading->scope) ? $heading->scope : 'col'; 2216 } 2217 2218 $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n"; 2219 } 2220 $output .= html_writer::end_tag('tr') . "\n"; 2221 $output .= html_writer::end_tag('thead') . "\n"; 2222 2223 if (empty($table->data)) { 2224 // For valid XHTML strict every table must contain either a valid tr 2225 // or a valid tbody... both of which must contain a valid td 2226 $output .= html_writer::start_tag('tbody', array('class' => 'empty')); 2227 $output .= html_writer::tag('tr', html_writer::tag('td', '', array('colspan'=>count($table->head)))); 2228 $output .= html_writer::end_tag('tbody'); 2229 } 2230 } 2231 2232 if (!empty($table->data)) { 2233 $keys = array_keys($table->data); 2234 $lastrowkey = end($keys); 2235 $output .= html_writer::start_tag('tbody', array()); 2236 2237 foreach ($table->data as $key => $row) { 2238 if (($row === 'hr') && ($countcols)) { 2239 $output .= html_writer::tag('td', html_writer::tag('div', '', array('class' => 'tabledivider')), array('colspan' => $countcols)); 2240 } else { 2241 // Convert array rows to html_table_rows and cell strings to html_table_cell objects 2242 if (!($row instanceof html_table_row)) { 2243 $newrow = new html_table_row(); 2244 2245 foreach ($row as $cell) { 2246 if (!($cell instanceof html_table_cell)) { 2247 $cell = new html_table_cell($cell); 2248 } 2249 $newrow->cells[] = $cell; 2250 } 2251 $row = $newrow; 2252 } 2253 2254 if (isset($table->rowclasses[$key])) { 2255 $row->attributes['class'] .= ' ' . $table->rowclasses[$key]; 2256 } 2257 2258 if ($key == $lastrowkey) { 2259 $row->attributes['class'] .= ' lastrow'; 2260 } 2261 2262 // Explicitly assigned properties should override those defined in the attributes. 2263 $row->attributes['class'] = trim($row->attributes['class']); 2264 $trattributes = array_merge($row->attributes, array( 2265 'id' => $row->id, 2266 'style' => $row->style, 2267 )); 2268 $output .= html_writer::start_tag('tr', $trattributes) . "\n"; 2269 $keys2 = array_keys($row->cells); 2270 $lastkey = end($keys2); 2271 2272 $gotlastkey = false; //flag for sanity checking 2273 foreach ($row->cells as $key => $cell) { 2274 if ($gotlastkey) { 2275 //This should never happen. Why do we have a cell after the last cell? 2276 mtrace("A cell with key ($key) was found after the last key ($lastkey)"); 2277 } 2278 2279 if (!($cell instanceof html_table_cell)) { 2280 $mycell = new html_table_cell(); 2281 $mycell->text = $cell; 2282 $cell = $mycell; 2283 } 2284 2285 if (($cell->header === true) && empty($cell->scope)) { 2286 $cell->scope = 'row'; 2287 } 2288 2289 if (isset($table->colclasses[$key])) { 2290 $cell->attributes['class'] .= ' ' . $table->colclasses[$key]; 2291 } 2292 2293 $cell->attributes['class'] .= ' cell c' . $key; 2294 if ($key == $lastkey) { 2295 $cell->attributes['class'] .= ' lastcol'; 2296 $gotlastkey = true; 2297 } 2298 $tdstyle = ''; 2299 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : ''; 2300 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : ''; 2301 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : ''; 2302 $cell->attributes['class'] = trim($cell->attributes['class']); 2303 $tdattributes = array_merge($cell->attributes, array( 2304 'style' => $tdstyle . $cell->style, 2305 'colspan' => $cell->colspan, 2306 'rowspan' => $cell->rowspan, 2307 'id' => $cell->id, 2308 'abbr' => $cell->abbr, 2309 'scope' => $cell->scope, 2310 )); 2311 $tagtype = 'td'; 2312 if ($cell->header === true) { 2313 $tagtype = 'th'; 2314 } 2315 $output .= html_writer::tag($tagtype, $cell->text, $tdattributes) . "\n"; 2316 } 2317 } 2318 $output .= html_writer::end_tag('tr') . "\n"; 2319 } 2320 $output .= html_writer::end_tag('tbody') . "\n"; 2321 } 2322 $output .= html_writer::end_tag('table') . "\n"; 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 /** 2776 * Constructor 2777 */ 2778 public function __construct() { 2779 $this->attributes['class'] = ''; 2780 } 2781 } 2782 2783 /** 2784 * Component representing a table row. 2785 * 2786 * @copyright 2009 Nicolas Connault 2787 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2788 * @since Moodle 2.0 2789 * @package core 2790 * @category output 2791 */ 2792 class html_table_row { 2793 2794 /** 2795 * @var string Value to use for the id attribute of the row. 2796 */ 2797 public $id = null; 2798 2799 /** 2800 * @var array Array of html_table_cell objects 2801 */ 2802 public $cells = array(); 2803 2804 /** 2805 * @var string Value to use for the style attribute of the table row 2806 */ 2807 public $style = null; 2808 2809 /** 2810 * @var array Attributes of additional HTML attributes for the <tr> element 2811 */ 2812 public $attributes = array(); 2813 2814 /** 2815 * Constructor 2816 * @param array $cells 2817 */ 2818 public function __construct(array $cells=null) { 2819 $this->attributes['class'] = ''; 2820 $cells = (array)$cells; 2821 foreach ($cells as $cell) { 2822 if ($cell instanceof html_table_cell) { 2823 $this->cells[] = $cell; 2824 } else { 2825 $this->cells[] = new html_table_cell($cell); 2826 } 2827 } 2828 } 2829 } 2830 2831 /** 2832 * Component representing a table cell. 2833 * 2834 * @copyright 2009 Nicolas Connault 2835 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2836 * @since Moodle 2.0 2837 * @package core 2838 * @category output 2839 */ 2840 class html_table_cell { 2841 2842 /** 2843 * @var string Value to use for the id attribute of the cell. 2844 */ 2845 public $id = null; 2846 2847 /** 2848 * @var string The contents of the cell. 2849 */ 2850 public $text; 2851 2852 /** 2853 * @var string Abbreviated version of the contents of the cell. 2854 */ 2855 public $abbr = null; 2856 2857 /** 2858 * @var int Number of columns this cell should span. 2859 */ 2860 public $colspan = null; 2861 2862 /** 2863 * @var int Number of rows this cell should span. 2864 */ 2865 public $rowspan = null; 2866 2867 /** 2868 * @var string Defines a way to associate header cells and data cells in a table. 2869 */ 2870 public $scope = null; 2871 2872 /** 2873 * @var bool Whether or not this cell is a header cell. 2874 */ 2875 public $header = null; 2876 2877 /** 2878 * @var string Value to use for the style attribute of the table cell 2879 */ 2880 public $style = null; 2881 2882 /** 2883 * @var array Attributes of additional HTML attributes for the <td> element 2884 */ 2885 public $attributes = array(); 2886 2887 /** 2888 * Constructs a table cell 2889 * 2890 * @param string $text 2891 */ 2892 public function __construct($text = null) { 2893 $this->text = $text; 2894 $this->attributes['class'] = ''; 2895 } 2896 } 2897 2898 /** 2899 * Component representing a paging bar. 2900 * 2901 * @copyright 2009 Nicolas Connault 2902 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2903 * @since Moodle 2.0 2904 * @package core 2905 * @category output 2906 */ 2907 class paging_bar implements renderable, templatable { 2908 2909 /** 2910 * @var int The maximum number of pagelinks to display. 2911 */ 2912 public $maxdisplay = 18; 2913 2914 /** 2915 * @var int The total number of entries to be pages through.. 2916 */ 2917 public $totalcount; 2918 2919 /** 2920 * @var int The page you are currently viewing. 2921 */ 2922 public $page; 2923 2924 /** 2925 * @var int The number of entries that should be shown per page. 2926 */ 2927 public $perpage; 2928 2929 /** 2930 * @var string|moodle_url If this is a string then it is the url which will be appended with $pagevar, 2931 * an equals sign and the page number. 2932 * If this is a moodle_url object then the pagevar param will be replaced by 2933 * the page no, for each page. 2934 */ 2935 public $baseurl; 2936 2937 /** 2938 * @var string This is the variable name that you use for the pagenumber in your 2939 * code (ie. 'tablepage', 'blogpage', etc) 2940 */ 2941 public $pagevar; 2942 2943 /** 2944 * @var string A HTML link representing the "previous" page. 2945 */ 2946 public $previouslink = null; 2947 2948 /** 2949 * @var string A HTML link representing the "next" page. 2950 */ 2951 public $nextlink = null; 2952 2953 /** 2954 * @var string A HTML link representing the first page. 2955 */ 2956 public $firstlink = null; 2957 2958 /** 2959 * @var string A HTML link representing the last page. 2960 */ 2961 public $lastlink = null; 2962 2963 /** 2964 * @var array An array of strings. One of them is just a string: the current page 2965 */ 2966 public $pagelinks = array(); 2967 2968 /** 2969 * Constructor paging_bar with only the required params. 2970 * 2971 * @param int $totalcount The total number of entries available to be paged through 2972 * @param int $page The page you are currently viewing 2973 * @param int $perpage The number of entries that should be shown per page 2974 * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added 2975 * @param string $pagevar name of page parameter that holds the page number 2976 */ 2977 public function __construct($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') { 2978 $this->totalcount = $totalcount; 2979 $this->page = $page; 2980 $this->perpage = $perpage; 2981 $this->baseurl = $baseurl; 2982 $this->pagevar = $pagevar; 2983 } 2984 2985 /** 2986 * Prepares the paging bar for output. 2987 * 2988 * This method validates the arguments set up for the paging bar and then 2989 * produces fragments of HTML to assist display later on. 2990 * 2991 * @param renderer_base $output 2992 * @param moodle_page $page 2993 * @param string $target 2994 * @throws coding_exception 2995 */ 2996 public function prepare(renderer_base $output, moodle_page $page, $target) { 2997 if (!isset($this->totalcount) || is_null($this->totalcount)) { 2998 throw new coding_exception('paging_bar requires a totalcount value.'); 2999 } 3000 if (!isset($this->page) || is_null($this->page)) { 3001 throw new coding_exception('paging_bar requires a page value.'); 3002 } 3003 if (empty($this->perpage)) { 3004 throw new coding_exception('paging_bar requires a perpage value.'); 3005 } 3006 if (empty($this->baseurl)) { 3007 throw new coding_exception('paging_bar requires a baseurl value.'); 3008 } 3009 3010 if ($this->totalcount > $this->perpage) { 3011 $pagenum = $this->page - 1; 3012 3013 if ($this->page > 0) { 3014 $this->previouslink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('previous'), array('class'=>'previous')); 3015 } 3016 3017 if ($this->perpage > 0) { 3018 $lastpage = ceil($this->totalcount / $this->perpage); 3019 } else { 3020 $lastpage = 1; 3021 } 3022 3023 if ($this->page > round(($this->maxdisplay/3)*2)) { 3024 $currpage = $this->page - round($this->maxdisplay/3); 3025 3026 $this->firstlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>0)), '1', array('class'=>'first')); 3027 } else { 3028 $currpage = 0; 3029 } 3030 3031 $displaycount = $displaypage = 0; 3032 3033 while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { 3034 $displaypage = $currpage + 1; 3035 3036 if ($this->page == $currpage) { 3037 $this->pagelinks[] = html_writer::span($displaypage, 'current-page'); 3038 } else { 3039 $pagelink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$currpage)), $displaypage); 3040 $this->pagelinks[] = $pagelink; 3041 } 3042 3043 $displaycount++; 3044 $currpage++; 3045 } 3046 3047 if ($currpage < $lastpage) { 3048 $lastpageactual = $lastpage - 1; 3049 $this->lastlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$lastpageactual)), $lastpage, array('class'=>'last')); 3050 } 3051 3052 $pagenum = $this->page + 1; 3053 3054 if ($pagenum != $lastpage) { 3055 $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next')); 3056 } 3057 } 3058 } 3059 3060 /** 3061 * Export for template. 3062 * 3063 * @param renderer_base $output The renderer. 3064 * @return stdClass 3065 */ 3066 public function export_for_template(renderer_base $output) { 3067 $data = new stdClass(); 3068 $data->previous = null; 3069 $data->next = null; 3070 $data->first = null; 3071 $data->last = null; 3072 $data->label = get_string('page'); 3073 $data->pages = []; 3074 $data->haspages = $this->totalcount > $this->perpage; 3075 $data->pagesize = $this->perpage; 3076 3077 if (!$data->haspages) { 3078 return $data; 3079 } 3080 3081 if ($this->page > 0) { 3082 $data->previous = [ 3083 'page' => $this->page, 3084 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page - 1]))->out(false) 3085 ]; 3086 } 3087 3088 $currpage = 0; 3089 if ($this->page > round(($this->maxdisplay / 3) * 2)) { 3090 $currpage = $this->page - round($this->maxdisplay / 3); 3091 $data->first = [ 3092 'page' => 1, 3093 'url' => (new moodle_url($this->baseurl, [$this->pagevar => 0]))->out(false) 3094 ]; 3095 } 3096 3097 $lastpage = 1; 3098 if ($this->perpage > 0) { 3099 $lastpage = ceil($this->totalcount / $this->perpage); 3100 } 3101 3102 $displaycount = 0; 3103 $displaypage = 0; 3104 while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { 3105 $displaypage = $currpage + 1; 3106 3107 $iscurrent = $this->page == $currpage; 3108 $link = new moodle_url($this->baseurl, [$this->pagevar => $currpage]); 3109 3110 $data->pages[] = [ 3111 'page' => $displaypage, 3112 'active' => $iscurrent, 3113 'url' => $iscurrent ? null : $link->out(false) 3114 ]; 3115 3116 $displaycount++; 3117 $currpage++; 3118 } 3119 3120 if ($currpage < $lastpage) { 3121 $data->last = [ 3122 'page' => $lastpage, 3123 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $lastpage - 1]))->out(false) 3124 ]; 3125 } 3126 3127 if ($this->page + 1 != $lastpage) { 3128 $data->next = [ 3129 'page' => $this->page + 2, 3130 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page + 1]))->out(false) 3131 ]; 3132 } 3133 3134 return $data; 3135 } 3136 } 3137 3138 /** 3139 * Component representing initials bar. 3140 * 3141 * @copyright 2017 Ilya Tregubov 3142 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3143 * @since Moodle 3.3 3144 * @package core 3145 * @category output 3146 */ 3147 class initials_bar implements renderable, templatable { 3148 3149 /** 3150 * @var string Currently selected letter. 3151 */ 3152 public $current; 3153 3154 /** 3155 * @var string Class name to add to this initial bar. 3156 */ 3157 public $class; 3158 3159 /** 3160 * @var string The name to put in front of this initial bar. 3161 */ 3162 public $title; 3163 3164 /** 3165 * @var string URL parameter name for this initial. 3166 */ 3167 public $urlvar; 3168 3169 /** 3170 * @var string URL object. 3171 */ 3172 public $url; 3173 3174 /** 3175 * @var array An array of letters in the alphabet. 3176 */ 3177 public $alpha; 3178 3179 /** 3180 * Constructor initials_bar with only the required params. 3181 * 3182 * @param string $current the currently selected letter. 3183 * @param string $class class name to add to this initial bar. 3184 * @param string $title the name to put in front of this initial bar. 3185 * @param string $urlvar URL parameter name for this initial. 3186 * @param string $url URL object. 3187 * @param array $alpha of letters in the alphabet. 3188 */ 3189 public function __construct($current, $class, $title, $urlvar, $url, $alpha = null) { 3190 $this->current = $current; 3191 $this->class = $class; 3192 $this->title = $title; 3193 $this->urlvar = $urlvar; 3194 $this->url = $url; 3195 $this->alpha = $alpha; 3196 } 3197 3198 /** 3199 * Export for template. 3200 * 3201 * @param renderer_base $output The renderer. 3202 * @return stdClass 3203 */ 3204 public function export_for_template(renderer_base $output) { 3205 $data = new stdClass(); 3206 3207 if ($this->alpha == null) { 3208 $this->alpha = explode(',', get_string('alphabet', 'langconfig')); 3209 } 3210 3211 if ($this->current == 'all') { 3212 $this->current = ''; 3213 } 3214 3215 // We want to find a letter grouping size which suits the language so 3216 // find the largest group size which is less than 15 chars. 3217 // The choice of 15 chars is the largest number of chars that reasonably 3218 // fits on the smallest supported screen size. By always using a max number 3219 // of groups which is a factor of 2, we always get nice wrapping, and the 3220 // last row is always the shortest. 3221 $groupsize = count($this->alpha); 3222 $groups = 1; 3223 while ($groupsize > 15) { 3224 $groups *= 2; 3225 $groupsize = ceil(count($this->alpha) / $groups); 3226 } 3227 3228 $groupsizelimit = 0; 3229 $groupnumber = 0; 3230 foreach ($this->alpha as $letter) { 3231 if ($groupsizelimit++ > 0 && $groupsizelimit % $groupsize == 1) { 3232 $groupnumber++; 3233 } 3234 $groupletter = new stdClass(); 3235 $groupletter->name = $letter; 3236 $groupletter->url = $this->url->out(false, array($this->urlvar => $letter)); 3237 if ($letter == $this->current) { 3238 $groupletter->selected = $this->current; 3239 } 3240 if (!isset($data->group[$groupnumber])) { 3241 $data->group[$groupnumber] = new stdClass(); 3242 } 3243 $data->group[$groupnumber]->letter[] = $groupletter; 3244 } 3245 3246 $data->class = $this->class; 3247 $data->title = $this->title; 3248 $data->url = $this->url->out(false, array($this->urlvar => '')); 3249 $data->current = $this->current; 3250 $data->all = get_string('all'); 3251 3252 return $data; 3253 } 3254 } 3255 3256 /** 3257 * This class represents how a block appears on a page. 3258 * 3259 * During output, each block instance is asked to return a block_contents object, 3260 * those are then passed to the $OUTPUT->block function for display. 3261 * 3262 * contents should probably be generated using a moodle_block_..._renderer. 3263 * 3264 * Other block-like things that need to appear on the page, for example the 3265 * add new block UI, are also represented as block_contents objects. 3266 * 3267 * @copyright 2009 Tim Hunt 3268 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3269 * @since Moodle 2.0 3270 * @package core 3271 * @category output 3272 */ 3273 class block_contents { 3274 3275 /** Used when the block cannot be collapsed **/ 3276 const NOT_HIDEABLE = 0; 3277 3278 /** Used when the block can be collapsed but currently is not **/ 3279 const VISIBLE = 1; 3280 3281 /** Used when the block has been collapsed **/ 3282 const HIDDEN = 2; 3283 3284 /** 3285 * @var int Used to set $skipid. 3286 */ 3287 protected static $idcounter = 1; 3288 3289 /** 3290 * @var int All the blocks (or things that look like blocks) printed on 3291 * a page are given a unique number that can be used to construct id="" attributes. 3292 * This is set automatically be the {@link prepare()} method. 3293 * Do not try to set it manually. 3294 */ 3295 public $skipid; 3296 3297 /** 3298 * @var int If this is the contents of a real block, this should be set 3299 * to the block_instance.id. Otherwise this should be set to 0. 3300 */ 3301 public $blockinstanceid = 0; 3302 3303 /** 3304 * @var int If this is a real block instance, and there is a corresponding 3305 * block_position.id for the block on this page, this should be set to that id. 3306 * Otherwise it should be 0. 3307 */ 3308 public $blockpositionid = 0; 3309 3310 /** 3311 * @var array An array of attribute => value pairs that are put on the outer div of this 3312 * block. {@link $id} and {@link $classes} attributes should be set separately. 3313 */ 3314 public $attributes; 3315 3316 /** 3317 * @var string The title of this block. If this came from user input, it should already 3318 * have had format_string() processing done on it. This will be output inside 3319 * <h2> tags. Please do not cause invalid XHTML. 3320 */ 3321 public $title = ''; 3322 3323 /** 3324 * @var string The label to use when the block does not, or will not have a visible title. 3325 * You should never set this as well as title... it will just be ignored. 3326 */ 3327 public $arialabel = ''; 3328 3329 /** 3330 * @var string HTML for the content 3331 */ 3332 public $content = ''; 3333 3334 /** 3335 * @var array An alternative to $content, it you want a list of things with optional icons. 3336 */ 3337 public $footer = ''; 3338 3339 /** 3340 * @var string Any small print that should appear under the block to explain 3341 * to the teacher about the block, for example 'This is a sticky block that was 3342 * added in the system context.' 3343 */ 3344 public $annotation = ''; 3345 3346 /** 3347 * @var int One of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether 3348 * the user can toggle whether this block is visible. 3349 */ 3350 public $collapsible = self::NOT_HIDEABLE; 3351 3352 /** 3353 * Set this to true if the block is dockable. 3354 * @var bool 3355 */ 3356 public $dockable = false; 3357 3358 /** 3359 * @var array A (possibly empty) array of editing controls. Each element of 3360 * this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption). 3361 * $icon is the icon name. Fed to $OUTPUT->image_url. 3362 */ 3363 public $controls = array(); 3364 3365 3366 /** 3367 * Create new instance of block content 3368 * @param array $attributes 3369 */ 3370 public function __construct(array $attributes = null) { 3371 $this->skipid = self::$idcounter; 3372 self::$idcounter += 1; 3373 3374 if ($attributes) { 3375 // standard block 3376 $this->attributes = $attributes; 3377 } else { 3378 // simple "fake" blocks used in some modules and "Add new block" block 3379 $this->attributes = array('class'=>'block'); 3380 } 3381 } 3382 3383 /** 3384 * Add html class to block 3385 * 3386 * @param string $class 3387 */ 3388 public function add_class($class) { 3389 $this->attributes['class'] .= ' '.$class; 3390 } 3391 3392 /** 3393 * Check if the block is a fake block. 3394 * 3395 * @return boolean 3396 */ 3397 public function is_fake() { 3398 return isset($this->attributes['data-block']) && $this->attributes['data-block'] == '_fake'; 3399 } 3400 } 3401 3402 3403 /** 3404 * This class represents a target for where a block can go when it is being moved. 3405 * 3406 * This needs to be rendered as a form with the given hidden from fields, and 3407 * clicking anywhere in the form should submit it. The form action should be 3408 * $PAGE->url. 3409 * 3410 * @copyright 2009 Tim Hunt 3411 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3412 * @since Moodle 2.0 3413 * @package core 3414 * @category output 3415 */ 3416 class block_move_target { 3417 3418 /** 3419 * @var moodle_url Move url 3420 */ 3421 public $url; 3422 3423 /** 3424 * Constructor 3425 * @param moodle_url $url 3426 */ 3427 public function __construct(moodle_url $url) { 3428 $this->url = $url; 3429 } 3430 } 3431 3432 /** 3433 * Custom menu item 3434 * 3435 * This class is used to represent one item within a custom menu that may or may 3436 * not have children. 3437 * 3438 * @copyright 2010 Sam Hemelryk 3439 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3440 * @since Moodle 2.0 3441 * @package core 3442 * @category output 3443 */ 3444 class custom_menu_item implements renderable, templatable { 3445 3446 /** 3447 * @var string The text to show for the item 3448 */ 3449 protected $text; 3450 3451 /** 3452 * @var moodle_url The link to give the icon if it has no children 3453 */ 3454 protected $url; 3455 3456 /** 3457 * @var string A title to apply to the item. By default the text 3458 */ 3459 protected $title; 3460 3461 /** 3462 * @var int A sort order for the item, not necessary if you order things in 3463 * the CFG var. 3464 */ 3465 protected $sort; 3466 3467 /** 3468 * @var custom_menu_item A reference to the parent for this item or NULL if 3469 * it is a top level item 3470 */ 3471 protected $parent; 3472 3473 /** 3474 * @var array A array in which to store children this item has. 3475 */ 3476 protected $children = array(); 3477 3478 /** 3479 * @var int A reference to the sort var of the last child that was added 3480 */ 3481 protected $lastsort = 0; 3482 3483 /** @var array Array of other HTML attributes for the custom menu item. */ 3484 protected $attributes = []; 3485 3486 /** 3487 * Constructs the new custom menu item 3488 * 3489 * @param string $text 3490 * @param moodle_url $url A moodle url to apply as the link for this item [Optional] 3491 * @param string $title A title to apply to this item [Optional] 3492 * @param int $sort A sort or to use if we need to sort differently [Optional] 3493 * @param custom_menu_item $parent A reference to the parent custom_menu_item this child 3494 * belongs to, only if the child has a parent. [Optional] 3495 * @param array $attributes Array of other HTML attributes for the custom menu item. 3496 */ 3497 public function __construct($text, moodle_url $url = null, $title = null, $sort = null, custom_menu_item $parent = null, 3498 array $attributes = []) { 3499 $this->text = $text; 3500 $this->url = $url; 3501 $this->title = $title; 3502 $this->sort = (int)$sort; 3503 $this->parent = $parent; 3504 $this->attributes = $attributes; 3505 } 3506 3507 /** 3508 * Adds a custom menu item as a child of this node given its properties. 3509 * 3510 * @param string $text 3511 * @param moodle_url $url 3512 * @param string $title 3513 * @param int $sort 3514 * @param array $attributes Array of other HTML attributes for the custom menu item. 3515 * @return custom_menu_item 3516 */ 3517 public function add($text, moodle_url $url = null, $title = null, $sort = null, $attributes = []) { 3518 $key = count($this->children); 3519 if (empty($sort)) { 3520 $sort = $this->lastsort + 1; 3521 } 3522 $this->children[$key] = new custom_menu_item($text, $url, $title, $sort, $this, $attributes); 3523 $this->lastsort = (int)$sort; 3524 return $this->children[$key]; 3525 } 3526 3527 /** 3528 * Removes a custom menu item that is a child or descendant to the current menu. 3529 * 3530 * Returns true if child was found and removed. 3531 * 3532 * @param custom_menu_item $menuitem 3533 * @return bool 3534 */ 3535 public function remove_child(custom_menu_item $menuitem) { 3536 $removed = false; 3537 if (($key = array_search($menuitem, $this->children)) !== false) { 3538 unset($this->children[$key]); 3539 $this->children = array_values($this->children); 3540 $removed = true; 3541 } else { 3542 foreach ($this->children as $child) { 3543 if ($removed = $child->remove_child($menuitem)) { 3544 break; 3545 } 3546 } 3547 } 3548 return $removed; 3549 } 3550 3551 /** 3552 * Returns the text for this item 3553 * @return string 3554 */ 3555 public function get_text() { 3556 return $this->text; 3557 } 3558 3559 /** 3560 * Returns the url for this item 3561 * @return moodle_url 3562 */ 3563 public function get_url() { 3564 return $this->url; 3565 } 3566 3567 /** 3568 * Returns the title for this item 3569 * @return string 3570 */ 3571 public function get_title() { 3572 return $this->title; 3573 } 3574 3575 /** 3576 * Sorts and returns the children for this item 3577 * @return array 3578 */ 3579 public function get_children() { 3580 $this->sort(); 3581 return $this->children; 3582 } 3583 3584 /** 3585 * Gets the sort order for this child 3586 * @return int 3587 */ 3588 public function get_sort_order() { 3589 return $this->sort; 3590 } 3591 3592 /** 3593 * Gets the parent this child belong to 3594 * @return custom_menu_item 3595 */ 3596 public function get_parent() { 3597 return $this->parent; 3598 } 3599 3600 /** 3601 * Sorts the children this item has 3602 */ 3603 public function sort() { 3604 usort($this->children, array('custom_menu','sort_custom_menu_items')); 3605 } 3606 3607 /** 3608 * Returns true if this item has any children 3609 * @return bool 3610 */ 3611 public function has_children() { 3612 return (count($this->children) > 0); 3613 } 3614 3615 /** 3616 * Sets the text for the node 3617 * @param string $text 3618 */ 3619 public function set_text($text) { 3620 $this->text = (string)$text; 3621 } 3622 3623 /** 3624 * Sets the title for the node 3625 * @param string $title 3626 */ 3627 public function set_title($title) { 3628 $this->title = (string)$title; 3629 } 3630 3631 /** 3632 * Sets the url for the node 3633 * @param moodle_url $url 3634 */ 3635 public function set_url(moodle_url $url) { 3636 $this->url = $url; 3637 } 3638 3639 /** 3640 * Export this data so it can be used as the context for a mustache template. 3641 * 3642 * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. 3643 * @return array 3644 */ 3645 public function export_for_template(renderer_base $output) { 3646 global $CFG; 3647 3648 require_once($CFG->libdir . '/externallib.php'); 3649 3650 $syscontext = context_system::instance(); 3651 3652 $context = new stdClass(); 3653 $context->text = external_format_string($this->text, $syscontext->id); 3654 $context->url = $this->url ? $this->url->out() : null; 3655 // No need for the title if it's the same with text. 3656 if ($this->text !== $this->title) { 3657 // Show the title attribute only if it's different from the text. 3658 $context->title = external_format_string($this->title, $syscontext->id); 3659 } 3660 $context->sort = $this->sort; 3661 if (!empty($this->attributes)) { 3662 $context->attributes = $this->attributes; 3663 } 3664 $context->children = array(); 3665 if (preg_match("/^#+$/", $this->text)) { 3666 $context->divider = true; 3667 } 3668 $context->haschildren = !empty($this->children) && (count($this->children) > 0); 3669 foreach ($this->children as $child) { 3670 $child = $child->export_for_template($output); 3671 array_push($context->children, $child); 3672 } 3673 3674 return $context; 3675 } 3676 } 3677 3678 /** 3679 * Custom menu class 3680 * 3681 * This class is used to operate a custom menu that can be rendered for the page. 3682 * The custom menu is built using $CFG->custommenuitems and is a structured collection 3683 * of custom_menu_item nodes that can be rendered by the core renderer. 3684 * 3685 * To configure the custom menu: 3686 * Settings: Administration > Appearance > Themes > Theme settings 3687 * 3688 * @copyright 2010 Sam Hemelryk 3689 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3690 * @since Moodle 2.0 3691 * @package core 3692 * @category output 3693 */ 3694 class custom_menu extends custom_menu_item { 3695 3696 /** 3697 * @var string The language we should render for, null disables multilang support. 3698 */ 3699 protected $currentlanguage = null; 3700 3701 /** 3702 * Creates the custom menu 3703 * 3704 * @param string $definition the menu items definition in syntax required by {@link convert_text_to_menu_nodes()} 3705 * @param string $currentlanguage the current language code, null disables multilang support 3706 */ 3707 public function __construct($definition = '', $currentlanguage = null) { 3708 $this->currentlanguage = $currentlanguage; 3709 parent::__construct('root'); // create virtual root element of the menu 3710 if (!empty($definition)) { 3711 $this->override_children(self::convert_text_to_menu_nodes($definition, $currentlanguage)); 3712 } 3713 } 3714 3715 /** 3716 * Overrides the children of this custom menu. Useful when getting children 3717 * from $CFG->custommenuitems 3718 * 3719 * @param array $children 3720 */ 3721 public function override_children(array $children) { 3722 $this->children = array(); 3723 foreach ($children as $child) { 3724 if ($child instanceof custom_menu_item) { 3725 $this->children[] = $child; 3726 } 3727 } 3728 } 3729 3730 /** 3731 * Converts a string into a structured array of custom_menu_items which can 3732 * then be added to a custom menu. 3733 * 3734 * Structure: 3735 * text|url|title|langs 3736 * The number of hyphens at the start determines the depth of the item. The 3737 * languages are optional, comma separated list of languages the line is for. 3738 * 3739 * Example structure: 3740 * First level first item|http://www.moodle.com/ 3741 * -Second level first item|http://www.moodle.com/partners/ 3742 * -Second level second item|http://www.moodle.com/hq/ 3743 * --Third level first item|http://www.moodle.com/jobs/ 3744 * -Second level third item|http://www.moodle.com/development/ 3745 * First level second item|http://www.moodle.com/feedback/ 3746 * First level third item 3747 * English only|http://moodle.com|English only item|en 3748 * German only|http://moodle.de|Deutsch|de,de_du,de_kids 3749 * 3750 * 3751 * @static 3752 * @param string $text the menu items definition 3753 * @param string $language the language code, null disables multilang support 3754 * @return array 3755 */ 3756 public static function convert_text_to_menu_nodes($text, $language = null) { 3757 $root = new custom_menu(); 3758 $lastitem = $root; 3759 $lastdepth = 0; 3760 $hiddenitems = array(); 3761 $lines = explode("\n", $text); 3762 foreach ($lines as $linenumber => $line) { 3763 $line = trim($line); 3764 if (strlen($line) == 0) { 3765 continue; 3766 } 3767 // Parse item settings. 3768 $itemtext = null; 3769 $itemurl = null; 3770 $itemtitle = null; 3771 $itemvisible = true; 3772 $settings = explode('|', $line); 3773 foreach ($settings as $i => $setting) { 3774 $setting = trim($setting); 3775 if (!empty($setting)) { 3776 switch ($i) { 3777 case 0: // Menu text. 3778 $itemtext = ltrim($setting, '-'); 3779 break; 3780 case 1: // URL. 3781 try { 3782 $itemurl = new moodle_url($setting); 3783 } catch (moodle_exception $exception) { 3784 // We're not actually worried about this, we don't want to mess up the display 3785 // just for a wrongly entered URL. 3786 $itemurl = null; 3787 } 3788 break; 3789 case 2: // Title attribute. 3790 $itemtitle = $setting; 3791 break; 3792 case 3: // Language. 3793 if (!empty($language)) { 3794 $itemlanguages = array_map('trim', explode(',', $setting)); 3795 $itemvisible &= in_array($language, $itemlanguages); 3796 } 3797 break; 3798 } 3799 } 3800 } 3801 // Get depth of new item. 3802 preg_match('/^(\-*)/', $line, $match); 3803 $itemdepth = strlen($match[1]) + 1; 3804 // Find parent item for new item. 3805 while (($lastdepth - $itemdepth) >= 0) { 3806 $lastitem = $lastitem->get_parent(); 3807 $lastdepth--; 3808 } 3809 $lastitem = $lastitem->add($itemtext, $itemurl, $itemtitle, $linenumber + 1); 3810 $lastdepth++; 3811 if (!$itemvisible) { 3812 $hiddenitems[] = $lastitem; 3813 } 3814 } 3815 foreach ($hiddenitems as $item) { 3816 $item->parent->remove_child($item); 3817 } 3818 return $root->get_children(); 3819 } 3820 3821 /** 3822 * Sorts two custom menu items 3823 * 3824 * This function is designed to be used with the usort method 3825 * usort($this->children, array('custom_menu','sort_custom_menu_items')); 3826 * 3827 * @static 3828 * @param custom_menu_item $itema 3829 * @param custom_menu_item $itemb 3830 * @return int 3831 */ 3832 public static function sort_custom_menu_items(custom_menu_item $itema, custom_menu_item $itemb) { 3833 $itema = $itema->get_sort_order(); 3834 $itemb = $itemb->get_sort_order(); 3835 if ($itema == $itemb) { 3836 return 0; 3837 } 3838 return ($itema > $itemb) ? +1 : -1; 3839 } 3840 } 3841 3842 /** 3843 * Stores one tab 3844 * 3845 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3846 * @package core 3847 */ 3848 class tabobject implements renderable, templatable { 3849 /** @var string unique id of the tab in this tree, it is used to find selected and/or inactive tabs */ 3850 var $id; 3851 /** @var moodle_url|string link */ 3852 var $link; 3853 /** @var string text on the tab */ 3854 var $text; 3855 /** @var string title under the link, by defaul equals to text */ 3856 var $title; 3857 /** @var bool whether to display a link under the tab name when it's selected */ 3858 var $linkedwhenselected = false; 3859 /** @var bool whether the tab is inactive */ 3860 var $inactive = false; 3861 /** @var bool indicates that this tab's child is selected */ 3862 var $activated = false; 3863 /** @var bool indicates that this tab is selected */ 3864 var $selected = false; 3865 /** @var array stores children tabobjects */ 3866 var $subtree = array(); 3867 /** @var int level of tab in the tree, 0 for root (instance of tabtree), 1 for the first row of tabs */ 3868 var $level = 1; 3869 3870 /** 3871 * Constructor 3872 * 3873 * @param string $id unique id of the tab in this tree, it is used to find selected and/or inactive tabs 3874 * @param string|moodle_url $link 3875 * @param string $text text on the tab 3876 * @param string $title title under the link, by defaul equals to text 3877 * @param bool $linkedwhenselected whether to display a link under the tab name when it's selected 3878 */ 3879 public function __construct($id, $link = null, $text = '', $title = '', $linkedwhenselected = false) { 3880 $this->id = $id; 3881 $this->link = $link; 3882 $this->text = $text; 3883 $this->title = $title ? $title : $text; 3884 $this->linkedwhenselected = $linkedwhenselected; 3885 } 3886 3887 /** 3888 * Travels through tree and finds the tab to mark as selected, all parents are automatically marked as activated 3889 * 3890 * @param string $selected the id of the selected tab (whatever row it's on), 3891 * if null marks all tabs as unselected 3892 * @return bool whether this tab is selected or contains selected tab in its subtree 3893 */ 3894 protected function set_selected($selected) { 3895 if ((string)$selected === (string)$this->id) { 3896 $this->selected = true; 3897 // This tab is selected. No need to travel through subtree. 3898 return true; 3899 } 3900 foreach ($this->subtree as $subitem) { 3901 if ($subitem->set_selected($selected)) { 3902 // This tab has child that is selected. Mark it as activated. No need to check other children. 3903 $this->activated = true; 3904 return true; 3905 } 3906 } 3907 return false; 3908 } 3909 3910 /** 3911 * Travels through tree and finds a tab with specified id 3912 * 3913 * @param string $id 3914 * @return tabtree|null 3915 */ 3916 public function find($id) { 3917 if ((string)$this->id === (string)$id) { 3918 return $this; 3919 } 3920 foreach ($this->subtree as $tab) { 3921 if ($obj = $tab->find($id)) { 3922 return $obj; 3923 } 3924 } 3925 return null; 3926 } 3927 3928 /** 3929 * Allows to mark each tab's level in the tree before rendering. 3930 * 3931 * @param int $level 3932 */ 3933 protected function set_level($level) { 3934 $this->level = $level; 3935 foreach ($this->subtree as $tab) { 3936 $tab->set_level($level + 1); 3937 } 3938 } 3939 3940 /** 3941 * Export for template. 3942 * 3943 * @param renderer_base $output Renderer. 3944 * @return object 3945 */ 3946 public function export_for_template(renderer_base $output) { 3947 if ($this->inactive || ($this->selected && !$this->linkedwhenselected) || $this->activated) { 3948 $link = null; 3949 } else { 3950 $link = $this->link; 3951 } 3952 $active = $this->activated || $this->selected; 3953 3954 return (object) [ 3955 'id' => $this->id, 3956 'link' => is_object($link) ? $link->out(false) : $link, 3957 'text' => $this->text, 3958 'title' => $this->title, 3959 'inactive' => !$active && $this->inactive, 3960 'active' => $active, 3961 'level' => $this->level, 3962 ]; 3963 } 3964 3965 } 3966 3967 /** 3968 * Renderable for the main page header. 3969 * 3970 * @package core 3971 * @category output 3972 * @since 2.9 3973 * @copyright 2015 Adrian Greeve <adrian@moodle.com> 3974 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3975 */ 3976 class context_header implements renderable { 3977 3978 /** 3979 * @var string $heading Main heading. 3980 */ 3981 public $heading; 3982 /** 3983 * @var int $headinglevel Main heading 'h' tag level. 3984 */ 3985 public $headinglevel; 3986 /** 3987 * @var string|null $imagedata HTML code for the picture in the page header. 3988 */ 3989 public $imagedata; 3990 /** 3991 * @var array $additionalbuttons Additional buttons for the header e.g. Messaging button for the user header. 3992 * array elements - title => alternate text for the image, or if no image is available the button text. 3993 * url => Link for the button to head to. Should be a moodle_url. 3994 * image => location to the image, or name of the image in /pix/t/{image name}. 3995 * linkattributes => additional attributes for the <a href> element. 3996 * page => page object. Don't include if the image is an external image. 3997 */ 3998 public $additionalbuttons; 3999 4000 /** 4001 * Constructor. 4002 * 4003 * @param string $heading Main heading data. 4004 * @param int $headinglevel Main heading 'h' tag level. 4005 * @param string|null $imagedata HTML code for the picture in the page header. 4006 * @param string $additionalbuttons Buttons for the header e.g. Messaging button for the user header. 4007 */ 4008 public function __construct($heading = null, $headinglevel = 1, $imagedata = null, $additionalbuttons = null) { 4009 4010 $this->heading = $heading; 4011 $this->headinglevel = $headinglevel; 4012 $this->imagedata = $imagedata; 4013 $this->additionalbuttons = $additionalbuttons; 4014 // If we have buttons then format them. 4015 if (isset($this->additionalbuttons)) { 4016 $this->format_button_images(); 4017 } 4018 } 4019 4020 /** 4021 * Adds an array element for a formatted image. 4022 */ 4023 protected function format_button_images() { 4024 4025 foreach ($this->additionalbuttons as $buttontype => $button) { 4026 $page = $button['page']; 4027 // If no image is provided then just use the title. 4028 if (!isset($button['image'])) { 4029 $this->additionalbuttons[$buttontype]['formattedimage'] = $button['title']; 4030 } else { 4031 // Check to see if this is an internal Moodle icon. 4032 $internalimage = $page->theme->resolve_image_location('t/' . $button['image'], 'moodle'); 4033 if ($internalimage) { 4034 $this->additionalbuttons[$buttontype]['formattedimage'] = 't/' . $button['image']; 4035 } else { 4036 // Treat as an external image. 4037 $this->additionalbuttons[$buttontype]['formattedimage'] = $button['image']; 4038 } 4039 } 4040 4041 if (isset($button['linkattributes']['class'])) { 4042 $class = $button['linkattributes']['class'] . ' btn'; 4043 } else { 4044 $class = 'btn'; 4045 } 4046 // Add the bootstrap 'btn' class for formatting. 4047 $this->additionalbuttons[$buttontype]['linkattributes'] = array_merge($button['linkattributes'], 4048 array('class' => $class)); 4049 } 4050 } 4051 } 4052 4053 /** 4054 * Stores tabs list 4055 * 4056 * Example how to print a single line tabs: 4057 * $rows = array( 4058 * new tabobject(...), 4059 * new tabobject(...) 4060 * ); 4061 * echo $OUTPUT->tabtree($rows, $selectedid); 4062 * 4063 * Multiple row tabs may not look good on some devices but if you want to use them 4064 * you can specify ->subtree for the active tabobject. 4065 * 4066 * @copyright 2013 Marina Glancy 4067 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4068 * @since Moodle 2.5 4069 * @package core 4070 * @category output 4071 */ 4072 class tabtree extends tabobject { 4073 /** 4074 * Constuctor 4075 * 4076 * It is highly recommended to call constructor when list of tabs is already 4077 * populated, this way you ensure that selected and inactive tabs are located 4078 * and attribute level is set correctly. 4079 * 4080 * @param array $tabs array of tabs, each of them may have it's own ->subtree 4081 * @param string|null $selected which tab to mark as selected, all parent tabs will 4082 * automatically be marked as activated 4083 * @param array|string|null $inactive list of ids of inactive tabs, regardless of 4084 * their level. Note that you can as weel specify tabobject::$inactive for separate instances 4085 */ 4086 public function __construct($tabs, $selected = null, $inactive = null) { 4087 $this->subtree = $tabs; 4088 if ($selected !== null) { 4089 $this->set_selected($selected); 4090 } 4091 if ($inactive !== null) { 4092 if (is_array($inactive)) { 4093 foreach ($inactive as $id) { 4094 if ($tab = $this->find($id)) { 4095 $tab->inactive = true; 4096 } 4097 } 4098 } else if ($tab = $this->find($inactive)) { 4099 $tab->inactive = true; 4100 } 4101 } 4102 $this->set_level(0); 4103 } 4104 4105 /** 4106 * Export for template. 4107 * 4108 * @param renderer_base $output Renderer. 4109 * @return object 4110 */ 4111 public function export_for_template(renderer_base $output) { 4112 $tabs = []; 4113 $secondrow = false; 4114 4115 foreach ($this->subtree as $tab) { 4116 $tabs[] = $tab->export_for_template($output); 4117 if (!empty($tab->subtree) && ($tab->level == 0 || $tab->selected || $tab->activated)) { 4118 $secondrow = new tabtree($tab->subtree); 4119 } 4120 } 4121 4122 return (object) [ 4123 'tabs' => $tabs, 4124 'secondrow' => $secondrow ? $secondrow->export_for_template($output) : false 4125 ]; 4126 } 4127 } 4128 4129 /** 4130 * An action menu. 4131 * 4132 * This action menu component takes a series of primary and secondary actions. 4133 * The primary actions are displayed permanently and the secondary attributes are displayed within a drop 4134 * down menu. 4135 * 4136 * @package core 4137 * @category output 4138 * @copyright 2013 Sam Hemelryk 4139 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4140 */ 4141 class action_menu implements renderable, templatable { 4142 4143 /** 4144 * Top right alignment. 4145 */ 4146 const TL = 1; 4147 4148 /** 4149 * Top right alignment. 4150 */ 4151 const TR = 2; 4152 4153 /** 4154 * Top right alignment. 4155 */ 4156 const BL = 3; 4157 4158 /** 4159 * Top right alignment. 4160 */ 4161 const BR = 4; 4162 4163 /** 4164 * The instance number. This is unique to this instance of the action menu. 4165 * @var int 4166 */ 4167 protected $instance = 0; 4168 4169 /** 4170 * An array of primary actions. Please use {@link action_menu::add_primary_action()} to add actions. 4171 * @var array 4172 */ 4173 protected $primaryactions = array(); 4174 4175 /** 4176 * An array of secondary actions. Please use {@link action_menu::add_secondary_action()} to add actions. 4177 * @var array 4178 */ 4179 protected $secondaryactions = array(); 4180 4181 /** 4182 * An array of attributes added to the container of the action menu. 4183 * Initialised with defaults during construction. 4184 * @var array 4185 */ 4186 public $attributes = array(); 4187 /** 4188 * An array of attributes added to the container of the primary actions. 4189 * Initialised with defaults during construction. 4190 * @var array 4191 */ 4192 public $attributesprimary = array(); 4193 /** 4194 * An array of attributes added to the container of the secondary actions. 4195 * Initialised with defaults during construction. 4196 * @var array 4197 */ 4198 public $attributessecondary = array(); 4199 4200 /** 4201 * The string to use next to the icon for the action icon relating to the secondary (dropdown) menu. 4202 * @var array 4203 */ 4204 public $actiontext = null; 4205 4206 /** 4207 * The string to use for the accessible label for the menu. 4208 * @var array 4209 */ 4210 public $actionlabel = null; 4211 4212 /** 4213 * An icon to use for the toggling the secondary menu (dropdown). 4214 * @var pix_icon 4215 */ 4216 public $actionicon; 4217 4218 /** 4219 * Any text to use for the toggling the secondary menu (dropdown). 4220 * @var string 4221 */ 4222 public $menutrigger = ''; 4223 4224 /** 4225 * Any extra classes for toggling to the secondary menu. 4226 * @var string 4227 */ 4228 public $triggerextraclasses = ''; 4229 4230 /** 4231 * Place the action menu before all other actions. 4232 * @var bool 4233 */ 4234 public $prioritise = false; 4235 4236 /** 4237 * Constructs the action menu with the given items. 4238 * 4239 * @param array $actions An array of actions (action_menu_link|pix_icon|string). 4240 */ 4241 public function __construct(array $actions = array()) { 4242 static $initialised = 0; 4243 $this->instance = $initialised; 4244 $initialised++; 4245 4246 $this->attributes = array( 4247 'id' => 'action-menu-'.$this->instance, 4248 'class' => 'moodle-actionmenu', 4249 'data-enhance' => 'moodle-core-actionmenu' 4250 ); 4251 $this->attributesprimary = array( 4252 'id' => 'action-menu-'.$this->instance.'-menubar', 4253 'class' => 'menubar', 4254 ); 4255 $this->attributessecondary = array( 4256 'id' => 'action-menu-'.$this->instance.'-menu', 4257 'class' => 'menu', 4258 'data-rel' => 'menu-content', 4259 'aria-labelledby' => 'action-menu-toggle-'.$this->instance, 4260 'role' => 'menu' 4261 ); 4262 $this->set_alignment(self::TR, self::BR); 4263 foreach ($actions as $action) { 4264 $this->add($action); 4265 } 4266 } 4267 4268 /** 4269 * Sets the label for the menu trigger. 4270 * 4271 * @param string $label The text 4272 */ 4273 public function set_action_label($label) { 4274 $this->actionlabel = $label; 4275 } 4276 4277 /** 4278 * Sets the menu trigger text. 4279 * 4280 * @param string $trigger The text 4281 * @param string $extraclasses Extra classes to style the secondary menu toggle. 4282 */ 4283 public function set_menu_trigger($trigger, $extraclasses = '') { 4284 $this->menutrigger = $trigger; 4285 $this->triggerextraclasses = $extraclasses; 4286 } 4287 4288 /** 4289 * Return true if there is at least one visible link in the menu. 4290 * 4291 * @return bool 4292 */ 4293 public function is_empty() { 4294 return !count($this->primaryactions) && !count($this->secondaryactions); 4295 } 4296 4297 /** 4298 * Initialises JS required fore the action menu. 4299 * The JS is only required once as it manages all action menu's on the page. 4300 * 4301 * @param moodle_page $page 4302 */ 4303 public function initialise_js(moodle_page $page) { 4304 static $initialised = false; 4305 if (!$initialised) { 4306 $page->requires->yui_module('moodle-core-actionmenu', 'M.core.actionmenu.init'); 4307 $initialised = true; 4308 } 4309 } 4310 4311 /** 4312 * Adds an action to this action menu. 4313 * 4314 * @param action_menu_link|pix_icon|string $action 4315 */ 4316 public function add($action) { 4317 if ($action instanceof action_link) { 4318 if ($action->primary) { 4319 $this->add_primary_action($action); 4320 } else { 4321 $this->add_secondary_action($action); 4322 } 4323 } else if ($action instanceof pix_icon) { 4324 $this->add_primary_action($action); 4325 } else { 4326 $this->add_secondary_action($action); 4327 } 4328 } 4329 4330 /** 4331 * Adds a primary action to the action menu. 4332 * 4333 * @param action_menu_link|action_link|pix_icon|string $action 4334 */ 4335 public function add_primary_action($action) { 4336 if ($action instanceof action_link || $action instanceof pix_icon) { 4337 $action->attributes['role'] = 'menuitem'; 4338 $action->attributes['tabindex'] = '-1'; 4339 if ($action instanceof action_menu_link) { 4340 $action->actionmenu = $this; 4341 } 4342 } 4343 $this->primaryactions[] = $action; 4344 } 4345 4346 /** 4347 * Adds a secondary action to the action menu. 4348 * 4349 * @param action_link|pix_icon|string $action 4350 */ 4351 public function add_secondary_action($action) { 4352 if ($action instanceof action_link || $action instanceof pix_icon) { 4353 $action->attributes['role'] = 'menuitem'; 4354 $action->attributes['tabindex'] = '-1'; 4355 if ($action instanceof action_menu_link) { 4356 $action->actionmenu = $this; 4357 } 4358 } 4359 $this->secondaryactions[] = $action; 4360 } 4361 4362 /** 4363 * Returns the primary actions ready to be rendered. 4364 * 4365 * @param core_renderer $output The renderer to use for getting icons. 4366 * @return array 4367 */ 4368 public function get_primary_actions(core_renderer $output = null) { 4369 global $OUTPUT; 4370 if ($output === null) { 4371 $output = $OUTPUT; 4372 } 4373 $pixicon = $this->actionicon; 4374 $linkclasses = array('toggle-display'); 4375 4376 $title = ''; 4377 if (!empty($this->menutrigger)) { 4378 $pixicon = '<b class="caret"></b>'; 4379 $linkclasses[] = 'textmenu'; 4380 } else { 4381 $title = new lang_string('actionsmenu', 'moodle'); 4382 $this->actionicon = new pix_icon( 4383 't/edit_menu', 4384 '', 4385 'moodle', 4386 array('class' => 'iconsmall actionmenu', 'title' => '') 4387 ); 4388 $pixicon = $this->actionicon; 4389 } 4390 if ($pixicon instanceof renderable) { 4391 $pixicon = $output->render($pixicon); 4392 if ($pixicon instanceof pix_icon && isset($pixicon->attributes['alt'])) { 4393 $title = $pixicon->attributes['alt']; 4394 } 4395 } 4396 $string = ''; 4397 if ($this->actiontext) { 4398 $string = $this->actiontext; 4399 } 4400 $label = ''; 4401 if ($this->actionlabel) { 4402 $label = $this->actionlabel; 4403 } else { 4404 $label = $title; 4405 } 4406 $actions = $this->primaryactions; 4407 $attributes = array( 4408 'class' => implode(' ', $linkclasses), 4409 'title' => $title, 4410 'aria-label' => $label, 4411 'id' => 'action-menu-toggle-'.$this->instance, 4412 'role' => 'menuitem', 4413 'tabindex' => '-1', 4414 ); 4415 $link = html_writer::link('#', $string . $this->menutrigger . $pixicon, $attributes); 4416 if ($this->prioritise) { 4417 array_unshift($actions, $link); 4418 } else { 4419 $actions[] = $link; 4420 } 4421 return $actions; 4422 } 4423 4424 /** 4425 * Returns the secondary actions ready to be rendered. 4426 * @return array 4427 */ 4428 public function get_secondary_actions() { 4429 return $this->secondaryactions; 4430 } 4431 4432 /** 4433 * Sets the selector that should be used to find the owning node of this menu. 4434 * @param string $selector A CSS/YUI selector to identify the owner of the menu. 4435 */ 4436 public function set_owner_selector($selector) { 4437 $this->attributes['data-owner'] = $selector; 4438 } 4439 4440 /** 4441 * Sets the alignment of the dialogue in relation to button used to toggle it. 4442 * 4443 * @param int $dialogue One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4444 * @param int $button One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4445 */ 4446 public function set_alignment($dialogue, $button) { 4447 if (isset($this->attributessecondary['data-align'])) { 4448 // We've already got one set, lets remove the old class so as to avoid troubles. 4449 $class = $this->attributessecondary['class']; 4450 $search = 'align-'.$this->attributessecondary['data-align']; 4451 $this->attributessecondary['class'] = str_replace($search, '', $class); 4452 } 4453 $align = $this->get_align_string($dialogue) . '-' . $this->get_align_string($button); 4454 $this->attributessecondary['data-align'] = $align; 4455 $this->attributessecondary['class'] .= ' align-'.$align; 4456 } 4457 4458 /** 4459 * Returns a string to describe the alignment. 4460 * 4461 * @param int $align One of action_menu::TL, action_menu::TR, action_menu::BL, action_menu::BR. 4462 * @return string 4463 */ 4464 protected function get_align_string($align) { 4465 switch ($align) { 4466 case self::TL : 4467 return 'tl'; 4468 case self::TR : 4469 return 'tr'; 4470 case self::BL : 4471 return 'bl'; 4472 case self::BR : 4473 return 'br'; 4474 default : 4475 return 'tl'; 4476 } 4477 } 4478 4479 /** 4480 * Sets a constraint for the dialogue. 4481 * 4482 * The constraint is applied when the dialogue is shown and limits the display of the dialogue to within the 4483 * element the constraint identifies. 4484 * 4485 * This is required whenever the action menu is displayed inside any CSS element with the .no-overflow class 4486 * (flexible_table and any of it's child classes are a likely candidate). 4487 * 4488 * @param string $ancestorselector A snippet of CSS used to identify the ancestor to contrain the dialogue to. 4489 */ 4490 public function set_constraint($ancestorselector) { 4491 $this->attributessecondary['data-constraint'] = $ancestorselector; 4492 } 4493 4494 /** 4495 * If you call this method the action menu will be displayed but will not be enhanced. 4496 * 4497 * By not displaying the menu enhanced all items will be displayed in a single row. 4498 * 4499 * @deprecated since Moodle 3.2 4500 */ 4501 public function do_not_enhance() { 4502 debugging('The method action_menu::do_not_enhance() is deprecated, use a list of action_icon instead.', DEBUG_DEVELOPER); 4503 } 4504 4505 /** 4506 * Returns true if this action menu will be enhanced. 4507 * 4508 * @return bool 4509 */ 4510 public function will_be_enhanced() { 4511 return isset($this->attributes['data-enhance']); 4512 } 4513 4514 /** 4515 * Sets nowrap on items. If true menu items should not wrap lines if they are longer than the available space. 4516 * 4517 * This property can be useful when the action menu is displayed within a parent element that is either floated 4518 * or relatively positioned. 4519 * In that situation the width of the menu is determined by the width of the parent element which may not be large 4520 * enough for the menu items without them wrapping. 4521 * This disables the wrapping so that the menu takes on the width of the longest item. 4522 * 4523 * @param bool $value If true nowrap gets set, if false it gets removed. Defaults to true. 4524 */ 4525 public function set_nowrap_on_items($value = true) { 4526 $class = 'nowrap-items'; 4527 if (!empty($this->attributes['class'])) { 4528 $pos = strpos($this->attributes['class'], $class); 4529 if ($value === true && $pos === false) { 4530 // The value is true and the class has not been set yet. Add it. 4531 $this->attributes['class'] .= ' '.$class; 4532 } else if ($value === false && $pos !== false) { 4533 // The value is false and the class has been set. Remove it. 4534 $this->attributes['class'] = substr($this->attributes['class'], $pos, strlen($class)); 4535 } 4536 } else if ($value) { 4537 // The value is true and the class has not been set yet. Add it. 4538 $this->attributes['class'] = $class; 4539 } 4540 } 4541 4542 /** 4543 * Export for template. 4544 * 4545 * @param renderer_base $output The renderer. 4546 * @return stdClass 4547 */ 4548 public function export_for_template(renderer_base $output) { 4549 $data = new stdClass(); 4550 $attributes = $this->attributes; 4551 $attributesprimary = $this->attributesprimary; 4552 $attributessecondary = $this->attributessecondary; 4553 4554 $data->instance = $this->instance; 4555 4556 $data->classes = isset($attributes['class']) ? $attributes['class'] : ''; 4557 unset($attributes['class']); 4558 4559 $data->attributes = array_map(function($key, $value) { 4560 return [ 'name' => $key, 'value' => $value ]; 4561 }, array_keys($attributes), $attributes); 4562 4563 $primary = new stdClass(); 4564 $primary->title = ''; 4565 $primary->prioritise = $this->prioritise; 4566 4567 $primary->classes = isset($attributesprimary['class']) ? $attributesprimary['class'] : ''; 4568 unset($attributesprimary['class']); 4569 $primary->attributes = array_map(function($key, $value) { 4570 return [ 'name' => $key, 'value' => $value ]; 4571 }, array_keys($attributesprimary), $attributesprimary); 4572 4573 $actionicon = $this->actionicon; 4574 if (!empty($this->menutrigger)) { 4575 $primary->menutrigger = $this->menutrigger; 4576 $primary->triggerextraclasses = $this->triggerextraclasses; 4577 if ($this->actionlabel) { 4578 $primary->title = $this->actionlabel; 4579 } else if ($this->actiontext) { 4580 $primary->title = $this->actiontext; 4581 } else { 4582 $primary->title = strip_tags($this->menutrigger); 4583 } 4584 } else { 4585 $primary->title = get_string('actionsmenu'); 4586 $iconattributes = ['class' => 'iconsmall actionmenu', 'title' => $primary->title]; 4587 $actionicon = new pix_icon('t/edit_menu', '', 'moodle', $iconattributes); 4588 } 4589 4590 if ($actionicon instanceof pix_icon) { 4591 $primary->icon = $actionicon->export_for_pix(); 4592 if (!empty($actionicon->attributes['alt'])) { 4593 $primary->title = $actionicon->attributes['alt']; 4594 } 4595 } else { 4596 $primary->iconraw = $actionicon ? $output->render($actionicon) : ''; 4597 } 4598 4599 $primary->actiontext = $this->actiontext ? (string) $this->actiontext : ''; 4600 $primary->items = array_map(function($item) use ($output) { 4601 $data = (object) []; 4602 if ($item instanceof action_menu_link) { 4603 $data->actionmenulink = $item->export_for_template($output); 4604 } else if ($item instanceof action_menu_filler) { 4605 $data->actionmenufiller = $item->export_for_template($output); 4606 } else if ($item instanceof action_link) { 4607 $data->actionlink = $item->export_for_template($output); 4608 } else if ($item instanceof pix_icon) { 4609 $data->pixicon = $item->export_for_template($output); 4610 } else { 4611 $data->rawhtml = ($item instanceof renderable) ? $output->render($item) : $item; 4612 } 4613 return $data; 4614 }, $this->primaryactions); 4615 4616 $secondary = new stdClass(); 4617 $secondary->classes = isset($attributessecondary['class']) ? $attributessecondary['class'] : ''; 4618 unset($attributessecondary['class']); 4619 $secondary->attributes = array_map(function($key, $value) { 4620 return [ 'name' => $key, 'value' => $value ]; 4621 }, array_keys($attributessecondary), $attributessecondary); 4622 $secondary->items = array_map(function($item) use ($output) { 4623 $data = (object) []; 4624 if ($item instanceof action_menu_link) { 4625 $data->actionmenulink = $item->export_for_template($output); 4626 } else if ($item instanceof action_menu_filler) { 4627 $data->actionmenufiller = $item->export_for_template($output); 4628 } else if ($item instanceof action_link) { 4629 $data->actionlink = $item->export_for_template($output); 4630 } else if ($item instanceof pix_icon) { 4631 $data->pixicon = $item->export_for_template($output); 4632 } else { 4633 $data->rawhtml = ($item instanceof renderable) ? $output->render($item) : $item; 4634 } 4635 return $data; 4636 }, $this->secondaryactions); 4637 4638 $data->primary = $primary; 4639 $data->secondary = $secondary; 4640 4641 return $data; 4642 } 4643 4644 } 4645 4646 /** 4647 * An action menu filler 4648 * 4649 * @package core 4650 * @category output 4651 * @copyright 2013 Andrew Nicols 4652 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4653 */ 4654 class action_menu_filler extends action_link implements renderable { 4655 4656 /** 4657 * True if this is a primary action. False if not. 4658 * @var bool 4659 */ 4660 public $primary = true; 4661 4662 /** 4663 * Constructs the object. 4664 */ 4665 public function __construct() { 4666 } 4667 } 4668 4669 /** 4670 * An action menu action 4671 * 4672 * @package core 4673 * @category output 4674 * @copyright 2013 Sam Hemelryk 4675 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4676 */ 4677 class action_menu_link extends action_link implements renderable { 4678 4679 /** 4680 * True if this is a primary action. False if not. 4681 * @var bool 4682 */ 4683 public $primary = true; 4684 4685 /** 4686 * The action menu this link has been added to. 4687 * @var action_menu 4688 */ 4689 public $actionmenu = null; 4690 4691 /** 4692 * The number of instances of this action menu link (and its subclasses). 4693 * @var int 4694 */ 4695 protected static $instance = 1; 4696 4697 /** 4698 * Constructs the object. 4699 * 4700 * @param moodle_url $url The URL for the action. 4701 * @param pix_icon|null $icon The icon to represent the action. 4702 * @param string $text The text to represent the action. 4703 * @param bool $primary Whether this is a primary action or not. 4704 * @param array $attributes Any attribtues associated with the action. 4705 */ 4706 public function __construct(moodle_url $url, ?pix_icon $icon, $text, $primary = true, array $attributes = array()) { 4707 parent::__construct($url, $text, null, $attributes, $icon); 4708 $this->primary = (bool)$primary; 4709 $this->add_class('menu-action'); 4710 $this->attributes['role'] = 'menuitem'; 4711 } 4712 4713 /** 4714 * Export for template. 4715 * 4716 * @param renderer_base $output The renderer. 4717 * @return stdClass 4718 */ 4719 public function export_for_template(renderer_base $output) { 4720 $data = parent::export_for_template($output); 4721 $data->instance = self::$instance++; 4722 4723 // Ignore what the parent did with the attributes, except for ID and class. 4724 $data->attributes = []; 4725 $attributes = $this->attributes; 4726 unset($attributes['id']); 4727 unset($attributes['class']); 4728 4729 // Handle text being a renderable. 4730 if ($this->text instanceof renderable) { 4731 $data->text = $this->render($this->text); 4732 } 4733 4734 $data->showtext = (!$this->icon || $this->primary === false); 4735 4736 $data->icon = null; 4737 if ($this->icon) { 4738 $icon = $this->icon; 4739 if ($this->primary || !$this->actionmenu->will_be_enhanced()) { 4740 $attributes['title'] = $data->text; 4741 } 4742 $data->icon = $icon ? $icon->export_for_pix() : null; 4743 } 4744 4745 $data->disabled = !empty($attributes['disabled']); 4746 unset($attributes['disabled']); 4747 4748 $data->attributes = array_map(function($key, $value) { 4749 return [ 4750 'name' => $key, 4751 'value' => $value 4752 ]; 4753 }, array_keys($attributes), $attributes); 4754 4755 return $data; 4756 } 4757 } 4758 4759 /** 4760 * A primary action menu action 4761 * 4762 * @package core 4763 * @category output 4764 * @copyright 2013 Sam Hemelryk 4765 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4766 */ 4767 class action_menu_link_primary extends action_menu_link { 4768 /** 4769 * Constructs the object. 4770 * 4771 * @param moodle_url $url 4772 * @param pix_icon|null $icon 4773 * @param string $text 4774 * @param array $attributes 4775 */ 4776 public function __construct(moodle_url $url, ?pix_icon $icon, $text, array $attributes = array()) { 4777 parent::__construct($url, $icon, $text, true, $attributes); 4778 } 4779 } 4780 4781 /** 4782 * A secondary action menu action 4783 * 4784 * @package core 4785 * @category output 4786 * @copyright 2013 Sam Hemelryk 4787 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4788 */ 4789 class action_menu_link_secondary extends action_menu_link { 4790 /** 4791 * Constructs the object. 4792 * 4793 * @param moodle_url $url 4794 * @param pix_icon|null $icon 4795 * @param string $text 4796 * @param array $attributes 4797 */ 4798 public function __construct(moodle_url $url, ?pix_icon $icon, $text, array $attributes = array()) { 4799 parent::__construct($url, $icon, $text, false, $attributes); 4800 } 4801 } 4802 4803 /** 4804 * Represents a set of preferences groups. 4805 * 4806 * @package core 4807 * @category output 4808 * @copyright 2015 Frédéric Massart - FMCorz.net 4809 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4810 */ 4811 class preferences_groups implements renderable { 4812 4813 /** 4814 * Array of preferences_group. 4815 * @var array 4816 */ 4817 public $groups; 4818 4819 /** 4820 * Constructor. 4821 * @param array $groups of preferences_group 4822 */ 4823 public function __construct($groups) { 4824 $this->groups = $groups; 4825 } 4826 4827 } 4828 4829 /** 4830 * Represents a group of preferences page link. 4831 * 4832 * @package core 4833 * @category output 4834 * @copyright 2015 Frédéric Massart - FMCorz.net 4835 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4836 */ 4837 class preferences_group implements renderable { 4838 4839 /** 4840 * Title of the group. 4841 * @var string 4842 */ 4843 public $title; 4844 4845 /** 4846 * Array of navigation_node. 4847 * @var array 4848 */ 4849 public $nodes; 4850 4851 /** 4852 * Constructor. 4853 * @param string $title The title. 4854 * @param array $nodes of navigation_node. 4855 */ 4856 public function __construct($title, $nodes) { 4857 $this->title = $title; 4858 $this->nodes = $nodes; 4859 } 4860 } 4861 4862 /** 4863 * Progress bar class. 4864 * 4865 * Manages the display of a progress bar. 4866 * 4867 * To use this class. 4868 * - construct 4869 * - call create (or use the 3rd param to the constructor) 4870 * - call update or update_full() or update() repeatedly 4871 * 4872 * @copyright 2008 jamiesensei 4873 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4874 * @package core 4875 * @category output 4876 */ 4877 class progress_bar implements renderable, templatable { 4878 /** @var string html id */ 4879 private $html_id; 4880 /** @var int total width */ 4881 private $width; 4882 /** @var int last percentage printed */ 4883 private $percent = 0; 4884 /** @var int time when last printed */ 4885 private $lastupdate = 0; 4886 /** @var int when did we start printing this */ 4887 private $time_start = 0; 4888 4889 /** 4890 * Constructor 4891 * 4892 * Prints JS code if $autostart true. 4893 * 4894 * @param string $htmlid The container ID. 4895 * @param int $width The suggested width. 4896 * @param bool $autostart Whether to start the progress bar right away. 4897 */ 4898 public function __construct($htmlid = '', $width = 500, $autostart = false) { 4899 if (!CLI_SCRIPT && !NO_OUTPUT_BUFFERING) { 4900 debugging('progress_bar used in a non-CLI script without setting NO_OUTPUT_BUFFERING.', DEBUG_DEVELOPER); 4901 } 4902 4903 if (!empty($htmlid)) { 4904 $this->html_id = $htmlid; 4905 } else { 4906 $this->html_id = 'pbar_'.uniqid(); 4907 } 4908 4909 $this->width = $width; 4910 4911 if ($autostart) { 4912 $this->create(); 4913 } 4914 } 4915 4916 /** 4917 * Getter for ID 4918 * @return string id 4919 */ 4920 public function get_id() : string { 4921 return $this->html_id; 4922 } 4923 4924 /** 4925 * Create a new progress bar, this function will output html. 4926 * 4927 * @return void Echo's output 4928 */ 4929 public function create() { 4930 global $OUTPUT; 4931 4932 $this->time_start = microtime(true); 4933 4934 flush(); 4935 echo $OUTPUT->render($this); 4936 flush(); 4937 } 4938 4939 /** 4940 * Update the progress bar. 4941 * 4942 * @param int $percent From 1-100. 4943 * @param string $msg The message. 4944 * @return void Echo's output 4945 * @throws coding_exception 4946 */ 4947 private function _update($percent, $msg) { 4948 global $OUTPUT; 4949 4950 if (empty($this->time_start)) { 4951 throw new coding_exception('You must call create() (or use the $autostart ' . 4952 'argument to the constructor) before you try updating the progress bar.'); 4953 } 4954 4955 $estimate = $this->estimate($percent); 4956 4957 if ($estimate === null) { 4958 // Always do the first and last updates. 4959 } else if ($estimate == 0) { 4960 // Always do the last updates. 4961 } else if ($this->lastupdate + 20 < time()) { 4962 // We must update otherwise browser would time out. 4963 } else if (round($this->percent, 2) === round($percent, 2)) { 4964 // No significant change, no need to update anything. 4965 return; 4966 } 4967 4968 $estimatemsg = ''; 4969 if ($estimate != 0 && is_numeric($estimate)) { 4970 $estimatemsg = format_time(round($estimate)); 4971 } 4972 4973 $this->percent = $percent; 4974 $this->lastupdate = microtime(true); 4975 4976 echo $OUTPUT->render_progress_bar_update($this->html_id, sprintf("%.1f", $this->percent), $msg, $estimatemsg); 4977 flush(); 4978 } 4979 4980 /** 4981 * Estimate how much time it is going to take. 4982 * 4983 * @param int $pt From 1-100. 4984 * @return mixed Null (unknown), or int. 4985 */ 4986 private function estimate($pt) { 4987 if ($this->lastupdate == 0) { 4988 return null; 4989 } 4990 if ($pt < 0.00001) { 4991 return null; // We do not know yet how long it will take. 4992 } 4993 if ($pt > 99.99999) { 4994 return 0; // Nearly done, right? 4995 } 4996 $consumed = microtime(true) - $this->time_start; 4997 if ($consumed < 0.001) { 4998 return null; 4999 } 5000 5001 return (100 - $pt) * ($consumed / $pt); 5002 } 5003 5004 /** 5005 * Update progress bar according percent. 5006 * 5007 * @param int $percent From 1-100. 5008 * @param string $msg The message needed to be shown. 5009 */ 5010 public function update_full($percent, $msg) { 5011 $percent = max(min($percent, 100), 0); 5012 $this->_update($percent, $msg); 5013 } 5014 5015 /** 5016 * Update progress bar according the number of tasks. 5017 * 5018 * @param int $cur Current task number. 5019 * @param int $total Total task number. 5020 * @param string $msg The message needed to be shown. 5021 */ 5022 public function update($cur, $total, $msg) { 5023 $percent = ($cur / $total) * 100; 5024 $this->update_full($percent, $msg); 5025 } 5026 5027 /** 5028 * Restart the progress bar. 5029 */ 5030 public function restart() { 5031 $this->percent = 0; 5032 $this->lastupdate = 0; 5033 $this->time_start = 0; 5034 } 5035 5036 /** 5037 * Export for template. 5038 * 5039 * @param renderer_base $output The renderer. 5040 * @return array 5041 */ 5042 public function export_for_template(renderer_base $output) { 5043 return [ 5044 'id' => $this->html_id, 5045 'width' => $this->width, 5046 ]; 5047 } 5048 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body