Subversion Repositories web.active

Rev

Blame | Last modification | View Log | Download

<?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');
    $inputfield->set('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) {
    $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;
  }
}