1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-16 11:44:42 +02:00

Refactoring, minor improvements and phpdoc additions to FieldtypeInteger, FieldtypeFloat, InputfieldInteger, InputfieldFloat. No new features added, just code improvements.

This commit is contained in:
Ryan Cramer
2020-08-20 07:38:39 -04:00
parent 0b9f07f3c4
commit 2b69123f59
4 changed files with 480 additions and 222 deletions

View File

@@ -8,7 +8,7 @@
* For documentation about the fields used in this class, please see: * For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php * /wire/core/Fieldtype.php
* *
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer * ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
*/ */
@@ -19,90 +19,118 @@ class FieldtypeFloat extends Fieldtype {
return array( return array(
'title' => __('Float', __FILE__), 'title' => __('Float', __FILE__),
'summary' => __('Field that stores a floating point (decimal) number', __FILE__), 'summary' => __('Field that stores a floating point (decimal) number', __FILE__),
'version' => 105, 'version' => 106,
'permanent' => true, 'permanent' => true,
); );
} }
/**
* Get compatible Fieldtypes
*
* @param Field $field
* @return null|Fieldtypes
*
*/
public function ___getCompatibleFieldtypes(Field $field) { public function ___getCompatibleFieldtypes(Field $field) {
$fieldtypes = parent::___getCompatibleFieldtypes($field); $fieldtypes = parent::___getCompatibleFieldtypes($field);
foreach($fieldtypes as $type) { foreach($fieldtypes as $type) {
if( !$type instanceof FieldtypeInteger && if(!$type instanceof FieldtypeInteger && !$type instanceof FieldtypeFloat && $type != 'FieldtypeText') {
!$type instanceof FieldtypeFloat &&
$type != 'FieldtypeText') {
$fieldtypes->remove($type); $fieldtypes->remove($type);
} }
} }
return $fieldtypes; return $fieldtypes;
} }
/**
* Get blank value
*
* @param Page $page
* @param Field $field
* @return string
*
*/
public function getBlankValue(Page $page, Field $field) { public function getBlankValue(Page $page, Field $field) {
return ''; return '';
} }
/**
* Is given value considered empty to this Fieldtype?
*
* @param Field $field
* @param mixed $value
* @return bool
*
*/
public function isEmptyValue(Field $field, $value) { 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 // 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); 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) { public function sanitizeValue(Page $page, Field $field, $value) {
if(!strlen("$value")) return ''; if(!strlen("$value")) return '';
if(!is_float($value) && !is_int($value)) { 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; $value = (float) $value;
} else { } else {
$value = round((float) $value, $field->precision); $value = round((float) $value, $precision);
} }
return $value; return $value;
} }
/* /**
public function formatValue(Page $page, Field $field, $value) { * Get Inputfield for this Fieldtype
// @todo add support for number_format options *
return $value; * @param Page $page
} * @param Field $field
*/ * @return Inputfield
*
*/
public function getInputfield(Page $page, Field $field) { public function getInputfield(Page $page, Field $field) {
$inputfield = $this->modules->get('InputfieldFloat'); /** @var InputfieldFloat $inputfield */
$inputfield->class = $this->className(); $inputfield = $this->wire()->modules->get('InputfieldFloat');
$inputfield->precision = $field->precision; $inputfield->addClass($this->className());
$inputfield->precision = $field->get('precision');
return $inputfield; 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) { public function ___sleepValue(Page $page, Field $field, $value) {
$precision = $field->precision; $precision = $field->get('precision');
if(is_null($precision)) $precision = self::getPrecision($value); if(is_null($precision) || $precision === '') $precision = self::getPrecision($value);
if(!is_string($value)) $value = number_format($value, $precision, '.', ''); 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; return $value;
} }
/**
* Get precision of given value
*
* @param string|float $value
* @return int
*
*/
public static function getPrecision($value) { public static function getPrecision($value) {
$value = (float) $value; $value = (float) $value;
$remainder = ceil($value) - $value; $remainder = ceil($value) - $value;
@@ -111,31 +139,67 @@ class FieldtypeFloat extends Fieldtype {
return $precision; return $precision;
} }
/**
* Get DB schema for this Fieldtype
*
* @param Field $field
* @return array
*
*/
public function getDatabaseSchema(Field $field) { public function getDatabaseSchema(Field $field) {
$schema = parent::getDatabaseSchema($field); $schema = parent::getDatabaseSchema($field);
$schema['data'] = 'float NOT NULL'; $schema['data'] = 'float NOT NULL';
return $schema; return $schema;
} }
/**
* Get field configuration
*
* @param Field $field
* @return InputfieldWrapper
*
*/
public function ___getConfigInputfields(Field $field) { public function ___getConfigInputfields(Field $field) {
$inputfields = parent::___getConfigInputfields($field); $inputfields = parent::___getConfigInputfields($field);
$precision = $field->get('precision');
if(is_null($field->precision)) $field->precision = 2; if($precision === null) $precision = 2;
/** @var InputfieldInteger $f */ /** @var InputfieldInteger $f */
$f = $this->modules->get('InputfieldInteger'); $f = $this->wire()->modules->get('InputfieldInteger');
$f->attr('name', 'precision'); $f->attr('name', 'precision');
$f->label = $this->_('Number of decimal digits to round to'); $f->label = $this->_('Number of decimal digits to round to');
$f->attr('value', $field->precision); if($precision !== '') $f->val($precision);
$f->attr('size', 8); $f->attr('size', 8);
$inputfields->append($f); $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); if($f) $inputfields->add($f);
return $inputfields; 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) { public function getMatchQuery($query, $table, $subfield, $operator, $value) {
if(!is_int($value) && !is_float($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' => ''));
}
} }

View File

@@ -8,7 +8,7 @@
* For documentation about the fields used in this class, please see: * For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php * /wire/core/Fieldtype.php
* *
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer * ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
* @todo allow for more integer types (tiny, small, medium, big) and unsigned option * @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() { public static function getModuleInfo() {
return array( return array(
'title' => 'Integer', 'title' => 'Integer',
'version' => 101, 'version' => 102,
'summary' => 'Field that stores an integer', 'summary' => 'Field that stores an integer',
'permanent' => true, 'permanent' => true,
); );
} }
/**
* Get compatible Fieldtypes
*
* @param Field $field
* @return Fieldtypes
*
*/
public function ___getCompatibleFieldtypes(Field $field) { public function ___getCompatibleFieldtypes(Field $field) {
$fieldtypes = parent::___getCompatibleFieldtypes($field); $fieldtypes = parent::___getCompatibleFieldtypes($field);
foreach($fieldtypes as $type) { foreach($fieldtypes as $type) {
if( !$type instanceof FieldtypeInteger && if(!$type instanceof FieldtypeInteger && !$type instanceof FieldtypeFloat && $type != 'FieldtypeText') {
!$type instanceof FieldtypeFloat &&
$type != 'FieldtypeText') {
$fieldtypes->remove($type); $fieldtypes->remove($type);
} }
} }
return $fieldtypes; return $fieldtypes;
} }
/**
* Is given value considered empty by this Fieldtype?
*
* @param Field $field
* @param mixed $value
* @return bool
*
*/
public function isEmptyValue(Field $field, $value) { public function isEmptyValue(Field $field, $value) {
if(($value === "0" || $value === 0) && $field->get('zeroNotEmpty')) { 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 // when zeroNotEmpty option is set, we don't count a literal "0" is being a blank value
@@ -47,89 +59,153 @@ class FieldtypeInteger extends Fieldtype {
return empty($value); 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) { public function isDeleteValue(Page $page, Field $field, $value) {
return $this->isEmptyValue($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) { public function getBlankValue(Page $page, Field $field) {
return ''; 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) { public function sanitizeValue(Page $page, Field $field, $value) {
if(is_string($value) && strlen($value) && !ctype_digit(ltrim($value, '-'))) { if(is_string($value) && strlen($value) && !ctype_digit(ltrim($value, '-'))) {
// string value with one or more non-digit characters $value = $this->sanitizeValueString($value);
$value = trim($value); } else {
// trim off common currency symbols $value = strlen("$value") ? (int) $value : '';
$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 = strlen("$value") ? (int) $value : '';
return $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) { public function getInputfield(Page $page, Field $field) {
/** @var InputfieldInteger $inputfield */ /** @var InputfieldInteger $inputfield */
$inputfield = $this->modules->get('InputfieldInteger'); $inputfield = $this->wire()->modules->get('InputfieldInteger');
$inputfield->class = $this->className(); $inputfield->addClass($this->className());
return $inputfield; return $inputfield;
} }
/**
* Get DB schema
*
* @param Field $field
* @return array
*
*/
public function getDatabaseSchema(Field $field) { public function getDatabaseSchema(Field $field) {
$schema = parent::getDatabaseSchema($field); $schema = parent::getDatabaseSchema($field);
$schema['data'] = 'int NOT NULL'; $schema['data'] = 'int NOT NULL';
return $schema; return $schema;
} }
/**
* Get Inputfields to configure integer field
*
* @param Field $field
* @return InputfieldWrapper
*
*/
public function ___getConfigInputfields(Field $field) { public function ___getConfigInputfields(Field $field) {
$inputfields = parent::___getConfigInputfields($field); $inputfields = parent::___getConfigInputfields($field);
/** @var InputfieldRadios $f */ /** @var InputfieldRadios $f */
$f = $this->wire('modules')->get('InputfieldRadios'); $f = $this->wire()->modules->get('InputfieldRadios');
$f->label = $this->_('Are blank and 0 equivalent?'); $f->label = $this->_('Are blank and 0 equivalent?');
$f->description = $this->_('This affects how ProcessWire matches pages during database find operations.') . ' ' . $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.') . ' ' . $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); $inputfields->add($f);
/** @var InputfieldInteger $f */ /** @var InputfieldInteger $f */
$f = $this->wire('modules')->get('InputfieldInteger'); $f = $this->wire()->modules->get('InputfieldInteger');
$f->attr('name', 'defaultValue'); $f->attr('name', 'defaultValue');
$f->label = $this->_('Default value'); $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.'); $f->description = $this->_('This value is assigned as the default for this field on newly created pages. It does not affect existing pages.');

View File

@@ -3,7 +3,18 @@
/** /**
* Inputfield for floating point numbers * Inputfield for floating point numbers
* *
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @property int $precision * @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( return array(
'title' => __('Float', __FILE__), // Module Title 'title' => __('Float', __FILE__), // Module Title
'summary' => __('Floating point number with precision', __FILE__), // Module Summary 'summary' => __('Floating point number with precision', __FILE__), // Module Summary
'version' => 103, 'version' => 104,
'permanent' => true, 'permanent' => true,
); );
} }
/**
* Construct
*
*/
public function __construct() { public function __construct() {
$this->set('precision', 2); $this->set('precision', 2);
parent::__construct(); parent::__construct();
} }
/**
* Module init
*
*/
public function init() { public function init() {
parent::init(); parent::init();
$this->attr('step', 'any'); // HTML5 attr required to support decimals with 'number' types $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) : '';
}
/** /**
* Returns true if number is in valid range, false if not * Get configured precision setting, or if given a value, precision of the value
* *
* Overriding the function from InputfieldInteger to ensure float types (rather than int types) are used * @param float|string|null $value
* * @return int|string Returns integer of precision or blank string if none defined
* @param float $value
* @return bool
* *
*/ */
protected function isInRange($value) { protected function getPrecision($value = null) {
$inRange = true; if($value !== null) return FieldtypeFloat::getPrecision($value);
$min = $this->attr('min'); $precision = $this->precision;
$max = $this->attr('max'); return $precision === null || $precision === '' ? '' : (int) $precision;
if(strlen("$value")) {
if(strlen("$min") && ((float) $value) < ((float) $min)) {
$inRange = false;
}
if(strlen("$max") && ((float) $value) > ((float) $max)) {
$inRange = false;
}
}
return $inRange;
} }
/** /**
* Override method from Inputfield to properly handle HTML5 number values * Sanitize value
*
* @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 typeValue($value) {
return (float) $value;
}
/**
* Override method from Inputfield to convert local specific decimals for input[type=number]
* *
* @param array $attributes * @param array $attributes
* @return string * @return string
* *
*/ */
public function getAttributesString(array $attributes = null) { public function getAttributesString(array $attributes = null) {
if($attributes && $attributes['type'] === 'number' && is_float($attributes['value'])) { if($attributes && $attributes['type'] === 'number') {
if(strlen("$attributes[value]") && !ctype_digit(str_replace('.', '', $attributes['value']))) { $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 // the HTML5 number input type requires "." as the decimal
$locale = localeconv(); $attributes['value'] = $this->localeConvertValue($value);
$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);
}
}
} }
} }
return parent::getAttributesString($attributes); 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;
}
} }

View File

@@ -1,13 +1,19 @@
<?php namespace ProcessWire; <?php namespace ProcessWire;
/** /**
* Class InputfieldInteger * Integer Inputfield
* *
* @property string $inputType * ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* @property int|float $initValue * https://processwire.com
*
* @property string $inputType Input type to use, one of "text" or "number"
* @property int|float $min * @property int|float $min
* @property int|float $max * @property int|float $max
* @property int|float|string $step
* @property int $size * @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)
* *
*/ */
@@ -17,48 +23,57 @@ class InputfieldInteger extends Inputfield {
return array( return array(
'title' => __('Integer', __FILE__), // Module Title 'title' => __('Integer', __FILE__), // Module Title
'summary' => __('Integer (positive or negative)', __FILE__), // Module Summary 'summary' => __('Integer (positive or negative)', __FILE__), // Module Summary
'version' => 104, 'version' => 105,
'permanent' => true, 'permanent' => true,
); );
} }
/**
* Module init
*
*/
public function init() { public function init() {
parent::init(); parent::init();
$this->set('inputType', 'text');
$this->attr('type', 'text'); $this->attr('type', 'text');
$this->attr('min', ''); // blank means not set $this->attr('min', '');
$this->attr('max', ''); // blank means not set $this->attr('max', '');
$this->attr('step', '');
$this->attr('size', '10'); $this->attr('size', '10');
$this->attr('placeholder', '');
$this->set('inputType', 'text');
$this->set('initValue', ''); $this->set('initValue', '');
$this->set('defaultValue', ''); $this->set('defaultValue', '');
} }
/**
* Render Inputfield
*
* @return string
*
*/
public function ___render() { public function ___render() {
if(!$this->attr('type')) $this->attr('type', 'text'); if(!$this->attr('type')) $this->attr('type', 'text');
$attrs = $this->getAttributes(); $attrs = $this->getAttributes();
$note = '';
if(empty($attrs['size'])) { if(empty($attrs['size'])) {
$attrs['class'] = (isset($attrs['class']) ? "$attrs[class] " : "") . 'InputfieldMaxWidth'; $attrs['class'] = (isset($attrs['class']) ? "$attrs[class] " : "") . 'InputfieldMaxWidth';
unset($attrs['size']); unset($attrs['size']);
} }
if(!strlen("$attrs[min]") && !strlen("$attrs[max]")) { if($attrs['type'] === 'text') {
// if both min+max are empty, then consider them non-applicable // input[type=text] unset attributes not applicable
unset($attrs['min'], $attrs['max']); unset($attrs['step'], $attrs['min'], $attrs['max']);
} else { } else {
// unset any that aren't applicable // input[type=number] unset any that aren't applicable
if(strlen("$attrs[min]")) $note .= sprintf($this->_('Min: %d'), $attrs['min']); foreach(array('min', 'max', 'step') as $name) {
else unset($attrs['min']); if(!strlen((string) $attrs[$name])) unset($attrs[$name]);
if(strlen("$attrs[max]")) $note .= ($note ? ', ' : '') . sprintf($this->_('Max: %d'), $attrs['max']); }
else unset($attrs['max']);
} }
// these attributes not valid for 'text' type
if($attrs['type'] == 'text') unset($attrs['step'], $attrs['min'], $attrs['max']);
if($note) $note = " <span class='details'>$note</span>";
if(!strlen($attrs['value'])) { if(!strlen($attrs['value'])) {
if(strlen($this->initValue)) { if(strlen($this->initValue)) {
$attrs['value'] = (int) $this->initValue; // Inputfield-only version $attrs['value'] = (int) $this->initValue; // Inputfield-only version
@@ -67,76 +82,125 @@ class InputfieldInteger extends Inputfield {
} }
} }
$out = "<input " . $this->getAttributesString($attrs) . " />"; // . $note; $out = "<input " . $this->getAttributesString($attrs) . " />";
return $out; return $out;
} }
/**
* Sanitize value
*
* @param string|int|float $value
* @return int
*
*/
protected function sanitizeValue($value) { protected function sanitizeValue($value) {
if(is_int($value)) return $value;
$value = trim($value); $value = trim($value);
if(!strlen("$value")) return ''; if(!strlen("$value")) return '';
$negative = substr($value, 0, 1) === '-'; $negative = substr($value, 0, 1) === '-';
if($negative) $value = substr($value, 1); if($negative) $value = substr($value, 1);
if(!ctype_digit("$value")) $value = preg_replace('/[^\d,.]/', '', $value); // remove non digits, like commas, etc. // remove non digits, except commas and periods
if(!ctype_digit("$value")) $value = preg_replace('/[^\d,.]/', '', $value);
if(!strlen("$value")) return ''; 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; $value = (int) $value;
if($negative) $value = -1 * $value; if($negative) $value = -1 * $value;
return $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() { 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) { protected function isInRange($value) {
$inRange = true; if(!strlen("$value")) return true; // no value present yet
$min = $this->attr('min'); list($min, $max) = array($this->attr('min'), $this->attr('max'));
$max = $this->attr('max'); if(strlen("$min") && ($this->typeValue($value)) < ($this->typeValue($min))) return false;
if(strlen("$value")) { if(strlen("$max") && ($this->typeValue($value)) > ($this->typeValue($max))) return false;
if(strlen("$min") && ((int) $value) < ((int) $min)) { return true;
$inRange = false;
}
if(strlen("$max") && ((int) $value) > ((int) $max)) {
$inRange = false;
}
}
return $inRange;
} }
/**
* Set attribute
*
* @param array|string $key
* @param array|bool|int|string $value
* @return Inputfield
*
*/
public function setAttribute($key, $value) { public function setAttribute($key, $value) {
if($key == 'value') { if($key === 'value') {
$value = $this->sanitizeValue($value); $value = $this->sanitizeValue($value);
if(strlen("$value") && !$this->isInRange($value)) { if(strlen("$value") && !$this->isInRange($value)) {
$min = $this->attr('min'); $min = $this->attr('min');
$max = $this->attr('max'); $max = $this->attr('max');
$any = $this->_('any'); $any = $this->_('any'); // referring to “any” minimum or maximum number
if(!strlen("$min")) $min = $any; if(!strlen("$min")) $min = $any;
if(!strlen("$max")) $max = $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)); $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); return parent::setAttribute($key, $value);
} }
/**
* Set setting or attribute
*
* @param string $key
* @param mixed $value
* @return Inputfield|WireData
*
*/
public function set($key, $value) { public function set($key, $value) {
if($key == 'inputType') { if($key === 'inputType') $this->attr('type', $value);
$this->attr('type', $value);
} else if($key == 'min' || $key == 'max') {
if(strlen("$value")) {
$value = strpos($value, '.') !== false ? (float) $value : (int) $value;
}
}
return parent::set($key, $value); return parent::set($key, $value);
} }
/**
* Inputfield configuration
*
* @return InputfieldWrapper
*
*/
public function getConfigInputfields() { public function getConfigInputfields() {
$inputfields = parent::getConfigInputfields(); $inputfields = parent::getConfigInputfields();
$modules = $this->wire()->modules;
$f = $this->wire('modules')->get('InputfieldRadios'); /** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'inputType'); $f->attr('name', 'inputType');
$f->label = $this->_('Numeric Input Type'); $f->label = $this->_('Numeric Input Type');
$f->addOption('text', $this->_('Text')); $f->addOption('text', $this->_('Text'));
@@ -146,7 +210,8 @@ class InputfieldInteger extends Inputfield {
$f->columnWidth = 50; $f->columnWidth = 50;
$inputfields->add($f); $inputfields->add($f);
$f = $this->wire('modules')->get('InputfieldInteger'); /** @var InputfieldInteger $f */
$f = $modules->get('InputfieldInteger');
$f->attr('name', 'size'); $f->attr('name', 'size');
$f->label = $this->_('Input Size'); $f->label = $this->_('Input Size');
$f->description = $this->_('Specify the size attribute for the input, or specify 0 for full width.'); $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'); $f->attr('type', 'number');
$inputfields->add($f); $inputfields->add($f);
$f = $this->wire('modules')->get('InputfieldText'); /** @var InputfieldText $f */
$f = $modules->get('InputfieldText');
$f->attr('name', 'min'); $f->attr('name', 'min');
$f->attr('value', $this->attr('min')); $f->attr('value', $this->attr('min'));
$f->label = $this->_('Minimum Value'); $f->label = $this->_('Minimum Value');
@@ -165,7 +231,7 @@ class InputfieldInteger extends Inputfield {
$f->columnWidth = 50; $f->columnWidth = 50;
$inputfields->add($f); $inputfields->add($f);
$f = $this->wire('modules')->get('InputfieldText'); $f = $modules->get('InputfieldText');
$f->attr('name', 'max'); $f->attr('name', 'max');
$f->attr('value', $this->attr('max')); $f->attr('value', $this->attr('max'));
$f->label = $this->_('Maximum Value'); $f->label = $this->_('Maximum Value');
@@ -174,7 +240,7 @@ class InputfieldInteger extends Inputfield {
$inputfields->add($f); $inputfields->add($f);
if($this->hasFieldtype === false) { if($this->hasFieldtype === false) {
$f = $this->wire('modules')->get('InputfieldText'); $f = $modules->get('InputfieldText');
$f->attr('name', 'initValue'); $f->attr('name', 'initValue');
$f->attr('value', $this->initValue); $f->attr('value', $this->initValue);
$f->label = $this->_('Initial value'); $f->label = $this->_('Initial value');