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