Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  •    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]);
     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                  }
     249              }
     250          }
     251  
     252          return $context;
     253      }
     254  }