From 2b69123f598ac7893ee7b483ce9ed6372bae5a1a Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 20 Aug 2020 07:38:39 -0400 Subject: [PATCH] Refactoring, minor improvements and phpdoc additions to FieldtypeInteger, FieldtypeFloat, InputfieldInteger, InputfieldFloat. No new features added, just code improvements. --- wire/modules/Fieldtype/FieldtypeFloat.module | 179 ++++++++++------ .../modules/Fieldtype/FieldtypeInteger.module | 202 ++++++++++++------ .../modules/Inputfield/InputfieldFloat.module | 141 ++++++++---- .../Inputfield/InputfieldInteger.module | 180 +++++++++++----- 4 files changed, 480 insertions(+), 222 deletions(-) diff --git a/wire/modules/Fieldtype/FieldtypeFloat.module b/wire/modules/Fieldtype/FieldtypeFloat.module index c650a8d0..fd571b6d 100644 --- a/wire/modules/Fieldtype/FieldtypeFloat.module +++ b/wire/modules/Fieldtype/FieldtypeFloat.module @@ -8,7 +8,7 @@ * For documentation about the fields used in this class, please see: * /wire/core/Fieldtype.php * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2020 by Ryan Cramer * https://processwire.com * */ @@ -19,90 +19,118 @@ class FieldtypeFloat extends Fieldtype { return array( 'title' => __('Float', __FILE__), 'summary' => __('Field that stores a floating point (decimal) number', __FILE__), - 'version' => 105, + 'version' => 106, 'permanent' => true, - ); + ); } + /** + * Get compatible Fieldtypes + * + * @param Field $field + * @return null|Fieldtypes + * + */ public function ___getCompatibleFieldtypes(Field $field) { $fieldtypes = parent::___getCompatibleFieldtypes($field); foreach($fieldtypes as $type) { - if( !$type instanceof FieldtypeInteger && - !$type instanceof FieldtypeFloat && - $type != 'FieldtypeText') { - + if(!$type instanceof FieldtypeInteger && !$type instanceof FieldtypeFloat && $type != 'FieldtypeText') { $fieldtypes->remove($type); } } return $fieldtypes; } + /** + * Get blank value + * + * @param Page $page + * @param Field $field + * @return string + * + */ public function getBlankValue(Page $page, Field $field) { return ''; } + /** + * Is given value considered empty to this Fieldtype? + * + * @param Field $field + * @param mixed $value + * @return bool + * + */ public function isEmptyValue(Field $field, $value) { - if(($value === "0" || $value === 0 || $value === "0.0" || $value === 0.0) && $field->zeroNotEmpty) { + if($value === "0" || $value === 0 || $value === "0.0" || $value === 0.0) { // when zeroNotEmpty option is set, we don't count a literal "0" is being a blank value - return false; + if($field->get('zeroNotEmpty')) return false; } return empty($value); } + /** + * Sanitize value + * + * @param Page $page + * @param Field $field + * @param float|int|string $value + * @return float|string + * + */ public function sanitizeValue(Page $page, Field $field, $value) { if(!strlen("$value")) return ''; if(!is_float($value) && !is_int($value)) { - $value = $this->wire('sanitizer')->float((string) $value, array('blankValue' => '')); + $value = $this->wire()->sanitizer->float((string) $value, array('blankValue' => '')); } - if(is_null($field->precision)) { + $precision = $field->get('precision'); + if($precision === null || $precision === '') { $value = (float) $value; } else { - $value = round((float) $value, $field->precision); + $value = round((float) $value, $precision); } return $value; } - /* - public function formatValue(Page $page, Field $field, $value) { - // @todo add support for number_format options - return $value; - } - */ - + /** + * Get Inputfield for this Fieldtype + * + * @param Page $page + * @param Field $field + * @return Inputfield + * + */ public function getInputfield(Page $page, Field $field) { - $inputfield = $this->modules->get('InputfieldFloat'); - $inputfield->class = $this->className(); - $inputfield->precision = $field->precision; + /** @var InputfieldFloat $inputfield */ + $inputfield = $this->wire()->modules->get('InputfieldFloat'); + $inputfield->addClass($this->className()); + $inputfield->precision = $field->get('precision'); return $inputfield; } + /** + * Sleep value for DB storage + * + * @param Page $page + * @param Field $field + * @param string|float|int + * @return string + * + */ public function ___sleepValue(Page $page, Field $field, $value) { - $precision = $field->precision; - if(is_null($precision)) $precision = self::getPrecision($value); + $precision = $field->get('precision'); + if(is_null($precision) || $precision === '') $precision = self::getPrecision($value); if(!is_string($value)) $value = number_format($value, $precision, '.', ''); - - /* - - // handle commas vs. dots issue with other locales - $info = localeconv(); - - if(!empty($info['mon_thousands_sep'])) { - if(strpos($value, $info["mon_thousands_sep"]) !== false) { - $value = str_replace($info["mon_thousands_sep"] , "", $value); - } - } - - if(!empty($info['mon_decimal_point'])) { - if($info["mon_decimal_point"] !== '.' && strpos($value, $info["mon_decimal_point"]) !== false) { - $value = str_replace($info["mon_decimal_point"] , ".", $value); - } - } - - $value = floatval($value); - */ return $value; } - + + /** + * Get precision of given value + * + * @param string|float $value + * @return int + * + */ public static function getPrecision($value) { $value = (float) $value; $remainder = ceil($value) - $value; @@ -111,31 +139,67 @@ class FieldtypeFloat extends Fieldtype { return $precision; } + /** + * Get DB schema for this Fieldtype + * + * @param Field $field + * @return array + * + */ public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'float NOT NULL'; return $schema; } + /** + * Get field configuration + * + * @param Field $field + * @return InputfieldWrapper + * + */ public function ___getConfigInputfields(Field $field) { $inputfields = parent::___getConfigInputfields($field); - - if(is_null($field->precision)) $field->precision = 2; + $precision = $field->get('precision'); + if($precision === null) $precision = 2; /** @var InputfieldInteger $f */ - $f = $this->modules->get('InputfieldInteger'); + $f = $this->wire()->modules->get('InputfieldInteger'); $f->attr('name', 'precision'); $f->label = $this->_('Number of decimal digits to round to'); - $f->attr('value', $field->precision); + if($precision !== '') $f->val($precision); $f->attr('size', 8); $inputfields->append($f); - - $f = $this->wire('modules')->get('FieldtypeInteger')->___getConfigInputfields($field)->getChildByName('zeroNotEmpty'); + + /** @var FieldtypeInteger $ft */ + $ft = $this->wire()->modules->get('FieldtypeInteger'); + // use the same 'zeroNotEmpty' setting as FieldtypeInteger + $f = $ft->___getConfigInputfields($field)->getChildByName('zeroNotEmpty'); if($f) $inputfields->add($f); return $inputfields; } + /** + * Convert float string with commas to float value + * + * @param string $str + * @return float|string + * @deprecated Use $sanitizer->float($value, [ 'blankValue' = '' ]) instead + * + */ + public static function strToFloat($str) { + return wire('sanitizer')->float($str, array('blankValue' => '')); + } + + /* + public function formatValue(Page $page, Field $field, $value) { + // @todo add support for number_format options + return $value; + } + */ + /* public function getMatchQuery($query, $table, $subfield, $operator, $value) { if(!is_int($value) && !is_float($value)) { @@ -145,19 +209,6 @@ class FieldtypeFloat extends Fieldtype { } */ - /** - * Convert float string with commas to float value - * - * Most based on: http://php.net/manual/en/function.floatval.php#114486 - * - * @param string $str - * @return float|string - * @deprecated - * - */ - public static function strToFloat($str) { - return wire('sanitizer')->float($str, array('blankValue' => '')); - } } diff --git a/wire/modules/Fieldtype/FieldtypeInteger.module b/wire/modules/Fieldtype/FieldtypeInteger.module index b00a59ac..8a17b1dd 100644 --- a/wire/modules/Fieldtype/FieldtypeInteger.module +++ b/wire/modules/Fieldtype/FieldtypeInteger.module @@ -8,7 +8,7 @@ * For documentation about the fields used in this class, please see: * /wire/core/Fieldtype.php * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2020 by Ryan Cramer * https://processwire.com * * @todo allow for more integer types (tiny, small, medium, big) and unsigned option @@ -20,25 +20,37 @@ class FieldtypeInteger extends Fieldtype { public static function getModuleInfo() { return array( 'title' => 'Integer', - 'version' => 101, + 'version' => 102, 'summary' => 'Field that stores an integer', 'permanent' => true, - ); + ); } + /** + * Get compatible Fieldtypes + * + * @param Field $field + * @return Fieldtypes + * + */ public function ___getCompatibleFieldtypes(Field $field) { $fieldtypes = parent::___getCompatibleFieldtypes($field); foreach($fieldtypes as $type) { - if( !$type instanceof FieldtypeInteger && - !$type instanceof FieldtypeFloat && - $type != 'FieldtypeText') { - + if(!$type instanceof FieldtypeInteger && !$type instanceof FieldtypeFloat && $type != 'FieldtypeText') { $fieldtypes->remove($type); } } return $fieldtypes; } - + + /** + * Is given value considered empty by this Fieldtype? + * + * @param Field $field + * @param mixed $value + * @return bool + * + */ public function isEmptyValue(Field $field, $value) { if(($value === "0" || $value === 0) && $field->get('zeroNotEmpty')) { // when zeroNotEmpty option is set, we don't count a literal "0" is being a blank value @@ -46,90 +58,154 @@ class FieldtypeInteger extends Fieldtype { } return empty($value); } - + + /** + * Is value one that should be deleted rather than stored in the DB? + * + * @param Page $page + * @param Field $field + * @param mixed $value + * @return bool + * + */ public function isDeleteValue(Page $page, Field $field, $value) { return $this->isEmptyValue($field, $value); } + /** + * Get the blank value for this Fieldtype + * + * @param Page $page + * @param Field $field + * @return string + * + */ public function getBlankValue(Page $page, Field $field) { return ''; } - + + /** + * Sanitize value to integer or blank string (for no value) + * + * @param Page $page + * @param Field $field + * @param int|string|mixed $value + * @return int|string + * + */ public function sanitizeValue(Page $page, Field $field, $value) { if(is_string($value) && strlen($value) && !ctype_digit(ltrim($value, '-'))) { - // string value with one or more non-digit characters - $value = trim($value); - // trim off common currency symbols - $value = trim($value, '$€ '); - - if(ctype_digit("$value")) { - // trimming reduced it to an int - - } else if(preg_match('/^(\de\d|0x\d+|\+\d+)/', $value)) { - // likely a valid number, but in a non-native format to PW - // examples: 1e123213, 0x1234, +123 (intval handles these) - $value = intval($value); - - } else if(preg_match('/^[^-+\d.]+/', $value)) { - // string starting with something we don't recognize, let PHP decide - // example: bd#79 - $value = intval($value); - if($value === 0) $value = ''; // blank rather than zero - - } else { - // string value that looks like a number but has some other stuff in it - - // see if there are some definitely non-number chars in there, and truncate - // the string to that point if we find any - if(preg_match('/^(-?[\d,. ]+)([^\d,. ]+)/', $value, $matches)) { - $value = $matches[1]; - } - - // check to see if we're dealing with a potential float val or thousands separators - if(strpos($value, '.') !== false || strpos($value, ',') !== false || strpos($value, ' ') !== false) { - // convert float values to rounded integers - // also handles values with thousands separators - $value = round(FieldtypeFloat::strToFloat($value)); - - } else if(is_numeric($value)) { - // let PHP decide how to convert it - $value = intval($value); - - } else { - // default: replace non numeric characters - $negative = substr(trim($value), 0, 1) == '-'; - $value = preg_replace('/[^\d]/', '', $value); - $value = strlen($value) ? (int) $value : ''; - if($negative && is_int($value)) $value = $value * -1; - } - } + $value = $this->sanitizeValueString($value); + } else { + $value = strlen("$value") ? (int) $value : ''; } - - $value = strlen("$value") ? (int) $value : ''; return $value; } + /** + * Sanitize string to integer or blank string if empty + * + * @param string $value + * @return int|string + * + */ + protected function sanitizeValueString($value) { + + // string value with one or more non-digit characters + $value = trim($value); + + // trim off common currency symbols + $value = trim($value, '$€ '); + + if(ctype_digit("$value")) { + // trimming reduced it to an int + + } else if(preg_match('/^(\de\d|0x\d+|\+\d+)/', $value)) { + // likely a valid number, but in a non-native format to PW + // examples: 1e123213, 0x1234, +123 (intval handles these) + $value = intval($value); + + } else if(preg_match('/^[^-+\d.]+/', $value)) { + // string starting with something we don't recognize, let PHP decide + // example: bd#79 + $value = intval($value); + if($value === 0) $value = ''; // blank rather than zero + + } else { + // string value that looks like a number but has some other stuff in it + + // see if there are some definitely non-number chars in there, and truncate + // the string to that point if we find any + if(preg_match('/^(-?[\d,. ]+)([^\d,. ]+)/', $value, $matches)) { + $value = $matches[1]; + } + + // check to see if we're dealing with a potential float val or thousands separators + if(strpos($value, '.') !== false || strpos($value, ',') !== false || strpos($value, ' ') !== false) { + // convert float values to rounded integers + // also handles values with thousands separators + $value = $this->wire()->sanitizer->float($value, array('blankValue' => '')); + if($value !== '') $value = round($value); + + } else if(is_numeric($value)) { + // let PHP decide how to convert it + $value = intval($value); + + } else { + // default: replace non numeric characters + $negative = substr(trim($value), 0, 1) == '-'; + $value = preg_replace('/[^\d]/', '', $value); + $value = strlen($value) ? (int) $value : ''; + if($negative && is_int($value)) $value = $value * -1; + } + } + + return strlen("$value") ? (int) $value : ''; + } + + /** + * Get Inputfield for this Fieldtype + * + * @param Page $page + * @param Field $field + * @return Inputfield|InputfieldInteger + * + */ public function getInputfield(Page $page, Field $field) { /** @var InputfieldInteger $inputfield */ - $inputfield = $this->modules->get('InputfieldInteger'); - $inputfield->class = $this->className(); + $inputfield = $this->wire()->modules->get('InputfieldInteger'); + $inputfield->addClass($this->className()); return $inputfield; } + /** + * Get DB schema + * + * @param Field $field + * @return array + * + */ public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'int NOT NULL'; return $schema; } - + + /** + * Get Inputfields to configure integer field + * + * @param Field $field + * @return InputfieldWrapper + * + */ public function ___getConfigInputfields(Field $field) { $inputfields = parent::___getConfigInputfields($field); /** @var InputfieldRadios $f */ - $f = $this->wire('modules')->get('InputfieldRadios'); + $f = $this->wire()->modules->get('InputfieldRadios'); $f->label = $this->_('Are blank and 0 equivalent?'); $f->description = $this->_('This affects how ProcessWire matches pages during database find operations.') . ' ' . $this->_('If 0 and blank are equivalent (the Yes option) then a search for **field=0** or **field=""** will produce the same results.') . ' ' . @@ -142,7 +218,7 @@ class FieldtypeInteger extends Fieldtype { $inputfields->add($f); /** @var InputfieldInteger $f */ - $f = $this->wire('modules')->get('InputfieldInteger'); + $f = $this->wire()->modules->get('InputfieldInteger'); $f->attr('name', 'defaultValue'); $f->label = $this->_('Default value'); $f->description = $this->_('This value is assigned as the default for this field on newly created pages. It does not affect existing pages.'); diff --git a/wire/modules/Inputfield/InputfieldFloat.module b/wire/modules/Inputfield/InputfieldFloat.module index 4723b8f6..0302bd4b 100644 --- a/wire/modules/Inputfield/InputfieldFloat.module +++ b/wire/modules/Inputfield/InputfieldFloat.module @@ -2,8 +2,19 @@ /** * Inputfield for floating point numbers + * + * ProcessWire 3.x, Copyright 2020 by Ryan Cramer + * https://processwire.com * * @property int $precision + * @property string $inputType Input type to use, one of "text" or "number" + * @property int|float $min + * @property int|float $max + * @property int|float|string $step + * @property int $size + * @property string $placeholder + * @property int|float $initValue Initial/default value (when used as independent Inputfield) + * @property int|float|string $defaultValue Initial/default value (when used with FieldtypeInteger) * */ @@ -13,76 +24,130 @@ class InputfieldFloat extends InputfieldInteger { return array( 'title' => __('Float', __FILE__), // Module Title 'summary' => __('Floating point number with precision', __FILE__), // Module Summary - 'version' => 103, + 'version' => 104, 'permanent' => true, - ); + ); } + /** + * Construct + * + */ public function __construct() { $this->set('precision', 2); parent::__construct(); } + /** + * Module init + * + */ public function init() { parent::init(); $this->attr('step', 'any'); // HTML5 attr required to support decimals with 'number' types } - protected function sanitizeValue($value) { - if(!is_float($value) && !is_int($value)) { - $value = FieldtypeFloat::strToFloat((string) $value); - } - $precision = $this->precision; - if(is_null($precision)) $precision = FieldtypeFloat::getPrecision($value); - return strlen("$value") ? round((float) $value, $precision) : ''; + /** + * Get configured precision setting, or if given a value, precision of the value + * + * @param float|string|null $value + * @return int|string Returns integer of precision or blank string if none defined + * + */ + protected function getPrecision($value = null) { + if($value !== null) return FieldtypeFloat::getPrecision($value); + $precision = $this->precision; + return $precision === null || $precision === '' ? '' : (int) $precision; } /** - * Returns true if number is in valid range, false if not - * - * Overriding the function from InputfieldInteger to ensure float types (rather than int types) are used + * Sanitize value * - * @param float $value - * @return bool + * @param float|string $value + * @return float|string + * + */ + protected function sanitizeValue($value) { + if(!strlen("$value")) return ''; + if(!is_float($value) && !is_int($value)) { + $value = $this->wire()->sanitizer->float($value, array('blankValue' => '')); + if(!strlen("$value")) return ''; + } + $precision = $this->precision; + if($precision === null || $precision === '') { + $precision = FieldtypeFloat::getPrecision($value); + } + return round((float) $value, $precision); + } + + /** + * Typecast value to float, override from InputfieldInteger + * + * @param string|int|float $value + * @return int * */ - protected function isInRange($value) { - $inRange = true; - $min = $this->attr('min'); - $max = $this->attr('max'); - if(strlen("$value")) { - if(strlen("$min") && ((float) $value) < ((float) $min)) { - $inRange = false; - } - if(strlen("$max") && ((float) $value) > ((float) $max)) { - $inRange = false; - } - } - return $inRange; + protected function typeValue($value) { + return (float) $value; } /** - * Override method from Inputfield to properly handle HTML5 number values + * Override method from Inputfield to convert local specific decimals for input[type=number] * * @param array $attributes * @return string * */ public function getAttributesString(array $attributes = null) { - if($attributes && $attributes['type'] === 'number' && is_float($attributes['value'])) { - if(strlen("$attributes[value]") && !ctype_digit(str_replace('.', '', $attributes['value']))) { + if($attributes && $attributes['type'] === 'number') { + $value = isset($attributes['value']) ? $attributes['value'] : null; + if(is_float($value) && strlen("$value") && !ctype_digit(str_replace('.', '', $value))) { + // float value is using a non "." as decimal point, needs conversion because // the HTML5 number input type requires "." as the decimal - $locale = localeconv(); - $decimal = $locale['decimal_point']; - if($decimal !== '.' && strpos("$attributes[value]", $decimal) !== false) { - $parts = explode($decimal, $attributes['value'], 2); - if(count($parts) == 2) { - $attributes['value'] = implode('.', $parts); - } - } + $attributes['value'] = $this->localeConvertValue($value); } } return parent::getAttributesString($attributes); } + /** + * Convert floats with non "." decimal points to use "." decimal point according to locale + * + * @param float|string $value + * @return string|float Returns string representation of float when value was converted + * + */ + protected function localeConvertValue($value) { + if(!strlen("$value")) return $value; + if(ctype_digit(str_replace('.', '', $value))) return $value; + $locale = localeconv(); + $decimal = $locale['decimal_point']; + if($decimal === '.' || strpos($value, $decimal) === false) return $value; + $parts = explode($decimal, $value, 2); + $value = implode('.', $parts); + return $value; + } + + /** + * Inputfield config + * + * @return InputfieldWrapper + * + */ + public function getConfigInputfields() { + $inputfields = parent::getConfigInputfields(); + if($this->hasFieldtype === false) { + /** @var InputfieldInteger $f */ + $f = $this->wire()->modules->get('InputfieldInteger'); + $f->attr('name', 'precision'); + $f->label = $this->_('Number of decimal digits to round to'); + $f->attr('value', $this->precision); + $f->attr('size', 8); + $inputfields->add($f); + } else { + // precision is configured with FieldtypeFloat + } + return $inputfields; + } + } diff --git a/wire/modules/Inputfield/InputfieldInteger.module b/wire/modules/Inputfield/InputfieldInteger.module index 7656c2ae..375225b2 100644 --- a/wire/modules/Inputfield/InputfieldInteger.module +++ b/wire/modules/Inputfield/InputfieldInteger.module @@ -1,13 +1,19 @@ __('Integer', __FILE__), // Module Title 'summary' => __('Integer (positive or negative)', __FILE__), // Module Summary - 'version' => 104, + 'version' => 105, 'permanent' => true, - ); + ); } + /** + * Module init + * + */ public function init() { parent::init(); - $this->set('inputType', 'text'); + $this->attr('type', 'text'); - $this->attr('min', ''); // blank means not set - $this->attr('max', ''); // blank means not set + $this->attr('min', ''); + $this->attr('max', ''); + $this->attr('step', ''); $this->attr('size', '10'); + $this->attr('placeholder', ''); + + $this->set('inputType', 'text'); $this->set('initValue', ''); $this->set('defaultValue', ''); } + /** + * Render Inputfield + * + * @return string + * + */ public function ___render() { + if(!$this->attr('type')) $this->attr('type', 'text'); + $attrs = $this->getAttributes(); - $note = ''; if(empty($attrs['size'])) { $attrs['class'] = (isset($attrs['class']) ? "$attrs[class] " : "") . 'InputfieldMaxWidth'; unset($attrs['size']); } - if(!strlen("$attrs[min]") && !strlen("$attrs[max]")) { - // if both min+max are empty, then consider them non-applicable - unset($attrs['min'], $attrs['max']); + if($attrs['type'] === 'text') { + // input[type=text] unset attributes not applicable + unset($attrs['step'], $attrs['min'], $attrs['max']); } else { - // unset any that aren't applicable - if(strlen("$attrs[min]")) $note .= sprintf($this->_('Min: %d'), $attrs['min']); - else unset($attrs['min']); - if(strlen("$attrs[max]")) $note .= ($note ? ', ' : '') . sprintf($this->_('Max: %d'), $attrs['max']); - else unset($attrs['max']); + // input[type=number] unset any that aren't applicable + foreach(array('min', 'max', 'step') as $name) { + if(!strlen((string) $attrs[$name])) unset($attrs[$name]); + } } - // these attributes not valid for 'text' type - if($attrs['type'] == 'text') unset($attrs['step'], $attrs['min'], $attrs['max']); - - if($note) $note = " $note"; - if(!strlen($attrs['value'])) { if(strlen($this->initValue)) { $attrs['value'] = (int) $this->initValue; // Inputfield-only version @@ -67,76 +82,125 @@ class InputfieldInteger extends Inputfield { } } - $out = "getAttributesString($attrs) . " />"; // . $note; + $out = "getAttributesString($attrs) . " />"; return $out; } + /** + * Sanitize value + * + * @param string|int|float $value + * @return int + * + */ protected function sanitizeValue($value) { + if(is_int($value)) return $value; $value = trim($value); if(!strlen("$value")) return ''; $negative = substr($value, 0, 1) === '-'; - if($negative) $value = substr($value, 1); - if(!ctype_digit("$value")) $value = preg_replace('/[^\d,.]/', '', $value); // remove non digits, like commas, etc. + if($negative) $value = substr($value, 1); + // remove non digits, except commas and periods + if(!ctype_digit("$value")) $value = preg_replace('/[^\d,.]/', '', $value); if(!strlen("$value")) return ''; - if(strpos($value, '.') !== false || strpos($value, ',') !== false) $value = round($value); + if(strpos($value, '.') !== false || strpos($value, ',') !== false) $value = round((float) $value); $value = (int) $value; if($negative) $value = -1 * $value; return $value; } + /** + * Typecast value to integer + * + * @param string|int|float $value + * @return int + * + */ + protected function typeValue($value) { + return (int) $value; + } + + /** + * Is current value considered empty? + * + * @return bool + * + */ public function isEmpty() { - return strlen("{$this->value}") === 0; + $value = $this->val(); + return strlen("$value") === 0; } + /** + * Is current value in specified min/max range? + * + * @param int $value + * @return bool + * + */ protected function isInRange($value) { - $inRange = true; - $min = $this->attr('min'); - $max = $this->attr('max'); - if(strlen("$value")) { - if(strlen("$min") && ((int) $value) < ((int) $min)) { - $inRange = false; - } - if(strlen("$max") && ((int) $value) > ((int) $max)) { - $inRange = false; - } - } - return $inRange; + if(!strlen("$value")) return true; // no value present yet + list($min, $max) = array($this->attr('min'), $this->attr('max')); + if(strlen("$min") && ($this->typeValue($value)) < ($this->typeValue($min))) return false; + if(strlen("$max") && ($this->typeValue($value)) > ($this->typeValue($max))) return false; + return true; } + /** + * Set attribute + * + * @param array|string $key + * @param array|bool|int|string $value + * @return Inputfield + * + */ public function setAttribute($key, $value) { - if($key == 'value') { + if($key === 'value') { $value = $this->sanitizeValue($value); if(strlen("$value") && !$this->isInRange($value)) { $min = $this->attr('min'); $max = $this->attr('max'); - $any = $this->_('any'); + $any = $this->_('any'); // referring to “any” minimum or maximum number if(!strlen("$min")) $min = $any; if(!strlen("$max")) $max = $any; $this->error(sprintf($this->_('Specified value %3$s removed because it is out of bounds (min=%1$s, max=%2$s)'), $min, $max, $value)); - $value = $this->attr('value'); // restore previous value + $value = $this->val(); // restore previous value + } + } else if($key === 'min' || $key === 'max') { + if(strlen("$value")) { + $value = strpos("$value", '.') !== false ? (float) $value : (int) $value; } } return parent::setAttribute($key, $value); } - + + /** + * Set setting or attribute + * + * @param string $key + * @param mixed $value + * @return Inputfield|WireData + * + */ public function set($key, $value) { - if($key == 'inputType') { - $this->attr('type', $value); - } else if($key == 'min' || $key == 'max') { - if(strlen("$value")) { - $value = strpos($value, '.') !== false ? (float) $value : (int) $value; - } - } + if($key === 'inputType') $this->attr('type', $value); return parent::set($key, $value); } + /** + * Inputfield configuration + * + * @return InputfieldWrapper + * + */ public function getConfigInputfields() { $inputfields = parent::getConfigInputfields(); - - $f = $this->wire('modules')->get('InputfieldRadios'); + $modules = $this->wire()->modules; + + /** @var InputfieldRadios $f */ + $f = $modules->get('InputfieldRadios'); $f->attr('name', 'inputType'); $f->label = $this->_('Numeric Input Type'); $f->addOption('text', $this->_('Text')); @@ -145,8 +209,9 @@ class InputfieldInteger extends Inputfield { $f->description = $this->_('Choosing the "Number" type enables some additional client-side validation in browsers that support it.'); $f->columnWidth = 50; $inputfields->add($f); - - $f = $this->wire('modules')->get('InputfieldInteger'); + + /** @var InputfieldInteger $f */ + $f = $modules->get('InputfieldInteger'); $f->attr('name', 'size'); $f->label = $this->_('Input Size'); $f->description = $this->_('Specify the size attribute for the input, or specify 0 for full width.'); @@ -157,7 +222,8 @@ class InputfieldInteger extends Inputfield { $f->attr('type', 'number'); $inputfields->add($f); - $f = $this->wire('modules')->get('InputfieldText'); + /** @var InputfieldText $f */ + $f = $modules->get('InputfieldText'); $f->attr('name', 'min'); $f->attr('value', $this->attr('min')); $f->label = $this->_('Minimum Value'); @@ -165,7 +231,7 @@ class InputfieldInteger extends Inputfield { $f->columnWidth = 50; $inputfields->add($f); - $f = $this->wire('modules')->get('InputfieldText'); + $f = $modules->get('InputfieldText'); $f->attr('name', 'max'); $f->attr('value', $this->attr('max')); $f->label = $this->_('Maximum Value'); @@ -174,7 +240,7 @@ class InputfieldInteger extends Inputfield { $inputfields->add($f); if($this->hasFieldtype === false) { - $f = $this->wire('modules')->get('InputfieldText'); + $f = $modules->get('InputfieldText'); $f->attr('name', 'initValue'); $f->attr('value', $this->initValue); $f->label = $this->_('Initial value');