1<?php
2/**
3 * DokuWiki Plugin structprogress
4 * Most Code is taken from decimal Type: https://github.com/cosmocode/dokuwiki-plugin-struct/blob/5c37a46b990a9bc0e314c8faa228db6012387b5f/types/Decimal.php
5 *
6 * @author: saggi <saggi@gmx.de>
7 */
8
9namespace dokuwiki\plugin\structprogress\types;
10
11use dokuwiki\plugin\struct\meta\QueryBuilder;
12use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
13use dokuwiki\plugin\struct\meta\ValidationException;
14use dokuwiki\plugin\struct\types\AbstractMultiBaseType;
15
16class Progress extends AbstractMultiBaseType
17{
18
19    protected $config = array(
20        'max'     => '100',
21        'prefix'  => '',
22        'postfix' => '',
23        'type'    => 'default',
24    );
25
26    /**
27     * @inheritDoc
28     */
29    public function validate($rawvalue)
30    {
31        $rawvalue = parent::validate($rawvalue);
32        $rawvalue = str_replace(',', '.', $rawvalue); // we accept both
33        $minvalue = 0;
34        $maxvalue = 100;
35        if ($this->config['max'] !== '' && floatval($this->config['max']) > 0) {
36            $maxvalue = floatval($this->config['max']);
37        }
38
39        if ((string)$rawvalue != (string)floatval($rawvalue)) {
40            throw new ValidationException('Decimal needed');
41        }
42
43        if (floatval($rawvalue) < $minvalue) {
44            throw new ValidationException('Decimal min', $minvalue);
45        }
46
47        if ($maxvalue !== 0 && floatval($rawvalue) > $maxvalue) {
48            throw new ValidationException('Decimal max', $maxvalue);
49        }
50
51        return $rawvalue;
52    }
53
54    /**
55     * @inheritDoc
56     */
57    public function renderValue($value, \Doku_Renderer $R, $mode)
58    {
59        if ($mode == 'xhtml') {
60            $maxvalue = 100;
61            if ($this->config['max'] !== '' && floatval($this->config['max']) > 0) {
62                $maxvalue = floatval($this->config['max']);
63            }
64            $progress = 100 * $value / $maxvalue;
65            $progress = (int)($progress > 0) ? (($progress > 100) ? 100 : $progress) : 0; // Prevents overflow of the bar for imported data (<0 || >max)
66            $R->doc   .= '<div title="' . hsc($value) . '" class="struct_progress-background_' . hsc($this->config['type']) . '">';
67            $R->doc   .= '<div title="' . hsc($value) . '" style="width: ' . $progress . '% ;" class="struct_progress_' . hsc($this->config['type']) . '">';
68            $R->doc   .= '<p>' . hsc($this->config['prefix'] . $value . $this->config['postfix']) . '</p>';
69            $R->doc   .= '</div>';
70            $R->doc   .= '</div>';
71        } else {
72            $R->cdata($value);
73        }
74
75        return true;
76    }
77
78    /**
79     * @inheritDoc
80     */
81    public function renderMultiValue($values, \Doku_Renderer $R, $mode)
82    {
83        if ($mode == 'xhtml') {
84            $R->doc .= '<div title="Multi-Value" class="struct_progress-multi">';
85            foreach ($values as $value) {
86                $this->renderValue($value, $R, $mode);
87            }
88            $R->doc .= '</div>';
89        } else {
90            $R->cdata(join(', ', $values));
91        }
92        return true;
93    }
94
95    /**
96     * Works like number_format but keeps the decimals as is
97     *
98     * @link   http://php.net/manual/en/function.number-format.php#91047
99     * @author info at daniel-marschall dot de
100     * @param float  $number
101     * @param string $dec_point
102     * @param string $thousands_sep
103     * @return string
104     */
105    protected function formatWithoutRounding($number, $dec_point, $thousands_sep)
106    {
107        $was_neg = $number < 0; // Because +0 == -0
108
109        $tmp = explode('.', $number);
110        $out = number_format(abs(floatval($tmp[0])), 0, $dec_point, $thousands_sep);
111        if (isset($tmp[1])) {
112            $out .= $dec_point . $tmp[1];
113        }
114
115        if ($was_neg) {
116            $out = "-$out";
117        }
118
119        return $out;
120    }
121
122    /**
123     * Decimals need to be casted to the proper type for sorting
124     *
125     * @param QueryBuilder $QB
126     * @param string       $tablealias
127     * @param string       $colname
128     * @param string       $order
129     */
130    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
131    {
132        $QB->addOrderBy("CAST($tablealias.$colname AS DECIMAL) $order");
133    }
134
135    /**
136     * Decimals need to be casted to proper type for comparison
137     *
138     * @param QueryBuilderWhere $add
139     * @param string            $tablealias
140     * @param string            $colname
141     * @param string            $comp
142     * @param string|\string[]  $value
143     * @param string            $op
144     */
145    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
146    {
147        $add = $add->where($op); // open a subgroup
148        $add->where('AND', "$tablealias.$colname != ''"); // make sure the field isn't empty
149        $op = 'AND';
150
151        /** @var QueryBuilderWhere $add Where additionional queries are added to */
152        if (is_array($value)) {
153            $add = $add->where($op); // sub where group
154            $op  = 'OR';
155        }
156
157        foreach ((array)$value as $item) {
158            $pl = $add->getQB()->addValue($item);
159            $add->where($op, "CAST($tablealias.$colname AS DECIMAL) $comp CAST($pl AS DECIMAL)");
160        }
161    }
162
163    /**
164     * Only exact matches for numbers
165     *
166     * @return string
167     */
168    public function getDefaultComparator()
169    {
170        return '=';
171    }
172}
173
174