PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 3.2.2
Pods – Custom Content Types and Fields v3.2.2
trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / classes / fields / datetime.php
pods / classes / fields Last commit date
avatar.php 3 years ago boolean.php 3 years ago code.php 2 years ago color.php 3 years ago comment.php 4 years ago currency.php 3 years ago date.php 2 years ago datetime.php 2 years ago email.php 3 years ago file.php 2 years ago heading.php 2 years ago html.php 2 years ago link.php 3 years ago number.php 2 years ago oembed.php 2 years ago paragraph.php 2 years ago password.php 3 years ago phone.php 3 years ago pick.php 2 years ago slug.php 3 years ago taxonomy.php 4 years ago text.php 2 years ago time.php 2 years ago website.php 3 years ago wysiwyg.php 2 years ago
datetime.php
1317 lines
1 <?php
2
3 use Pods\Static_Cache;
4 use Pods\Whatsit\Field;
5
6 /**
7 * @package Pods\Fields
8 */
9 class PodsField_DateTime extends PodsField {
10
11 /**
12 * {@inheritdoc}
13 */
14 public static $group = 'Date / Time';
15
16 /**
17 * {@inheritdoc}
18 */
19 public static $type = 'datetime';
20
21 /**
22 * {@inheritdoc}
23 */
24 public static $label = 'Date / Time';
25
26 /**
27 * {@inheritdoc}
28 */
29 public static $prepare = '%s';
30
31 /**
32 * Storage format.
33 *
34 * @var string
35 * @since 2.7.0
36 */
37 public static $storage_format = 'Y-m-d H:i:s';
38
39 /**
40 * The default empty value (database)
41 *
42 * @var string
43 * @since 2.7.0
44 */
45 public static $empty_value = '0000-00-00 00:00:00';
46
47 /**
48 * {@inheritdoc}
49 */
50 public function setup() {
51
52 static::$group = __( 'Date / Time', 'pods' );
53 static::$label = __( 'Date / Time', 'pods' );
54 }
55
56 /**
57 * {@inheritdoc}
58 */
59 public function options() {
60
61 $options = array(
62 static::$type . '_type' => array(
63 'label' => __( 'Date Format Type', 'pods' ),
64 'default' => 'format',
65 // Backwards compatibility
66 'type' => 'pick',
67 'help' => __( 'WordPress Default is the format used in Settings, General under "Date Format".', 'pods' ) . '<br>' . __( 'Predefined Format will allow you to select from a list of commonly used date formats.', 'pods' ) . '<br>' . __( 'Custom will allow you to enter your own using PHP Date/Time Strings.', 'pods' ),
68 'data' => array(
69 'wp' => __( 'WordPress default', 'pods' ) . ': ' . date_i18n( get_option( 'date_format' ) ),
70 'format' => __( 'Predefined format', 'pods' ),
71 'custom' => __( 'Custom format', 'pods' ),
72 ),
73 'pick_format_single' => 'dropdown',
74 'pick_show_select_text' => 0,
75 'dependency' => true,
76 ),
77 static::$type . '_format_custom' => array(
78 'label' => __( 'Date Format for Display', 'pods' ),
79 'depends-on' => array( static::$type . '_type' => 'custom' ),
80 'default' => '',
81 'type' => 'text',
82 'help' => sprintf(
83 '<a href="https://docs.pods.io/fields/date-time-fields/datetime/" target="_blank" rel="noopener noreferrer">%1$s</a>',
84 esc_html__( 'Date / Time field documentation', 'pods' )
85 ),
86 ),
87 static::$type . '_format_custom_js' => array(
88 'label' => __( 'Date Format for Input', 'pods' ),
89 'depends-on' => array( static::$type . '_type' => 'custom' ),
90 'default' => '',
91 'type' => 'text',
92 'help' => sprintf(
93 '<a href="https://docs.pods.io/fields/date-time-fields/datetime/" target="_blank" rel="noopener noreferrer">%1$s</a><br />%2$s',
94 esc_html__( 'Date / Time field documentation', 'pods' ),
95 esc_html__( 'Leave empty to auto-generate from PHP format.', 'pods' )
96 ),
97 ),
98 static::$type . '_format' => array(
99 'label' => __( 'Date Format (predefined)', 'pods' ),
100 'depends-on' => array( static::$type . '_type' => 'format' ),
101 'default' => 'mdy',
102 'type' => 'pick',
103 'data' => array(
104 'mdy' => date_i18n( 'm/d/Y' ),
105 'mdy_dash' => date_i18n( 'm-d-Y' ),
106 'mdy_dot' => date_i18n( 'm.d.Y' ),
107 'ymd_slash' => date_i18n( 'Y/m/d' ),
108 'ymd_dash' => date_i18n( 'Y-m-d' ),
109 'ymd_dot' => date_i18n( 'Y.m.d' ),
110 'fjy' => date_i18n( 'F j, Y' ),
111 'fjsy' => date_i18n( 'F jS, Y' ),
112 'c' => date_i18n( 'c' ),
113 ),
114 'pick_format_single' => 'dropdown',
115 'pick_show_select_text' => 0,
116 'dependency' => true,
117 ),
118 static::$type . '_time_type' => array(
119 'label' => __( 'Time Format Type', 'pods' ),
120 'excludes-on' => array( static::$type . '_format' => 'c' ),
121 'default' => '12',
122 // Backwards compatibility
123 'type' => 'pick',
124 'help' => __( 'WordPress Default is the format used in Settings, General under "Time Format".', 'pods' ) . '<br>' . __( '12/24 hour will allow you to select from a list of commonly used time formats.', 'pods' ) . '<br>' . __( 'Custom will allow you to enter your own using PHP Date/Time Strings.', 'pods' ),
125 'data' => array(
126 'wp' => __( 'WordPress default', 'pods' ) . ': ' . date_i18n( get_option( 'time_format' ) ),
127 '12' => __( '12 hour', 'pods' ),
128 '24' => __( '24 hour', 'pods' ),
129 'custom' => __( 'Custom', 'pods' ),
130 ),
131 'pick_format_single' => 'dropdown',
132 'pick_show_select_text' => 0,
133 'dependency' => true,
134 ),
135 static::$type . '_time_format_custom' => array(
136 'label' => __( 'Time Format for Display', 'pods' ),
137 'depends-on' => array( static::$type . '_time_type' => 'custom' ),
138 'excludes-on' => array( static::$type . '_format' => 'c' ),
139 'default' => '',
140 'type' => 'text',
141 'help' => sprintf(
142 '<a href="https://docs.pods.io/fields/date-time-fields/datetime/" target="_blank" rel="noopener noreferrer">%1$s</a>',
143 esc_html__( 'Date / Time field documentation', 'pods' )
144 ),
145 ),
146 static::$type . '_time_format_custom_js' => array(
147 'label' => __( 'Time Format for Input', 'pods' ),
148 'depends-on' => array( static::$type . '_time_type' => 'custom' ),
149 'excludes-on' => array( static::$type . '_format' => 'c' ),
150 'default' => '',
151 'type' => 'text',
152 'help' => sprintf(
153 '<a href="https://docs.pods.io/fields/date-time-fields/datetime/" target="_blank" rel="noopener noreferrer">%1$s</a><br />%2$s',
154 esc_html__( 'Date / Time field documentation', 'pods' ),
155 esc_html__( 'Leave empty to auto-generate from PHP format.', 'pods' )
156 ),
157 ),
158 static::$type . '_time_format' => array(
159 'label' => __( 'Time Format (12 hour)', 'pods' ),
160 'depends-on' => array( static::$type . '_time_type' => '12' ),
161 'excludes-on' => array( static::$type . '_format' => 'c' ),
162 'default' => 'h_mma',
163 'type' => 'pick',
164 'data' => array(
165 'h_mm_A' => date_i18n( 'g:i A' ),
166 'h_mm_ss_A' => date_i18n( 'g:i:s A' ),
167 'hh_mm_A' => date_i18n( 'h:i A' ),
168 'hh_mm_ss_A' => date_i18n( 'h:i:s A' ),
169 'h_mma' => date_i18n( 'g:ia' ),
170 'hh_mma' => date_i18n( 'h:ia' ),
171 'h_mm' => date_i18n( 'g:i' ),
172 'h_mm_ss' => date_i18n( 'g:i:s' ),
173 'hh_mm' => date_i18n( 'h:i' ),
174 'hh_mm_ss' => date_i18n( 'h:i:s' ),
175 ),
176 'pick_format_single' => 'dropdown',
177 'pick_show_select_text' => 0,
178 ),
179 static::$type . '_time_format_24' => array(
180 'label' => __( 'Time Format (24 hour)', 'pods' ),
181 'depends-on' => array( static::$type . '_time_type' => '24' ),
182 'excludes-on' => array( static::$type . '_format' => 'c' ),
183 'default' => 'hh_mm',
184 'type' => 'pick',
185 'data' => array(
186 'hh_mm' => date_i18n( 'H:i' ),
187 'hh_mm_ss' => date_i18n( 'H:i:s' ),
188 ),
189 'pick_format_single' => 'dropdown',
190 'pick_show_select_text' => 0,
191 ),
192 static::$type . '_year_range_custom' => array(
193 'label' => __( 'Year Range', 'pods' ),
194 'default' => '',
195 'type' => 'text',
196 'help' => sprintf(
197 '%1$s<br /><a href="https://docs.pods.io/fields/date-time-fields/datetime/" target="_blank" rel="noopener noreferrer">%2$s</a>',
198 sprintf(
199 esc_html__( 'Example: %1$s for specifying a hard coded year range or %2$s for the last and next 10 years.', 'pods' ),
200 '<code>2010:2030</code>',
201 '<code>-10:+10</code>'
202 ),
203 esc_html__( 'Date / Time field documentation', 'pods' )
204 ),
205 ),
206 static::$type . '_allow_empty' => array(
207 'label' => __( 'Allow empty value', 'pods' ),
208 'default' => 1,
209 'type' => 'boolean',
210 ),
211 static::$type . '_html5' => array(
212 'label' => __( 'Enable HTML5 Input Field', 'pods' ),
213 'default' => apply_filters( 'pods_form_ui_field_html5', 0, static::$type ),
214 'type' => 'boolean',
215 ),
216 );
217
218 // Check if PHP DateTime::createFromFormat exists for additional supported formats
219 if ( method_exists( 'DateTime', 'createFromFormat' ) || apply_filters( 'pods_form_ui_field_datetime_custom_formatter', false ) ) {
220 $options[ static::$type . '_format' ]['data'] = array_merge(
221 $options[ static::$type . '_format' ]['data'], array(
222 'dmy' => date_i18n( 'd/m/Y' ),
223 'dmy_dash' => date_i18n( 'd-m-Y' ),
224 'dmy_dot' => date_i18n( 'd.m.Y' ),
225 'dMy' => date_i18n( 'd/M/Y' ),
226 'dMy_dash' => date_i18n( 'd-M-Y' ),
227 )
228 );
229 }
230
231 $options[ static::$type . '_format' ]['data'] = apply_filters( 'pods_form_ui_field_date_format_options', $options[ static::$type . '_format' ]['data'] );
232 $options[ static::$type . '_format' ]['default'] = apply_filters( 'pods_form_ui_field_date_format_default', $options[ static::$type . '_format' ]['default'] );
233
234 $options[ static::$type . '_time_type' ]['default'] = apply_filters( 'pods_form_ui_field_time_format_type_default', $options[ static::$type . '_time_type' ]['default'] );
235 $options[ static::$type . '_time_format' ]['data'] = apply_filters( 'pods_form_ui_field_time_format_options', $options[ static::$type . '_time_format' ]['data'] );
236 $options[ static::$type . '_time_format' ]['default'] = apply_filters( 'pods_form_ui_field_time_format_default', $options[ static::$type . '_time_format' ]['default'] );
237 $options[ static::$type . '_time_format_24' ]['data'] = apply_filters( 'pods_form_ui_field_time_format_24_options', $options[ static::$type . '_time_format_24' ]['data'] );
238 $options[ static::$type . '_time_format_24' ]['default'] = apply_filters( 'pods_form_ui_field_time_format_24_default', $options[ static::$type . '_time_format_24' ]['default'] );
239
240 return $options;
241 }
242
243 /**
244 * {@inheritdoc}
245 */
246 public function schema( $options = null ) {
247
248 $schema = 'DATETIME NOT NULL default \'0000-00-00 00:00:00\'';
249
250 return $schema;
251 }
252
253 /**
254 * {@inheritdoc}
255 */
256 public function is_empty( $value = null ) {
257
258 $is_empty = false;
259
260 $value = trim( $value );
261
262 if ( empty( $value ) || in_array( $value, array( '0000-00-00', '0000-00-00 00:00:00' ), true ) ) {
263 $is_empty = true;
264 }
265
266 return $is_empty;
267
268 }
269
270 /**
271 * {@inheritdoc}
272 */
273 public function display( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
274 return $this->format_value_display( $value, $options, false );
275 }
276
277 /**
278 * {@inheritdoc}
279 */
280 public function input( $name, $value = null, $options = null, $pod = null, $id = null ) {
281
282 $options = ( is_array( $options ) || is_object( $options ) ) ? $options : (array) $options;
283 $form_field_type = PodsForm::$field_type;
284
285 $value = $this->normalize_value_for_input( $value, $options );
286
287 // @todo Remove? Format Value (done in field template).
288 //$value = $this->format_value_display( $value, $options, true );
289
290 $field_type = static::$type;
291
292 $is_read_only = (boolean) pods_v( 'read_only', $options, false );
293
294 if ( isset( $options['name'] ) && ! pods_permission( $options ) ) {
295 if ( $is_read_only ) {
296 $options['readonly'] = true;
297
298 $field_type = 'text';
299 } else {
300 return;
301 }
302 } elseif ( ! pods_has_permissions( $options ) && $is_read_only ) {
303 $options['readonly'] = true;
304
305 $field_type = 'text';
306 }
307
308 if ( ! empty( $options['disable_dfv'] ) ) {
309 return pods_view( PODS_DIR . 'ui/fields/' . $field_type . '.php', compact( array_keys( get_defined_vars() ) ) );
310 }
311
312 // Convert the date/time formats to MomentJS.
313 $options = $this->prepare_options_for_moment_js( $options );
314
315 $type = pods_v( 'type', $options, static::$type );
316
317 $args = compact( array_keys( get_defined_vars() ) );
318 $args = (object) $args;
319
320 $this->render_input_script( $args );
321 }
322
323 /**
324 * {@inheritdoc}
325 */
326 public function validate( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
327 $validate = parent::validate( $value, $name, $options, $fields, $pod, $id, $params );
328
329 $errors = array();
330
331 if ( is_array( $validate ) ) {
332 $errors = $validate;
333 }
334
335 if ( ! $this->is_empty( $value ) ) {
336
337 // Value should always be passed as storage format since 2.7.15.
338 // This was broken since 2.8.x and restored in 2.9.2 (#6389).
339 $formats = [
340 static::$storage_format,
341 ];
342
343 if ( ! $this->is_storage_format( $value ) ) {
344 // Allow input values compatible with the input (JS) or display (PHP) formats.
345 $formats = [
346 $this->format_display( $options, true ),
347 $this->format_display( $options, false ),
348 ];
349
350 $formats = array_unique( array_filter( $formats ) );
351 }
352
353 $check = $this->convert_date( $value, static::$storage_format, $formats, true );
354
355 if ( false === $check ) {
356 $label = pods_v( 'label', $options, ucwords( str_replace( '_', ' ', $name ) ) );
357
358 // Translators: %1$s is the field label and %2$s is the input value.
359 $errors[] = sprintf( esc_html__( '%1$s was not provided in a recognizable format: "%2$s"', 'pods' ), $label, $value );
360 }
361 }
362
363 if ( ! empty( $errors ) ) {
364 return $errors;
365 }
366
367 return $validate;
368 }
369
370 /**
371 * {@inheritdoc}
372 */
373 public function pre_save( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
374
375 // Value should always be passed as storage format since 2.7.15.
376 $format = static::$storage_format;
377
378 if ( ! $this->is_empty( $value ) ) {
379 if ( ! $this->is_storage_format( $value ) ) {
380 // Allow input values compatible with the display format.
381 $format = $this->format_display( $options, false );
382 }
383 $value = $this->convert_date( $value, static::$storage_format, $format );
384 } elseif ( pods_v( static::$type . '_allow_empty', $options, 1 ) ) {
385 $value = static::$empty_value;
386 } else {
387 $value = date_i18n( static::$storage_format );
388 }
389
390 return $value;
391 }
392
393 /**
394 * {@inheritdoc}
395 */
396 public function ui( $id, $value, $name = null, $options = null, $fields = null, $pod = null ) {
397
398 $value = $this->display( $value, $name, $options, $pod, $id );
399
400 if ( $this->is_empty( $value ) && pods_v( static::$type . '_allow_empty', $options, 1 ) ) {
401 $value = false;
402 }
403
404 return $value;
405 }
406
407 /**
408 * Convert value to the correct format for display.
409 *
410 * @param string $value Field value.
411 * @param array $options Field options.
412 * @param bool $js Return formatted from jQuery UI format? (only for custom formats).
413 *
414 * @return string
415 * @since 2.7.0
416 */
417 public function format_value_display( $value, $options, $js = false ) {
418
419 $format = $this->format_display( $options, $js );
420
421 if ( ! $this->is_empty( $value ) ) {
422 // Try default storage format.
423 $date = $this->createFromFormat( static::$storage_format, (string) $value );
424
425 // Convert to timestamp.
426 if ( $date instanceof DateTime ) {
427 $timestamp = $date->getTimestamp();
428 } else {
429 // Try field format.
430 $date_local = $this->createFromFormat( $format, (string) $value );
431
432 if ( $date_local instanceof DateTime ) {
433 $timestamp = $date_local->getTimestamp();
434 } else {
435 // Fallback.
436 $timestamp = strtotime( (string) $value );
437 }
438 }
439
440 $value = date_i18n( $format, $timestamp );
441 } elseif ( ! pods_v( static::$type . '_allow_empty', $options, 1 ) ) {
442 $value = date_i18n( $format );
443 } else {
444 $value = '';
445 }
446
447 return $value;
448 }
449
450 /**
451 * Build date and/or time display format string based on options
452 *
453 * @since 2.7.13
454 *
455 * @param array $options Field options.
456 * @param bool $js Whether to return format for jQuery UI.
457 *
458 * @return string
459 */
460 public function format_display( $options, $js = false ) {
461
462 if ( 'custom' === pods_v( static::$type . '_type', $options, 'format' ) ) {
463 if ( $js ) {
464
465 // Gets format strings in jQuery UI format.
466 $date = $this->format_date( $options, $js );
467 $time = $this->format_time( $options, $js );
468
469 // Convert them to PHP date format.
470 $date = $this->convert_format( $date, array( 'source' => 'jquery_ui', 'type' => 'date' ) );
471 $time = $this->convert_format( $time, array( 'source' => 'jquery_ui', 'type' => 'time' ) );
472
473 return $date . ' ' . $time;
474
475 } else {
476 $format = $this->format_datetime( $options, $js );
477 }
478 } else {
479 $js = false;
480 $format = $this->format_datetime( $options, $js );
481 }
482
483 return $format;
484 }
485
486 /**
487 * Build date and/or time format string based on options
488 *
489 * @since 2.7.0
490 *
491 * @param array $options Field options.
492 * @param bool $js Whether to return format for jQuery UI.
493 *
494 * @return string
495 */
496 public function format_datetime( $options, $js = false ) {
497
498 $format = $this->format_date( $options, $js );
499
500 $type = pods_v( static::$type . '_type', $options, 'format' );
501
502 if ( 'format' !== $type || 'c' !== pods_v( static::$type . '_format', $options, '' ) ) {
503 $format .= ' ' . $this->format_time( $options, $js );
504 }
505
506 return $format;
507 }
508
509 /**
510 * Build date format string based on options
511 *
512 * @since 2.7.0
513 *
514 * @param array $options Field options.
515 * @param bool $js Whether to return format for jQuery UI.
516 *
517 * @return string
518 */
519 public function format_date( $options, $js = false ) {
520
521 switch ( (string) pods_v( static::$type . '_type', $options, 'format', true ) ) {
522 case 'wp':
523 $format = get_option( 'date_format' );
524
525 if ( $js ) {
526 $format = $this->convert_format( $format, array( 'source' => 'php', 'type' => 'date' ) );
527 }
528
529 break;
530 case 'custom':
531 if ( ! $js ) {
532 $format = pods_v( static::$type . '_format_custom', $options, '' );
533 } else {
534 $format = pods_v( static::$type . '_format_custom_js', $options, '' );
535
536 if ( empty( $format ) ) {
537 $format = pods_v( static::$type . '_format_custom', $options, '' );
538
539 if ( $js ) {
540 $format = $this->convert_format( $format, array( 'source' => 'php', 'type' => 'date' ) );
541 }
542 }
543 }
544
545 break;
546 default:
547 $date_format = $this->get_date_formats( $js );
548
549 $format = $date_format[ pods_v( static::$type . '_format', $options, 'ymd_dash', true ) ];
550
551 break;
552 }//end switch
553
554 return $format;
555 }
556
557 /**
558 * Build time format string based on options
559 *
560 * @since 2.7.0
561 *
562 * @param array $options Field options.
563 * @param bool $js Whether to return format for jQuery UI.
564 *
565 * @return string
566 */
567 public function format_time( $options, $js = false ) {
568
569 switch ( (string) pods_v( static::$type . '_time_type', $options, '12', true ) ) {
570 case '12':
571 $time_format = $this->get_time_formats( $js );
572
573 $format = $time_format[ pods_v( static::$type . '_time_format', $options, 'hh_mm', true ) ];
574
575 break;
576 case '24':
577 $time_format_24 = $this->get_time_formats_24( $js );
578
579 $format = $time_format_24[ pods_v( static::$type . '_time_format_24', $options, 'hh_mm', true ) ];
580
581 break;
582 case 'custom':
583 if ( ! $js ) {
584 $format = pods_v( static::$type . '_time_format_custom', $options, '' );
585 } else {
586 $format = pods_v( static::$type . '_time_format_custom_js', $options, '' );
587 $js = false; // Already in JS format.
588
589 if ( empty( $format ) ) {
590 $format = pods_v( static::$type . '_time_format_custom', $options, '' );
591 $js = true;
592 }
593 }
594
595 break;
596 default:
597 $format = get_option( 'time_format' );
598
599 break;
600 }//end switch
601
602 return $format;
603 }
604
605 /**
606 * Get the date formats.
607 *
608 * @since 2.7.0
609 *
610 * @param bool $js Whether to return format for jQuery UI.
611 *
612 * @return array
613 */
614 public function get_date_formats( $js = false ) {
615
616 $date_format = array(
617 'mdy' => 'm/d/Y',
618 'mdy_dash' => 'm-d-Y',
619 'mdy_dot' => 'm.d.Y',
620 'dmy' => 'd/m/Y',
621 'dmy_dash' => 'd-m-Y',
622 'dmy_dot' => 'd.m.Y',
623 'ymd_slash' => 'Y/m/d',
624 'ymd_dash' => 'Y-m-d',
625 'ymd_dot' => 'Y.m.d',
626 'dMy' => 'd/M/Y',
627 'dMy_dash' => 'd-M-Y',
628 'fjy' => 'F j, Y',
629 'fjsy' => 'F jS, Y',
630 'y' => 'Y',
631 'c' => 'c',
632 );
633
634 $filter = 'pods_form_ui_field_date_formats';
635
636 if ( $js ) {
637 foreach ( $date_format as $key => $value ) {
638 $date_format[ $key ] = $this->convert_format( $value, array( 'type' => 'date' ) );
639 }
640
641 $filter = 'pods_form_ui_field_date_js_formats';
642 }
643
644 return apply_filters( $filter, $date_format );
645 }
646
647 /**
648 * Get the time formats.
649 *
650 * @since 2.7.0
651 *
652 * @param bool $js Whether to return format for jQuery UI.
653 *
654 * @return array
655 */
656 public function get_time_formats( $js = false ) {
657
658 $time_format = array(
659 'h_mm_A' => 'g:i A',
660 'h_mm_ss_A' => 'g:i:s A',
661 'hh_mm_A' => 'h:i A',
662 'hh_mm_ss_A' => 'h:i:s A',
663 'h_mma' => 'g:ia',
664 'hh_mma' => 'h:ia',
665 'h_mm' => 'g:i',
666 'h_mm_ss' => 'g:i:s',
667 'hh_mm' => 'h:i',
668 'hh_mm_ss' => 'h:i:s',
669 );
670
671 $filter = 'pods_form_ui_field_time_formats';
672
673 if ( $js ) {
674 foreach ( $time_format as $key => $value ) {
675 $time_format[ $key ] = $this->convert_format( $value, array( 'type' => 'time' ) );
676 }
677
678 $filter = 'pods_form_ui_field_time_js_formats';
679 }
680
681 return apply_filters( $filter, $time_format );
682 }
683
684 /**
685 * Get the time formats.
686 *
687 * @since 2.7.0
688 *
689 * @param bool $js Whether to return format for jQuery UI.
690 *
691 * @return array
692 */
693 public function get_time_formats_24( $js = false ) {
694
695 $time_format_24 = array(
696 'hh_mm' => 'H:i',
697 'hh_mm_ss' => 'H:i:s',
698 );
699
700 $filter = 'pods_form_ui_field_time_formats_24';
701
702 if ( $js ) {
703 foreach ( $time_format_24 as $key => $value ) {
704 $time_format_24[ $key ] = $this->convert_format( $value, array( 'type' => 'time' ) );
705 }
706
707 $filter = 'pods_form_ui_field_time_js_formats_24';
708 }
709
710 return apply_filters( $filter, $time_format_24 );
711 }
712
713 /**
714 * PHP backwards compatibility for createFromFormat.
715 *
716 * @param string $format Format string.
717 * @param string $date Defaults to time() if empty.
718 * @param boolean $return_timestamp Whether to return the strtotime() or createFromFormat result or not.
719 *
720 * @return DateTime|null|int|false
721 */
722 public function createFromFormat( $format, $date, $return_timestamp = false ) {
723
724 $datetime = null;
725
726 try {
727 if ( method_exists( 'DateTime', 'createFromFormat' ) ) {
728
729 $datetime = DateTime::createFromFormat( $format, (string) $date );
730
731 if ( false === $datetime ) {
732 $datetime = DateTime::createFromFormat( static::$storage_format, (string) $date );
733 }
734
735 if ( false !== $datetime && $return_timestamp ) {
736 return $datetime;
737 }
738
739 }//end if
740
741 if ( in_array( $datetime, array( null, false ), true ) ) {
742 if ( empty( $date ) ) {
743 $timestamp = time();
744 } else {
745 $timestamp = strtotime( (string) $date );
746
747 if ( $return_timestamp ) {
748 return $timestamp;
749 }
750 }
751
752 if ( $timestamp ) {
753 $datetime = new DateTime( date_i18n( static::$storage_format, $timestamp ) );
754 }
755 }
756 } catch ( Exception $exception ) {
757 // There is no saving this time value, it's an exception to the rule.
758 pods_debug_log( $exception );
759 }
760
761 return apply_filters( 'pods_form_ui_field_datetime_formatter', $datetime, $format, $date );
762 }
763
764 /**
765 * Check if a value is compatible with the storage format.
766 *
767 * Valid:
768 * - 0000-00-00 00:00:00
769 * - 0000-00-00 00:00
770 * - 0000-00-00
771 * - 0000-00
772 * - etc.
773 *
774 * @param string $value The date value.
775 * @return bool
776 */
777 public function is_storage_format( $value ) {
778 $value_parts = str_split( $value );
779 $format_parts = str_split( gmdate( static::$storage_format ) );
780
781 $valid = true;
782 foreach ( $value_parts as $i => $part ) {
783 if ( isset( $format_parts[ $i ] ) ) {
784 if ( is_numeric( $format_parts[ $i ] ) ) {
785 if ( ! is_numeric( $part ) ) {
786 $valid = false;
787 break;
788 }
789 } elseif ( $format_parts[ $i ] !== $part ) {
790 $valid = false;
791 break;
792 }
793 }
794 }
795 return $valid;
796 }
797
798 /**
799 * Convert a date from one format to another.
800 *
801 * @param string $value Field value.
802 * @param string $new_format New format string.
803 * @param string|array $original_format Original format string(s) (if known).
804 * @param boolean $return_timestamp Whether to return the strtotime() or createFromFormat result or not.
805 *
806 * @return string|int|boolean|DateTime
807 */
808 public function convert_date( $value, $new_format, $original_format = '', $return_timestamp = false ) {
809
810 if ( empty( $original_format ) ) {
811 $original_format = static::$storage_format;
812 }
813
814 if ( is_array( $original_format ) ) {
815 foreach ( $original_format as $original_format_option ) {
816 $value = $this->convert_date( $value, $new_format, $original_format_option, $return_timestamp );
817
818 if ( false !== $value ) {
819 return $value;
820 }
821 }
822
823 return false;
824 }
825
826 $date = '';
827
828 if ( ! $this->is_empty( $value ) ) {
829 $date = $this->createFromFormat( $original_format, (string) $value, $return_timestamp );
830
831 if ( $date instanceof DateTime ) {
832 $value = $date->format( $new_format );
833 } elseif ( false !== $date ) {
834 $date = strtotime( (string) $value );
835
836 $value = date_i18n( $new_format, $date );
837 }
838 } else {
839 $value = date_i18n( $new_format );
840 }
841
842 // Return timestamp conversion result instead
843 if ( $return_timestamp ) {
844 return $date;
845 }
846
847 return $value;
848 }
849
850 /**
851 * Matches each symbol of PHP date format standard with jQuery equivalent codeword.
852 *
853 * @link http://stackoverflow.com/questions/16702398/convert-a-php-date-format-to-a-jqueryui-datepicker-date-format
854 * @link https://api.jqueryui.com/datepicker/
855 * @link http://trentrichardson.com/examples/timepicker/
856 *
857 * @since 2.7.0
858 *
859 * @param string $source_format Source format string.
860 * @param array $args Format arguments.
861 *
862 * @return string
863 */
864 public function convert_format( $source_format, $args = array() ) {
865
866 // @todo Improve source/target logic.
867 $args = array_merge(
868 array(
869 'source' => 'php',
870 'type' => 'date',
871 // 'jquery_ui' for reverse.
872 ), $args
873 );
874
875 // Keep keys and values sorted by string length.
876 if ( 'time' === $args['type'] || 'time' === static::$type ) {
877
878 $symbols = array(
879 // AM/PM.
880 'a' => 'tt',
881 'A' => 'TT',
882 // Swatch internet time (not supported).
883 'B' => '',
884 // Hour.
885 'h' => 'hh',
886 'H' => 'HH',
887 'g' => 'h',
888 'G' => 'H',
889 // Minute.
890 'i' => 'mm',
891 // Second.
892 's' => 'ss',
893 // Microsecond.
894 'u' => 'c',
895 // Timezone.
896 'O' => 'z',
897 'P' => 'Z',
898 );
899
900 if ( version_compare( PHP_VERSION, '7.0.0' ) >= 0 ) {
901 // Millisecond.
902 $symbols['v'] = 'l';
903 }
904
905 } else {
906
907 $symbols = array(
908 // Day.
909 'd' => 'dd',
910 'l' => 'DD',
911 'D' => 'D',
912 'j' => 'd',
913 'N' => '',
914 'S' => '',
915 'w' => '',
916 'z' => 'o',
917 // Week.
918 'W' => '',
919 // Month.
920 'F' => 'MM',
921 'm' => 'mm',
922 'M' => 'M',
923 'n' => 'm',
924 't' => '',
925 // Year.
926 'L' => '',
927 'o' => '',
928 'Y' => 'yy',
929 'y' => 'y',
930 );
931 }
932
933 if ( 'jquery_ui' === $args['source'] ) {
934 // Remove empty values.
935 $symbols = array_filter( $symbols );
936 $symbols = array_flip( $symbols );
937 }
938
939 $new_format = '';
940 $escaping = false;
941
942 $source_format_length = strlen( $source_format );
943
944 for ( $i = 0; $i < $source_format_length; $i ++ ) {
945 $char = $source_format[ $i ];
946
947 // PHP date format escaping character
948 // @todo Do we want to support non-format characters?
949 if ( '\\' === $char ) {
950 $i ++;
951
952 if ( $escaping ) {
953 $new_format .= $source_format[ $i ];
954 } else {
955 $new_format .= '\'' . $source_format[ $i ];
956 }
957
958 $escaping = true;
959 } else {
960 if ( $escaping ) {
961 $new_format .= "'";
962 $escaping = false;
963 }
964
965 $symbol_key = false;
966
967 if ( isset( $source_format[ $i + 1 ] ) ) {
968 $symbol_key = $char . $source_format[ $i + 1 ];
969 }
970
971 // Support 2 characters.
972 if ( $symbol_key && isset( $symbols[ $symbol_key ] ) ) {
973 $new_format .= $symbols[ $symbol_key ];
974
975 $i ++;
976 } elseif ( isset( $symbols[ $char ] ) ) {
977 $new_format .= $symbols[ $char ];
978 } else {
979 $new_format .= $char;
980 }
981 }//end if
982 }//end for
983
984 return $new_format;
985 }
986
987 /**
988 * Prepare the date/datetime/time field object or options for MomentJS formatting.
989 *
990 * @since 2.8.11
991 *
992 * @param array|Field $options The field object or options.
993 *
994 * @return array|Field The field object or options.
995 */
996 public function prepare_options_for_moment_js( $options ) {
997 // Handle time formats for datetime.
998 if ( 'datetime' === static::$type ) {
999 $date_format = $this->get_format_from_options_for_type( $options, static::$type, '', true );
1000 $time_format = $this->get_format_from_options_for_type( $options, static::$type, '_time', true );
1001
1002 $date_format_moment_js = $this->convert_format_to_moment_js( $date_format['format'], [
1003 'source' => $date_format['is_js'] ? 'jquery_ui' : 'php',
1004 'type' => 'date',
1005 ] );
1006 $time_format_moment_js = $this->convert_format_to_moment_js( $time_format['format'], [
1007 'source' => $time_format['is_js'] ? 'jquery_ui' : 'php',
1008 'type' => 'time',
1009 ] );
1010
1011 $options[ static::$type . '_date_format_moment_js' ] = $date_format_moment_js;
1012 $options[ static::$type . '_time_format_moment_js' ] = $time_format_moment_js;
1013 } elseif ( 'date' === static::$type ) {
1014 $date_format = $this->get_format_from_options_for_type( $options, static::$type, '', true );
1015
1016 $date_format_moment_js = $this->convert_format_to_moment_js( $date_format['format'], [
1017 'source' => $date_format['is_js'] ? 'jquery_ui' : 'php',
1018 'type' => 'date',
1019 ] );
1020
1021 $options[ static::$type . '_format_moment_js' ] = $date_format_moment_js;
1022 } elseif ( 'time' === static::$type ) {
1023 $time_format = $this->get_format_from_options_for_type( $options, static::$type, '', true );
1024
1025 $time_format_moment_js = $this->convert_format_to_moment_js( $time_format['format'], [
1026 'source' => $time_format['is_js'] ? 'jquery_ui' : 'php',
1027 'type' => 'time',
1028 ] );
1029
1030 $options[ static::$type . '_format_moment_js' ] = $time_format_moment_js;
1031 }
1032
1033 return $options;
1034 }
1035
1036 /**
1037 * Get the format from the options for a specific type (date/datetime/time).
1038 *
1039 * @since 2.8.11
1040 *
1041 * @param array|Field $options The field object or options.
1042 * @param string $type The specific field type.
1043 * @param string $prefix The prefix to use on the format options if needed (like "_time" in datetime_time_format).
1044 * @param bool $js Whether we want to get the format in the JS context.
1045 *
1046 * @return array The format information including if found/using the JS context option and the format.
1047 */
1048 public function get_format_from_options_for_type( $options, $type, $prefix = '', $js = false ) {
1049 $format_type = pods_v( $type . $prefix . '_type', $options );
1050
1051 $is_date_format = (
1052 'date' === $type
1053 || (
1054 'datetime' === $type
1055 && '' === $prefix
1056 )
1057 );
1058
1059 $is_24_hour = '24' === $format_type;
1060
1061 if ( '12' === $format_type || $is_24_hour ) {
1062 $format_type = 'format';
1063 }
1064
1065 // Get the format from the field setting.
1066 if ( 'format' === $format_type ) {
1067 if ( $is_date_format ) {
1068 // Get the format for date.
1069 $formats = $this->get_date_formats();
1070 } elseif ( $is_24_hour ) {
1071 // Get the format for time (24-hour).
1072 $formats = $this->get_time_formats_24();
1073 } else {
1074 // Get the format for time (12-hour).
1075 $formats = $this->get_time_formats();
1076 }
1077
1078 $format = pods_v( $type . $prefix . '_format', $options );
1079
1080 // Get 24-hour format.
1081 if ( $is_24_hour ) {
1082 $format = pods_v( $type . $prefix . '_format_24', $options );
1083 }
1084
1085 // Check if format is registered.
1086 if ( ! empty( $formats[ $format ] ) ) {
1087 return [
1088 'is_js' => false,
1089 'format' => $formats[ $format ],
1090 ];
1091 }
1092 }
1093
1094 // Get the custom format from the field setting.
1095 if ( 'custom' === $format_type ) {
1096 $format_custom = pods_v( $type . $prefix . '_format_custom', $options );
1097
1098 $is_js = false;
1099
1100 if ( $js ) {
1101 $format_custom_js = pods_v( $type . $prefix . '_format_custom_js', $options );
1102
1103 if ( ! empty( $format_custom_js ) ) {
1104 $is_js = true;
1105
1106 $format_custom = $format_custom_js;
1107 }
1108 }
1109
1110 // Check if there's a custom format.
1111 if ( ! empty( $format_custom ) ) {
1112 return [
1113 'is_js' => $is_js,
1114 'format' => $format_custom,
1115 ];
1116 }
1117 }
1118
1119 // Fallback to wp format.
1120 $options[ $type . $prefix . '_type' ] = 'wp';
1121
1122 // Maybe get the date format from WordPress.
1123 if ( $is_date_format ) {
1124 return [
1125 'is_js' => false,
1126 'format' => get_option( 'date_format' ),
1127 ];
1128 }
1129
1130 // Get the time format from WordPress.
1131 return [
1132 'is_js' => false,
1133 'format' => get_option( 'time_format' ),
1134 ];
1135 }
1136
1137 /**
1138 * Convert the source format to MomentJS format for PHP / jQuery UI formats.
1139 *
1140 * @since 2.8.11
1141 *
1142 * @param string|mixed $source_format The source format.
1143 * @param array $args The list of format arguments including source (php/jquery_ui) and type (date/time).
1144 *
1145 * @return string|mixed The converted MomentJS format.
1146 */
1147 public function convert_format_to_moment_js( $source_format, $args = array() ) {
1148 if ( ! is_string( $source_format ) || '' === trim( $source_format ) ) {
1149 return $source_format;
1150 }
1151
1152 $defaults = [
1153 'source' => 'php', // php or jquery_ui.
1154 'type' => 'date', // date or time.
1155 ];
1156
1157 $args = array_merge( $defaults, $args );
1158
1159 // For PHP symbols, see https://www.php.net/manual/en/datetime.format.php
1160 // For Moment.js symbols, see https://momentjs.com/docs/#/displaying/format/
1161 $php_replacements = [
1162 'A' => 'A', // for the sake of escaping below
1163 'a' => 'a', // for the sake of escaping below
1164 'B' => '', // Swatch internet time (.beats), no equivalent
1165 'c' => 'YYYY-MM-DD[T]HH:mm:ssZ', // ISO 8601
1166 'D' => 'ddd',
1167 'd' => 'DD',
1168 'e' => 'zz', // deprecated since version 1.6.0 of moment.js
1169 'F' => 'MMMM',
1170 'G' => 'H',
1171 'g' => 'h',
1172 'H' => 'HH',
1173 'h' => 'hh',
1174 'I' => '', // Daylight Saving Time? => moment().isDST();
1175 'i' => 'mm',
1176 'j' => 'D',
1177 'L' => '', // Leap year? => moment().isLeapYear();
1178 'l' => 'dddd',
1179 'M' => 'MMM',
1180 'm' => 'MM',
1181 'N' => 'E',
1182 'n' => 'M',
1183 'O' => 'ZZ',
1184 'o' => 'YYYY',
1185 'P' => 'Z',
1186 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822
1187 'S' => 'o',
1188 's' => 'ss',
1189 'T' => 'z', // deprecated since version 1.6.0 of moment.js
1190 't' => '', // days in the month => moment().daysInMonth();
1191 'U' => 'X',
1192 'u' => 'SSSSSS', // microseconds
1193 'v' => 'SSS', // milliseconds (from PHP 7.0.0)
1194 'W' => 'W', // for the sake of escaping below
1195 'w' => 'e',
1196 'Y' => 'YYYY',
1197 'y' => 'YY',
1198 'Z' => '', // time zone offset in minutes => moment().zone();
1199 'z' => 'DDD',
1200 ];
1201
1202 // For jQuery symbols, see https://api.jqueryui.com/datepicker/#utility-formatDate
1203 // For Moment.js symbols, see https://momentjs.com/docs/#/displaying/format/
1204 $jquery_ui_date_replacements = [
1205 'dd' => 'DD', // day of month (two digit)
1206 'd' => 'D', // day of month (no leading zero)
1207 'oo' => 'DDDD', // day of the year (three digit)
1208 'o' => 'DDD', // day of the year (no leading zeros)
1209 'DD' => 'dddd', // day name long
1210 'D' => 'dd', // day name short
1211 'mm' => 'MM', // month of year (two digit)
1212 'm' => 'M', // month of year (no leading zero)
1213 'MM' => 'MMMM', // month name long
1214 'M' => 'MMM', // month name short
1215 'yy' => 'YYYY', // year (four digit)
1216 'y' => 'YY', // year (two digit)
1217 '@' => 'X', // Unix timestamp (ms since 01/01/1970)
1218 '!' => '', // Windows ticks (100ns since 01/01/0001), no equivalent
1219 ];
1220
1221 // For jQuery symbols, see http://trentrichardson.com/examples/timepicker/#tp-formatting
1222 // For Moment.js symbols, see https://momentjs.com/docs/#/displaying/format/
1223 $jquery_ui_time_replacements = [
1224 'HH' => 'HH', // Hour with leading 0 (24 hour)
1225 'H' => 'H', // Hour with no leading 0 (24 hour)
1226 'hh' => 'hh', // Hour with leading 0 (12 hour)
1227 'h' => 'h', // Hour with no leading 0 (12 hour)
1228 'mm' => 'mm', // Minute with leading 0
1229 'm' => 'm', // Minute with no leading 0
1230 'i' => 'mm', // In case they got confused with PHP time format
1231 'ss' => 'ss', // Second with leading 0
1232 's' => 's', // Second with no leading 0
1233 'l' => 'SSS', // Milliseconds always with leading 0
1234 'c' => 'SSSSSS', // Microseconds always with leading 0
1235 't' => 'a', // a or p for AM/PM, no equivalent, switches to am/pm
1236 'TT' => 'A', // AM or PM for AM/PM
1237 'tt' => 'a', // am or pm for AM/PM
1238 'T' => 'A', // A or P for AM/PM, no equivalent, switches to AM/PM
1239 'z' => '', // Timezone as defined by timezoneList => moment().zone();
1240 'Z' => '', // Timezone in Iso 8601 format (+04:45) => moment().zone();
1241 ];
1242
1243 // Handle PHP first since it only has one replacement logic.
1244 if ( 'php' === $args['source'] ) {
1245 return pods_replace_keys_to_values( $source_format, $php_replacements );
1246 }
1247
1248 // Handle jQuery UI replacements.
1249 if ( 'jquery_ui' === $args['source'] ) {
1250 if ( 'date' === $args['type'] ) {
1251 return pods_replace_keys_to_values( $source_format, $jquery_ui_date_replacements );
1252 } elseif ( 'time' === $args['type'] ) {
1253 return pods_replace_keys_to_values( $source_format, $jquery_ui_time_replacements );
1254 }
1255 }
1256
1257 return $source_format;
1258 }
1259
1260 /**
1261 * Enqueue the i18n files for jquery date/timepicker
1262 *
1263 * @deprecated since 2.8.0
1264 *
1265 * @since 2.7.0
1266 */
1267 public function enqueue_jquery_ui_i18n() {
1268 $done = (array) pods_static_cache_get( 'done', __METHOD__ );
1269
1270 $types = array();
1271
1272 switch ( static::$type ) {
1273 case 'time':
1274 $types['time'] = true;
1275
1276 break;
1277 case 'date':
1278 $types['date'] = true;
1279
1280 break;
1281 case 'datetime':
1282 $types['time'] = true;
1283 $types['date'] = true;
1284
1285 break;
1286 }
1287
1288 $locale = str_replace( '_', '-', get_locale() );
1289
1290 if ( isset( $types['date'] ) && ! isset( $done[ 'date-' . $locale ] ) ) {
1291 if ( function_exists( 'wp_localize_jquery_ui_datepicker' ) ) {
1292 wp_localize_jquery_ui_datepicker();
1293 }
1294
1295 $done['date'] = true;
1296 }
1297
1298 if ( isset( $types['time'] ) && ! isset( $done[ 'time-' . $locale ] ) ) {
1299 $locale_exists = file_exists( PODS_DIR . 'ui/js/timepicker/i18n/jquery-ui-timepicker-' . $locale . '.js' );
1300
1301 // Local files.
1302 if ( ! $locale_exists ) {
1303 // Fallback to the base language (non-region specific).
1304 $locale = substr( $locale, 0, strpos( $locale, '-' ) );
1305
1306 $locale_exists = file_exists( PODS_DIR . 'ui/js/timepicker/i18n/jquery-ui-timepicker-' . $locale . '.js' );
1307 }
1308
1309 if ( $locale_exists && ! wp_script_is( 'jquery-ui-timepicker-i18n-' . $locale ) ) {
1310 wp_enqueue_script( 'jquery-ui-timepicker-i18n-' . $locale, PODS_URL . 'ui/js/timepicker/i18n/jquery-ui-timepicker-' . $locale . '.js', [ 'jquery-ui-timepicker' ], '1.6.3' );
1311 }
1312
1313 $done[ 'time-' . $locale ] = true;
1314 }
1315 }
1316 }
1317