diff --git a/wire/modules/Fieldtype/FieldtypeDecimal.module b/wire/modules/Fieldtype/FieldtypeDecimal.module new file mode 100644 index 00000000..d1512d62 --- /dev/null +++ b/wire/modules/Fieldtype/FieldtypeDecimal.module @@ -0,0 +1,248 @@ + __('Decimal', __FILE__), + 'summary' => __('Field that stores a decimal number', __FILE__), + 'version' => 1, + ); + } + + /** + * 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) { + // when zeroNotEmpty option is set, we don't count a literal "0" is being a blank value + if($field->get('zeroNotEmpty') && strlen("$value") && !strlen(trim($value, "0.,"))) return false; + return empty($value); + } + + /** + * Sanitize value + * + * @param Page $page + * @param Field $field + * @param float|int|string $value + * @return string + * + */ + public function sanitizeValue(Page $page, Field $field, $value) { + + list(/*$digits*/, $precision) = $this->getDigitsAndPrecision($field); + + if(!strlen("$value")) return ''; + + $value = $this->wire()->sanitizer->float($value, array( + 'precision' => null, + 'blankValue' => '', + 'getString' => true, + )); + + if(!strlen($value)) return ''; + + if(strpos($value, '.') !== false) { + list($a, $b) = explode('.', $value, 2); + if(!strlen($a)) $a = '0'; + while(strlen($b) < $precision) $b .= '0'; + $value = "$a.$b"; + if(strlen($b) > $precision) { + $value = (string) round((float) $value, $precision); + } + } + + return $value; + } + + /** + * Get Inputfield for this Fieldtype + * + * @param Page $page + * @param Field $field + * @return Inputfield + * + */ + public function getInputfield(Page $page, Field $field) { + /** @var InputfieldFloat $inputfield */ + $inputfield = $this->wire()->modules->get('InputfieldFloat'); + $precision = (int) $field->get('precision'); + if($field->get('inputType') === 'number' && $precision > 0) { + $inputfield->attr('step', '.' . ($precision > 1 ? str_repeat('0', $precision - 1) : '') . '1'); + } + 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) { + $value = (string) $value; + if(!strlen($value)) return $value; + if(!ctype_digit(str_replace('.', '', $value))) $value = $this->sanitizeValue($page, $field, $value); + return $value; + } + + /** + * 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 FieldtypeDecimal) continue; + if($type instanceof FieldtypeInteger || $type instanceof FieldtypeFloat) continue; + $fieldtypes->remove($type); + } + return $fieldtypes; + } + + /** + * Get DB schema for this Fieldtype + * + * @param Field $field + * @return array + * + */ + public function getDatabaseSchema(Field $field) { + $schema = parent::getDatabaseSchema($field); + list($digits, $precision) = $this->getDigitsAndPrecision($field); + $schema['data'] = "DECIMAL($digits,$precision)"; + return $schema; + } + + /** + * Called when Field using this Fieldtype has been saved + * + * Check and update database schema as necessary + * + * @param Field $field + * + */ + public function ___savedField(Field $field) { + $database = $this->wire()->database; + $info = $database->columnExists($field->getTable(), 'data', true); + if(!$info) return; + $table = $field->getTable(); + $schema = $this->getDatabaseSchema($field); + $colType = str_replace(' ', '', strtoupper($info['Type'])); + $defType = str_replace(' ', '', strtoupper($schema['data'])); + if($colType !== $defType) { + $database->exec("ALTER TABLE `$table` MODIFY `data` $defType"); + $this->message(sprintf($this->_('Updated field ā€œ%1$sā€ schema: %2$s => %3$s'), $field->name, $colType, $defType)); + } + } + + /** + * Get digits and precision + * + * @param Field $field + * @return array of [ digits, precision ] + * + */ + protected function getDigitsAndPrecision(Field $field) { + + $digits = $field->get('digits'); + if($digits === null || $digits < 1) $digits = 10; + $digits = (int) $digits; + + $precision = $field->get('precision'); + if($precision === null || $precision < 0) $precision = 2; + $precision = (int) $precision; + + if($digits < $precision) $digits = $precision; + if($digits > 65) $digits = 65; + + return array($digits, $precision); + } + + /** + * Get field configuration + * + * @param Field $field + * @return InputfieldWrapper + * + */ + public function ___getConfigInputfields(Field $field) { + + $inputfields = parent::___getConfigInputfields($field); + list($digits, $precision) = $this->getDigitsAndPrecision($field); + + $example = $this->_('For example:') . ' '; + + /** @var InputfieldInteger $f */ + $f = $this->wire()->modules->get('InputfieldInteger'); + $f->attr('name', 'digits'); + $f->label = $this->_('Total number of supported digits'); + $f->description = $this->_('Includes digits both before and after the decimal point.'); + $f->val($digits); + $f->inputType = 'number'; + $f->columnWidth = 50; + $f->min = 1; + $f->max = 65; + $f->detail = $example . $this->_('999.99 is 5 digits total so would require a value of 5 or higher.'); + $inputfields->add($f); + + /** @var InputfieldInteger $f */ + $f = $this->wire()->modules->get('InputfieldInteger'); + $f->attr('name', 'precision'); + $f->label = $this->_('Number of digits present after decimal'); + $f->description = $this->_('This value will be less than (or in some cases equal to) the total number of digits.'); + $f->detail = $example . $this->_('999.99 and 100.00 have 2 digits after the decimal'); + $f->val((int) $precision); + $f->inputType = 'number'; + $f->columnWidth = 50; + $f->min = 0; + $f->max = 65; + $inputfields->add($f); + + /** @var FieldtypeInteger $ft */ + $ft = $this->wire()->fieldtypes->FieldtypeInteger; + + // use the same 'zeroNotEmpty' setting as FieldtypeInteger + $f = $ft->___getConfigInputfields($field)->getChildByName('zeroNotEmpty'); + if($f) $inputfields->add($f); + + return $inputfields; + } +} +