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   * Group of date and time input element
  20   *
  21   * Contains class for a group of elements used to input a date and time.
  22   *
  23   * @package   core_form
  24   * @copyright 2006 Jamie Pratt <me@jamiep.org>
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  global $CFG;
  29  require_once($CFG->libdir . '/form/group.php');
  30  require_once($CFG->libdir . '/formslib.php');
  31  
  32  /**
  33   * Element used to input a date and time.
  34   *
  35   * Class for a group of elements used to input a date and time.
  36   *
  37   * @package   core_form
  38   * @category  form
  39   * @copyright 2006 Jamie Pratt <me@jamiep.org>
  40   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group {
  43  
  44      /**
  45       * Options for the element.
  46       *
  47       * startyear => int start of range of years that can be selected
  48       * stopyear => int last year that can be selected
  49       * defaulttime => default time value if the field is currently not set
  50       * timezone => int|float|string (optional) timezone modifier used for edge case only.
  51       *      If not specified, then date is caclulated based on current user timezone.
  52       *      Note: dst will be calculated for string timezones only
  53       *      {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
  54       * step => step to increment minutes by
  55       * optional => if true, show a checkbox beside the date to turn it on (or off)
  56       * @var array
  57       */
  58      protected $_options = array();
  59  
  60      /**
  61       * @var array These complement separators, they are appended to the resultant HTML.
  62       */
  63      protected $_wrap = array('', '');
  64  
  65      /**
  66       * @var null|bool Keeps track of whether the date selector was initialised using createElement
  67       *                or addElement. If true, createElement was used signifying the element has been
  68       *                added to a group - see MDL-39187.
  69       */
  70      protected $_usedcreateelement = true;
  71  
  72      /**
  73       * Class constructor
  74       *
  75       * @param string $elementName Element's name
  76       * @param mixed $elementLabel Label(s) for an element
  77       * @param array $options Options to control the element's display
  78       * @param mixed $attributes Either a typical HTML attribute string or an associative array
  79       */
  80      public function __construct($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
  81          // Get the calendar type used - see MDL-18375.
  82          $calendartype = \core_calendar\type_factory::get_calendar_instance();
  83          $this->_options = array('startyear' => $calendartype->get_min_year(), 'stopyear' => $calendartype->get_max_year(),
  84              'defaulttime' => 0, 'timezone' => 99, 'step' => 1, 'optional' => false);
  85  
  86          // TODO MDL-52313 Replace with the call to parent::__construct().
  87          HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes);
  88          $this->_persistantFreeze = true;
  89          $this->_appendName = true;
  90          $this->_type = 'date_time_selector';
  91          // set the options, do not bother setting bogus ones
  92          if (is_array($options)) {
  93              foreach ($options as $name => $value) {
  94                  if (isset($this->_options[$name])) {
  95                      if (is_array($value) && is_array($this->_options[$name])) {
  96                          $this->_options[$name] = @array_merge($this->_options[$name], $value);
  97                      } else {
  98                          $this->_options[$name] = $value;
  99                      }
 100                  }
 101              }
 102          }
 103      }
 104  
 105      /**
 106       * Old syntax of class constructor. Deprecated in PHP7.
 107       *
 108       * @deprecated since Moodle 3.1
 109       */
 110      public function MoodleQuickForm_date_time_selector($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
 111          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 112          self::__construct($elementName, $elementLabel, $options, $attributes);
 113      }
 114  
 115      /**
 116       * This will create date group element constisting of day, month and year.
 117       *
 118       * @access private
 119       */
 120      function _createElements() {
 121          global $OUTPUT;
 122  
 123          // Get the calendar type used - see MDL-18375.
 124          $calendartype = \core_calendar\type_factory::get_calendar_instance();
 125  
 126          for ($i = 0; $i <= 23; $i++) {
 127              $hours[$i] = sprintf("%02d", $i);
 128          }
 129          for ($i = 0; $i < 60; $i += $this->_options['step']) {
 130              $minutes[$i] = sprintf("%02d", $i);
 131          }
 132  
 133          $this->_elements = array();
 134          // If optional we add a checkbox which the user can use to turn if on.
 135          if ($this->_options['optional']) {
 136              $this->_elements[] = $this->createFormElement('checkbox', 'enabled', null,
 137                  get_string('enable'), $this->getAttributesForFormElement(), true);
 138          }
 139          $dateformat = $calendartype->get_date_order($this->_options['startyear'], $this->_options['stopyear']);
 140          if (right_to_left()) {   // Display time to the right of date, in RTL mode.
 141              $this->_elements[] = $this->createFormElement('select', 'minute', get_string('minute', 'form'),
 142                  $minutes, $this->getAttributesForFormElement(), true);
 143              $this->_elements[] = $this->createFormElement('select', 'hour', get_string('hour', 'form'),
 144                  $hours, $this->getAttributesForFormElement(), true);
 145              // Reverse date element (Should be: Day, Month, Year), in RTL mode.
 146              $dateformat = array_reverse($dateformat);
 147          }
 148          foreach ($dateformat as $key => $date) {
 149              // E_STRICT creating elements without forms is nasty because it internally uses $this
 150              $this->_elements[] = $this->createFormElement('select', $key, get_string($key, 'form'), $date,
 151                  $this->getAttributesForFormElement(), true);
 152          }
 153          if (!right_to_left()) {   // Display time to the left of date, in LTR mode.
 154              $this->_elements[] = $this->createFormElement('select', 'hour', get_string('hour', 'form'), $hours,
 155                  $this->getAttributesForFormElement(), true);
 156              $this->_elements[] = $this->createFormElement('select', 'minute', get_string('minute', 'form'), $minutes,
 157                  $this->getAttributesForFormElement(), true);
 158          }
 159          // The YUI2 calendar only supports the gregorian calendar type so only display the calendar image if this is being used.
 160          if ($calendartype->get_name() === 'gregorian') {
 161              $image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
 162              $this->_elements[] = $this->createFormElement('link', 'calendar',
 163                      null, '#', $image);
 164          }
 165          foreach ($this->_elements as $element){
 166              if (method_exists($element, 'setHiddenLabel')){
 167                  $element->setHiddenLabel(true);
 168              }
 169          }
 170  
 171      }
 172  
 173      /**
 174       * Called by HTML_QuickForm whenever form event is made on this element
 175       *
 176       * @param string $event Name of event
 177       * @param mixed $arg event arguments
 178       * @param object $caller calling object
 179       * @return bool
 180       */
 181      function onQuickFormEvent($event, $arg, &$caller) {
 182          $this->setMoodleForm($caller);
 183          switch ($event) {
 184              case 'updateValue':
 185                  // Constant values override both default and submitted ones
 186                  // default values are overriden by submitted.
 187                  $value = $this->_findValue($caller->_constantValues);
 188                  if (null === $value) {
 189                      // If no boxes were checked, then there is no value in the array
 190                      // yet we don't want to display default value in this case.
 191                      if ($caller->isSubmitted() && !$caller->is_new_repeat($this->getName())) {
 192                          $value = $this->_findValue($caller->_submitValues);
 193                      } else {
 194                          $value = $this->_findValue($caller->_defaultValues);
 195                      }
 196                  }
 197                  $requestvalue=$value;
 198                  if ($value == 0 || $value === '') {
 199                      $value = $this->_options['defaulttime'];
 200                      if (!$value) {
 201                          $value = time();
 202                      }
 203                  }
 204                  if (!is_array($value)) {
 205                      $calendartype = \core_calendar\type_factory::get_calendar_instance();
 206                      $currentdate = $calendartype->timestamp_to_date_array($value, $this->_options['timezone']);
 207                      // Round minutes to the previous multiple of step.
 208                      $currentdate['minutes'] -= $currentdate['minutes'] % $this->_options['step'];
 209                      $value = array(
 210                          'minute' => $currentdate['minutes'],
 211                          'hour' => $currentdate['hours'],
 212                          'day' => $currentdate['mday'],
 213                          'month' => $currentdate['mon'],
 214                          'year' => $currentdate['year']);
 215                      // If optional, default to off, unless a date was provided.
 216                      if ($this->_options['optional']) {
 217                          $value['enabled'] = $requestvalue != 0;
 218                      }
 219                  } else {
 220                      $value['enabled'] = isset($value['enabled']);
 221                  }
 222                  if (null !== $value) {
 223                      $this->setValue($value);
 224                  }
 225                  break;
 226              case 'createElement':
 227                  if (isset($arg[2]['optional']) && $arg[2]['optional']) {
 228                      // When using the function addElement, rather than createElement, we still
 229                      // enter this case, making this check necessary.
 230                      if ($this->_usedcreateelement) {
 231                          $caller->disabledIf($arg[0] . '[day]', $arg[0] . '[enabled]');
 232                          $caller->disabledIf($arg[0] . '[month]', $arg[0] . '[enabled]');
 233                          $caller->disabledIf($arg[0] . '[year]', $arg[0] . '[enabled]');
 234                          $caller->disabledIf($arg[0] . '[hour]', $arg[0] . '[enabled]');
 235                          $caller->disabledIf($arg[0] . '[minute]', $arg[0] . '[enabled]');
 236                      } else {
 237                          $caller->disabledIf($arg[0], $arg[0] . '[enabled]');
 238                      }
 239                  }
 240                  return parent::onQuickFormEvent($event, $arg, $caller);
 241                  break;
 242              case 'addElement':
 243                  $this->_usedcreateelement = false;
 244                  return parent::onQuickFormEvent($event, $arg, $caller);
 245                  break;
 246              default:
 247                  return parent::onQuickFormEvent($event, $arg, $caller);
 248          }
 249      }
 250  
 251      /**
 252       * Returns HTML for advchecbox form element.
 253       *
 254       * @return string
 255       */
 256      function toHtml() {
 257          include_once('HTML/QuickForm/Renderer/Default.php');
 258          $renderer = new HTML_QuickForm_Renderer_Default();
 259          $renderer->setElementTemplate('{element}');
 260          parent::accept($renderer);
 261  
 262          $html = $this->_wrap[0];
 263          if ($this->_usedcreateelement) {
 264              $html .= html_writer::tag('span', $renderer->toHtml(), array('class' => 'fdate_time_selector'));
 265          } else {
 266              $html .= $renderer->toHtml();
 267          }
 268          $html .= $this->_wrap[1];
 269  
 270          return $html;
 271      }
 272  
 273      /**
 274       * Accepts a renderer
 275       *
 276       * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
 277       * @param bool $required Whether a group is required
 278       * @param string $error An error message associated with a group
 279       */
 280      function accept(&$renderer, $required = false, $error = null) {
 281          form_init_date_js();
 282          $renderer->renderElement($this, $required, $error);
 283      }
 284  
 285      /**
 286       * Export for template
 287       *
 288       * @param renderer_base $output
 289       * @return array|stdClass
 290       */
 291      public function export_for_template(renderer_base $output) {
 292          form_init_date_js();
 293          return parent::export_for_template($output);
 294      }
 295  
 296      /**
 297       * Output a timestamp. Give it the name of the group.
 298       *
 299       * @param array $submitValues values submitted.
 300       * @param bool $assoc specifies if returned array is associative
 301       * @return array
 302       */
 303      function exportValue(&$submitValues, $assoc = false) {
 304          $valuearray = array();
 305          foreach ($this->_elements as $element){
 306              $thisexport = $element->exportValue($submitValues[$this->getName()], true);
 307              if ($thisexport!=null){
 308                  $valuearray += $thisexport;
 309              }
 310          }
 311          if (count($valuearray)){
 312              if($this->_options['optional']) {
 313                  // If checkbox is on, the value is zero, so go no further
 314                  if(empty($valuearray['enabled'])) {
 315                      return $this->_prepareValue(0, $assoc);
 316                  }
 317              }
 318              // Get the calendar type used - see MDL-18375.
 319              $calendartype = \core_calendar\type_factory::get_calendar_instance();
 320              $gregoriandate = $calendartype->convert_to_gregorian($valuearray['year'],
 321                                                                   $valuearray['month'],
 322                                                                   $valuearray['day'],
 323                                                                   $valuearray['hour'],
 324                                                                   $valuearray['minute']);
 325              $value = make_timestamp($gregoriandate['year'],
 326                                                        $gregoriandate['month'],
 327                                                        $gregoriandate['day'],
 328                                                        $gregoriandate['hour'],
 329                                                        $gregoriandate['minute'],
 330                                                        0,
 331                                                        $this->_options['timezone'],
 332                                                        true);
 333  
 334              return $this->_prepareValue($value, $assoc);
 335          } else {
 336              return null;
 337          }
 338      }
 339  }