PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 3.3.7
Pods – Custom Content Types and Fields v3.3.7
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 / number.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
number.php
576 lines
1 <?php
2
3 // Don't load directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 die( '-1' );
6 }
7
8 /**
9 * @package Pods\Fields
10 */
11 class PodsField_Number extends PodsField {
12
13 /**
14 * {@inheritdoc}
15 */
16 public static $group = 'Number';
17
18 /**
19 * {@inheritdoc}
20 */
21 public static $type = 'number';
22
23 /**
24 * {@inheritdoc}
25 */
26 public static $label = 'Plain Number';
27
28 /**
29 * {@inheritdoc}
30 */
31 public static $prepare = '%d';
32
33 /**
34 * {@inheritdoc}
35 */
36 public function setup() {
37
38 static::$group = __( 'Number', 'pods' );
39 static::$label = __( 'Plain Number', 'pods' );
40 }
41
42 /**
43 * {@inheritdoc}
44 */
45 public function options() {
46
47 $options = [
48 static::$type . '_format_type' => [
49 'label' => __( 'Input Type', 'pods' ),
50 'default' => 'number',
51 'type' => 'pick',
52 'data' => [
53 'number' => __( 'Freeform Number', 'pods' ),
54 'slider' => __( 'Slider', 'pods' ),
55 ],
56 'pick_format_single' => 'dropdown',
57 'pick_show_select_text' => 0,
58 'dependency' => true,
59 ],
60 static::$type . '_format' => [
61 'label' => __( 'Number Format', 'pods' ),
62 'default' => apply_filters( 'pods_form_ui_field_number_format_default', 'i18n' ),
63 'type' => 'pick',
64 'data' => [
65 'i18n' => __( 'Localized Default', 'pods' ),
66 '9,999.99' => '1,234.00',
67 '9999.99' => '1234.00',
68 '9.999,99' => '1.234,00',
69 '9999,99' => '1234,00',
70 '9 999,99' => '1 234,00',
71 '9\'999.99' => '1\'234.00',
72 ],
73 'pick_format_single' => 'dropdown',
74 'pick_show_select_text' => 0,
75 ],
76 static::$type . '_decimals' => [
77 'label' => __( 'Decimals', 'pods' ),
78 'default' => 0,
79 'type' => 'number',
80 'dependency' => true,
81 'help' => __( 'Set to a positive number to enable decimals. The upper limit in the database for this field is 30 decimals.', 'pods' ),
82 ],
83 static::$type . '_format_soft' => [
84 'label' => __( 'Soft Formatting (deprecated, use Decimal handling instead)', 'pods' ),
85 'help' => __( 'Remove trailing decimals (0)', 'pods' ),
86 'default' => 0,
87 'type' => 'boolean',
88 'excludes-on' => [ static::$type . '_decimals' => 0 ],
89 ],
90 static::$type . '_decimal_handling' => [
91 'label' => __( 'Decimal handling for trailing zero decimals', 'pods' ),
92 'default' => 'none',
93 'type' => 'pick',
94 'data' => [
95 'none' => __( 'Default (Examples: "1.00", "0.00")', 'pods' ),
96 'remove' => __( 'Remove decimals (Examples: "1", "0")', 'pods' ),
97 'remove_only_zero' => __( 'Remove decimals only when number is zero (Examples: "1.00", "0")', 'pods' ),
98 'dash' => __( 'Convert to dash (Examples: "1.-", "0.-")', 'pods' ),
99 'dash_only_zero' => __( 'Convert to dash only when number is zero (Examples: "1.00", "0.-")', 'pods' ),
100 'dash_whole_zero' => __( 'Convert to the whole value to dash when number is zero (Examples: "1.00", "-")', 'pods' ),
101 ],
102 'pick_format_single' => 'dropdown',
103 'pick_show_select_text' => 0,
104 ],
105 static::$type . '_step' => [
106 'label' => __( 'Slider Increment (Step)', 'pods' ),
107 'depends-on' => [ static::$type . '_format_type' => 'slider' ],
108 'default' => 1,
109 'type' => 'text',
110 ],
111 static::$type . '_min' => [
112 'label' => __( 'Minimum Number', 'pods' ),
113 'depends-on-any' => [
114 static::$type . '_format_type' => 'slider',
115 static::$type . '_html5' => true,
116 ],
117 'default' => '',
118 'type' => 'text',
119 ],
120 static::$type . '_max' => [
121 'label' => __( 'Maximum Number', 'pods' ),
122 'depends-on-any' => [
123 static::$type . '_format_type' => 'slider',
124 static::$type . '_html5' => true,
125 ],
126 'default' => '',
127 'type' => 'text',
128 ],
129 static::$type . '_max_length' => [
130 'label' => __( 'Maximum Digits', 'pods' ),
131 'default' => 12,
132 'type' => 'number',
133 'help' => __( 'Set to -1 for no limit. The upper limit in the database for this field is 64 digits.', 'pods' ),
134 ],
135 static::$type . '_html5' => [
136 'label' => __( 'Enable HTML5 Input Field', 'pods' ),
137 'default' => apply_filters( 'pods_form_ui_field_html5', 0, static::$type ),
138 'depends-on' => [ static::$type . '_format_type' => 'number' ],
139 'type' => 'boolean',
140 ],
141 static::$type . '_placeholder' => [
142 'label' => __( 'HTML Placeholder', 'pods' ),
143 'default' => '',
144 'type' => 'text',
145 'help' => [
146 __( 'Placeholders can provide instructions or an example of the required data format for a field. Please note: It is not a replacement for labels or description text, and it is less accessible for people using screen readers.', 'pods' ),
147 'https://www.w3.org/WAI/tutorials/forms/instructions/#placeholder-text',
148 ],
149 ],
150 ];
151
152 return $options;
153 }
154
155 /**
156 * {@inheritdoc}
157 */
158 public function schema( $options = null ) {
159
160 $length = (int) pods_v( static::$type . '_max_length', $options, 12, true );
161
162 if ( $length < 1 || 64 < $length ) {
163 $length = 64;
164 }
165
166 $decimals = $this->get_max_decimals( $options );
167
168 $schema = 'DECIMAL(' . $length . ',' . $decimals . ')';
169
170 return $schema;
171
172 }
173
174 /**
175 * {@inheritdoc}
176 */
177 public function prepare( $options = null ) {
178
179 $format = static::$prepare;
180
181 $decimals = $this->get_max_decimals( $options );
182
183 if ( 6 < $decimals ) {
184 // %F only allows 6 decimals by default
185 $format = '%.' . $decimals . 'F';
186 } elseif ( 0 < $decimals ) {
187 $format = '%F';
188 }
189
190 return $format;
191
192 }
193
194 /**
195 * @todo 2.8 Centralize the usage of this method. See PR #5540.
196 * {@inheritdoc}
197 */
198 public function is_empty( $value = null ) {
199
200 $is_empty = false;
201
202 $value = (float) $value;
203
204 if ( empty( $value ) ) {
205 $is_empty = true;
206 }
207
208 return $is_empty;
209
210 }
211
212 /**
213 * {@inheritdoc}
214 */
215 public function display( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
216 return $this->format( $value, $name, $options, $pod, $id );
217 }
218
219 /**
220 * {@inheritdoc}
221 */
222 public function input( $name, $value = null, $options = null, $pod = null, $id = null ) {
223
224 $options = ( is_array( $options ) || is_object( $options ) ) ? $options : (array) $options;
225 $form_field_type = PodsForm::$field_type;
226 $is_read_only = false;
227
228 $value = $this->normalize_value_for_input( $value, $options, '' );
229
230 if ( 'slider' === pods_v( static::$type . '_format_type', $options, 'number' ) ) {
231 $field_type = 'slider';
232 } else {
233 $field_type = static::$type;
234 }
235
236 if ( isset( $options['name'] ) && ! pods_permission( $options ) ) {
237 if ( pods_v_bool( 'read_only_restricted', $options ) ) {
238 $is_read_only = true;
239 } else {
240 return;
241 }
242 } elseif ( ! pods_has_permissions( $options ) ) {
243 if ( pods_v_bool( 'read_only', $options ) ) {
244 $options['readonly'] = true;
245 }
246 }
247
248 if ( $is_read_only ) {
249 $options['readonly'] = true;
250
251 $field_type = 'text';
252 }
253
254 // Enforce boolean.
255 $options[ static::$type . '_html5' ] = filter_var( pods_v( static::$type . '_html5', $options, false ), FILTER_VALIDATE_BOOLEAN );
256 $options[ static::$type . '_format_soft' ] = filter_var( pods_v( static::$type . '_format_soft', $options, false ), FILTER_VALIDATE_BOOLEAN );
257
258 // Only format the value for non-HTML5 inputs.
259 if ( ! $options[ static::$type . '_html5' ] ) {
260 // Ensure proper format
261 if ( is_array( $value ) ) {
262 foreach ( $value as $k => $repeatable_value ) {
263 $value[ $k ] = $this->format( $repeatable_value, $name, $options, $pod, $id );
264 }
265 } else {
266 $value = $this->format( $value, $name, $options, $pod, $id );
267 }
268 }
269
270 if ( ! empty( $options['disable_dfv'] ) ) {
271 return pods_view( PODS_DIR . 'ui/fields/number.php', compact( array_keys( get_defined_vars() ) ) );
272 }
273
274 $type = pods_v( 'type', $options, static::$type );
275
276 $args = compact( array_keys( get_defined_vars() ) );
277 $args = (object) $args;
278
279 $this->render_input_script( $args );
280 }
281
282 /**
283 * {@inheritdoc}
284 */
285 public function regex( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
286
287 $format_args = $this->get_number_format_args( $options );
288 $thousands = $format_args['thousands'];
289 $dot = $format_args['dot'];
290
291 return '\-*[0-9\\' . implode( '\\', array_filter( [ $dot, $thousands ] ) ) . ']+';
292 }
293
294 /**
295 * {@inheritdoc}
296 */
297 public function validate( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
298 $validate = parent::validate( $value, $name, $options, $fields, $pod, $id, $params );
299
300 $errors = [];
301
302 if ( is_array( $validate ) ) {
303 $errors = $validate;
304 }
305
306 $format_args = $this->get_number_format_args( $options );
307 $thousands = $format_args['thousands'];
308 $dot = $format_args['dot'];
309
310 $check = str_replace(
311 [ $thousands, $dot, html_entity_decode( $thousands, ENT_COMPAT ) ],
312 [ '', '.', '' ],
313 $value
314 );
315
316 $check = trim( $check );
317
318 $check = preg_replace( '/[0-9\.\-\s]/', '', $check );
319
320 $label = pods_v( 'label', $options, ucwords( str_replace( '_', ' ', $name ) ) );
321
322 if ( 0 < strlen( (string) $check ) ) {
323 // Translators: %s stands for the input value.
324 $errors[] = sprintf( esc_html__( '%s is not numeric', 'pods' ), $label );
325 }
326
327 if ( ! empty( $errors ) ) {
328 return $errors;
329 }
330
331 return $validate;
332 }
333
334 /**
335 * {@inheritdoc}
336 */
337 public function pre_save( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
338
339 $format_args = $this->get_number_format_args( $options );
340 $thousands = $format_args['thousands'];
341 $dot = $format_args['dot'];
342 $decimals = $format_args['decimals'];
343
344 // Slider only supports `1234.00` format so no need for replacing characters.
345 if ( 'slider' !== pods_v( static::$type . '_format_type', $options ) ) {
346 // Not a slider so we need to replace format characters.
347 $value = str_replace(
348 [ $thousands, html_entity_decode( $thousands, ENT_COMPAT ), $dot, html_entity_decode( $dot, ENT_COMPAT ) ],
349 [ '', '', '.', '.' ],
350 $value
351 );
352
353 // HTML5 supports both `1234.00` and `1234,00` formats so let's replace commas as decimals (thousands replaced above).
354 if ( 1 === (int) pods_v( static::$type . '_html5', $options, false ) ) {
355 $value = str_replace( ',', '.', $value );
356 }
357 }
358
359 $value = trim( $value );
360
361 $value = preg_replace( '/[^0-9\.\-]/', '', $value );
362
363 if ( $this->is_empty( $value ) && ( ! is_numeric( $value ) || 0.0 !== (float) $value ) ) {
364 // Don't enforce a default value here.
365 return null;
366 }
367
368 $value = number_format( (float) $value, $decimals, '.', '' );
369
370 // Optionally remove trailing decimal zero's.
371 if ( pods_v( static::$type . '_format_soft', $options, false ) ) {
372 $value = $this->trim_decimals( $value, '.' );
373 }
374
375 return $value;
376 }
377
378 /**
379 * {@inheritdoc}
380 */
381 public function format( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
382
383 if ( $this->is_empty( $value ) && ( ! is_numeric( $value ) || 0.0 !== (float) $value ) ) {
384 // Don't enforce a default value here.
385 return null;
386 }
387
388 $format_args = $this->get_number_format_args( $options );
389 $thousands = $format_args['thousands'];
390 $dot = $format_args['dot'];
391 $decimals = $format_args['decimals'];
392
393 if ( 'i18n' === pods_v( static::$type . '_format', $options ) ) {
394 $value = number_format_i18n( (float) $value, $decimals );
395 } else {
396 $value = number_format( (float) $value, $decimals, $dot, $thousands );
397 }
398
399 // Additional output handling for decimals
400 $decimals = (int) pods_v( static::$type . '_decimals', $options, 2 );
401
402 if ( $decimals < 1 ) {
403 return $value;
404 }
405
406 $decimal_handling = pods_v( static::$type . '_decimal_handling', $options, 'none' );
407
408 if ( 'none' === $decimal_handling && (bool) pods_v( static::$type . '_format_soft', $options, false ) ) {
409 $decimal_handling = 'remove';
410 }
411
412 if ( 'none' !== $decimal_handling ) {
413 $format_args = $this->get_number_format_args( $options );
414 $dot = $format_args['dot'];
415 $thousands = $format_args['thousands'];
416
417 $value_parts = explode( $dot, $value );
418
419 $value_number_int = isset( $value_parts[0] ) ? (int) str_replace( $thousands, '', $value_parts[0] ) : 0;
420 $is_zero = 0 === $value_number_int;
421
422 if ( isset( $value_parts[1] ) && 0 === (int) $value_parts[1] ) {
423 switch ( $decimal_handling ) {
424 case 'remove':
425 // Remove decimals.
426 $value = $value_parts[0];
427 break;
428
429 case 'remove_only_zero':
430 // Remove decimals only when number is zero.
431 if ( $is_zero ) {
432 $value = $value_parts[0];
433 }
434 break;
435
436 case 'dash':
437 // Convert to dash.
438 $value = $value_parts[0] . $dot . '-';
439 break;
440
441 case 'dash_only_zero':
442 // Convert to dash only when number is zero.
443 if ( $is_zero ) {
444 $value = $value_parts[0] . $dot . '-';
445 }
446 break;
447
448 case 'dash_whole_zero':
449 // Convert to the whole value to dash when number is zero.
450 if ( $is_zero ) {
451 $value = '-';
452 }
453 break;
454 }
455 }
456 }
457
458 return $value;
459 }
460
461 /**
462 * Trim trailing 0 decimals from numbers.
463 *
464 * @since 2.7.15
465 *
466 * @param string $value
467 * @param string $dot
468 *
469 * @return string
470 */
471 public function trim_decimals( $value, $dot ) {
472 $parts = explode( $dot, $value );
473
474 if ( isset( $parts[1] ) ) {
475 $parts[1] = rtrim( $parts[1], '0' );
476
477 if ( empty( $parts[1] ) ) {
478 unset( $parts[1] );
479 }
480 }
481
482 return implode( $dot, $parts );
483 }
484
485 /**
486 * Get the formatting arguments for numbers.
487 *
488 * @since 2.7.0
489 *
490 * @param array $options Field options.
491 *
492 * @return array {
493 * @type string $thousands
494 * @type string $dot
495 * @type int $decimals
496 * }
497 */
498 public function get_number_format_args( $options ) {
499
500 $format = pods_v( static::$type . '_format', $options );
501 $format = pods_unslash( $format );
502
503 switch ( $format ) {
504 case '9.999,99':
505 $thousands = '.';
506 $dot = ',';
507 break;
508 case '9,999.99':
509 $thousands = ',';
510 $dot = '.';
511 break;
512 case '9\'999.99':
513 $thousands = '\'';
514 $dot = '.';
515 break;
516 case '9 999,99':
517 $thousands = ' ';
518 $dot = ',';
519 break;
520 case '9999.99':
521 $thousands = '';
522 $dot = '.';
523 break;
524 case '9999,99':
525 $thousands = '';
526 $dot = ',';
527 break;
528 default:
529 global $wp_locale;
530 $thousands = $wp_locale->number_format['thousands_sep'];
531 $dot = $wp_locale->number_format['decimal_point'];
532 break;
533 }
534
535 $decimals = $this->get_max_decimals( $options );
536
537 return [
538 'thousands' => $thousands,
539 'dot' => $dot,
540 'decimals' => $decimals,
541 ];
542 }
543
544 /**
545 * Get the max allowed decimals.
546 *
547 * @since 2.7.0
548 *
549 * @param array $options Field options.
550 *
551 * @return int
552 */
553 public function get_max_decimals( $options ) {
554
555 $length = (int) pods_v( static::$type . '_max_length', $options, 12, true );
556
557 if ( $length < 1 || 64 < $length ) {
558 $length = 64;
559 }
560
561 $decimals = (int) pods_v( static::$type . '_decimals', $options, 0 );
562
563 if ( $decimals < 1 ) {
564 $decimals = 0;
565 } elseif ( 30 < $decimals ) {
566 $decimals = 30;
567 }
568
569 if ( $length < $decimals ) {
570 $decimals = $length;
571 }
572
573 return $decimals;
574 }
575 }
576