1399c73d3SAndreas Gohr<?php 2d6d97f60SAnna Dabrowska 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\types; 4399c73d3SAndreas Gohr 58fd5dfa8SAndreas Gohruse dokuwiki\plugin\struct\meta\QueryBuilder; 68fd5dfa8SAndreas Gohruse dokuwiki\plugin\struct\meta\QueryBuilderWhere; 7ba766201SAndreas Gohruse dokuwiki\plugin\struct\meta\ValidationException; 8399c73d3SAndreas Gohr 9399c73d3SAndreas Gohr/** 10399c73d3SAndreas Gohr * Class Decimal 11399c73d3SAndreas Gohr * 12399c73d3SAndreas Gohr * A field accepting decimal numbers 13399c73d3SAndreas Gohr * 14ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\types 15399c73d3SAndreas Gohr */ 16d6d97f60SAnna Dabrowskaclass Decimal extends AbstractMultiBaseType 17d6d97f60SAnna Dabrowska{ 187234bfb1Ssplitbrain protected $config = [ 19399c73d3SAndreas Gohr 'min' => '', 20399c73d3SAndreas Gohr 'max' => '', 21965c6ba8SAndreas Gohr 'roundto' => '-1', 22965c6ba8SAndreas Gohr 'decpoint' => '.', 237fe2cdf2SAndreas Gohr 'thousands' => "\xE2\x80\xAF", // narrow no-break space 24524b102eSAndreas Gohr 'trimzeros' => true, 25524b102eSAndreas Gohr 'prefix' => '', 2651dec368SPhilipp Cochems 'postfix' => '', 277234bfb1Ssplitbrain 'engineering' => false, 287234bfb1Ssplitbrain ]; 29399c73d3SAndreas Gohr 30399c73d3SAndreas Gohr /** 31399c73d3SAndreas Gohr * Output the stored data 32399c73d3SAndreas Gohr * 33399c73d3SAndreas Gohr * @param string|int $value the value stored in the database 34399c73d3SAndreas Gohr * @param \Doku_Renderer $R the renderer currently used to render the data 35399c73d3SAndreas Gohr * @param string $mode The mode the output is rendered in (eg. XHTML) 36399c73d3SAndreas Gohr * @return bool true if $mode could be satisfied 37399c73d3SAndreas Gohr */ 38d6d97f60SAnna Dabrowska public function renderValue($value, \Doku_Renderer $R, $mode) 39d6d97f60SAnna Dabrowska { 4051dec368SPhilipp Cochems 4151dec368SPhilipp Cochems if ($this->config['engineering']) { 427234bfb1Ssplitbrain $unitsh = ['', 'k', 'M', 'G', 'T']; 437234bfb1Ssplitbrain $unitsl = ['', 'm', 'µ', 'n', 'p', 'f', 'a']; 4451dec368SPhilipp Cochems 4551dec368SPhilipp Cochems $exp = floor(log10($value) / 3); 4651dec368SPhilipp Cochems 4751dec368SPhilipp Cochems if ($exp < 0) { 4851dec368SPhilipp Cochems $units = $unitsl; 49521d4bebSPhilipp Cochems $pfkey = -1 * $exp; 5051dec368SPhilipp Cochems } else { 5151dec368SPhilipp Cochems $units = $unitsh; 52521d4bebSPhilipp Cochems $pfkey = $exp; 5351dec368SPhilipp Cochems } 5451dec368SPhilipp Cochems 55df519224SPhilipp Cochems if (count($units) <= ($pfkey + 1)) { //check if number is within prefixes 567234bfb1Ssplitbrain $pfkey = count($units) - 1; 57521d4bebSPhilipp Cochems $exp = $pfkey * $exp / abs($exp); 58521d4bebSPhilipp Cochems } 5951dec368SPhilipp Cochems 60460e822fSAndreas Gohr $R->cdata( 61460e822fSAndreas Gohr $this->config['prefix'] . 62460e822fSAndreas Gohr $value / 10 ** ($exp * 3) . "\xE2\x80\xAF" . $units[$pfkey] . 63460e822fSAndreas Gohr $this->config['postfix'] 64460e822fSAndreas Gohr ); 6551dec368SPhilipp Cochems return true; 6651dec368SPhilipp Cochems } 6751dec368SPhilipp Cochems 6851dec368SPhilipp Cochems 69965c6ba8SAndreas Gohr if ($this->config['roundto'] == -1) { 70965c6ba8SAndreas Gohr $value = $this->formatWithoutRounding( 71965c6ba8SAndreas Gohr $value, 72965c6ba8SAndreas Gohr $this->config['decpoint'], 73965c6ba8SAndreas Gohr $this->config['thousands'] 74965c6ba8SAndreas Gohr ); 75965c6ba8SAndreas Gohr } else { 767234bfb1Ssplitbrain $value = (float) $value; 77965c6ba8SAndreas Gohr $value = number_format( 78965c6ba8SAndreas Gohr $value, 79da776bcdSAnna Dabrowska (int)$this->config['roundto'], 80965c6ba8SAndreas Gohr $this->config['decpoint'], 81965c6ba8SAndreas Gohr $this->config['thousands'] 82965c6ba8SAndreas Gohr ); 83965c6ba8SAndreas Gohr } 84*ba662a60SAndreas Gohr if ($this->config['trimzeros'] && (str_contains($value, (string) $this->config['decpoint']))) { 85965c6ba8SAndreas Gohr $value = rtrim($value, '0'); 86965c6ba8SAndreas Gohr $value = rtrim($value, $this->config['decpoint']); 87965c6ba8SAndreas Gohr } 88965c6ba8SAndreas Gohr 8951dec368SPhilipp Cochems 90524b102eSAndreas Gohr $R->cdata($this->config['prefix'] . $value . $this->config['postfix']); 91399c73d3SAndreas Gohr return true; 92399c73d3SAndreas Gohr } 93399c73d3SAndreas Gohr 94399c73d3SAndreas Gohr /** 9523169abeSAndreas Gohr * @param int|string $rawvalue 96399c73d3SAndreas Gohr * @return int|string 97399c73d3SAndreas Gohr * @throws ValidationException 98399c73d3SAndreas Gohr */ 99d6d97f60SAnna Dabrowska public function validate($rawvalue) 100d6d97f60SAnna Dabrowska { 10123169abeSAndreas Gohr $rawvalue = parent::validate($rawvalue); 10223169abeSAndreas Gohr $rawvalue = str_replace(',', '.', $rawvalue); // we accept both 103399c73d3SAndreas Gohr 1047fe2cdf2SAndreas Gohr if ((string)$rawvalue != (string)(float) $rawvalue) { 105399c73d3SAndreas Gohr throw new ValidationException('Decimal needed'); 106399c73d3SAndreas Gohr } 107399c73d3SAndreas Gohr 1087234bfb1Ssplitbrain if ($this->config['min'] !== '' && (float) $rawvalue < (float) $this->config['min']) { 1097234bfb1Ssplitbrain throw new ValidationException('Decimal min', (float) $this->config['min']); 110399c73d3SAndreas Gohr } 111399c73d3SAndreas Gohr 1127234bfb1Ssplitbrain if ($this->config['max'] !== '' && (float) $rawvalue > (float) $this->config['max']) { 1137234bfb1Ssplitbrain throw new ValidationException('Decimal max', (float) $this->config['max']); 114399c73d3SAndreas Gohr } 115399c73d3SAndreas Gohr 11623169abeSAndreas Gohr return $rawvalue; 117399c73d3SAndreas Gohr } 118399c73d3SAndreas Gohr 119965c6ba8SAndreas Gohr /** 120965c6ba8SAndreas Gohr * Works like number_format but keeps the decimals as is 121965c6ba8SAndreas Gohr * 122965c6ba8SAndreas Gohr * @link http://php.net/manual/en/function.number-format.php#91047 123965c6ba8SAndreas Gohr * @author info at daniel-marschall dot de 124965c6ba8SAndreas Gohr * @param float $number 125965c6ba8SAndreas Gohr * @param string $dec_point 126965c6ba8SAndreas Gohr * @param string $thousands_sep 127965c6ba8SAndreas Gohr * @return string 128965c6ba8SAndreas Gohr */ 129748e747fSAnna Dabrowska protected function formatWithoutRounding($number, $dec_point, $thousands_sep) 130d6d97f60SAnna Dabrowska { 131965c6ba8SAndreas Gohr $was_neg = $number < 0; // Because +0 == -0 132965c6ba8SAndreas Gohr 133965c6ba8SAndreas Gohr $tmp = explode('.', $number); 1347234bfb1Ssplitbrain $out = number_format(abs((float) $tmp[0]), 0, $dec_point, $thousands_sep); 135965c6ba8SAndreas Gohr if (isset($tmp[1])) $out .= $dec_point . $tmp[1]; 136965c6ba8SAndreas Gohr 137965c6ba8SAndreas Gohr if ($was_neg) $out = "-$out"; 138965c6ba8SAndreas Gohr 139965c6ba8SAndreas Gohr return $out; 140965c6ba8SAndreas Gohr } 141965c6ba8SAndreas Gohr 1428fd5dfa8SAndreas Gohr /** 1438fd5dfa8SAndreas Gohr * Decimals need to be casted to the proper type for sorting 1448fd5dfa8SAndreas Gohr * 1458fd5dfa8SAndreas Gohr * @param QueryBuilder $QB 1468fd5dfa8SAndreas Gohr * @param string $tablealias 1478fd5dfa8SAndreas Gohr * @param string $colname 1488fd5dfa8SAndreas Gohr * @param string $order 1498fd5dfa8SAndreas Gohr */ 150d6d97f60SAnna Dabrowska public function sort(QueryBuilder $QB, $tablealias, $colname, $order) 151d6d97f60SAnna Dabrowska { 1528fd5dfa8SAndreas Gohr $QB->addOrderBy("CAST($tablealias.$colname AS DECIMAL) $order"); 1538fd5dfa8SAndreas Gohr } 1548fd5dfa8SAndreas Gohr 1558fd5dfa8SAndreas Gohr /** 1568fd5dfa8SAndreas Gohr * Decimals need to be casted to proper type for comparison 1578fd5dfa8SAndreas Gohr * 158af993d55SMichael Grosse * @param QueryBuilderWhere $add 1598fd5dfa8SAndreas Gohr * @param string $tablealias 1608fd5dfa8SAndreas Gohr * @param string $colname 1618fd5dfa8SAndreas Gohr * @param string $comp 1628fd5dfa8SAndreas Gohr * @param string|\string[] $value 1638fd5dfa8SAndreas Gohr * @param string $op 1648fd5dfa8SAndreas Gohr */ 165d6d97f60SAnna Dabrowska public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) 166d6d97f60SAnna Dabrowska { 167f9a2fce6SAndreas Gohr $add = $add->where($op); // open a subgroup 1687234bfb1Ssplitbrain $add->where('AND', "$tablealias.$colname != ''"); 1697234bfb1Ssplitbrain // make sure the field isn't empty 170f9a2fce6SAndreas Gohr $op = 'AND'; 171f9a2fce6SAndreas Gohr 1728fd5dfa8SAndreas Gohr /** @var QueryBuilderWhere $add Where additionional queries are added to */ 1738fd5dfa8SAndreas Gohr if (is_array($value)) { 174af993d55SMichael Grosse $add = $add->where($op); // sub where group 1758fd5dfa8SAndreas Gohr $op = 'OR'; 1768fd5dfa8SAndreas Gohr } 177f9a2fce6SAndreas Gohr 1788fd5dfa8SAndreas Gohr foreach ((array)$value as $item) { 179af993d55SMichael Grosse $pl = $add->getQB()->addValue($item); 180f9a2fce6SAndreas Gohr $add->where($op, "CAST($tablealias.$colname AS DECIMAL) $comp CAST($pl AS DECIMAL)"); 1818fd5dfa8SAndreas Gohr } 1828fd5dfa8SAndreas Gohr } 1838fd5dfa8SAndreas Gohr 184db9b8745SAndreas Gohr /** 185db9b8745SAndreas Gohr * Only exact matches for numbers 186db9b8745SAndreas Gohr * 187db9b8745SAndreas Gohr * @return string 188db9b8745SAndreas Gohr */ 189d6d97f60SAnna Dabrowska public function getDefaultComparator() 190d6d97f60SAnna Dabrowska { 191db9b8745SAndreas Gohr return '='; 192db9b8745SAndreas Gohr } 193399c73d3SAndreas Gohr} 194