mirror of
https://github.com/processwire/processwire.git
synced 2025-08-14 10:45:54 +02:00
Add a FieldtypeDecimal to the core per request. This is a newly written module that's different from the 3rd party FieldtypeDecimal, so consider it beta. If using the 3rd party FieldtypeDecimal, this one uses the same configuration setting names, so they are intended to be compatible. You may have to delete /site/modules/FieldtypeDecimal or rename it to /site/modules/.FieldtypeDecimal, and then Modules > Refresh, before PW will use the core one.
This commit is contained in:
248
wire/modules/Fieldtype/FieldtypeDecimal.module
Normal file
248
wire/modules/Fieldtype/FieldtypeDecimal.module
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* ProcessWire Decimal Fieldtype
|
||||
*
|
||||
* Field that stores a decimal number.
|
||||
*
|
||||
* For documentation about the fields used in this class, please see:
|
||||
* /wire/core/Fieldtype.php
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
*/
|
||||
|
||||
class FieldtypeDecimal extends Fieldtype {
|
||||
|
||||
public static function getModuleInfo() {
|
||||
return array(
|
||||
'title' => __('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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user