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