xref: /plugin/struct/types/Decimal.php (revision ba662a609884bbbecde8cffec91014be306b652b)
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