Projet

Général

Profil

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

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

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