1<?php 2 3namespace dokuwiki\plugin\struct\types; 4 5use dokuwiki\plugin\struct\meta\QueryBuilder; 6use dokuwiki\plugin\struct\meta\QueryBuilderWhere; 7use dokuwiki\plugin\struct\meta\ValidationException; 8 9/** 10 * Class Decimal 11 * 12 * A field accepting decimal numbers 13 * 14 * @package dokuwiki\plugin\struct\types 15 */ 16class Decimal extends AbstractMultiBaseType 17{ 18 19 protected $config = array( 20 'min' => '', 21 'max' => '', 22 'roundto' => '-1', 23 'decpoint' => '.', 24 'thousands' => "\xE2\x80\xAF", // narrow no-break space 25 'trimzeros' => true, 26 'prefix' => '', 27 'postfix' => '' 28 ); 29 30 /** 31 * Output the stored data 32 * 33 * @param string|int $value the value stored in the database 34 * @param \Doku_Renderer $R the renderer currently used to render the data 35 * @param string $mode The mode the output is rendered in (eg. XHTML) 36 * @return bool true if $mode could be satisfied 37 */ 38 public function renderValue($value, \Doku_Renderer $R, $mode) 39 { 40 if ($this->config['roundto'] == -1) { 41 $value = $this->formatWithoutRounding( 42 $value, 43 $this->config['decpoint'], 44 $this->config['thousands'] 45 ); 46 } else { 47 $value = floatval($value); 48 $value = number_format( 49 $value, 50 $this->config['roundto'], 51 $this->config['decpoint'], 52 $this->config['thousands'] 53 ); 54 } 55 if ($this->config['trimzeros'] && (strpos($value, $this->config['decpoint']) !== false)) { 56 $value = rtrim($value, '0'); 57 $value = rtrim($value, $this->config['decpoint']); 58 } 59 60 $R->cdata($this->config['prefix'] . $value . $this->config['postfix']); 61 return true; 62 } 63 64 /** 65 * @param int|string $rawvalue 66 * @return int|string 67 * @throws ValidationException 68 */ 69 public function validate($rawvalue) 70 { 71 $rawvalue = parent::validate($rawvalue); 72 $rawvalue = str_replace(',', '.', $rawvalue); // we accept both 73 74 if ((string) $rawvalue != (string) floatval($rawvalue)) { 75 throw new ValidationException('Decimal needed'); 76 } 77 78 if ($this->config['min'] !== '' && floatval($rawvalue) < floatval($this->config['min'])) { 79 throw new ValidationException('Decimal min', floatval($this->config['min'])); 80 } 81 82 if ($this->config['max'] !== '' && floatval($rawvalue) > floatval($this->config['max'])) { 83 throw new ValidationException('Decimal max', floatval($this->config['max'])); 84 } 85 86 return $rawvalue; 87 } 88 89 /** 90 * Works like number_format but keeps the decimals as is 91 * 92 * @link http://php.net/manual/en/function.number-format.php#91047 93 * @author info at daniel-marschall dot de 94 * @param float $number 95 * @param string $dec_point 96 * @param string $thousands_sep 97 * @return string 98 */ 99 protected function formatWithoutRounding($number, $dec_point, $thousands_sep) 100 { 101 $was_neg = $number < 0; // Because +0 == -0 102 103 $tmp = explode('.', $number); 104 $out = number_format(abs(floatval($tmp[0])), 0, $dec_point, $thousands_sep); 105 if (isset($tmp[1])) $out .= $dec_point . $tmp[1]; 106 107 if ($was_neg) $out = "-$out"; 108 109 return $out; 110 } 111 112 /** 113 * Decimals need to be casted to the proper type for sorting 114 * 115 * @param QueryBuilder $QB 116 * @param string $tablealias 117 * @param string $colname 118 * @param string $order 119 */ 120 public function sort(QueryBuilder $QB, $tablealias, $colname, $order) 121 { 122 $QB->addOrderBy("CAST($tablealias.$colname AS DECIMAL) $order"); 123 } 124 125 /** 126 * Decimals need to be casted to proper type for comparison 127 * 128 * @param QueryBuilderWhere $add 129 * @param string $tablealias 130 * @param string $colname 131 * @param string $comp 132 * @param string|\string[] $value 133 * @param string $op 134 */ 135 public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) 136 { 137 $add = $add->where($op); // open a subgroup 138 $add->where('AND', "$tablealias.$colname != ''"); // make sure the field isn't empty 139 $op = 'AND'; 140 141 /** @var QueryBuilderWhere $add Where additionional queries are added to*/ 142 if (is_array($value)) { 143 $add = $add->where($op); // sub where group 144 $op = 'OR'; 145 } 146 147 foreach ((array) $value as $item) { 148 $pl = $add->getQB()->addValue($item); 149 $add->where($op, "CAST($tablealias.$colname AS DECIMAL) $comp CAST($pl AS DECIMAL)"); 150 } 151 } 152 153 /** 154 * Only exact matches for numbers 155 * 156 * @return string 157 */ 158 public function getDefaultComparator() 159 { 160 return '='; 161 } 162} 163