xref: /plugin/struct/types/Decimal.php (revision 6c71c03110f8317d20cb4e430b8578e403e279de)
1<?php
2namespace plugin\struct\types;
3
4use plugin\struct\meta\ValidationException;
5
6/**
7 * Class Decimal
8 *
9 * A field accepting decimal numbers
10 *
11 * @package plugin\struct\types
12 */
13class Decimal extends AbstractMultiBaseType {
14
15    protected $config = array(
16        'min' => '',
17        'max' => '',
18        'roundto' => '-1',
19        'decpoint' => '.',
20        'thousands' => "\xE2\x80\xAF", // narrow no-break space
21        'trimzeros' => true,
22        'prefix' => '',
23        'postfix' => ''
24    );
25
26    /**
27     * Output the stored data
28     *
29     * @param string|int $value the value stored in the database
30     * @param \Doku_Renderer $R the renderer currently used to render the data
31     * @param string $mode The mode the output is rendered in (eg. XHTML)
32     * @return bool true if $mode could be satisfied
33     */
34    public function renderValue($value, \Doku_Renderer $R, $mode) {
35        if($this->config['roundto'] == -1) {
36            $value = $this->formatWithoutRounding(
37                $value,
38                $this->config['decpoint'],
39                $this->config['thousands']
40            );
41        } else {
42            $value = floatval($value);
43            $value = number_format(
44                $value,
45                $this->config['roundto'],
46                $this->config['decpoint'],
47                $this->config['thousands']
48            );
49        }
50        if($this->config['trimzeros'] && (strpos($value, $this->config['decpoint']) !== false)) {
51            $value = rtrim($value, '0');
52            $value = rtrim($value, $this->config['decpoint']);
53        }
54
55        $R->cdata($this->config['prefix'] . $value . $this->config['postfix']);
56        return true;
57    }
58
59    /**
60     * @param int|string $value
61     * @return int|string
62     * @throws ValidationException
63     */
64    public function validate($value) {
65        $value = parent::validate($value);
66        $value = str_replace(',', '.', $value); // we accept both
67
68        if((string) $value != (string) floatval($value)) {
69            throw new ValidationException('Decimal needed');
70        }
71
72        if($this->config['min'] !== '' && floatval($value) <= floatval($this->config['min'])) {
73            throw new ValidationException('Decimal min', floatval($this->config['min']));
74        }
75
76        if($this->config['max'] !== '' && floatval($value) >= floatval($this->config['max'])) {
77            throw new ValidationException('Decimal max', floatval($this->config['max']));
78        }
79
80        return $value;
81    }
82
83    /**
84     * Works like number_format but keeps the decimals as is
85     *
86     * @link http://php.net/manual/en/function.number-format.php#91047
87     * @author info at daniel-marschall dot de
88     * @param float $number
89     * @param string $dec_point
90     * @param string $thousands_sep
91     * @return string
92     */
93    function formatWithoutRounding($number, $dec_point, $thousands_sep) {
94        $was_neg = $number < 0; // Because +0 == -0
95
96        $tmp = explode('.', $number);
97        $out = number_format(abs(floatval($tmp[0])), 0, $dec_point, $thousands_sep);
98        if(isset($tmp[1])) $out .= $dec_point . $tmp[1];
99
100        if($was_neg) $out = "-$out";
101
102        return $out;
103    }
104
105}
106