Passed
Pull Request — master (#6612)
by
unknown
09:26
created

DateTimePicker::setValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Form element to select a date and hour.
7
 */
8
class DateTimePicker extends HTML_QuickForm_text
9
{
10
    /**
11
     * DateTimePicker constructor.
12
     *
13
     * @param string       $elementName
14
     * @param string|array $elementLabel
15
     * @param array        $attributes
16
     */
17
    public function __construct($elementName, $elementLabel = null, $attributes = null)
18
    {
19
        if (!isset($attributes['id'])) {
20
            $attributes['id'] = $elementName;
21
        }
22
        $attributes['class'] = 'p-component p-inputtext p-filled';
23
        parent::__construct($elementName, $elementLabel, $attributes);
24
        $this->_appendName = true;
25
    }
26
27
    /**
28
     * HTML code to display this datepicker.
29
     *
30
     * @return string
31
     */
32
    public function toHtml()
33
    {
34
        if ($this->_flagFrozen) {
35
            return $this->getFrozenHtml();
36
        }
37
38
        $id = $this->getAttribute('id');
39
        $value = $this->getValue();
40
41
        $formattedValue = '';
42
        if (!empty($value)) {
43
            $formattedValue = api_format_date($value, DATE_TIME_FORMAT_LONG_24H);
44
        }
45
46
        $label = $this->getLabel();
47
        if (is_array($label) && isset($label[0])) {
48
            $label = $label[0];
49
        }
50
51
        //$resetFieldX = sprintf(get_lang('Reset %s'), $label);
52
53
        return '<input '.$this->_getAttrString($this->_attributes).' />'.$this->getElementJS();
54
    }
55
56
    /**
57
     * @param string $value
58
     */
59
    public function setValue($value)
60
    {
61
        $value = substr($value, 0, 16);
62
        $this->updateAttributes(['value' => $value]);
63
    }
64
65
    /**
66
     * Injects the JS for initializing flatpickr with Intl-driven localization.
67
     * All comments/console messages are in English.
68
     */
69
    private function getElementJS(): string
70
    {
71
        $localeCode = $this->getLocaleCode();
72
        $id = $this->getAttribute('id');
73
74
        $altFormat = ($localeCode === 'en') ? 'F d, Y - H:i' : 'd F, Y - H:i';
75
76
        $js = "<script>
77
            document.addEventListener('DOMContentLoaded', function () {
78
              var input = document.getElementById('{$id}');
79
              if (!input) return;
80
81
              function cap(s){ return s ? s.charAt(0).toUpperCase() + s.slice(1) : s; }
82
83
              // Build a flatpickr locale object using Intl (no external l10n files)
84
              function buildFlatpickrLocale(loc) {
85
                try {
86
                  var fmtWeekLong  = new Intl.DateTimeFormat(loc, { weekday: 'long' });
87
                  var fmtWeekShort = new Intl.DateTimeFormat(loc, { weekday: 'short' });
88
                  var fmtMonthLong = new Intl.DateTimeFormat(loc, { month: 'long' });
89
                  var fmtMonthShort= new Intl.DateTimeFormat(loc, { month: 'short' });
90
91
                  // Weekdays: flatpickr expects 0..6 starting on Sunday
92
                  var sun = new Date(Date.UTC(2020, 0, 5)); // a Sunday
93
                  var weekdaysLong = [], weekdaysShort = [];
94
                  for (var i=0;i<7;i++){
95
                    var d = new Date(sun); d.setUTCDate(sun.getUTCDate()+i);
96
                    weekdaysLong.push(cap(fmtWeekLong.format(d)));
97
                    weekdaysShort.push(cap(fmtWeekShort.format(d)));
98
                  }
99
100
                  // Months 0..11
101
                  var monthsLong = [], monthsShort = [];
102
                  for (var m=0;m<12;m++){
103
                    var dm = new Date(Date.UTC(2020, m, 1));
104
                    monthsLong.push(cap(fmtMonthLong.format(dm)));
105
                    monthsShort.push(cap(fmtMonthShort.format(dm)));
106
                  }
107
108
                  // First day of week (fallback to Monday if not available)
109
                  var firstDay = 1; // 0=Sun, 1=Mon
110
                  try {
111
                    if (window.Intl && Intl.Locale) {
112
                      var inf = new Intl.Locale(loc);
113
                      if (inf.weekInfo && inf.weekInfo.firstDay) {
114
                        firstDay = (inf.weekInfo.firstDay === 7) ? 0 : inf.weekInfo.firstDay;
115
                      }
116
                    }
117
                  } catch(e){}
118
119
                  return {
120
                    weekdays: { shorthand: weekdaysShort, longhand: weekdaysLong },
121
                    months:   { shorthand: monthsShort,  longhand: monthsLong  },
122
                    firstDayOfWeek: firstDay,
123
                    weekAbbreviation: 'Wk',
124
                    rangeSeparator: ' – ',
125
                    time_24hr: true
126
                  };
127
                } catch(e) {
128
                  return 'en';
129
                }
130
              }
131
132
              function initialize() {
133
                try {
134
                  if (!window.flatpickr) return;
135
                  if (input._flatpickr) { input._flatpickr.destroy(); }
136
137
                  var loc = buildFlatpickrLocale('{$localeCode}');
138
139
                  // Set as global default when possible
140
                  if (typeof flatpickr.localize === 'function' && typeof loc === 'object') {
141
                    flatpickr.localize(loc);
142
                  }
143
144
                  // Also register under the key for string-based locale usage
145
                  if (flatpickr.l10ns && typeof loc === 'object') {
146
                    flatpickr.l10ns['{$localeCode}'] = loc;
147
                  }
148
149
                  var instance = flatpickr('#{$id}', {
150
                    locale: loc,
151
                    altInput: true,
152
                    altFormat: '{$altFormat}',
153
                    enableTime: true,
154
                    dateFormat: 'Y-m-d H:i',
155
                    time_24hr: true,
156
                    wrap: false,
157
                    onReady: function(selectedDates, dateStr, fp) {
158
                      const btn = document.createElement('button');
159
                      btn.textContent = '".get_lang('Validate')."';
160
                      btn.className = 'flatpickr-validate-btn';
161
                      btn.type = 'button';
162
                      btn.onclick = function(){ fp.close(); };
163
                      fp.calendarContainer.appendChild(btn);
164
165
                      try { fp.redraw(); } catch(e){}
166
                    }
167
                  });
168
169
                  // Ensure l10n is applied and redraw if needed
170
                  try {
171
                    if (instance && instance.l10n && typeof loc === 'object') {
172
                      Object.assign(instance.l10n, loc);
173
                      instance.redraw();
174
                    }
175
                  } catch(e){}
176
177
                  // Debug (optional):
178
                  // console.log('[DateTimePicker]', '{$localeCode}', instance && instance.l10n);
179
                } catch(e) {
180
                  console.error('[DateTimePicker] flatpickr init error', e);
181
                }
182
              }
183
184
              initialize();
185
            });
186
            </script>";
187
188
        return $js;
189
    }
190
191
    /**
192
     * Returns a normalized 2-letter locale to be used by JS/Intl.
193
     * Priority: course > user > platform.
194
     */
195
    private function getLocaleCode(): string
196
    {
197
        // 1) platform default
198
        $raw = (string) api_get_language_isocode();
199
200
        // 2) user (if not anonymous)
201
        $user = api_get_user_info();
202
        if (is_array($user) && !empty($user['language']) && ANONYMOUS != $user['status']) {
203
            $raw = (string) $user['language'];
204
        }
205
206
        // 3) course (highest priority)
207
        $course = api_get_course_info();
208
        if (!empty($course) && !empty($course['language'])) {
209
            $raw = (string) $course['language'];
210
        }
211
212
        return $this->normalizeIsoKey($raw);
213
    }
214
215
    /**
216
     * Normalizes any ISO/custom-ISO to a base language code that Intl can handle safely.
217
     * Rules:
218
     *  - 'xx'                -> 'xx'
219
     *  - 'xx_YY' / 'xx-YY'   -> 'xx'   (e.g., pt_PT -> pt, nn_NO -> nn, zh_TW -> zh)
220
     *  - 'xx_suffix*'        -> 'xx'   (e.g., de_german2 -> de, es_spanish -> es, fr_french2 -> fr)
221
     *  - 'longtag_ES'        -> 'es'   (e.g., ast_ES, eu_ES -> es, instead of falling back to en)
222
     *  - otherwise           -> 'en'
223
     */
224
    private function normalizeIsoKey(string $raw): string
225
    {
226
        $s = strtolower(trim($raw));
227
        if ($s === '') {
228
            return 'en';
229
        }
230
231
        // unify separator
232
        $s = str_replace('-', '_', $s);
233
234
        // direct 2-letter language (es, en, fr, de, ...)
235
        if (preg_match('/^[a-z]{2}$/', $s)) {
236
            return $s;
237
        }
238
239
        // 'xx_YY' or 'xx_anything' -> keep base 'xx'
240
        if (preg_match('/^([a-z]{2})_[a-z0-9]+$/', $s, $m)) {
241
            return $m[1];
242
        }
243
244
        // 'xx_suffix' with digits (custom like es_spanish, de_german2, ...)
245
        if (preg_match('/^([a-z]{2})_[a-z]+[0-9]*$/', $s, $m)) {
246
            return $m[1];
247
        }
248
249
        // long language tag followed by region, prefer 'es' if region is ES
250
        if (preg_match('/^[a-z]{3,}_(..)$/', $s, $m)) {
251
            return ($m[1] === 'es') ? 'es' : 'en';
252
        }
253
254
        // fallback: extract first 2 letters if available
255
        if (preg_match('/^([a-z]{2})/', $s, $m)) {
256
            return $m[1];
257
        }
258
259
        return 'en';
260
    }
261
}
262