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 @ b720ea3e

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
  // Create a unique id for each set of custom settings.
398
  $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
399

    
400
  // Manually build this element and set the value -
401
  // this will prevent corrupting the parent value.
402
  $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
    '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)),
408
    '#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
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
415
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
416
  );
417
  $sub_element['#value'] = $sub_element['#default_value'];
418
  // 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

    
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
  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

    
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
        'timeSteps' => array(
456
          1,
457
          intval($element['#date_increment']),
458
          (in_array('second', $granularity) ? $element['#date_increment'] : 0),
459
        ),
460
        'spinnerImage' => '',
461
        'fromTo' => isset($fromto),
462
      );
463
      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

    
472
    case 'wvega':
473
      $func = 'timepicker';
474
      $grans = array('hour', 'minute', 'second');
475
      $time_granularity = array_intersect($granularity, $grans);
476
      $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

    
491
    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
  // Manually build this element and set the value -
501
  // this will prevent corrupting the parent value.
502
  $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
    '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']',
514
    '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
515
  );
516

    
517
  $sub_element['#value'] = $sub_element['#default_value'];
518

    
519
  // TODO, figure out exactly when we want this description.
520
  // In many places it is not desired.
521
  $example_date = date_now();
522
  date_increment_round($example_date, $element['#date_increment']);
523
  $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

    
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
  // 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

    
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
  // @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
  $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
 * @param bool $auto_complete
611
 *   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
  $datetime = trim($input['date']);
626
  $datetime .= $has_time ? ' ' . trim($input['time']) : '';
627
  $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
  );
646
}
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
  // 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
  $formats = drupal_map_assoc($formats);
664

    
665
  return $formats;
666
}
667

    
668
/**
669
 * Recreate a date format string so it has the values popup expects.
670
 *
671
 * @param string $format
672
 *   A normal date format string, like Y-m-d
673
 *
674
 * @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
 *   A normal time format string, like h:i (a)
692
 *
693
 * @return string
694
 *   A format string that the popup can accept like h:i a
695
 */
696
function date_popup_format_to_popup_time($format, $timepicker = NULL) {
697
  if (empty($format)) {
698
    $format = 'H:i';
699
  }
700
  $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
  $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
 *   A string in popup format, like YMD-
728
 *
729
 * @return string
730
 *   A normal date format string, like Y-m-d
731
 */
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
 * @param string $timepicker
745
 *   The time entry plugin being used: either 'wvega' or 'default'.
746
 *
747
 * @return array
748
 *   A map of replacements.
749
 */
750
function date_popup_timepicker_format_replacements($timepicker = 'default') {
751
  switch ($timepicker) {
752
    case 'wvega':
753
      // The wvega timepicker only supports uppercase AM/PM.
754
      return array('a' => 'A');
755

    
756
    default:
757
      // The default timeEntry plugin requires leading zeros.
758
      return array('G' => 'H', 'g' => 'h');
759
  }
760
}
761

    
762
/**
763
 * The format replacement patterns for the new datepicker.
764
 */
765
function date_popup_datepicker_format_replacements() {
766
  return array(
767
    '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
  );
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
  // If there is no description, the floating date
790
  // elements need some extra padding below them.
791
  $wrapper_attributes = array('class' => array('date-padding'));
792
  if (empty($element['date']['#description'])) {
793
    $wrapper_attributes['class'][] = 'clearfix';
794
  }
795
  // Add an wrapper to mimic the way a single value field works,
796
  // for ease in using #states.
797
  if (isset($element['#children'])) {
798
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
799
  }
800
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
801
}
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
      'wvega'   => t('Use dropdown timepicker'),
832
      'none'    => t('Manual time entry, no jQuery timepicker'),
833
    ),
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
  $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

    
860
  return system_settings_form($form);
861
}