Projet

Général

Profil

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

root / drupal7 / sites / all / modules / date / date_popup / date_popup.module @ 599a39cd

1
<?php
2

    
3
/**
4
 * @file
5
 * A module to enable jquery calendar and time entry popups.
6
 * Requires the Date API.
7
 *
8
 * Add a type of #date_popup to any date, time, or datetime field that will
9
 * use this popup. Set #date_format to the way the date should be presented
10
 * to the user in the form. Set #default_value to be a date in the local
11
 * timezone, and note the timezone name in #date_timezone.
12
 *
13
 * The element will create two textfields, one for the date and one for the
14
 * time. The date textfield will include a jQuery popup calendar date picker,
15
 * and the time textfield uses a jQuery timepicker.
16
 *
17
 * If no time elements are included in the format string, only the date
18
 * textfield will be created. If no date elements are included in the format
19
 * string, only the time textfield, will be created.
20
 */
21

    
22
/**
23
 * Load needed files.
24
 *
25
 * Play nice with jQuery UI.
26
 */
27
function date_popup_add() {
28
  static $loaded = FALSE;
29
  if ($loaded) {
30
    return;
31
  }
32
  drupal_add_library('system', 'ui.datepicker');
33
  drupal_add_library('date_popup', 'timeentry');
34

    
35
  // Add the wvega-timepicker library if it's available.
36
  $wvega_path = date_popup_get_wvega_path();
37
  if ($wvega_path) {
38
    drupal_add_js($wvega_path . '/jquery.timepicker.js');
39
    drupal_add_css($wvega_path . '/jquery.timepicker.css');
40
  }
41
  $loaded = TRUE;
42
}
43

    
44
/**
45
 * Get the location of the Willington Vega timepicker library.
46
 *
47
 * @return string
48
 *   The location of the library, or FALSE if the library isn't installed.
49
 */
50
function date_popup_get_wvega_path() {
51
  $path = FALSE;
52
  if (function_exists('libraries_get_path')) {
53
    // Try loading the wvega timepicker library.
54
    $path = libraries_get_path('wvega-timepicker');
55
    // Check if the library actually exists.
56
    if (empty($path) || !file_exists($path)) {
57
      $path = FALSE;
58
    }
59
  }
60
  elseif (file_exists('sites/all/libraries/wvega-timepicker/jquery.timepicker.js')) {
61
    $path = 'sites/all/libraries/wvega-timepicker';
62
  }
63
  return $path;
64
}
65

    
66
/**
67
 * Get the name of the preferred default timepicker.
68
 *
69
 * If the wvega timepicker is available on the system, default to using that,
70
 * unless the administrator has specifically chosen otherwise.
71
 */
72
function date_popup_get_preferred_timepicker() {
73
  $wvega_available = date_popup_get_wvega_path();
74
  return $wvega_available ? 'wvega' : 'default';
75
}
76

    
77
/**
78
 * Implements hook_library().
79
 */
80
function date_popup_library() {
81
  $libraries = array();
82

    
83
  $path = drupal_get_path('module', 'date_popup');
84
  $libraries['timeentry'] = array(
85
    'title' => 'Time Entry',
86
    'website' => 'http://plugins.jquery.com/project/timeEntry',
87
    'version' => '1.4.7',
88
    'js' => array(
89
      $path . '/jquery.timeentry.pack.js' => array(),
90
    ),
91
    'css' => array(
92
      $path . '/themes/jquery.timeentry.css' => array(),
93
    ),
94
  );
95
  return $libraries;
96
}
97

    
98
/**
99
 * Create a unique CSS id name and output a single inline JS block.
100
 *
101
 * For each startup function to call and settings array to pass it.
102
 *
103
 * This used to create a unique CSS class for each unique combination of
104
 * function and settings, but using classes requires a DOM traversal
105
 * and is much slower than an id lookup.  The new approach returns to
106
 * requiring a duplicate copy of the settings/code for every element
107
 * that uses them, but is much faster.  We could combine the logic by
108
 * putting the ids for each unique function/settings combo into
109
 * Drupal.settings and searching for each listed id.
110
 *
111
 * @param string $id
112
 *   The CSS class prefix to search the DOM for.
113
 *   @todo unused ?
114
 * @param string $func
115
 *   The jQuery function to invoke on each DOM element
116
 *   containing the returned CSS class.
117
 * @param array $settings
118
 *   The settings array to pass to the jQuery function.
119
 *
120
 * @returns
121
 *   The CSS id to assign to the element that should have $func($settings)
122
 *   invoked on it.
123
 */
124
function date_popup_js_settings_id($id, $func, $settings) {
125
  static $js_added = FALSE;
126
  static $id_count = array();
127

    
128
  // Make sure popup date selector grid is in correct year.
129
  if (!empty($settings['yearRange'])) {
130
    $parts = explode(':', $settings['yearRange']);
131
    // Set the default date to 0 or the lowest bound if the date ranges do not
132
    // include the current year. Necessary for the datepicker to render and
133
    // select dates correctly.
134
    $default_date = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0;
135
    $settings += array('defaultDate' => (string) $default_date . 'y');
136
  }
137

    
138
  if (!$js_added) {
139
    drupal_add_js(drupal_get_path('module', 'date_popup') . '/date_popup.js');
140
    $js_added = TRUE;
141
  }
142

    
143
  // We use a static array to account for possible multiple form_builder()
144
  // calls in the same request (form instance on 'Preview').
145
  if (!isset($id_count[$id])) {
146
    $id_count[$id] = 0;
147
  }
148

    
149
  // It looks like we need the additional id_count for this to work correctly
150
  // when there are multiple values.
151
  $return_id = "$id-$func-popup-" . $id_count[$id]++;
152
  $js_settings['datePopup'][$return_id] = array(
153
    'func' => $func,
154
    'settings' => $settings,
155
  );
156
  drupal_add_js($js_settings, 'setting');
157
  return $return_id;
158
}
159

    
160
/**
161
 * Date popup theme handler.
162
 */
163
function date_popup_theme() {
164
  return array(
165
    'date_popup' => array(
166
      'render element' => 'element',
167
    ),
168
  );
169
}
170

    
171
/**
172
 * Implements hook_element_info().
173
 */
174
function date_popup_element_info() {
175
  $timepicker = date_popup_get_preferred_timepicker();
176

    
177
  // Set the #type to date_popup and fill the element #default_value with
178
  // a date adjusted to the proper local timezone in datetime format
179
  // (YYYY-MM-DD HH:MM:SS).
180
  //
181
  // The element will create two textfields, one for the date and one for the
182
  // time. The date textfield will include a jQuery popup calendar date picker,
183
  // and the time textfield uses a jQuery timepicker.
184
  //
185
  // NOTE - Converting a date stored in the database from UTC to the local zone
186
  // and converting it back to UTC before storing it is not handled by this
187
  // element and must be done in pre-form and post-form processing!!
188
  //
189
  // #date_timezone
190
  //   The local timezone to be used to create this date.
191
  //
192
  // #date_format
193
  //   Unlike earlier versions of this popup, most formats will work.
194
  //
195
  // #date_increment
196
  //   Increment minutes and seconds by this amount, default is 1.
197
  //
198
  // #date_year_range
199
  //   The number of years to go back and forward in a year selector,
200
  //   default is -3:+3 (3 back and 3 forward).
201
  //
202
  // #datepicker_options
203
  //   An associative array representing the jQuery datepicker options you want
204
  //   to set for this element. Use the jQuery datepicker option names as keys.
205
  //   Hard coded defaults are:
206
  //   - changeMonth => TRUE
207
  //   - changeYear => TRUE
208
  //   - autoPopUp => 'focus'
209
  //   - closeAtTop => FALSE
210
  //   - speed => 'immediate'
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
    '#attached' => array('css' => array(
226
      drupal_get_path('module', 'date_popup'). '/themes/datepicker.1.7.css',
227
    )),
228
  );
229

    
230
  if (module_exists('ctools')) {
231
    $type['date_popup']['#pre_render'] = array('ctools_dependent_pre_render');
232
  }
233

    
234
  return $type;
235
}
236

    
237
/**
238
 * Date popup date granularity.
239
 */
240
function date_popup_date_granularity($element) {
241
  $granularity = date_format_order($element['#date_format']);
242
  return array_intersect($granularity, array('month', 'day', 'year'));
243
}
244

    
245
/**
246
 * Date popup time granularity.
247
 */
248
function date_popup_time_granularity($element) {
249
  $granularity = date_format_order($element['#date_format']);
250
  return array_intersect($granularity, array('hour', 'minute', 'second'));
251
}
252

    
253
/**
254
 * Date popup date format.
255
 */
256
function date_popup_date_format($element) {
257
  return (date_limit_format($element['#date_format'], date_popup_date_granularity($element)));
258
}
259

    
260
/**
261
 * Date popup time format.
262
 */
263
function date_popup_time_format($element) {
264
  return date_popup_format_to_popup_time(date_limit_format($element['#date_format'], date_popup_time_granularity($element)), $element['#timepicker']);
265
}
266

    
267
/**
268
 * Element value callback for date_popup element.
269
 */
270
function date_popup_element_value_callback($element, $input = FALSE, &$form_state) {
271
  $granularity = date_format_order($element['#date_format']);
272
  $has_time = date_has_time($granularity);
273
  $date = NULL;
274
  $return = $has_time ? array('date' => '', 'time' => '') : array('date' => '');
275
  // Normal input from submitting the form element. Check is_array() to skip
276
  // the string input values created by Views pagers. Those string values, if
277
  // present, should be interpreted as empty input.
278
  if ($input !== FALSE && is_array($input)) {
279
    $return = $input;
280
    $date = date_popup_input_date($element, $input);
281
  }
282
  // No input? Try the default value.
283
  elseif (!empty($element['#default_value'])) {
284
    $date = date_default_date($element);
285
  }
286
  // Date with errors won't re-display.
287
  if (date_is_date($date)) {
288
    $return['date'] = !$date->timeOnly ? date_format_date($date, 'custom', date_popup_date_format($element)) : '';
289
    $return['time'] = $has_time ? date_format_date($date, 'custom', date_popup_time_format($element)) : '';
290
  }
291
  elseif (!empty($input)) {
292
    $return = $input;
293
  }
294
  return $return;
295

    
296
}
297

    
298
/**
299
 * Javascript popup element processing.
300
 *
301
 * Add popup attributes to $element.
302
 */
303
function date_popup_element_process($element, &$form_state, $form) {
304
  if (date_hidden_element($element)) {
305
    return $element;
306
  }
307

    
308
  date_popup_add();
309
  module_load_include('inc', 'date_api', 'date_api_elements');
310

    
311
  $element['#tree'] = TRUE;
312
  $element['#theme_wrappers'] = array('date_popup');
313

    
314
  if (!empty($element['#ajax'])) {
315
    $element['#ajax'] += array(
316
      'trigger_as' => array(
317
        'name' => $element['#name'],
318
      ),
319
      'event' => 'change',
320
    );
321
  }
322

    
323
  $element['date'] = date_popup_process_date_part($element);
324
  $element['time'] = date_popup_process_time_part($element);
325

    
326
  // Make changes if instance is set to be rendered as a regular field.
327
  if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) {
328
    if (!empty($element['date']) && empty($element['time'])) {
329
      $element['date']['#title'] = check_plain($element['#instance']['label']);
330
      $element['date']['#required'] = $element['#required'];
331
    }
332
    elseif (empty($element['date']) && !empty($element['time'])) {
333
      $element['time']['#title'] = check_plain($element['#instance']['label']);
334
      $element['time']['#required'] = $element['#required'];
335
    }
336
  }
337

    
338
  if (isset($element['#element_validate'])) {
339
    array_push($element['#element_validate'], 'date_popup_validate');
340
  }
341
  else {
342
    $element['#element_validate'] = array('date_popup_validate');
343
  }
344

    
345
  $context = array(
346
    'form' => $form,
347
  );
348
  drupal_alter('date_popup_process', $element, $form_state, $context);
349

    
350
  return $element;
351
}
352

    
353
/**
354
 * Process the date portion of the element.
355
 */
356
function date_popup_process_date_part(&$element) {
357
  $granularity = date_format_order($element['#date_format']);
358
  $date_granularity = date_popup_date_granularity($element);
359
  if (empty($date_granularity)) {
360
    return array();
361
  }
362

    
363
  // The datepicker can't handle zero or negative values like 0:+1 even though
364
  // the Date API can handle them, so rework the value we pass to the
365
  // datepicker to use defaults it can accept (such as +0:+1)
366
  // date_range_string() adds the necessary +/- signs to the range string.
367
  $this_year = date_format(date_now(), 'Y');
368
  // When used as a Views exposed filter widget, $element['#value'] contains an
369
  // array instead an string. Fill the 'date' string in this case.
370
  $mock = NULL;
371
  $callback_values = date_popup_element_value_callback($element, FALSE, $mock);
372
  if (!isset($element['#value']['date']) && isset($callback_values['date'])) {
373
    if (!is_array($element['#value'])) {
374
      $element['#value'] = array();
375
    }
376
    $element['#value']['date'] = $callback_values['date'];
377
  }
378
  $date = '';
379
  if (!empty($element['#value']['date'])) {
380
    // @todo If date does not meet the granularity or format condtions, then
381
    // $date will have error condtions and '#default_value' below will have the
382
    // global default date (most likely formatted string from 'now'). There
383
    // will be an validation error message referring to the actual input date
384
    // but the form element will display formatted 'now'.
385
    $date = new DateObject($element['#value']['date'], $element['#date_timezone'], date_popup_date_format($element));
386
    if (!date_is_date($date)) {
387
      $date = '';
388
    }
389
  }
390
  $range = date_range_years($element['#date_year_range'], $date);
391
  $year_range = date_range_string($range);
392

    
393
  // Add the dynamic datepicker options. Allow element-specific datepicker
394
  // preferences to override these options for whatever reason they see fit.
395
  $settings = $element['#datepicker_options'] + array(
396
    'changeMonth' => TRUE,
397
    'changeYear' => TRUE,
398
    'autoPopUp' => 'focus',
399
    'closeAtTop' => FALSE,
400
    'speed' => 'immediate',
401
    'firstDay' => intval(variable_get('date_first_day', 0)),
402
    // 'buttonImage' => base_path()
403
    // . drupal_get_path('module', 'date_api') ."/images/calendar.png",
404
    // 'buttonImageOnly' => TRUE,
405
    'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'),
406
    'yearRange' => $year_range,
407
    // Custom setting, will be expanded in Drupal.behaviors.date_popup()
408
    'fromTo' => isset($fromto),
409
  );
410

    
411
  if (!empty($element['#instance'])) {
412
    $settings['syncEndDate'] = $element['#instance']['settings']['default_value2'] == 'sync';
413
  }
414

    
415
  // Create a unique id for each set of custom settings.
416
  $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
417

    
418
  // Manually build this element and set the value - this will prevent
419
  // corrupting the parent value.
420
  // Do NOT set '#input' => FALSE as this prevents the form API from recursing
421
  // into this element (this was useful when the '#value' property was being
422
  // overwritten by the '#default_value' property).
423
  $parents = array_merge($element['#parents'], array('date'));
424
  $sub_element = array(
425
    '#type' => 'textfield',
426
    '#title' => theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)),
427
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
428
    '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)),
429
    '#id' => $id,
430
    '#size' => !empty($element['#size']) ? $element['#size'] : 20,
431
    '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30,
432
    '#attributes' => $element['#attributes'],
433
    '#parents' => $parents,
434
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
435
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
436
    '#required' => $element['#required'],
437
  );
438
  // Do NOT overwrite the actual input with the default value.
439
  // @todo Figure out exactly when this is needed, in many places it is not.
440
  $sub_element['#description'] = ' ' . t('E.g., @date', array(
441
      '@date' => date_format_date(
442
        date_example_date(),
443
        'custom',
444
        date_popup_date_format($element)
445
        ),
446
      ));
447

    
448
  return $sub_element;
449
}
450

    
451
/**
452
 * Process the time portion of the element.
453
 */
454
function date_popup_process_time_part(&$element) {
455
  $granularity = date_format_order($element['#date_format']);
456
  $has_time = date_has_time($granularity);
457
  if (empty($has_time)) {
458
    return array();
459
  }
460

    
461
  // When used as a Views exposed filter widget, $element['#value'] contains an
462
  // array instead an string. Fill the 'time' string in this case.
463
  $mock = NULL;
464
  $callback_values = date_popup_element_value_callback($element, FALSE, $mock);
465
  if (!isset($element['#value']['time']) && isset($callback_values['time'])) {
466
    $element['#value']['time'] = $callback_values['time'];
467
  }
468

    
469
  switch ($element['#timepicker']) {
470
    case 'default':
471
      $func = 'timeEntry';
472
      $settings = array(
473
        'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE,
474
        'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE),
475
        'timeSteps' => array(
476
          1,
477
          intval($element['#date_increment']),
478
          (in_array('second', $granularity) ? $element['#date_increment'] : 0),
479
        ),
480
        'spinnerImage' => '',
481
        'fromTo' => isset($fromto),
482
      );
483

    
484
      if (strpos($element['#date_format'], 'a') !== FALSE) {
485
        // Then we are using lowercase am/pm.
486
        $options = date_ampm_options(FALSE, FALSE);
487
        $settings['ampmNames'] = array($options['am'], $options['pm']);
488
      }
489

    
490
      if (strpos($element['#date_format'], 'A') !== FALSE) {
491
        // Then we are using uppercase am/pm.
492
        $options = date_ampm_options(FALSE, TRUE);
493
        $settings['ampmNames'] = array($options['am'], $options['pm']);
494
        $settings['ampmPrefix'] = ' ';
495
      }
496
      break;
497

    
498
    case 'wvega':
499
      $func = 'timepicker';
500
      $grans = array('hour', 'minute', 'second');
501
      $time_granularity = array_intersect($granularity, $grans);
502
      $format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega');
503
      $default_value = isset($element['#default_value']) ? $element['#default_value'] : '';
504
      // The first value in the dropdown list should be the same as the element
505
      // default_value, but it needs to be in JS format (i.e. milliseconds since
506
      // the epoch).
507
      $start_time = new DateObject($default_value, $element['#date_timezone'], DATE_FORMAT_DATETIME);
508
      date_increment_round($start_time, $element['#date_increment']);
509
      $start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000;
510
      $settings = array(
511
        'timeFormat' => $format,
512
        'interval' => $element['#date_increment'],
513
        'startTime' => $start_time,
514
        'scrollbar' => TRUE,
515
      );
516
      break;
517

    
518
    default:
519
      $func = '';
520
      $settings = array();
521
  }
522

    
523
  // Create a unique id for each set of custom settings.
524
  $id = date_popup_js_settings_id($element['#id'], $func, $settings);
525

    
526
  // Manually build this element and set the value - this will prevent
527
  // corrupting the parent value.
528
  $parents = array_merge($element['#parents'], array('time'));
529
  $sub_element = array(
530
    '#type' => 'textfield',
531
    '#title' => theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)),
532
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
533
    '#default_value' => $element['#value']['time'],
534
    '#id' => $id,
535
    '#size' => 15,
536
    '#maxlength' => 10,
537
    '#attributes' => $element['#attributes'],
538
    '#parents' => $parents,
539
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
540
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
541
    '#required' => $element['#required'],
542
  );
543

    
544
  // Do NOT overwrite the actual input with the default value.
545

    
546
  // @todo Figure out exactly when this is needed, in many places it is not.
547
  $example_date = date_now();
548
  date_increment_round($example_date, $element['#date_increment']);
549
  $sub_element['#description'] = t('E.g., @date', array(
550
    '@date' => date_format_date(
551
      $example_date,
552
      'custom',
553
      date_popup_time_format($element)
554
    )));
555

    
556
  return ($sub_element);
557
}
558

    
559
/**
560
 * Massage the input values back into a single date.
561
 *
562
 * When used as a Views widget, the validation step always gets triggered,
563
 * even with no form submission. Before form submission $element['#value']
564
 * contains a string, after submission it contains an array.
565
 */
566
function date_popup_validate($element, &$form_state) {
567
  if (date_hidden_element($element)) {
568
    return;
569
  }
570

    
571
  if (is_string($element['#value'])) {
572
    return;
573
  }
574

    
575
  module_load_include('inc', 'date_api', 'date_api_elements');
576
  date_popup_add();
577

    
578
  $input_exists = NULL;
579
  $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
580
  // If the date is a string, it is not considered valid and can cause problems
581
  // later on, so just exit out now.
582
  if (is_string($input)) {
583
    return;
584
  }
585

    
586
  drupal_alter('date_popup_pre_validate', $element, $form_state, $input);
587

    
588
  $granularity = date_format_order($element['#date_format']);
589
  $date_granularity = date_popup_date_granularity($element);
590
  $time_granularity = date_popup_time_granularity($element);
591
  $has_time = date_has_time($granularity);
592

    
593
  // @codingStandardsIgnoreStart
594
  $label = '';
595
  if (!empty($element['#date_title'])) {
596
    $label = t($element['#date_title']);
597
  }
598
  elseif (!empty($element['#title'])) {
599
    $label = t($element['#title']);
600
  }
601
  // @codingStandardsIgnoreEnd
602
  $date = date_popup_input_date($element, $input);
603

    
604
  // If the date has errors, display them. If something was input but there is
605
  // no date, the date is invalid. If the field is empty and required, set
606
  // error message and return.
607
  $error_field = implode('][', $element['#parents']);
608
  if ((empty($element['#value']['date']) && empty($element['#value']['time']))  || !empty($date->errors)) {
609
    if (is_object($date) && !empty($date->errors)) {
610
      $message = t('The value input for field %field is invalid:', array('%field' => $label));
611
      $message .= '<br />' . implode('<br />', $date->errors);
612
      form_set_error($error_field, $message);
613
      return;
614
    }
615
    if (!empty($input['date'])) {
616
      $message = t('The value input for field %field is invalid.', array('%field' => $label));
617
      form_set_error($error_field, $message);
618
      return;
619
    }
620
    if ($element['#required']) {
621
      $message = t('A valid date is required for %title.', array('%title' => $label));
622
      form_set_error($error_field, $message);
623
      return;
624
    }
625
  }
626

    
627
  // If the created date is valid, set it.
628
  $value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
629
  form_set_value($element, $value, $form_state);
630
}
631

    
632
/**
633
 * Helper function for extracting a date value out of user input.
634
 *
635
 * @param bool $auto_complete
636
 *   Should we add a time value to complete the date if there is no time?
637
 *   Useful anytime the time value is optional.
638
 */
639
function date_popup_input_date($element, $input, $auto_complete = FALSE) {
640
  if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || (empty($input['date']) && empty($input['time']))) {
641
    return NULL;
642
  }
643
  date_popup_add();
644
  $granularity = date_format_order($element['#date_format']);
645
  $has_time = date_has_time($granularity);
646
  $flexible = !empty($element['#date_flexible']) ? $element['#date_flexible'] : 0;
647

    
648
  $format = date_popup_date_format($element);
649
  $format .= $has_time ? ' ' . date_popup_time_format($element) : '';
650
  // check if date is empty, if yes, then leave it blank.
651
  $datetime = !empty($input['date']) ? trim($input['date']) : '';
652
  $datetime .= $has_time ? ' ' . trim($input['time']) : '';
653
  $date = new DateObject($datetime, $element['#date_timezone'], $format);
654
  // if the variable is time only then set TimeOnly to TRUE.
655
  if (empty($input['date']) && !empty($input['time'])) {
656
    $date->timeOnly = 'TRUE';
657
  }
658
  if (is_object($date)) {
659
    $date->limitGranularity($granularity);
660
    if ($date->validGranularity($granularity, $flexible)) {
661
      date_increment_round($date, $element['#date_increment']);
662
    }
663
    return $date;
664
  }
665
  return NULL;
666
}
667

    
668
/**
669
 * Allowable time formats.
670
 */
671
function date_popup_time_formats($with_seconds = FALSE) {
672
  return array(
673
    'H:i:s',
674
    'h:i:sA',
675
  );
676
}
677

    
678
/**
679
 * Format options array.
680
 *
681
 * @todo Remove any formats not supported by the widget, if any.
682
 */
683
function date_popup_formats() {
684
  // Load short date formats.
685
  $formats = system_get_date_formats('short');
686

    
687
  // Load custom date formats.
688
  if ($formats_custom = system_get_date_formats('custom')) {
689
    $formats = array_merge($formats, $formats_custom);
690
  }
691

    
692
  $formats = str_replace('i', 'i:s', array_keys($formats));
693
  $formats = drupal_map_assoc($formats);
694

    
695
  return $formats;
696
}
697

    
698
/**
699
 * Recreate a date format string so it has the values popup expects.
700
 *
701
 * @param string $format
702
 *   A normal date format string, like Y-m-d
703
 *
704
 * @return string
705
 *   A format string in popup format, like YMD-, for the earlier 'calendar'
706
 *   version, or m/d/Y for the later 'datepicker' version.
707
 */
708
function date_popup_format_to_popup($format) {
709
  if (empty($format)) {
710
    $format = 'Y-m-d';
711
  }
712
  $replace = date_popup_datepicker_format_replacements();
713
  return strtr($format, $replace);
714
}
715

    
716
/**
717
 * Recreate a time format string so it has the values popup expects.
718
 *
719
 * @param string $format
720
 *   A normal time format string, like h:i (a)
721
 *
722
 * @return string
723
 *   A format string that the popup can accept like h:i a
724
 */
725
function date_popup_format_to_popup_time($format, $timepicker = NULL) {
726
  if (empty($format)) {
727
    $format = 'H:i';
728
  }
729
  $symbols = array(
730
    '/',
731
    '-',
732
    ' .',
733
    ',',
734
    'F',
735
    'M',
736
    'l',
737
    'z',
738
    'w',
739
    'W',
740
    'd',
741
    'j',
742
    'm',
743
    'n',
744
    'y',
745
    'Y',
746
  );
747
  $format = str_replace($symbols, '', $format);
748
  $format = strtr($format, date_popup_timepicker_format_replacements($timepicker));
749
  return $format;
750
}
751

    
752
/**
753
 * Reconstruct popup format string into normal format string.
754
 *
755
 * @param string $format
756
 *   A string in popup format, like YMD-
757
 *
758
 * @return string
759
 *   A normal date format string, like Y-m-d
760
 */
761
function date_popup_popup_to_format($format) {
762
  $replace = array_flip(date_popup_datepicker_format_replacements());
763
  return strtr($format, $replace);
764
}
765

    
766
/**
767
 * Return a map of format replacements required for a given timepicker.
768
 *
769
 * Client-side time entry plugins don't support all possible date formats.
770
 * This function returns a map of format replacements required to change any
771
 * input format into one that the given timepicker can support.
772
 *
773
 * @param string $timepicker
774
 *   The time entry plugin being used: 'wvega', 'timepicker' or 'default'.
775
 *
776
 * @return array
777
 *   A map of replacements.
778
 */
779
function date_popup_timepicker_format_replacements($timepicker = 'default') {
780
  switch ($timepicker) {
781
    case 'wvega':
782
      // The wvega timepicker only supports uppercase AM/PM.
783
      return array('a' => 'A');
784

    
785
    case 'timepicker':
786
      // The jquery_ui timepicker supports all possible date formats.
787
      return array();
788

    
789
    default:
790
      // The default timeEntry plugin requires leading zeros.
791
      return array('G' => 'H', 'g' => 'h');
792
  }
793
}
794

    
795
/**
796
 * The format replacement patterns for the new datepicker.
797
 */
798
function date_popup_datepicker_format_replacements() {
799
  return array(
800
    'd' => 'dd',
801
    'j' => 'd',
802
    'l' => 'DD',
803
    'D' => 'D',
804
    'm' => 'mm',
805
    'n' => 'm',
806
    'F' => 'MM',
807
    'M' => 'M',
808
    'Y' => 'yy',
809
    'y' => 'y',
810
  );
811
}
812

    
813
/**
814
 * Format a date popup element.
815
 *
816
 * Use a class that will float date and time next to each other.
817
 */
818
function theme_date_popup($vars) {
819
  $element = $vars['element'];
820
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
821
  $attributes['class'][] = 'container-inline-date';
822
  // If there is no description, the floating date elements need some extra
823
  // padding below them.
824
  $wrapper_attributes = array('class' => array('date-padding'));
825
  if (empty($element['date']['#description'])) {
826
    $wrapper_attributes['class'][] = 'clearfix';
827
  }
828
  // Add an wrapper to mimic the way a single value field works, for ease in
829
  // using #states.
830
  if (isset($element['#children'])) {
831
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
832
  }
833
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
834
}
835

    
836
/**
837
 * Implements hook_date_field_instance_settings_form_alter().
838
 */
839
function date_popup_date_field_instance_settings_form_alter(&$form, $context) {
840
  // Add an extra option to sync the end date with the start date.
841
  $form['default_value2']['#options']['sync'] = t('Sync with start date');
842
}
843

    
844
/**
845
 * Implements hook_menu().
846
 */
847
function date_popup_menu() {
848
  // @todo Fix this later.
849
  $items['admin/config/date/date_popup'] = array(
850
    'title' => 'Date Popup',
851
    'description' => 'Configure the Date Popup settings.',
852
    'page callback' => 'drupal_get_form',
853
    'page arguments' => array('date_popup_settings'),
854
    'access callback' => 'user_access',
855
    'access arguments' => array('administer site configuration'),
856
  );
857
  return $items;
858
}
859

    
860
/**
861
 * General configuration form for controlling the Date Popup behaviour.
862
 */
863
function date_popup_settings() {
864
  $wvega_available = date_popup_get_wvega_path();
865
  $preferred_timepicker = date_popup_get_preferred_timepicker();
866

    
867
  $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>');
868
  $form['date_popup_timepicker'] = array(
869
    '#type' => 'select',
870
    '#options' => array(
871
      'default' => t('Use default jQuery timepicker'),
872
      'wvega'   => t('Use dropdown timepicker'),
873
      'none'    => t('Manual time entry, no jQuery timepicker'),
874
    ),
875
    '#title' => t('Timepicker'),
876
    '#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker),
877
  );
878

    
879
  if (!$wvega_available) {
880
    $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'));
881
    unset($form['date_popup_timepicker']['#options']['wvega']);
882
  }
883

    
884
  $css = <<<EOM
885
/* ___________ IE6 IFRAME FIX ________ */
886
.ui-datepicker-cover {
887
  display: none; /*sorry for IE5*/
888
  display/**/: block; /*sorry for IE5*/
889
  position: absolute; /*must have*/
890
  z-index: -1; /*must have*/
891
  filter: mask(); /*must have*/
892
  top: -4px; /*must have*/
893
  left: -4px; /*must have*/ /* LTR */
894
  width: 200px; /*must have*/
895
  height: 200px; /*must have*/
896
}
897
EOM;
898

    
899
  $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>';
900

    
901
  return system_settings_form($form);
902
}