Projet

Général

Profil

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

root / drupal7 / sites / all / modules / date / date_popup / date_popup.module @ 1f683914

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