Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]

   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   * autocomplete type form element
  20   *
  21   * Contains HTML class for a autocomplete type element
  22   *
  23   * @package   core_form
  24   * @copyright 2015 Damyon Wiese <damyon@moodle.com>
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  global $CFG;
  29  
  30  require_once($CFG->libdir . '/form/select.php');
  31  
  32  /**
  33   * Autocomplete as you type form element
  34   *
  35   * HTML class for a autocomplete type element
  36   *
  37   * @package   core_form
  38   * @copyright 2015 Damyon Wiese <damyon@moodle.com>
  39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
  42  
  43      /** @var boolean $tags Should we allow typing new entries to the field? */
  44      protected $tags = false;
  45      /** @var string $ajax Name of an AMD module to send/process ajax requests. */
  46      protected $ajax = '';
  47      /** @var string $placeholder Placeholder text for an empty list. */
  48      protected $placeholder = '';
  49      /** @var bool $casesensitive Whether the search has to be case-sensitive. */
  50      protected $casesensitive = false;
  51      /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
  52      protected $showsuggestions = true;
  53      /** @var string $noselectionstring String that is shown when there are no selections. */
  54      protected $noselectionstring = '';
  55      /** @var callable|null Function to call (with existing value) to render it to HTML */
  56      protected $valuehtmlcallback = null;
  57  
  58      /**
  59       * constructor
  60       *
  61       * @param string $elementName Select name attribute
  62       * @param mixed $elementLabel Label(s) for the select
  63       * @param mixed $options Data to be used to populate options
  64       * @param mixed $attributes Either a typical HTML attribute string or an associative array. Special options
  65       *                          "tags", "placeholder", "ajax", "multiple", "casesensitive" are supported.
  66       */
  67      public function __construct($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
  68          // Even if the constructor gets called twice we do not really want 2x options (crazy forms!).
  69          $this->_options = array();
  70          if ($attributes === null) {
  71              $attributes = array();
  72          }
  73          if (isset($attributes['tags'])) {
  74              $this->tags = $attributes['tags'];
  75              unset($attributes['tags']);
  76          }
  77          if (isset($attributes['showsuggestions'])) {
  78              $this->showsuggestions = $attributes['showsuggestions'];
  79              unset($attributes['showsuggestions']);
  80          }
  81          $this->placeholder = get_string('search');
  82          if (isset($attributes['placeholder'])) {
  83              $this->placeholder = $attributes['placeholder'];
  84              unset($attributes['placeholder']);
  85          }
  86          $this->noselectionstring = get_string('noselection', 'form');
  87          if (isset($attributes['noselectionstring'])) {
  88              $this->noselectionstring = $attributes['noselectionstring'];
  89              unset($attributes['noselectionstring']);
  90          }
  91  
  92          if (isset($attributes['ajax'])) {
  93              $this->ajax = $attributes['ajax'];
  94              unset($attributes['ajax']);
  95          }
  96          if (isset($attributes['casesensitive'])) {
  97              $this->casesensitive = $attributes['casesensitive'] ? true : false;
  98              unset($attributes['casesensitive']);
  99          }
 100          if (isset($attributes['valuehtmlcallback'])) {
 101              $this->valuehtmlcallback = $attributes['valuehtmlcallback'];
 102              unset($attributes['valuehtmlcallback']);
 103          }
 104          parent::__construct($elementName, $elementLabel, $options, $attributes);
 105  
 106          $this->_type = 'autocomplete';
 107      }
 108  
 109      /**
 110       * Old syntax of class constructor. Deprecated in PHP7.
 111       *
 112       * @deprecated since Moodle 3.1
 113       */
 114      public function MoodleQuickForm_autocomplete($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
 115          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 116          self::__construct($elementName, $elementLabel, $options, $attributes);
 117      }
 118  
 119      /**
 120       * Returns HTML for select form element.
 121       *
 122       * @return string
 123       */
 124      function toHtml(){
 125          global $PAGE;
 126  
 127          // Enhance the select with javascript.
 128          $this->_generateId();
 129          $id = $this->getAttribute('id');
 130  
 131          if (!$this->isFrozen()) {
 132              $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
 133                  $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring));
 134          }
 135  
 136          $html = parent::toHTML();
 137  
 138          // Hacky bodge to add in the HTML code to the option tag. There is a nicer
 139          // version of this code in the new template version (see export_for_template).
 140          if ($this->valuehtmlcallback) {
 141              $html = preg_replace_callback('~value="([^"]+)"~', function($matches) {
 142                  $value = html_entity_decode($matches[1], ENT_COMPAT);
 143                  $htmlvalue = call_user_func($this->valuehtmlcallback, $value);
 144                  if ($htmlvalue !== false) {
 145                      return $matches[0] . ' data-html="' . s($htmlvalue) . '"';
 146                  } else {
 147                      return $matches[0];
 148                  }
 149              }, $html);
 150          }
 151  
 152          return $html;
 153      }
 154  
 155      /**
 156       * Search the current list of options to see if there are any options with this value.
 157       * @param  string $value to search
 158       * @return boolean
 159       */
 160      function optionExists($value) {
 161          foreach ($this->_options as $option) {
 162              if (isset($option['attr']['value']) && ($option['attr']['value'] == $value)) {
 163                  return true;
 164              }
 165          }
 166          return false;
 167      }
 168  
 169      /**
 170       * Set the value of this element. If values can be added or are unknown, we will
 171       * make sure they exist in the options array.
 172       * @param  mixed string|array $value The value to set.
 173       * @return boolean
 174       */
 175      function setValue($value) {
 176          $values = (array) $value;
 177          foreach ($values as $onevalue) {
 178              if (($this->tags || $this->ajax) &&
 179                      (!$this->optionExists($onevalue)) &&
 180                      ($onevalue !== '_qf__force_multiselect_submission')) {
 181                  $this->addOption($onevalue, $onevalue);
 182              }
 183          }
 184          return parent::setValue($value);
 185      }
 186  
 187      /**
 188       * Returns a 'safe' element's value
 189       *
 190       * @param  array   array of submitted values to search
 191       * @param  bool    whether to return the value as associative array
 192       * @access public
 193       * @return mixed
 194       */
 195      function exportValue(&$submitValues, $assoc = false) {
 196          if ($this->ajax || $this->tags) {
 197              // When this was an ajax request, we do not know the allowed list of values.
 198              $value = $this->_findValue($submitValues);
 199              if (null === $value) {
 200                  $value = $this->getValue();
 201              }
 202              // Quickforms inserts a duplicate element in the form with
 203              // this value so that a value is always submitted for this form element.
 204              // Normally this is cleaned as a side effect of it not being a valid option,
 205              // but in this case we need to detect and skip it manually.
 206              if ($value === '_qf__force_multiselect_submission' || $value === null) {
 207                  $value = $this->getMultiple() ? [] : '';
 208              }
 209              return $this->_prepareValue($value, $assoc);
 210          } else {
 211              return parent::exportValue($submitValues, $assoc);
 212          }
 213      }
 214  
 215      /**
 216       * Called by HTML_QuickForm whenever form event is made on this element
 217       *
 218       * @param string $event Name of event
 219       * @param mixed $arg event arguments
 220       * @param object $caller calling object
 221       * @return bool
 222       */
 223      function onQuickFormEvent($event, $arg, &$caller)
 224      {
 225          switch ($event) {
 226              case 'createElement':
 227                  $caller->setType($arg[0], PARAM_TAGLIST);
 228                  break;
 229          }
 230          return parent::onQuickFormEvent($event, $arg, $caller);
 231      }
 232  
 233      public function export_for_template(renderer_base $output) {
 234          $this->_generateId();
 235          $context = parent::export_for_template($output);
 236          $context['tags'] = !empty($this->tags);
 237          $context['ajax'] = $this->ajax;
 238          $context['placeholder'] = $this->placeholder;
 239          $context['casesensitive'] = !empty($this->casesensitive);
 240          $context['showsuggestions'] = !empty($this->showsuggestions);
 241          $context['noselectionstring'] = $this->noselectionstring;
 242          if ($this->valuehtmlcallback) {
 243              foreach ($context['options'] as &$option) {
 244                  $value = $option['value'];
 245                  $html = call_user_func($this->valuehtmlcallback, $value);
 246                  if ($html !== false) {
 247                      $option['html'] = $html;
 248                      if ($this->isFrozen()) {
 249                          $option['text'] = $html;
 250                      }
 251                  }
 252              }
 253          }
 254  
 255          return $context;
 256      }
 257  }