Projet

Général

Profil

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

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

1
<?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
 * @return string
47
 *   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
 * 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
 * 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
 * @param string $id
109
 *   The CSS class prefix to search the DOM for.
110
 *   TODO : unused ?
111
 *
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
 *   The settings array to pass to the jQuery function.
118
 *
119
 * @returns
120
 *   The CSS id to assign to the element that should have $func($settings)
121
 *   invoked on it.
122
 */
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
    // 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
  }
136

    
137
  if (!$js_added) {
138
    drupal_add_js(drupal_get_path('module', 'date_popup') . '/date_popup.js');
139
    $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
  // 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
  $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
 * Set the #type to date_popup and fill the element #default_value with
175
 * a date adjusted to the proper local timezone in datetime format
176
 * (YYYY-MM-DD HH:MM:SS).
177
 *
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
/**
233
 * Date popup date granularity.
234
 */
235
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
/**
241
 * Date popup time granularity.
242
 */
243
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
/**
249
 * Date popup date format.
250
 */
251
function date_popup_date_format($element) {
252
  return (date_limit_format($element['#date_format'], date_popup_date_granularity($element)));
253
}
254

    
255
/**
256
 * Date popup time format.
257
 */
258
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
// @codingStandardsIgnoreStart
266
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
// @codingStandardsIgnoreEnd
294

    
295
/**
296
 * Javascript popup element processing.
297
 *
298
 * 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
      'trigger_as' => array(
314
        'name' => $element['#name'],
315
      ),
316
      '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
  // 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
  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
    'form' => $form,
344
  );
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
  if (empty($date_granularity)) {
357
    return array();
358
  }
359

    
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
  // 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
  $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
    // 'buttonImage' => base_path()
389
    // . drupal_get_path('module', 'date_api') ."/images/calendar.png",
390
    // 'buttonImageOnly' => TRUE,
391
    '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
  if (!empty($element['#instance'])) {
398
    $settings['syncEndDate'] = $element['#instance']['settings']['default_value2'] == 'sync';
399
  }
400

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

    
404
  // Manually build this element and set the value -
405
  // this will prevent corrupting the parent value.
406
  $parents = array_merge($element['#parents'], array('date'));
407
  $sub_element = array(
408
    '#type' => 'textfield',
409
    '#title' => theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)),
410
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
411
    '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)),
412
    '#id' => $id,
413
    '#input' => FALSE,
414
    '#size' => !empty($element['#size']) ? $element['#size'] : 20,
415
    '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30,
416
    '#attributes' => $element['#attributes'],
417
    '#parents' => $parents,
418
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
419
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
420
  );
421
  $sub_element['#value'] = $sub_element['#default_value'];
422
  // TODO, figure out exactly when we want this description.
423
  // In many places it is not desired.
424
  $sub_element['#description'] = ' ' . t('E.g., @date', array(
425
      '@date' => date_format_date(
426
        date_example_date(),
427
        'custom',
428
        date_popup_date_format($element)
429
        ),
430
      ));
431

    
432
  return $sub_element;
433
}
434

    
435
/**
436
 * Process the time portion of the element.
437
 */
438
function date_popup_process_time_part(&$element) {
439
  $granularity = date_format_order($element['#date_format']);
440
  $has_time = date_has_time($granularity);
441
  if (empty($has_time)) {
442
    return array();
443
  }
444

    
445
  // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string.
446
  // Fill the 'time' string in this case.
447
  $mock = NULL;
448
  $callback_values = date_popup_element_value_callback($element, FALSE, $mock);
449
  if (!isset($element['#value']['time']) && isset($callback_values['time'])) {
450
    $element['#value']['time'] = $callback_values['time'];
451
  }
452

    
453
  switch ($element['#timepicker']) {
454
    case 'default':
455
      $func = 'timeEntry';
456
      $settings = array(
457
        'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE,
458
        'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE),
459
        'timeSteps' => array(
460
          1,
461
          intval($element['#date_increment']),
462
          (in_array('second', $granularity) ? $element['#date_increment'] : 0),
463
        ),
464
        'spinnerImage' => '',
465
        'fromTo' => isset($fromto),
466
      );
467
      if (strpos($element['#date_format'], 'a') !== FALSE) {
468
        // Then we are using lowercase am/pm.
469
        $settings['ampmNames'] = array('am', 'pm');
470
      }
471
      if (strpos($element['#date_format'], ' A') !== FALSE || strpos($element['#date_format'], ' a') !== FALSE) {
472
        $settings['ampmPrefix'] = ' ';
473
      }
474
      break;
475

    
476
    case 'wvega':
477
      $func = 'timepicker';
478
      $grans = array('hour', 'minute', 'second');
479
      $time_granularity = array_intersect($granularity, $grans);
480
      $format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega');
481
      $default_value = isset($element['#default_value']) ? $element['#default_value'] : '';
482
      // The first value in the dropdown list should be the same as the element
483
      // default_value, but it needs to be in JS format (i.e. milliseconds since
484
      // the epoch).
485
      $start_time = new DateObject($default_value, $element['#date_timezone'], DATE_FORMAT_DATETIME);
486
      date_increment_round($start_time, $element['#date_increment']);
487
      $start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000;
488
      $settings = array(
489
        'timeFormat' => $format,
490
        'interval' => $element['#date_increment'],
491
        'startTime' => $start_time,
492
        'scrollbar' => TRUE,
493
      );
494
      break;
495

    
496
    default:
497
      $func = '';
498
      $settings = array();
499
      break;
500
  }
501

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

    
505
  // Manually build this element and set the value -
506
  // this will prevent corrupting the parent value.
507
  $parents = array_merge($element['#parents'], array('time'));
508
  $sub_element = array(
509
    '#type' => 'textfield',
510
    '#title' => theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)),
511
    '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible',
512
    '#default_value' => $element['#value']['time'],
513
    '#id' => $id,
514
    '#size' => 15,
515
    '#maxlength' => 10,
516
    '#attributes' => $element['#attributes'],
517
    '#parents' => $parents,
518
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
519
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
520
  );
521

    
522
  $sub_element['#value'] = $sub_element['#default_value'];
523

    
524
  // TODO, figure out exactly when we want this description.
525
  // In many places it is not desired.
526
  $example_date = date_now();
527
  date_increment_round($example_date, $element['#date_increment']);
528
  $sub_element['#description'] = t('E.g., @date', array(
529
    '@date' => date_format_date(
530
      $example_date,
531
      'custom',
532
      date_popup_time_format($element)
533
    )));
534

    
535
  return ($sub_element);
536
}
537

    
538
/**
539
 * Massage the input values back into a single date.
540
 *
541
 * When used as a Views widget, the validation step always gets triggered,
542
 * even with no form submission. Before form submission $element['#value']
543
 * contains a string, after submission it contains an array.
544
 */
545
function date_popup_validate($element, &$form_state) {
546

    
547
  if (date_hidden_element($element)) {
548
    return;
549
  }
550

    
551
  if (is_string($element['#value'])) {
552
    return;
553
  }
554

    
555
  module_load_include('inc', 'date_api', 'date_api_elements');
556
  date_popup_add();
557

    
558
  $input_exists = NULL;
559
  $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
560
  // If the date is a string, it is not considered valid and can cause problems
561
  // later on, so just exit out now.
562
  if (is_string($input)) {
563
    return;
564
  }
565

    
566
  drupal_alter('date_popup_pre_validate', $element, $form_state, $input);
567

    
568
  $granularity = date_format_order($element['#date_format']);
569
  $date_granularity = date_popup_date_granularity($element);
570
  $time_granularity = date_popup_time_granularity($element);
571
  $has_time = date_has_time($granularity);
572

    
573
  // @codingStandardsIgnoreStart
574
  $label = '';
575
  if (!empty($element['#date_title'])) {
576
    $label = t($element['#date_title']);
577
  }
578
  elseif (!empty($element['#title'])) {
579
    $label = t($element['#title']);
580
  }
581
  // @codingStandardsIgnoreEnd
582
  $date = date_popup_input_date($element, $input);
583

    
584
  // If the date has errors, display them.
585
  // If something was input but there is no date, the date is invalid.
586
  // If the field is empty and required, set error message and return.
587
  $error_field = implode('][', $element['#parents']);
588
  if (empty($date) || !empty($date->errors)) {
589
    if (is_object($date) && !empty($date->errors)) {
590
      $message = t('The value input for field %field is invalid:', array('%field' => $label));
591
      $message .= '<br />' . implode('<br />', $date->errors);
592
      form_set_error($error_field, $message);
593
      return;
594
    }
595
    if (!empty($input['date'])) {
596
      $message = t('The value input for field %field is invalid.', array('%field' => $label));
597
      form_set_error($error_field, $message);
598
      return;
599
    }
600
    if ($element['#required']) {
601
      $message = t('A valid date is required for %title.', array('%title' => $label));
602
      form_set_error($error_field, $message);
603
      return;
604
    }
605
  }
606

    
607
  // If the created date is valid, set it.
608
  $value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
609
  form_set_value($element, $value, $form_state);
610
}
611

    
612
/**
613
 * Helper function for extracting a date value out of user input.
614
 *
615
 * @param bool $auto_complete
616
 *   Should we add a time value to complete the date if there is no time?
617
 *   Useful anytime the time value is optional.
618
 */
619
function date_popup_input_date($element, $input, $auto_complete = FALSE) {
620
  if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) {
621
    return NULL;
622
  }
623
  date_popup_add();
624
  $granularity = date_format_order($element['#date_format']);
625
  $has_time = date_has_time($granularity);
626
  $flexible = !empty($element['#date_flexible']) ? $element['#date_flexible'] : 0;
627

    
628
  $format = date_popup_date_format($element);
629
  $format .= $has_time ? ' ' . date_popup_time_format($element) : '';
630
  $datetime = trim($input['date']);
631
  $datetime .= $has_time ? ' ' . trim($input['time']) : '';
632
  $date = new DateObject($datetime, $element['#date_timezone'], $format);
633
  if (is_object($date)) {
634
    $date->limitGranularity($granularity);
635
    if ($date->validGranularity($granularity, $flexible)) {
636
      date_increment_round($date, $element['#date_increment']);
637
    }
638
    return $date;
639
  }
640
  return NULL;
641
}
642

    
643
/**
644
 * Allowable time formats.
645
 */
646
function date_popup_time_formats($with_seconds = FALSE) {
647
  return array(
648
    'H:i:s',
649
    'h:i:sA',
650
  );
651
}
652

    
653
/**
654
 * Format options array.
655
 *
656
 * TODO Remove any formats not supported by the widget, if any.
657
 */
658
function date_popup_formats() {
659
  // Load short date formats.
660
  $formats = system_get_date_formats('short');
661

    
662
  // Load custom date formats.
663
  if ($formats_custom = system_get_date_formats('custom')) {
664
    $formats = array_merge($formats, $formats_custom);
665
  }
666

    
667
  $formats = str_replace('i', 'i:s', array_keys($formats));
668
  $formats = drupal_map_assoc($formats);
669

    
670
  return $formats;
671
}
672

    
673
/**
674
 * Recreate a date format string so it has the values popup expects.
675
 *
676
 * @param string $format
677
 *   A normal date format string, like Y-m-d
678
 *
679
 * @return string
680
 *   A format string in popup format, like YMD-, for the
681
 *   earlier 'calendar' version, or m/d/Y for the later 'datepicker'
682
 *   version.
683
 */
684
function date_popup_format_to_popup($format) {
685
  if (empty($format)) {
686
    $format = 'Y-m-d';
687
  }
688
  $replace = date_popup_datepicker_format_replacements();
689
  return strtr($format, $replace);
690
}
691

    
692
/**
693
 * Recreate a time format string so it has the values popup expects.
694
 *
695
 * @param string $format
696
 *   A normal time format string, like h:i (a)
697
 *
698
 * @return string
699
 *   A format string that the popup can accept like h:i a
700
 */
701
function date_popup_format_to_popup_time($format, $timepicker = NULL) {
702
  if (empty($format)) {
703
    $format = 'H:i';
704
  }
705
  $symbols = array(
706
    '/',
707
    '-',
708
    ' .',
709
    ',',
710
    'F',
711
    'M',
712
    'l',
713
    'z',
714
    'w',
715
    'W',
716
    'd',
717
    'j',
718
    'm',
719
    'n',
720
    'y',
721
    'Y',
722
  );
723
  $format = str_replace($symbols, '', $format);
724
  $format = strtr($format, date_popup_timepicker_format_replacements($timepicker));
725
  return $format;
726
}
727

    
728
/**
729
 * Reconstruct popup format string into normal format string.
730
 *
731
 * @param string $format
732
 *   A string in popup format, like YMD-
733
 *
734
 * @return string
735
 *   A normal date format string, like Y-m-d
736
 */
737
function date_popup_popup_to_format($format) {
738
  $replace = array_flip(date_popup_datepicker_format_replacements());
739
  return strtr($format, $replace);
740
}
741

    
742
/**
743
 * Return a map of format replacements required for a given timepicker.
744
 *
745
 * Client-side time entry plugins don't support all possible date formats.
746
 * This function returns a map of format replacements required to change any
747
 * input format into one that the given timepicker can support.
748
 *
749
 * @param string $timepicker
750
 *   The time entry plugin being used: either 'wvega' or 'default'.
751
 *
752
 * @return array
753
 *   A map of replacements.
754
 */
755
function date_popup_timepicker_format_replacements($timepicker = 'default') {
756
  switch ($timepicker) {
757
    case 'wvega':
758
      // The wvega timepicker only supports uppercase AM/PM.
759
      return array('a' => 'A');
760

    
761
    default:
762
      // The default timeEntry plugin requires leading zeros.
763
      return array('G' => 'H', 'g' => 'h');
764
  }
765
}
766

    
767
/**
768
 * The format replacement patterns for the new datepicker.
769
 */
770
function date_popup_datepicker_format_replacements() {
771
  return array(
772
    'd' => 'dd',
773
    'j' => 'd',
774
    'l' => 'DD',
775
    'D' => 'D',
776
    'm' => 'mm',
777
    'n' => 'm',
778
    'F' => 'MM',
779
    'M' => 'M',
780
    'Y' => 'yy',
781
    'y' => 'y',
782
  );
783
}
784

    
785
/**
786
 * Format a date popup element.
787
 *
788
 * Use a class that will float date and time next to each other.
789
 */
790
function theme_date_popup($vars) {
791
  $element = $vars['element'];
792
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
793
  $attributes['class'][] = 'container-inline-date';
794
  // If there is no description, the floating date
795
  // elements need some extra padding below them.
796
  $wrapper_attributes = array('class' => array('date-padding'));
797
  if (empty($element['date']['#description'])) {
798
    $wrapper_attributes['class'][] = 'clearfix';
799
  }
800
  // Add an wrapper to mimic the way a single value field works,
801
  // for ease in using #states.
802
  if (isset($element['#children'])) {
803
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
804
  }
805
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
806
}
807

    
808
/**
809
 * Implements hook_date_field_instance_settings_form_alter().
810
 */
811
function date_popup_date_field_instance_settings_form_alter(&$form, $context) {
812
  // Add an extra option to sync the end date with the start date.
813
  $form['default_value2']['#options']['sync'] = t('Sync with start date');
814
}
815

    
816
/**
817
 * Implements hook_menu().
818
 */
819
function date_popup_menu() {
820
  $items = array();
821
  // TODO Fix this later.
822
  $items['admin/config/date/date_popup'] = array(
823
    'title' => 'Date Popup',
824
    'description' => 'Configure the Date Popup settings.',
825
    'page callback' => 'drupal_get_form',
826
    'page arguments' => array('date_popup_settings'),
827
    'access callback' => 'user_access',
828
    'access arguments' => array('administer site configuration'),
829
  );
830
  return $items;
831
}
832
/**
833
 * General configuration form for controlling the Date Popup behaviour.
834
 */
835
function date_popup_settings() {
836
  $wvega_available = date_popup_get_wvega_path();
837
  $preferred_timepicker = date_popup_get_preferred_timepicker();
838

    
839
  $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>');
840
  $form['date_popup_timepicker'] = array(
841
    '#type' => 'select',
842
    '#options' => array(
843
      'default' => t('Use default jQuery timepicker'),
844
      'wvega'   => t('Use dropdown timepicker'),
845
      'none'    => t('Manual time entry, no jQuery timepicker'),
846
    ),
847
    '#title' => t('Timepicker'),
848
    '#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker),
849
  );
850

    
851
  if (!$wvega_available) {
852
    $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'));
853
    unset($form['date_popup_timepicker']['#options']['wvega']);
854
  }
855

    
856
  $css = <<<EOM
857
/* ___________ IE6 IFRAME FIX ________ */
858
.ui-datepicker-cover {
859
  display: none; /*sorry for IE5*/
860
  display/**/: block; /*sorry for IE5*/
861
  position: absolute; /*must have*/
862
  z-index: -1; /*must have*/
863
  filter: mask(); /*must have*/
864
  top: -4px; /*must have*/
865
  left: -4px; /*must have*/ /* LTR */
866
  width: 200px; /*must have*/
867
  height: 200px; /*must have*/
868
}
869
EOM;
870

    
871
  $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>';
872

    
873
  return system_settings_form($form);
874
}