Projet

Général

Profil

Paste
Télécharger (29 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / date / date_popup / date_popup.module @ 27370441

1 85ad3d82 Assos Assos
<?php
2
/**
3
 * @file
4
 * A module to enable jquery calendar and time entry popups.
5
 * Requires the Date API.
6
 *
7
 * Add a type of #date_popup to any date, time, or datetime field that will
8
 * use this popup. Set #date_format to the way the date should be presented
9
 * to the user in the form. Set #default_value to be a date in the local
10
 * timezone, and note the timezone name in #date_timezone.
11
 *
12
 * The element will create two textfields, one for the date and one for the
13
 * time. The date textfield will include a jQuery popup calendar date picker,
14
 * and the time textfield uses a jQuery timepicker.
15
 *
16
 * If no time elements are included in the format string, only the date
17
 * textfield will be created. If no date elements are included in the format
18
 * string, only the time textfield, will be created.
19
 */
20
21
/**
22
 * Load needed files.
23
 *
24
 * Play nice with jQuery UI.
25
 */
26
function date_popup_add() {
27
  static $loaded = FALSE;
28
  if ($loaded) {
29
    return;
30
  }
31
  drupal_add_library('system', 'ui.datepicker');
32
  drupal_add_library('date_popup', 'timeentry');
33
34
  // Add the wvega-timepicker library if it's available.
35
  $wvega_path = date_popup_get_wvega_path();
36
  if ($wvega_path) {
37
    drupal_add_js($wvega_path . '/jquery.timepicker.js');
38
    drupal_add_css($wvega_path . '/jquery.timepicker.css');
39
  }
40
  $loaded = TRUE;
41
}
42
43
/**
44
 * Get the location of the Willington Vega timepicker library.
45
 *
46 b720ea3e Assos Assos
 * @return string
47 85ad3d82 Assos Assos
 *   The location of the library, or FALSE if the library isn't installed.
48
 */
49
function date_popup_get_wvega_path() {
50
  $path = FALSE;
51
  if (function_exists('libraries_get_path')) {
52
    $path = libraries_get_path('wvega-timepicker');
53
    if (!file_exists($path)) {
54
      $path = FALSE;
55
    }
56
  }
57
  elseif (file_exists('sites/all/libraries/wvega-timepicker/jquery.timepicker.js')) {
58
    $path = 'sites/all/libraries/wvega-timepicker';
59
  }
60
  return $path;
61
}
62
63
/**
64
 * Get the name of the preferred default timepicker.
65
 *
66
 * If the wvega timepicker is available on the system, default to using that,
67
 * unless the administrator has specifically chosen otherwise.
68
 */
69
function date_popup_get_preferred_timepicker() {
70
  $wvega_available = date_popup_get_wvega_path();
71
  return $wvega_available ? 'wvega' : 'default';
72
}
73
74
/**
75
 * Implements hook_library().
76
 */
77
function date_popup_library() {
78
  $libraries = array();
79
80
  $path = drupal_get_path('module', 'date_popup');
81
  $libraries['timeentry'] = array(
82
    'title' => 'Time Entry',
83
    'website' => 'http://plugins.jquery.com/project/timeEntry',
84
    'version' => '1.4.7',
85
    'js' => array(
86
      $path . '/jquery.timeentry.pack.js' => array(),
87
    ),
88
    'css' => array(
89
      $path . '/themes/jquery.timeentry.css' => array(),
90
    ),
91
  );
92
  return $libraries;
93
}
94
95
/**
96 b720ea3e Assos Assos
 * Create a unique CSS id name and output a single inline JS block.
97
 *
98
 * For each startup function to call and settings array to pass it.
99
 *
100
 * This used to create a unique CSS class for each unique combination of
101 85ad3d82 Assos Assos
 * function and settings, but using classes requires a DOM traversal
102
 * and is much slower than an id lookup.  The new approach returns to
103
 * requiring a duplicate copy of the settings/code for every element
104
 * that uses them, but is much faster.  We could combine the logic by
105
 * putting the ids for each unique function/settings combo into
106
 * Drupal.settings and searching for each listed id.
107
 *
108 b720ea3e Assos Assos
 * @param string $id
109 85ad3d82 Assos Assos
 *   The CSS class prefix to search the DOM for.
110
 *   TODO : unused ?
111 b720ea3e Assos Assos
 *
112
 * @param string $func
113
 *   The jQuery function to invoke on each DOM element
114
 *   containing the returned CSS class.
115
 *
116
 * @param array $settings
117 85ad3d82 Assos Assos
 *   The settings array to pass to the jQuery function.
118 b720ea3e Assos Assos
 *
119 85ad3d82 Assos Assos
 * @returns
120 b720ea3e Assos Assos
 *   The CSS id to assign to the element that should have $func($settings)
121
 *   invoked on it.
122 85ad3d82 Assos Assos
 */
123
function date_popup_js_settings_id($id, $func, $settings) {
124
  static $js_added = FALSE;
125
  static $id_count = array();
126
127
  // Make sure popup date selector grid is in correct year.
128
  if (!empty($settings['yearRange'])) {
129
    $parts = explode(':', $settings['yearRange']);
130 b720ea3e Assos Assos
    // Set the default date to 0 or the lowest bound if
131
    // the date ranges do not include the current year.
132
    // Necessary for the datepicker to render and select dates correctly.
133
    $default_date = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0;
134
    $settings += array('defaultDate' => (string) $default_date . 'y');
135 85ad3d82 Assos Assos
  }
136
137
  if (!$js_added) {
138 b720ea3e Assos Assos
    drupal_add_js(drupal_get_path('module', 'date_popup') . '/date_popup.js');
139 85ad3d82 Assos Assos
    $js_added = TRUE;
140
  }
141
142
  // We use a static array to account for possible multiple form_builder()
143
  // calls in the same request (form instance on 'Preview').
144
  if (!isset($id_count[$id])) {
145
    $id_count[$id] = 0;
146
  }
147
148 b720ea3e Assos Assos
  // It looks like we need the additional id_count for this to
149
  // work correctly when there are multiple values.
150
  // $return_id = "$id-$func-popup";
151
  $return_id = "$id-$func-popup-" . $id_count[$id]++;
152 85ad3d82 Assos Assos
  $js_settings['datePopup'][$return_id] = array(
153
    'func' => $func,
154 b720ea3e Assos Assos
    'settings' => $settings,
155 85ad3d82 Assos Assos
  );
156
  drupal_add_js($js_settings, 'setting');
157
  return $return_id;
158
}
159
160 b720ea3e Assos Assos
/**
161
 * Date popup theme handler.
162
 */
163 85ad3d82 Assos Assos
function date_popup_theme() {
164
  return array(
165 b720ea3e Assos Assos
    'date_popup' => array(
166
      'render element' => 'element',
167
    ),
168
  );
169 85ad3d82 Assos Assos
}
170
171
/**
172
 * Implements hook_element_info().
173
 *
174
 * Set the #type to date_popup and fill the element #default_value with
175 b720ea3e Assos Assos
 * a date adjusted to the proper local timezone in datetime format
176
 * (YYYY-MM-DD HH:MM:SS).
177 85ad3d82 Assos Assos
 *
178
 * The element will create two textfields, one for the date and one for the
179
 * time. The date textfield will include a jQuery popup calendar date picker,
180
 * and the time textfield uses a jQuery timepicker.
181
 *
182
 * NOTE - Converting a date stored in the database from UTC to the local zone
183
 * and converting it back to UTC before storing it is not handled by this
184
 * element and must be done in pre-form and post-form processing!!
185
 *
186
 * #date_timezone
187
 *   The local timezone to be used to create this date.
188
 *
189
 * #date_format
190
 *   Unlike earlier versions of this popup, most formats will work.
191
 *
192
 * #date_increment
193
 *   Increment minutes and seconds by this amount, default is 1.
194
 *
195
 * #date_year_range
196
 *   The number of years to go back and forward in a year selector,
197
 *   default is -3:+3 (3 back and 3 forward).
198
 *
199
 * #datepicker_options
200
 *   An associative array representing the jQuery datepicker options you want
201
 *   to set for this element. Use the jQuery datepicker option names as keys.
202
 *   Hard coded defaults are:
203
 *   - changeMonth => TRUE
204
 *   - changeYear => TRUE
205
 *   - autoPopUp => 'focus'
206
 *   - closeAtTop => FALSE
207
 *   - speed => 'immediate'
208
 */
209
function date_popup_element_info() {
210
  $timepicker = date_popup_get_preferred_timepicker();
211
  $type['date_popup'] = array(
212
    '#input' => TRUE,
213
    '#tree' => TRUE,
214
    '#date_timezone' => date_default_timezone(),
215
    '#date_flexible' => 0,
216
    '#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'),
217
    '#datepicker_options' => array(),
218
    '#timepicker' => variable_get('date_popup_timepicker', $timepicker),
219
    '#date_increment' => 1,
220
    '#date_year_range' => '-3:+3',
221
    '#date_label_position' => 'above',
222
    '#process' => array('date_popup_element_process'),
223
    '#value_callback' => 'date_popup_element_value_callback',
224
    '#theme_wrappers' => array('date_popup'),
225
  );
226
  if (module_exists('ctools')) {
227
    $type['date_popup']['#pre_render'] = array('ctools_dependent_pre_render');
228
  }
229
  return $type;
230
}
231
232 b720ea3e Assos Assos
/**
233
 * Date popup date granularity.
234
 */
235 85ad3d82 Assos Assos
function date_popup_date_granularity($element) {
236
  $granularity = date_format_order($element['#date_format']);
237
  return array_intersect($granularity, array('month', 'day', 'year'));
238
}
239
240 b720ea3e Assos Assos
/**
241
 * Date popup time granularity.
242
 */
243 85ad3d82 Assos Assos
function date_popup_time_granularity($element) {
244
  $granularity = date_format_order($element['#date_format']);
245
  return array_intersect($granularity, array('hour', 'minute', 'second'));
246
}
247
248 b720ea3e Assos Assos
/**
249
 * Date popup date format.
250
 */
251 85ad3d82 Assos Assos
function date_popup_date_format($element) {
252
  return (date_limit_format($element['#date_format'], date_popup_date_granularity($element)));
253
}
254
255 b720ea3e Assos Assos
/**
256
 * Date popup time format.
257
 */
258 85ad3d82 Assos Assos
function date_popup_time_format($element) {
259
  return date_popup_format_to_popup_time(date_limit_format($element['#date_format'], date_popup_time_granularity($element)), $element['#timepicker']);
260
}
261
262
/**
263
 * Element value callback for date_popup element.
264
 */
265 b720ea3e Assos Assos
// @codingStandardsIgnoreStart
266 85ad3d82 Assos Assos
function date_popup_element_value_callback($element, $input = FALSE, &$form_state) {
267
  $granularity = date_format_order($element['#date_format']);
268
  $has_time = date_has_time($granularity);
269
  $date = NULL;
270
  $return = $has_time ? array('date' => '', 'time' => '') : array('date' => '');
271
  // Normal input from submitting the form element.
272
  // Check is_array() to skip the string input values created by Views pagers.
273
  // Those string values, if present, should be interpreted as empty input.
274
  if ($input !== FALSE && is_array($input)) {
275
    $return = $input;
276
    $date = date_popup_input_date($element, $input);
277
  }
278
  // No input? Try the default value.
279
  elseif (!empty($element['#default_value'])) {
280
    $date = date_default_date($element);
281
  }
282
  // Date with errors won't re-display.
283
  if (date_is_date($date)) {
284
    $return['date'] = !$date->timeOnly ? date_format_date($date, 'custom', date_popup_date_format($element)) : '';
285
    $return['time'] = $has_time ? date_format_date($date, 'custom', date_popup_time_format($element)) : '';
286
  }
287
  elseif (!empty($input)) {
288
    $return = $input;
289
  }
290
  return $return;
291
292
}
293 b720ea3e Assos Assos
// @codingStandardsIgnoreEnd
294 85ad3d82 Assos Assos
295
/**
296
 * Javascript popup element processing.
297 b720ea3e Assos Assos
 *
298 85ad3d82 Assos Assos
 * Add popup attributes to $element.
299
 */
300
function date_popup_element_process($element, &$form_state, $form) {
301
  if (date_hidden_element($element)) {
302
    return $element;
303
  }
304
305
  date_popup_add();
306
  module_load_include('inc', 'date_api', 'date_api_elements');
307
308
  $element['#tree'] = TRUE;
309
  $element['#theme_wrappers'] = array('date_popup');
310
311
  if (!empty($element['#ajax'])) {
312
    $element['#ajax'] += array(
313 b720ea3e Assos Assos
      'trigger_as' => array(
314
        'name' => $element['#name'],
315
      ),
316 85ad3d82 Assos Assos
      'event' => 'change',
317
    );
318
  }
319
320
  $element['date'] = date_popup_process_date_part($element);
321
  $element['time'] = date_popup_process_time_part($element);
322
323 b720ea3e Assos Assos
  // Make changes if instance is set to be rendered as a regular field.
324
  if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) {
325
    if (!empty($element['date']) && empty($element['time'])) {
326
      $element['date']['#title'] = check_plain($element['#instance']['label']);
327
      $element['date']['#required'] = $element['#required'];
328
    }
329
    elseif (empty($element['date']) && !empty($element['time'])) {
330
      $element['time']['#title'] = check_plain($element['#instance']['label']);
331
      $element['time']['#required'] = $element['#required'];
332
    }
333
  }
334
335 85ad3d82 Assos Assos
  if (isset($element['#element_validate'])) {
336
    array_push($element['#element_validate'], 'date_popup_validate');
337
  }
338
  else {
339
    $element['#element_validate'] = array('date_popup_validate');
340
  }
341
342
  $context = array(
343 b720ea3e Assos Assos
    'form' => $form,
344 85ad3d82 Assos Assos
  );
345
  drupal_alter('date_popup_process', $element, $form_state, $context);
346
347
  return $element;
348
}
349
350
/**
351
 * Process the date portion of the element.
352
 */
353
function date_popup_process_date_part(&$element) {
354
  $granularity = date_format_order($element['#date_format']);
355
  $date_granularity = date_popup_date_granularity($element);
356 b720ea3e Assos Assos
  if (empty($date_granularity)) {
357
    return array();
358
  }
359 85ad3d82 Assos Assos
360
  // The datepicker can't handle zero or negative values like 0:+1
361
  // even though the Date API can handle them, so rework the value
362
  // we pass to the datepicker to use defaults it can accept (such as +0:+1)
363
  // date_range_string() adds the necessary +/- signs to the range string.
364
  $this_year = date_format(date_now(), 'Y');
365 b720ea3e Assos Assos
  // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string.
366
  // Fill the 'date' string in this case.
367
  $mock = NULL;
368
  $callback_values = date_popup_element_value_callback($element, FALSE, $mock);
369
  if (!isset($element['#value']['date']) && isset($callback_values['date'])) {
370
    $element['#value']['date'] = $callback_values['date'];
371
  }
372 85ad3d82 Assos Assos
  $date = '';
373
  if (!empty($element['#value']['date'])) {
374
    $date = new DateObject($element['#value']['date'], $element['#date_timezone'], date_popup_date_format($element));
375
  }
376
  $range = date_range_years($element['#date_year_range'], $date);
377
  $year_range = date_range_string($range);
378
379
  // Add the dynamic datepicker options. Allow element-specific datepicker
380
  // preferences to override these options for whatever reason they see fit.
381
  $settings = $element['#datepicker_options'] + array(
382
    'changeMonth' => TRUE,
383
    'changeYear' => TRUE,
384
    'autoPopUp' => 'focus',
385
    'closeAtTop' => FALSE,
386
    'speed' => 'immediate',
387
    'firstDay' => intval(variable_get('date_first_day', 0)),
388 b720ea3e Assos Assos
    // 'buttonImage' => base_path()
389
    // . drupal_get_path('module', 'date_api') ."/images/calendar.png",
390
    // 'buttonImageOnly' => TRUE,
391 85ad3d82 Assos Assos
    'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'),
392
    'yearRange' => $year_range,
393
    // Custom setting, will be expanded in Drupal.behaviors.date_popup()
394
    'fromTo' => isset($fromto),
395
  );
396
397
  // Create a unique id for each set of custom settings.
398
  $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
399
400 b720ea3e Assos Assos
  // Manually build this element and set the value -
401
  // this will prevent corrupting the parent value.
402 85ad3d82 Assos Assos
  $parents = array_merge($element['#parents'], array('date'));
403
  $sub_element = array(
404
    '#type' => 'textfield',
405
    '#title' => theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)),
406
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
407 b720ea3e Assos Assos
    '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)),
408 85ad3d82 Assos Assos
    '#id' => $id,
409
    '#input' => FALSE,
410
    '#size' => !empty($element['#size']) ? $element['#size'] : 20,
411
    '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30,
412
    '#attributes' => $element['#attributes'],
413
    '#parents' => $parents,
414 b720ea3e Assos Assos
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
415 85ad3d82 Assos Assos
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
416
  );
417
  $sub_element['#value'] = $sub_element['#default_value'];
418 b720ea3e Assos Assos
  // TODO, figure out exactly when we want this description.
419
  // In many places it is not desired.
420
  $sub_element['#description'] = ' ' . t('E.g., @date', array(
421
      '@date' => date_format_date(
422
        date_example_date(),
423
        'custom',
424
        date_popup_date_format($element)
425
        ),
426
      ));
427 85ad3d82 Assos Assos
428
  return $sub_element;
429
}
430
431
/**
432
 * Process the time portion of the element.
433
 */
434
function date_popup_process_time_part(&$element) {
435
  $granularity = date_format_order($element['#date_format']);
436
  $has_time = date_has_time($granularity);
437 b720ea3e Assos Assos
  if (empty($has_time)) {
438
    return array();
439
  }
440
441
  // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string.
442
  // Fill the 'time' string in this case.
443
  $mock = NULL;
444
  $callback_values = date_popup_element_value_callback($element, FALSE, $mock);
445
  if (!isset($element['#value']['time']) && isset($callback_values['time'])) {
446
    $element['#value']['time'] = $callback_values['time'];
447
  }
448 85ad3d82 Assos Assos
449
  switch ($element['#timepicker']) {
450
    case 'default':
451
      $func = 'timeEntry';
452
      $settings = array(
453
        'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE,
454
        'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE),
455 b720ea3e Assos Assos
        'timeSteps' => array(
456
          1,
457
          intval($element['#date_increment']),
458
          (in_array('second', $granularity) ? $element['#date_increment'] : 0),
459
        ),
460 85ad3d82 Assos Assos
        'spinnerImage' => '',
461
        'fromTo' => isset($fromto),
462 b720ea3e Assos Assos
      );
463 85ad3d82 Assos Assos
      if (strpos($element['#date_format'], 'a') !== FALSE) {
464
        // Then we are using lowercase am/pm.
465
        $settings['ampmNames'] = array('am', 'pm');
466
      }
467
      if (strpos($element['#date_format'], ' A') !== FALSE || strpos($element['#date_format'], ' a') !== FALSE) {
468
        $settings['ampmPrefix'] = ' ';
469
      }
470
      break;
471 b720ea3e Assos Assos
472 85ad3d82 Assos Assos
    case 'wvega':
473
      $func = 'timepicker';
474 b720ea3e Assos Assos
      $grans = array('hour', 'minute', 'second');
475
      $time_granularity = array_intersect($granularity, $grans);
476 85ad3d82 Assos Assos
      $format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega');
477
      // The first value in the dropdown list should be the same as the element
478
      // default_value, but it needs to be in JS format (i.e. milliseconds since
479
      // the epoch).
480
      $start_time = new DateObject($element['#default_value'], $element['#date_timezone'], DATE_FORMAT_DATETIME);
481
      date_increment_round($start_time, $element['#date_increment']);
482
      $start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000;
483
      $settings = array(
484
        'timeFormat' => $format,
485
        'interval' => $element['#date_increment'],
486
        'startTime' => $start_time,
487
        'scrollbar' => TRUE,
488
      );
489
      break;
490 b720ea3e Assos Assos
491 85ad3d82 Assos Assos
    default:
492
      $func = '';
493
      $settings = array();
494
      break;
495
  }
496
497
  // Create a unique id for each set of custom settings.
498
  $id = date_popup_js_settings_id($element['#id'], $func, $settings);
499
500 b720ea3e Assos Assos
  // Manually build this element and set the value -
501
  // this will prevent corrupting the parent value.
502 85ad3d82 Assos Assos
  $parents = array_merge($element['#parents'], array('time'));
503
  $sub_element = array(
504
    '#type' => 'textfield',
505
    '#title' => theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)),
506
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
507
    '#default_value' => $element['#value']['time'],
508
    '#id' => $id,
509
    '#size' => 15,
510
    '#maxlength' => 10,
511
    '#attributes' => $element['#attributes'],
512
    '#parents' => $parents,
513 b720ea3e Assos Assos
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
514 85ad3d82 Assos Assos
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
515
  );
516
517
  $sub_element['#value'] = $sub_element['#default_value'];
518
519 b720ea3e Assos Assos
  // TODO, figure out exactly when we want this description.
520
  // In many places it is not desired.
521 85ad3d82 Assos Assos
  $example_date = date_now();
522
  date_increment_round($example_date, $element['#date_increment']);
523 b720ea3e Assos Assos
  $sub_element['#description'] = t('E.g., @date', array(
524
    '@date' => date_format_date(
525
      $example_date,
526
      'custom',
527
      date_popup_time_format($element)
528
    )));
529 85ad3d82 Assos Assos
530
  return ($sub_element);
531
}
532
533
/**
534
 * Massage the input values back into a single date.
535
 *
536
 * When used as a Views widget, the validation step always gets triggered,
537
 * even with no form submission. Before form submission $element['#value']
538
 * contains a string, after submission it contains an array.
539
 */
540
function date_popup_validate($element, &$form_state) {
541
542
  if (date_hidden_element($element)) {
543
    return;
544
  }
545
546
  if (is_string($element['#value'])) {
547
    return;
548
  }
549
550
  module_load_include('inc', 'date_api', 'date_api_elements');
551
  date_popup_add();
552
553
  $input_exists = NULL;
554
  $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
555 b720ea3e Assos Assos
  // If the date is a string, it is not considered valid and can cause problems
556
  // later on, so just exit out now.
557
  if (is_string($input)) {
558
    return;
559
  }
560 85ad3d82 Assos Assos
561
  drupal_alter('date_popup_pre_validate', $element, $form_state, $input);
562
563
  $granularity = date_format_order($element['#date_format']);
564
  $date_granularity = date_popup_date_granularity($element);
565
  $time_granularity = date_popup_time_granularity($element);
566
  $has_time = date_has_time($granularity);
567
568 b720ea3e Assos Assos
  // @codingStandardsIgnoreStart
569
  $label = '';
570
  if (!empty($element['#date_title'])) {
571
    $label = t($element['#date_title']);
572
  }
573
  elseif (!empty($element['#title'])) {
574
    $label = t($element['#title']);
575
  }
576
  // @codingStandardsIgnoreEnd
577 85ad3d82 Assos Assos
  $date = date_popup_input_date($element, $input);
578
579
  // If the date has errors, display them.
580
  // If something was input but there is no date, the date is invalid.
581
  // If the field is empty and required, set error message and return.
582
  $error_field = implode('][', $element['#parents']);
583
  if (empty($date) || !empty($date->errors)) {
584
    if (is_object($date) && !empty($date->errors)) {
585
      $message = t('The value input for field %field is invalid:', array('%field' => $label));
586
      $message .= '<br />' . implode('<br />', $date->errors);
587
      form_set_error($error_field, $message);
588
      return;
589
    }
590
    if (!empty($input['date'])) {
591
      $message = t('The value input for field %field is invalid.', array('%field' => $label));
592
      form_set_error($error_field, $message);
593
      return;
594
    }
595
    if ($element['#required']) {
596
      $message = t('A valid date is required for %title.', array('%title' => $label));
597
      form_set_error($error_field, $message);
598
      return;
599
    }
600
  }
601
602
  // If the created date is valid, set it.
603
  $value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
604
  form_set_value($element, $value, $form_state);
605
}
606
607
/**
608
 * Helper function for extracting a date value out of user input.
609
 *
610 b720ea3e Assos Assos
 * @param bool $auto_complete
611 85ad3d82 Assos Assos
 *   Should we add a time value to complete the date if there is no time?
612
 *   Useful anytime the time value is optional.
613
 */
614
function date_popup_input_date($element, $input, $auto_complete = FALSE) {
615
  if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) {
616
    return NULL;
617
  }
618
  date_popup_add();
619
  $granularity = date_format_order($element['#date_format']);
620
  $has_time = date_has_time($granularity);
621
  $flexible = !empty($element['#date_flexible']) ? $element['#date_flexible'] : 0;
622
623
  $format = date_popup_date_format($element);
624
  $format .= $has_time ? ' ' . date_popup_time_format($element) : '';
625 b720ea3e Assos Assos
  $datetime = trim($input['date']);
626
  $datetime .= $has_time ? ' ' . trim($input['time']) : '';
627 85ad3d82 Assos Assos
  $date = new DateObject($datetime, $element['#date_timezone'], $format);
628
  if (is_object($date)) {
629
    $date->limitGranularity($granularity);
630
    if ($date->validGranularity($granularity, $flexible)) {
631
      date_increment_round($date, $element['#date_increment']);
632
    }
633
    return $date;
634
  }
635
  return NULL;
636
}
637
638
/**
639
 * Allowable time formats.
640
 */
641
function date_popup_time_formats($with_seconds = FALSE) {
642
  return array(
643
    'H:i:s',
644
    'h:i:sA',
645 b720ea3e Assos Assos
  );
646 85ad3d82 Assos Assos
}
647
648
/**
649
 * Format options array.
650
 *
651
 * TODO Remove any formats not supported by the widget, if any.
652
 */
653
function date_popup_formats() {
654 b720ea3e Assos Assos
  // Load short date formats.
655
  $formats = system_get_date_formats('short');
656
657
  // Load custom date formats.
658
  if ($formats_custom = system_get_date_formats('custom')) {
659
    $formats = array_merge($formats, $formats_custom);
660
  }
661
662
  $formats = str_replace('i', 'i:s', array_keys($formats));
663 85ad3d82 Assos Assos
  $formats = drupal_map_assoc($formats);
664 b720ea3e Assos Assos
665 85ad3d82 Assos Assos
  return $formats;
666
}
667
668
/**
669
 * Recreate a date format string so it has the values popup expects.
670
 *
671
 * @param string $format
672 b720ea3e Assos Assos
 *   A normal date format string, like Y-m-d
673
 *
674 85ad3d82 Assos Assos
 * @return string
675
 *   A format string in popup format, like YMD-, for the
676
 *   earlier 'calendar' version, or m/d/Y for the later 'datepicker'
677
 *   version.
678
 */
679
function date_popup_format_to_popup($format) {
680
  if (empty($format)) {
681
    $format = 'Y-m-d';
682
  }
683
  $replace = date_popup_datepicker_format_replacements();
684
  return strtr($format, $replace);
685
}
686
687
/**
688
 * Recreate a time format string so it has the values popup expects.
689
 *
690
 * @param string $format
691 b720ea3e Assos Assos
 *   A normal time format string, like h:i (a)
692
 *
693 85ad3d82 Assos Assos
 * @return string
694 b720ea3e Assos Assos
 *   A format string that the popup can accept like h:i a
695 85ad3d82 Assos Assos
 */
696
function date_popup_format_to_popup_time($format, $timepicker = NULL) {
697
  if (empty($format)) {
698
    $format = 'H:i';
699
  }
700 b720ea3e Assos Assos
  $symbols = array(
701
    '/',
702
    '-',
703
    ' .',
704
    ',',
705
    'F',
706
    'M',
707
    'l',
708
    'z',
709
    'w',
710
    'W',
711
    'd',
712
    'j',
713
    'm',
714
    'n',
715
    'y',
716
    'Y',
717
  );
718
  $format = str_replace($symbols, '', $format);
719 85ad3d82 Assos Assos
  $format = strtr($format, date_popup_timepicker_format_replacements($timepicker));
720
  return $format;
721
}
722
723
/**
724
 * Reconstruct popup format string into normal format string.
725
 *
726
 * @param string $format
727 b720ea3e Assos Assos
 *   A string in popup format, like YMD-
728
 *
729 85ad3d82 Assos Assos
 * @return string
730 b720ea3e Assos Assos
 *   A normal date format string, like Y-m-d
731 85ad3d82 Assos Assos
 */
732
function date_popup_popup_to_format($format) {
733
  $replace = array_flip(date_popup_datepicker_format_replacements());
734
  return strtr($format, $replace);
735
}
736
737
/**
738
 * Return a map of format replacements required for a given timepicker.
739
 *
740
 * Client-side time entry plugins don't support all possible date formats.
741
 * This function returns a map of format replacements required to change any
742
 * input format into one that the given timepicker can support.
743
 *
744 b720ea3e Assos Assos
 * @param string $timepicker
745 85ad3d82 Assos Assos
 *   The time entry plugin being used: either 'wvega' or 'default'.
746 b720ea3e Assos Assos
 *
747
 * @return array
748 85ad3d82 Assos Assos
 *   A map of replacements.
749
 */
750
function date_popup_timepicker_format_replacements($timepicker = 'default') {
751
  switch ($timepicker) {
752
    case 'wvega':
753 b720ea3e Assos Assos
      // The wvega timepicker only supports uppercase AM/PM.
754
      return array('a' => 'A');
755
756 85ad3d82 Assos Assos
    default:
757 b720ea3e Assos Assos
      // The default timeEntry plugin requires leading zeros.
758
      return array('G' => 'H', 'g' => 'h');
759 85ad3d82 Assos Assos
  }
760
}
761
762
/**
763
 * The format replacement patterns for the new datepicker.
764
 */
765
function date_popup_datepicker_format_replacements() {
766
  return array(
767 b720ea3e Assos Assos
    'd' => 'dd',
768
    'j' => 'd',
769
    'l' => 'DD',
770
    'D' => 'D',
771
    'm' => 'mm',
772
    'n' => 'm',
773
    'F' => 'MM',
774
    'M' => 'M',
775
    'Y' => 'yy',
776
    'y' => 'y',
777 85ad3d82 Assos Assos
  );
778
}
779
780
/**
781
 * Format a date popup element.
782
 *
783
 * Use a class that will float date and time next to each other.
784
 */
785
function theme_date_popup($vars) {
786
  $element = $vars['element'];
787
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
788
  $attributes['class'][] = 'container-inline-date';
789 b720ea3e Assos Assos
  // If there is no description, the floating date
790
  // elements need some extra padding below them.
791 85ad3d82 Assos Assos
  $wrapper_attributes = array('class' => array('date-padding'));
792
  if (empty($element['date']['#description'])) {
793
    $wrapper_attributes['class'][] = 'clearfix';
794
  }
795 b720ea3e Assos Assos
  // Add an wrapper to mimic the way a single value field works,
796
  // for ease in using #states.
797 85ad3d82 Assos Assos
  if (isset($element['#children'])) {
798 b720ea3e Assos Assos
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
799 85ad3d82 Assos Assos
  }
800 b720ea3e Assos Assos
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
801 85ad3d82 Assos Assos
}
802
803
/**
804
 * Implements hook_menu().
805
 */
806
function date_popup_menu() {
807
  $items = array();
808
  // TODO Fix this later.
809
  $items['admin/config/date/date_popup'] = array(
810
    'title' => 'Date Popup',
811
    'description' => 'Configure the Date Popup settings.',
812
    'page callback' => 'drupal_get_form',
813
    'page arguments' => array('date_popup_settings'),
814
    'access callback' => 'user_access',
815
    'access arguments' => array('administer site configuration'),
816
  );
817
  return $items;
818
}
819
/**
820
 * General configuration form for controlling the Date Popup behaviour.
821
 */
822
function date_popup_settings() {
823
  $wvega_available = date_popup_get_wvega_path();
824
  $preferred_timepicker = date_popup_get_preferred_timepicker();
825
826
  $form['#prefix'] = t('<p>The Date Popup module allows for manual time entry or use of a jQuery timepicker plugin. The Date module comes with a default jQuery timepicker which is already installed. The module also supports a dropdown timepicker that must be downloaded separately. The dropdown timepicker will not appear as an option until the code is available in the libraries folder. If you do not want to use a jQuery timepicker, you can choose the "Manual time entry" option below and users will get a regular textfield instead.</p>');
827
  $form['date_popup_timepicker'] = array(
828
    '#type' => 'select',
829
    '#options' => array(
830
      'default' => t('Use default jQuery timepicker'),
831 b720ea3e Assos Assos
      'wvega'   => t('Use dropdown timepicker'),
832
      'none'    => t('Manual time entry, no jQuery timepicker'),
833 85ad3d82 Assos Assos
    ),
834
    '#title' => t('Timepicker'),
835
    '#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker),
836
  );
837
838
  if (!$wvega_available) {
839
    $form['#prefix'] .= t('<p>To install the dropdown timepicker, create a <code>!directory</code> directory in your site installation. Then visit <a href="@download">@download</a>, download the latest copy and unzip it. You will see files with names like jquery.timepicker-1.1.2.js and jquery.timepicker-1.1.2.css. Rename them to jquery.timepicker.js and jquery.timepicker.css and copy them into <code>!directory</code>.</p>', array('!directory' => 'sites/all/libraries/wvega-timepicker', '@download' => 'https://github.com/wvega/timepicker/archives/master'));
840
    unset($form['date_popup_timepicker']['#options']['wvega']);
841
  }
842
843
  $css = <<<EOM
844
/* ___________ IE6 IFRAME FIX ________ */
845
.ui-datepicker-cover {
846
  display: none; /*sorry for IE5*/
847
  display/**/: block; /*sorry for IE5*/
848
  position: absolute; /*must have*/
849
  z-index: -1; /*must have*/
850
  filter: mask(); /*must have*/
851
  top: -4px; /*must have*/
852
  left: -4px; /*must have*/ /* LTR */
853
  width: 200px; /*must have*/
854
  height: 200px; /*must have*/
855
}
856
EOM;
857
858 b720ea3e Assos Assos
  $form['#suffix'] = t('<p>The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:</p>') . '<blockquote><PRE>' . $css . '</PRE></blockquote>';
859 85ad3d82 Assos Assos
860
  return system_settings_form($form);
861
}