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]

   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   * Creates an element with a dropdown Default/Custom and an input for the value (text or date_selector)
  20   *
  21   * @package   core_form
  22   * @copyright 2017 Marina Glancy
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  global $CFG;
  27  require_once($CFG->libdir . '/form/group.php');
  28  require_once($CFG->libdir . '/formslib.php');
  29  
  30  /**
  31   * Creates an element with a dropdown Default/Custom and an input for the value (text or date_selector)
  32   *
  33   * @package   core_form
  34   * @copyright 2017 Marina Glancy
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class MoodleQuickForm_defaultcustom extends MoodleQuickForm_group {
  38  
  39      /**
  40       * @var array These complement separators, they are appended to the resultant HTML.
  41       */
  42      protected $_wrap = array('', '');
  43  
  44      /**
  45       * @var null|bool Keeps track of whether the date selector was initialised using createElement
  46       *                or addElement. If true, createElement was used signifying the element has been
  47       *                added to a group - see MDL-39187.
  48       */
  49      protected $_usedcreateelement = true;
  50  
  51      /** @var array */
  52      protected $_options;
  53  
  54      /**
  55       * Constructor
  56       *
  57       * @param string $elementname Element's name
  58       * @param mixed $elementlabel Label(s) for an element
  59       * @param array $options Options to control the element's display
  60       * @param mixed $attributes Either a typical HTML attribute string or an associative array
  61       */
  62      public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
  63          parent::__construct($elementname, $elementlabel);
  64          $this->setAttributes($attributes);
  65  
  66          $this->_appendName = true;
  67          $this->_type = 'defaultcustom';
  68  
  69          $calendartype = \core_calendar\type_factory::get_calendar_instance();
  70          $this->_options = [
  71              'type' => 'text', // Type of the element. Supported are 'text' and 'date_selector'.
  72              'defaultvalue' => null, // Value to be used when not overridden.
  73              'customvalue' => null, // Value to be used when overwriting.
  74              'customlabel' => get_string('custom', 'form'), // Label for 'customize' checkbox
  75              // Other options are the same as the ones that can be passed to 'date_selector' element.
  76              'timezone' => 99,
  77              'startyear' => $calendartype->get_min_year(),
  78              'stopyear' => $calendartype->get_max_year(),
  79              'defaulttime' => 0,
  80              'step' => 1,
  81              'optional' => false,
  82          ];
  83  
  84          if (is_array($options)) {
  85              foreach ($options as $name => $value) {
  86                  if (array_key_exists($name, $this->_options)) {
  87                      if ($name === 'type' && !in_array($value, ['text', 'date_selector', 'date_time_selector'])) {
  88                          throw new coding_exception('Only text, date_selector, and date_time_selector elements are supported in ' . $this->_type);
  89                      }
  90                      if ($name === 'optional' && $value) {
  91                          throw new coding_exception('Date selector can not be optional in ' . $this->_type);
  92                      }
  93                      $this->_options[$name] = $value;
  94                  }
  95              }
  96          }
  97      }
  98  
  99      /**
 100       * Converts timestamp to the day/month/year array in the current calendar format
 101       * @param int $value
 102       * @return array
 103       */
 104      protected function timestamp_to_date_array($value) {
 105          $calendartype = \core_calendar\type_factory::get_calendar_instance();
 106          $currentdate = $calendartype->timestamp_to_date_array((int)$value, $this->_options['timezone']);
 107          return array(
 108              'minute' => $currentdate['minutes'],
 109              'hour' => $currentdate['hours'],
 110              'day' => $currentdate['mday'],
 111              'month' => $currentdate['mon'],
 112              'year' => $currentdate['year']);
 113      }
 114  
 115      /**
 116       * Should this element have default/custom switch?
 117       *
 118       * @return bool
 119       */
 120      protected function has_customize_switch() {
 121          return $this->_options['defaultvalue'] !== null;
 122      }
 123  
 124      /**
 125       * This will create all elements in the group
 126       */
 127      public function _createElements() {
 128          if (!$this->has_customize_switch()) {
 129              $element = $this->createFormElement('hidden', 'customize', 1);
 130          } else {
 131              $element = $this->createFormElement('advcheckbox', 'customize', '', $this->_options['customlabel']);
 132          }
 133          $this->_elements[] = $element;
 134  
 135          if ($this->_options['type'] === 'text') {
 136              $element = $this->createFormElement($this->_options['type'], 'value',
 137                  get_string('newvaluefor', 'form', $this->getLabel()), $this->getAttributes());
 138              $element->setHiddenLabel(true);
 139          } else if ($this->_options['type'] === 'date_selector') {
 140              $element = $this->createFormElement($this->_options['type'], 'value', '', $this->_options,
 141                  $this->getAttributes());
 142          } else if ($this->_options['type'] === 'date_time_selector') {
 143              $element = $this->createFormElement($this->_options['type'], 'value', '', $this->_options,
 144                  $this->getAttributes());
 145          }
 146          $this->_elements[] = $element;
 147      }
 148  
 149      /**
 150       * Called by HTML_QuickForm whenever form event is made on this element
 151       *
 152       * @param string $event Name of event
 153       * @param mixed $arg event arguments
 154       * @param object $caller calling object
 155       * @return bool
 156       */
 157      public function onQuickFormEvent($event, $arg, &$caller) {
 158          $this->setMoodleForm($caller);
 159          switch ($event) {
 160              case 'updateValue':
 161                  // Constant values override both default and submitted ones
 162                  // default values are overriden by submitted.
 163                  $value = $this->_findValue($caller->_constantValues);
 164                  if (null === $value) {
 165                      // If no boxes were checked, then there is no value in the array
 166                      // yet we don't want to display default value in this case.
 167                      if ($caller->isSubmitted()) {
 168                          $value = $this->_findValue($caller->_submitValues);
 169                      } else {
 170                          $value = $this->_findValue($caller->_defaultValues);
 171                      }
 172                  }
 173                  if (!is_array($value)) {
 174                      $customize = ($value !== false || !$this->has_customize_switch());
 175                      if ($this->_options['type'] === 'text') {
 176                          $elementvalue = $customize ? $value : $this->_options['defaultvalue'];
 177                      } else {
 178                          $elementvalue = $this->timestamp_to_date_array($customize ? $value : $this->_options['defaultvalue']);
 179                      }
 180                      $value = [
 181                          'customize' => $customize,
 182                          'value' => $elementvalue
 183                      ];
 184                  }
 185                  $this->setValue($value);
 186                  break;
 187              case 'createElement':
 188                  $rv = parent::onQuickFormEvent($event, $arg, $caller);
 189                  if ($this->has_customize_switch()) {
 190                      if ($this->_options['type'] === 'text') {
 191                          $caller->disabledIf($arg[0] . '[value]', $arg[0] . '[customize]', 'notchecked');
 192                      } else if ($this->_options['type'] === 'date_selector') {
 193                          $caller->disabledIf($arg[0] . '[value][day]', $arg[0] . '[customize]', 'notchecked');
 194                          $caller->disabledIf($arg[0] . '[value][month]', $arg[0] . '[customize]', 'notchecked');
 195                          $caller->disabledIf($arg[0] . '[value][year]', $arg[0] . '[customize]', 'notchecked');
 196                      } else {
 197                          // Date / Time selector.
 198                          $caller->disabledIf($arg[0] . '[value][day]', $arg[0] . '[customize]', 'notchecked');
 199                          $caller->disabledIf($arg[0] . '[value][month]', $arg[0] . '[customize]', 'notchecked');
 200                          $caller->disabledIf($arg[0] . '[value][year]', $arg[0] . '[customize]', 'notchecked');
 201                          $caller->disabledIf($arg[0] . '[value][hour]', $arg[0] . '[customize]', 'notchecked');
 202                          $caller->disabledIf($arg[0] . '[value][minute]', $arg[0] . '[customize]', 'notchecked');
 203                      }
 204                  }
 205                  return $rv;
 206              case 'addElement':
 207                  $this->_usedcreateelement = false;
 208                  return parent::onQuickFormEvent($event, $arg, $caller);
 209                  break;
 210              default:
 211                  return parent::onQuickFormEvent($event, $arg, $caller);
 212          }
 213      }
 214  
 215      public function freeze() {
 216          parent::freeze();
 217          $this->setPersistantFreeze(true);
 218      }
 219  
 220      public function toHtml() {
 221          include_once('HTML/QuickForm/Renderer/Default.php');
 222          $renderer = new HTML_QuickForm_Renderer_Default();
 223          $renderer->setElementTemplate('{element}');
 224          parent::accept($renderer);
 225  
 226          $html = $this->_wrap[0];
 227          if ($this->_usedcreateelement) {
 228              $html .= html_writer::tag('span', $renderer->toHtml(), array('class' => 'fdefaultcustom'));
 229          } else {
 230              $html .= $renderer->toHtml();
 231          }
 232          $html .= $this->_wrap[1];
 233  
 234          return $html;
 235      }
 236  
 237      public function accept(&$renderer, $required = false, $error = null) {
 238          global $PAGE;
 239  
 240          if (!$this->_flagFrozen && $this->has_customize_switch()) {
 241              // Add JS to the default/custom switch.
 242              $firstelement = reset($this->_elements);
 243              $defaultvalue = $this->_options['defaultvalue'];
 244              $customvalue = $this->_options['customvalue'];
 245              if ($this->_options['type'] === 'date_selector' || $this->_options['type'] === 'date_time_selector') {
 246                  $defaultvalue = $this->timestamp_to_date_array($defaultvalue);
 247                  $customvalue = $this->timestamp_to_date_array($customvalue);
 248              }
 249              $firstelement->updateAttributes(['data-defaultcustom' => 'true',
 250                  'data-type' => $this->_options['type'],
 251                  'data-defaultvalue' => json_encode($defaultvalue),
 252                  'data-customvalue' => json_encode($customvalue)]);
 253              $PAGE->requires->js_amd_inline("require(['core_form/defaultcustom'], function() {});");
 254          }
 255  
 256          $renderer->renderElement($this, $required, $error);
 257      }
 258  
 259      /**
 260       * Output a value. Give it the name of the group. In case of "default" return false.
 261       *
 262       * @param array $submitvalues values submitted.
 263       * @param bool $assoc specifies if returned array is associative
 264       * @return array
 265       */
 266      public function exportValue(&$submitvalues, $assoc = false) {
 267          $valuearray = array();
 268          foreach ($this->_elements as $element) {
 269              $thisexport = $element->exportValue($submitvalues[$this->getName()], true);
 270              if ($thisexport != null) {
 271                  $valuearray += $thisexport;
 272              }
 273          }
 274          if (empty($valuearray['customize'])) {
 275              return $this->_prepareValue(false, $assoc);
 276          }
 277          return array_key_exists('value', $valuearray) ? $this->_prepareValue($valuearray['value'], $assoc) : [];
 278      }
 279  }