Completed
Pull Request — 2.x (#4569)
by Scott Kingsley
16:10 queued 06:09
created

PodsField_Number::validate()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
eloc 16
nc 2
nop 7
1
<?php
2
3
/**
4
 * @package Pods\Fields
5
 */
6
class PodsField_Number extends PodsField {
7
8
	/**
9
	 * {@inheritdoc}
10
	 */
11
	public static $group = 'Number';
12
13
	/**
14
	 * {@inheritdoc}
15
	 */
16
	public static $type = 'number';
17
18
	/**
19
	 * {@inheritdoc}
20
	 */
21
	public static $label = 'Plain Number';
22
23
	/**
24
	 * {@inheritdoc}
25
	 */
26
	public static $prepare = '%d';
27
28
	/**
29
	 * {@inheritdoc}
30
	 */
31
	public function setup() {
32
33
		self::$label = __( 'Plain Number', 'pods' );
34
	}
35
36
	/**
37
	 * {@inheritdoc}
38
	 */
39
	public function options() {
40
41
		$options = array(
42
			static::$type . '_repeatable'  => array(
43
				'label'             => __( 'Repeatable Field', 'pods' ),
44
				'default'           => 0,
45
				'type'              => 'boolean',
46
				'help'              => __( 'Making a field repeatable will add controls next to the field which allows users to Add/Remove/Reorder additional values. These values are saved in the database as an array, so searching and filtering by them may require further adjustments".', 'pods' ),
47
				'boolean_yes_label' => '',
48
				'dependency'        => true,
49
				'developer_mode'    => true,
50
			),
51
			static::$type . '_format_type' => array(
52
				'label'      => __( 'Input Type', 'pods' ),
53
				'default'    => 'number',
54
				'type'       => 'pick',
55
				'data'       => array(
56
					'number' => __( 'Freeform Number', 'pods' ),
57
					'slider' => __( 'Slider', 'pods' ),
58
				),
59
				'dependency' => true,
60
			),
61
			static::$type . '_format'      => array(
62
				'label'   => __( 'Format', 'pods' ),
63
				'default' => apply_filters( 'pods_form_ui_field_number_format_default', 'i18n' ),
64
				'type'    => 'pick',
65
				'data'    => array(
66
					'i18n'     => __( 'Localized Default', 'pods' ),
67
					'9,999.99' => '1,234.00',
68
					'9.999,99' => '1.234,00',
69
					'9 999,99' => '1 234,00',
70
					'9999.99'  => '1234.00',
71
					'9999,99'  => '1234,00',
72
				),
73
			),
74
			static::$type . '_decimals'    => array(
75
				'label'      => __( 'Decimals', 'pods' ),
76
				'default'    => 0,
77
				'type'       => 'number',
78
				'dependency' => true,
79
			),
80
			static::$type . '_format_soft' => array(
81
				'label'       => __( 'Soft format?', 'pods' ),
82
				'help'        => __( 'Remove trailing decimals (0)', 'pods' ),
83
				'default'     => 0,
84
				'type'        => 'boolean',
85
				'excludes-on' => array( static::$type . '_decimals' => 0 ),
86
			),
87
			static::$type . '_step'        => array(
88
				'label'      => __( 'Slider Increment (Step)', 'pods' ),
89
				'depends-on' => array( static::$type . '_format_type' => 'slider' ),
90
				'default'    => 1,
91
				'type'       => 'text',
92
			),
93
			static::$type . '_min'         => array(
94
				'label'      => __( 'Minimum Number', 'pods' ),
95
				'depends-on' => array( static::$type . '_format_type' => 'slider' ),
96
				'default'    => 0,
97
				'type'       => 'text',
98
			),
99
			static::$type . '_max'         => array(
100
				'label'      => __( 'Maximum Number', 'pods' ),
101
				'depends-on' => array( static::$type . '_format_type' => 'slider' ),
102
				'default'    => 100,
103
				'type'       => 'text',
104
			),
105
			static::$type . '_max_length'  => array(
106
				'label'   => __( 'Maximum Length', 'pods' ),
107
				'default' => 12,
108
				'type'    => 'number',
109
				'help'    => __( 'Set to -1 for no limit', 'pods' ),
110
			),
111
			static::$type . '_placeholder' => array(
112
				'label'   => __( 'HTML Placeholder', 'pods' ),
113
				'default' => '',
114
				'type'    => 'text',
115
				'help'    => array(
116
					__( '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' ),
117
					'https://www.w3.org/WAI/tutorials/forms/instructions/#placeholder-text',
118
				),
119
			),
120
		);
121
122
		return $options;
123
	}
124
125
	/**
126
	 * {@inheritdoc}
127
	 */
128
	public function schema( $options = null ) {
129
130
		$length = (int) pods_v( static::$type . '_max_length', $options, 12, true );
131
132
		if ( $length < 1 || 64 < $length ) {
133
			$length = 64;
134
		}
135
136
		$decimals = $this->get_max_decimals( $options );
137
138
		$schema = 'DECIMAL(' . $length . ',' . $decimals . ')';
139
140
		return $schema;
141
142
	}
143
144
	/**
145
	 * {@inheritdoc}
146
	 */
147
	public function prepare( $options = null ) {
148
149
		$format = static::$prepare;
150
151
		$decimals = $this->get_max_decimals( $options );
152
153
		if ( 0 < $decimals ) {
154
			$format = '%F';
155
		}
156
157
		return $format;
158
159
	}
160
161
	/**
162
	 * {@inheritdoc}
163
	 */
164
	public function is_empty( $value = null ) {
165
166
		$is_empty = false;
167
168
		$value += 0;
169
170
		if ( empty( $value ) ) {
171
			$is_empty = true;
172
		}
173
174
		return $is_empty;
175
176
	}
177
178
	/**
179
	 * {@inheritdoc}
180
	 */
181
	public function display( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
182
183
		$value = $this->format( $value, $name, $options, $pod, $id );
184
185
		return $value;
186
	}
187
188
	/**
189
	 * {@inheritdoc}
190
	 */
191
	public function input( $name, $value = null, $options = null, $pod = null, $id = null ) {
192
193
		$options         = (array) $options;
194
		$form_field_type = PodsForm::$field_type;
195
196
		if ( is_array( $value ) ) {
197
			$value = implode( '', $value );
198
		}
199
200
		if ( 'slider' === pods_v( static::$type . '_format_type', $options, 'number' ) ) {
201
			$field_type = 'slider';
202
		} else {
203
			$field_type = static::$type;
204
		}
205
206
		if ( isset( $options['name'] ) && false === PodsForm::permission( static::$type, $options['name'], $options, null, $pod, $id ) ) {
207
			if ( pods_v( 'read_only', $options, false ) ) {
208
				$options['readonly'] = true;
209
210
				$field_type = 'text';
211
212
				$value = $this->format( $value, $name, $options, $pod, $id );
213
			} else {
214
				return;
215
			}
216
		} elseif ( ! pods_has_permissions( $options ) && pods_v( 'read_only', $options, false ) ) {
217
			$options['readonly'] = true;
218
219
			$field_type = 'text';
220
221
			$value = $this->format( $value, $name, $options, $pod, $id );
222
		}
223
224
		pods_view( PODS_DIR . 'ui/fields/' . $field_type . '.php', compact( array_keys( get_defined_vars() ) ) );
225
226
	}
227
228
	/**
229
	 * {@inheritdoc}
230
	 */
231
	public function regex( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
232
233
		$format_args = $this->get_number_format_args( $options );
234
		$thousands   = $format_args['thousands'];
235
		$dot         = $format_args['dot'];
236
237
		return '\-*[0-9\\' . implode( '\\', array_filter( array( $dot, $thousands ) ) ) . ']+';
238
	}
239
240
	/**
241
	 * {@inheritdoc}
242
	 */
243
	public function validate( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
244
245
		$format_args = $this->get_number_format_args( $options );
246
		$thousands   = $format_args['thousands'];
247
		$dot         = $format_args['dot'];
248
249
		$check = str_replace(
250
			array( $thousands, $dot, html_entity_decode( $thousands ) ), array(
251
				'',
252
				'.',
253
				'',
254
			), $value
255
		);
256
		$check = trim( $check );
257
258
		$check = preg_replace( '/[0-9\.\-\s]/', '', $check );
259
260
		$label = pods_v( 'label', $options, ucwords( str_replace( '_', ' ', $name ) ) );
261
262
		if ( 0 < strlen( $check ) ) {
263
			return sprintf( __( '%s is not numeric', 'pods' ), $label );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return sprintf(__('%s is...ric', 'pods'), $label); (string) is incompatible with the return type of the parent method PodsField::validate of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
264
		}
265
266
		return true;
267
	}
268
269
	/**
270
	 * {@inheritdoc}
271
	 */
272
	public function pre_save( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
273
274
		$format_args = $this->get_number_format_args( $options );
275
		$thousands   = $format_args['thousands'];
276
		$dot         = $format_args['dot'];
277
		$decimals    = $format_args['decimals'];
278
279
		$value = str_replace( array( $thousands, $dot ), array( '', '.' ), $value );
280
		$value = trim( $value );
281
282
		$value = preg_replace( '/[^0-9\.\-]/', '', $value );
283
284
		$value = number_format( (float) $value, $decimals, '.', '' );
285
286
		return $value;
287
	}
288
289
	/**
290
	 * {@inheritdoc}
291
	 */
292
	public function format( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
293
294
		if ( null === $value ) {
295
			// Don't enforce a default value here.
296
			return null;
297
		}
298
299
		$format_args = $this->get_number_format_args( $options );
300
		$thousands   = $format_args['thousands'];
301
		$dot         = $format_args['dot'];
302
		$decimals    = $format_args['decimals'];
303
304
		if ( 'i18n' === pods_v( static::$type . '_format', $options ) ) {
305
			$value = number_format_i18n( (float) $value, $decimals );
306
		} else {
307
			$value = number_format( (float) $value, $decimals, $dot, $thousands );
308
		}
309
310
		// Optionally remove trailing decimal zero's.
311
		if ( pods_v( static::$type . '_format_soft', $options, 0 ) ) {
312
			$parts = explode( $dot, $value );
313
			if ( isset( $parts[1] ) ) {
314
				$parts[1] = rtrim( $parts[1], '0' );
315
				$parts    = array_filter( $parts );
316
			}
317
			$value = implode( $dot, $parts );
318
		}
319
320
		return $value;
321
	}
322
323
	/**
324
	 * Get the formatting arguments for numbers.
325
	 *
326
	 * @since 2.7
327
	 *
328
	 * @param array $options Field options.
329
	 *
330
	 * @return array {
331
	 * @type string $thousands
332
	 * @type string $dot
333
	 * @type int    $decimals
334
	 * }
335
	 */
336
	public function get_number_format_args( $options ) {
337
338
		global $wp_locale;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
339
340
		if ( '9.999,99' === pods_v( static::$type . '_format', $options ) ) {
341
			$thousands = '.';
342
			$dot       = ',';
343
		} elseif ( '9,999.99' === pods_v( static::$type . '_format', $options ) ) {
344
			$thousands = ',';
345
			$dot       = '.';
346
		} elseif ( '9\'999.99' === pods_v( static::$type . '_format', $options ) ) {
347
			$thousands = '\'';
348
			$dot       = '.';
349
		} elseif ( '9 999,99' === pods_v( static::$type . '_format', $options ) ) {
350
			$thousands = ' ';
351
			$dot       = ',';
352
		} elseif ( '9999.99' === pods_v( static::$type . '_format', $options ) ) {
353
			$thousands = '';
354
			$dot       = '.';
355
		} elseif ( '9999,99' === pods_v( static::$type . '_format', $options ) ) {
356
			$thousands = '';
357
			$dot       = ',';
358
		} else {
359
			$thousands = $wp_locale->number_format['thousands_sep'];
360
			$dot       = $wp_locale->number_format['decimal_point'];
361
		}//end if
362
363
		$decimals = $this->get_max_decimals( $options );
364
365
		return array(
366
			'thousands' => $thousands,
367
			'dot'       => $dot,
368
			'decimals'  => $decimals,
369
		);
370
	}
371
372
	/**
373
	 * Get the max allowed decimals.
374
	 *
375
	 * @since 2.7
376
	 *
377
	 * @param array $options Field options.
378
	 *
379
	 * @return int
380
	 */
381
	public function get_max_decimals( $options ) {
382
383
		$length = (int) pods_v( static::$type . '_max_length', $options, 12, true );
384
385
		if ( $length < 1 || 64 < $length ) {
386
			$length = 64;
387
		}
388
389
		$decimals = (int) pods_v( static::$type . '_decimals', $options, 0 );
390
391
		if ( $decimals < 1 ) {
392
			$decimals = 0;
393
		} elseif ( 30 < $decimals ) {
394
			$decimals = 30;
395
		}
396
397
		if ( $length < $decimals ) {
398
			$decimals = $length;
399
		}
400
401
		return $decimals;
402
	}
403
}
404