Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  
  18  /**
  19   * select type form element
  20   *
  21   * Class to dynamically create an HTML SELECT with all options grouped in optgroups
  22   *
  23   * @package   core_form
  24   * @copyright 2007 Jamie Pratt <me@jamiep.org>
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  require_once('HTML/QuickForm/element.php');
  29  require_once ('templatable_form_element.php');
  30  
  31  /**
  32   * select type form element
  33   *
  34   * Class to dynamically create an HTML SELECT with all options grouped in optgroups
  35   *
  36   * @package   core_form
  37   * @category  form
  38   * @copyright 2007 Jamie Pratt <me@jamiep.org>
  39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class MoodleQuickForm_selectgroups extends HTML_QuickForm_element implements templatable {
  42  
  43      use templatable_form_element {
  44          export_for_template as export_for_template_base;
  45      }
  46  
  47      /** @var bool add choose option */
  48      var $showchoose = false;
  49  
  50      /** @var array Contains the select optgroups */
  51      var $_optGroups = array();
  52  
  53      /** @var string Default values of the SELECT */
  54      var $_values = null;
  55  
  56      /** @var string html for help button, if empty then no help */
  57      var $_helpbutton='';
  58  
  59      /** @var bool if true label will be hidden */
  60      var $_hiddenLabel=false;
  61  
  62      /**
  63       * Class constructor
  64       *
  65       * @param string $elementName Select name attribute
  66       * @param mixed $elementLabel Label(s) for the select
  67       * @param array $optgrps Data to be used to populate options
  68       * @param mixed $attributes Either a typical HTML attribute string or an associative array
  69       * @param bool $showchoose add standard moodle "Choose..." option as first item
  70       */
  71      public function __construct($elementName=null, $elementLabel=null, $optgrps=null, $attributes=null, $showchoose=false)
  72      {
  73          $this->showchoose = $showchoose;
  74          parent::__construct($elementName, $elementLabel, $attributes);
  75          $this->_persistantFreeze = true;
  76          $this->_type = 'selectgroups';
  77          if (isset($optgrps)) {
  78              $this->loadArrayOptGroups($optgrps);
  79          }
  80      }
  81  
  82      /**
  83       * Old syntax of class constructor. Deprecated in PHP7.
  84       *
  85       * @deprecated since Moodle 3.1
  86       */
  87      public function MoodleQuickForm_selectgroups($elementName=null, $elementLabel=null, $optgrps=null, $attributes=null, $showchoose=false) {
  88          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
  89          self::__construct($elementName, $elementLabel, $optgrps, $attributes, $showchoose);
  90      }
  91  
  92      /**
  93       * Sets the default values of the select box
  94       *
  95       * @param mixed $values Array or comma delimited string of selected values
  96       */
  97      function setSelected($values)
  98      {
  99          if (is_string($values) && $this->getMultiple()) {
 100              $values = preg_split("/[ ]?,[ ]?/", $values);
 101          }
 102          if (is_array($values)) {
 103              $this->_values = array_values($values);
 104          } else {
 105              $this->_values = array($values);
 106          }
 107      }
 108  
 109      /**
 110       * Returns an array of the selected values
 111       *
 112       * @return array of selected values
 113       */
 114      function getSelected()
 115      {
 116          return $this->_values;
 117      }
 118  
 119      /**
 120       * Sets the input field name
 121       *
 122       * @param string $name Input field name attribute
 123       */
 124      function setName($name)
 125      {
 126          $this->updateAttributes(array('name' => $name));
 127      }
 128  
 129      /**
 130       * Returns the element name
 131       *
 132       * @return string
 133       */
 134      function getName()
 135      {
 136          return $this->getAttribute('name');
 137      }
 138  
 139      /**
 140       * Returns the element name (possibly with brackets appended)
 141       *
 142       * @return string
 143       */
 144      function getPrivateName()
 145      {
 146          if ($this->getAttribute('multiple')) {
 147              return $this->getName() . '[]';
 148          } else {
 149              return $this->getName();
 150          }
 151      }
 152  
 153      /**
 154       * Sets the value of the form element
 155       *
 156       * @param mixed $value Array or comma delimited string of selected values
 157       */
 158      function setValue($value)
 159      {
 160          $this->setSelected($value);
 161      }
 162  
 163      /**
 164       * Returns an array of the selected values
 165       *
 166       * @return array of selected values
 167       */
 168      function getValue()
 169      {
 170          return $this->_values;
 171      }
 172  
 173      /**
 174       * Sets the select field size, only applies to 'multiple' selects
 175       *
 176       * @param int $size Size of select  field
 177       */
 178      function setSize($size)
 179      {
 180          $this->updateAttributes(array('size' => $size));
 181      }
 182  
 183      /**
 184       * Returns the select field size
 185       *
 186       * @return int
 187       */
 188      function getSize()
 189      {
 190          return $this->getAttribute('size');
 191      }
 192  
 193      /**
 194       * Sets the select mutiple attribute
 195       *
 196       * @param bool $multiple Whether the select supports multi-selections
 197       */
 198      function setMultiple($multiple)
 199      {
 200          if ($multiple) {
 201              $this->updateAttributes(array('multiple' => 'multiple'));
 202          } else {
 203              $this->removeAttribute('multiple');
 204          }
 205      }
 206  
 207      /**
 208       * Returns the select mutiple attribute
 209       *
 210       * @return bool true if multiple select, false otherwise
 211       */
 212      function getMultiple()
 213      {
 214          return (bool)$this->getAttribute('multiple');
 215      }
 216  
 217      /**
 218       * Loads the options from an associative array
 219       *
 220       * @param array $arr Associative array of options
 221       * @param mixed $values (optional) Array or comma delimited string of selected values
 222       * @return PEAR_Error|bool on error or true
 223       * @throws PEAR_Error
 224       */
 225      function loadArrayOptGroups($arr, $values=null)
 226      {
 227          if (!is_array($arr)) {
 228              return self::raiseError('Argument 1 of HTML_Select::loadArrayOptGroups is not a valid array');
 229          }
 230          if (isset($values)) {
 231              $this->setSelected($values);
 232          }
 233          foreach ($arr as $key => $val) {
 234              // Warning: new API since release 2.3
 235              $this->addOptGroup($key, $val);
 236          }
 237          return true;
 238      }
 239  
 240      /**
 241       * Adds a new OPTION to the SELECT
 242       *
 243       * @param string $text Display text for the OPTION
 244       * @param string $value Value for the OPTION
 245       * @param mixed $attributes Either a typical HTML attribute string
 246       *              or an associative array
 247       */
 248      function addOptGroup($text, $value, $attributes=null)
 249      {
 250          if (null === $attributes) {
 251              $attributes = array('label' => $text);
 252          } else {
 253              $attributes = $this->_parseAttributes($attributes);
 254              $this->_updateAttrArray($attributes, array('label' => $text));
 255          }
 256          $index = count($this->_optGroups);
 257          $this->_optGroups[$index] = array('attr' => $attributes);
 258          $this->loadArrayOptions($index, $value);
 259      }
 260  
 261      /**
 262       * Loads the options from an associative array
 263       *
 264       * @param string $optgroup name of the options group
 265       * @param array $arr Associative array of options
 266       * @param mixed $values (optional) Array or comma delimited string of selected values
 267       * @return PEAR_Error|bool on error or true
 268       * @throws PEAR_Error
 269       */
 270      function loadArrayOptions($optgroup, $arr, $values=null)
 271      {
 272          if (!is_array($arr)) {
 273              return self::raiseError('Argument 1 of HTML_Select::loadArray is not a valid array');
 274          }
 275          if (isset($values)) {
 276              $this->setSelected($values);
 277          }
 278          foreach ($arr as $key => $val) {
 279              // Warning: new API since release 2.3
 280              $this->addOption($optgroup, $val, $key);
 281          }
 282          return true;
 283      }
 284  
 285      /**
 286       * Adds a new OPTION to an optgroup
 287       *
 288       * @param string $optgroup name of the option group
 289       * @param string $text Display text for the OPTION
 290       * @param string $value Value for the OPTION
 291       * @param mixed $attributes Either a typical HTML attribute string
 292       *              or an associative array
 293       */
 294      function addOption($optgroup, $text, $value, $attributes=null)
 295      {
 296          if (null === $attributes) {
 297              $attributes = array('value' => $value);
 298          } else {
 299              $attributes = $this->_parseAttributes($attributes);
 300              if (isset($attributes['selected'])) {
 301                  // the 'selected' attribute will be set in toHtml()
 302                  $this->_removeAttr('selected', $attributes);
 303                  if (is_null($this->_values)) {
 304                      $this->_values = array($value);
 305                  } elseif (!in_array($value, $this->_values)) {
 306                      $this->_values[] = $value;
 307                  }
 308              }
 309              $this->_updateAttrArray($attributes, array('value' => $value));
 310          }
 311          $this->_optGroups[$optgroup]['options'][] = array('text' => $text, 'attr' => $attributes);
 312      }
 313  
 314      /**
 315       * Returns the SELECT in HTML
 316       *
 317       * @return string
 318       */
 319      function toHtml()
 320      {
 321          if ($this->_flagFrozen) {
 322              return $this->getFrozenHtml();
 323          } else {
 324              $tabs    = $this->_getTabs();
 325              $strHtml = '';
 326  
 327              if ($this->getComment() != '') {
 328                  $strHtml .= $tabs . '<!-- ' . $this->getComment() . " //-->\n";
 329              }
 330  
 331              if (!$this->getMultiple()) {
 332                  $attrString = $this->_getAttrString($this->_attributes);
 333              } else {
 334                  $myName = $this->getName();
 335                  $this->setName($myName . '[]');
 336                  $attrString = $this->_getAttrString($this->_attributes);
 337                  $this->setName($myName);
 338              }
 339              $strHtml .= $tabs;
 340              if ($this->_hiddenLabel){
 341                  $this->_generateId();
 342                  $strHtml .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.
 343                              $this->getLabel().'</label>';
 344              }
 345              $strHtml .=  '<select' . $attrString . ">\n";
 346              if ($this->showchoose) {
 347                  $strHtml .= $tabs . "\t\t<option value=\"\">" . get_string('choose') . "...</option>\n";
 348              }
 349              foreach ($this->_optGroups as $optGroup) {
 350                  if (empty($optGroup['options'])) {
 351                      //xhtml strict
 352                      continue;
 353                  }
 354                  $strHtml .= $tabs . "\t<optgroup" . ($this->_getAttrString($optGroup['attr'])) . '>';
 355                  foreach ($optGroup['options'] as $option){
 356                      if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) {
 357                          $this->_updateAttrArray($option['attr'], array('selected' => 'selected'));
 358                      }
 359                      $strHtml .= $tabs . "\t\t<option" . $this->_getAttrString($option['attr']) . '>' .
 360                                  $option['text'] . "</option>\n";
 361                  }
 362                  $strHtml .= $tabs . "\t</optgroup>\n";
 363              }
 364              return $strHtml . $tabs . '</select>';
 365          }
 366      }
 367  
 368      /**
 369       * Returns the value of field without HTML tags
 370       *
 371       * @return string
 372       */
 373      function getFrozenHtml()
 374      {
 375          $value = array();
 376          if (is_array($this->_values)) {
 377              foreach ($this->_values as $key => $val) {
 378                  foreach ($this->_optGroups as $optGroup) {
 379                      if (empty($optGroup['options'])) {
 380                          continue;
 381                      }
 382                      for ($i = 0, $optCount = count($optGroup['options']); $i < $optCount; $i++) {
 383                          if ((string)$val == (string)$optGroup['options'][$i]['attr']['value']) {
 384                              $value[$key] = $optGroup['options'][$i]['text'];
 385                              break;
 386                          }
 387                      }
 388                  }
 389              }
 390          }
 391          $html = empty($value)? '&nbsp;': join('<br />', $value);
 392          if ($this->_persistantFreeze) {
 393              $name = $this->getPrivateName();
 394              // Only use id attribute if doing single hidden input
 395              if (1 == count($value)) {
 396                  $id     = $this->getAttribute('id');
 397                  $idAttr = isset($id)? array('id' => $id): array();
 398              } else {
 399                  $idAttr = array();
 400              }
 401              foreach ($value as $key => $item) {
 402                  $html .= '<input' . $this->_getAttrString(array(
 403                               'type'  => 'hidden',
 404                               'name'  => $name,
 405                               'value' => $this->_values[$key]
 406                           ) + $idAttr) . ' />';
 407              }
 408          }
 409          return $html;
 410      }
 411  
 412     /**
 413      * We check the options and return only the values that _could_ have been
 414      * selected. We also return a scalar value if select is not "multiple"
 415      *
 416      * @param array $submitValues submitted values
 417      * @param bool $assoc if true the retured value is associated array
 418      * @return mixed
 419      */
 420      function exportValue(&$submitValues, $assoc = false)
 421      {
 422          if (empty($this->_optGroups)) {
 423              return $this->_prepareValue(null, $assoc);
 424          }
 425  
 426          $value = $this->_findValue($submitValues);
 427          if (is_null($value)) {
 428              $value = $this->getValue();
 429          }
 430          $value = (array)$value;
 431  
 432          $cleaned = array();
 433          foreach ($value as $v) {
 434              foreach ($this->_optGroups as $optGroup){
 435                  if (empty($optGroup['options'])) {
 436                      continue;
 437                  }
 438                  foreach ($optGroup['options'] as $option) {
 439                      if ((string)$option['attr']['value'] === (string)$v) {
 440                          $cleaned[] = (string)$option['attr']['value'];
 441                          break;
 442                      }
 443                  }
 444              }
 445          }
 446  
 447          if (empty($cleaned)) {
 448              return $this->_prepareValue(null, $assoc);
 449          }
 450          if ($this->getMultiple()) {
 451              return $this->_prepareValue($cleaned, $assoc);
 452          } else {
 453              return $this->_prepareValue($cleaned[0], $assoc);
 454          }
 455      }
 456  
 457      /**
 458       * Called by HTML_QuickForm whenever form event is made on this element
 459       *
 460       * @param string $event Name of event
 461       * @param mixed $arg event arguments
 462       * @param object $caller calling object
 463       * @return bool
 464       */
 465      function onQuickFormEvent($event, $arg, &$caller)
 466      {
 467          if ('updateValue' == $event) {
 468              $value = $this->_findValue($caller->_constantValues);
 469              if (null === $value) {
 470                  $value = $this->_findValue($caller->_submitValues);
 471                  // Fix for bug #4465 & #5269
 472                  // XXX: should we push this to element::onQuickFormEvent()?
 473                  if (null === $value && (!$caller->isSubmitted() || !$this->getMultiple())) {
 474                      $value = $this->_findValue($caller->_defaultValues);
 475                  }
 476              }
 477              if (null !== $value) {
 478                  $this->setValue($value);
 479              }
 480              return true;
 481          } else {
 482              return parent::onQuickFormEvent($event, $arg, $caller);
 483          }
 484      }
 485  
 486      /**
 487       * Sets label to be hidden
 488       *
 489       * @param bool $hiddenLabel sets if label should be hidden
 490       */
 491      function setHiddenLabel($hiddenLabel){
 492          $this->_hiddenLabel = $hiddenLabel;
 493      }
 494  
 495      /**
 496       * get html for help button
 497       *
 498       * @return string html for help button
 499       */
 500      function getHelpButton(){
 501          return $this->_helpbutton;
 502      }
 503  
 504      /**
 505       * Slightly different container template when frozen. Don't want to use a label tag
 506       * with a for attribute in that case for the element label but instead use a div.
 507       * Templates are defined in renderer constructor.
 508       *
 509       * @return string
 510       */
 511      function getElementTemplateType(){
 512          if ($this->_flagFrozen){
 513              return 'static';
 514          } else {
 515              return 'default';
 516          }
 517      }
 518  
 519      public function export_for_template(renderer_base $output) {
 520          $context = $this->export_for_template_base($output);
 521          $optiongroups = [];
 522          if ($this->showchoose) {
 523              $optionsgroups[] = [
 524                  'text' => get_string('choosedots')
 525              ];
 526          }
 527  
 528          // Standard option attributes.
 529          $standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
 530          foreach ($this->_optGroups as $group) {
 531              $options = [];
 532  
 533              if (empty($group['options'])) {
 534                  continue;
 535              }
 536              foreach ($group['options'] as $option) {
 537                  $o = ['value' => (string)$option['attr']['value']];
 538                  if (is_array($this->_values) && in_array($o['value'], $this->_values)) {
 539                      $o['selected'] = true;
 540                  } else {
 541                      $o['selected'] = false;
 542                  }
 543                  $o['text'] = $option['text'];
 544                  $o['disabled'] = !empty($option['attr']['disabled']);
 545                  // Set other attributes.
 546                  $otheroptionattributes = [];
 547                  foreach ($option['attr'] as $attr => $value) {
 548                      if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
 549                          $otheroptionattributes[] = $attr . '="' . s($value) . '"';
 550                      }
 551                  }
 552                  $o['optionattributes'] = implode(' ', $otheroptionattributes);
 553                  $options[] = $o;
 554              }
 555  
 556              $og = [
 557                  'text' => $group['attr']['label'],
 558                  'options' => $options
 559              ];
 560  
 561              $optiongroups[] = $og;
 562          }
 563          $context['optiongroups'] = $optiongroups;
 564          // If the select is a static element and does not allow the user to change the value (Ex: Auth method),
 565          // we need to change the label to static.
 566          $context['staticlabel'] = $this->_flagFrozen;
 567  
 568          return $context;
 569      }
 570  }