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