1<?php
2
3namespace dokuwiki\plugin\config\core\Setting;
4
5/**
6 * Class setting_multicheckbox
7 */
8class SettingMulticheckbox extends SettingString
9{
10    protected $choices = [];
11    protected $combine = [];
12    protected $other = 'always';
13
14    /** @inheritdoc */
15    public function update($input)
16    {
17        if ($this->isProtected()) return false;
18
19        // split any combined values + convert from array to comma separated string
20        $input = $input ?: [];
21        $input = $this->array2str($input);
22
23        $value = is_null($this->local) ? $this->default : $this->local;
24        if ($value == $input) return false;
25
26        if ($this->pattern && !preg_match($this->pattern, $input)) {
27            $this->error = true;
28            $this->input = $input;
29            return false;
30        }
31
32        $this->local = $input;
33        return true;
34    }
35
36    /** @inheritdoc */
37    public function html(\admin_plugin_config $plugin, $echo = false)
38    {
39
40        $disable = '';
41
42        if ($this->isProtected()) {
43            $value = $this->protected;
44            $disable = 'disabled="disabled"';
45        } elseif ($echo && $this->error) {
46            $value = $this->input;
47        } else {
48            $value = is_null($this->local) ? $this->default : $this->local;
49        }
50
51        $key = htmlspecialchars($this->key);
52
53        // convert from comma separated list into array + combine complimentary actions
54        $value = $this->str2array($value);
55        $default = $this->str2array($this->default);
56
57        $input = '';
58        foreach ($this->choices as $choice) {
59            $idx = array_search($choice, $value);
60            $idx_default = array_search($choice, $default);
61
62            $checked = ($idx !== false) ? 'checked="checked"' : '';
63
64            // @todo ideally this would be handled using a second class of "default"
65            $class = (($idx !== false) === (false !== $idx_default)) ? " selectiondefault" : "";
66
67            $prompt = ($plugin->getLang($this->key . '_' . $choice) ?: htmlspecialchars($choice));
68
69            $input .= '<div class="selection' . $class . '">' . "\n";
70            $input .= '<label for="config___' . $key . '_' . $choice . '">' . $prompt . "</label>\n";
71            $input .= '<input id="config___' . $key . '_' . $choice . '" name="config[' . $key .
72                '][]" type="checkbox" class="checkbox" value="' . $choice . '" ' . $disable . ' ' . $checked . "/>\n";
73            $input .= "</div>\n";
74
75            // remove this action from the disabledactions array
76            if ($idx !== false) unset($value[$idx]);
77            if ($idx_default !== false) unset($default[$idx_default]);
78        }
79
80        // handle any remaining values
81        if ($this->other != 'never') {
82            $other = implode(',', $value);
83            // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists')
84            // use != 'exists' rather than == 'always' to ensure invalid values default to 'always'
85            if ($this->other != 'exists' || $other) {
86                $class = (
87                    (count($default) === count($value)) &&
88                    (count($value) === count(array_intersect($value, $default)))
89                ) ?
90                    " selectiondefault" : "";
91
92                $input .= '<div class="other' . $class . '">' . "\n";
93                $input .= '<label for="config___' . $key . '_other">' .
94                    $plugin->getLang($key . '_other') .
95                    "</label>\n";
96                $input .= '<input id="config___' . $key . '_other" name="config[' . $key .
97                    '][other]" type="text" class="edit" value="' . htmlspecialchars($other) .
98                    '" ' . $disable . " />\n";
99                $input .= "</div>\n";
100            }
101        }
102        $label = '<label>' . $this->prompt($plugin) . '</label>';
103        return [$label, $input];
104    }
105
106    /**
107     * convert comma separated list to an array and combine any complimentary values
108     *
109     * @param string $str
110     * @return array
111     */
112    protected function str2array($str)
113    {
114        $array = explode(',', $str);
115
116        if (!empty($this->combine)) {
117            foreach ($this->combine as $key => $combinators) {
118                $idx = [];
119                foreach ($combinators as $val) {
120                    if (($idx[] = array_search($val, $array)) === false) break;
121                }
122
123                if (count($idx) && $idx[count($idx) - 1] !== false) {
124                    foreach ($idx as $i) unset($array[$i]);
125                    $array[] = $key;
126                }
127            }
128        }
129
130        return $array;
131    }
132
133    /**
134     * convert array of values + other back to a comma separated list, incl. splitting any combined values
135     *
136     * @param array $input
137     * @return string
138     */
139    protected function array2str($input)
140    {
141
142        // handle other
143        $other = trim($input['other']);
144        $other = empty($other) ? [] : explode(',', str_replace(' ', '', $input['other']));
145        unset($input['other']);
146
147        $array = array_unique(array_merge($input, $other));
148
149        // deconstruct any combinations
150        if (!empty($this->combine)) {
151            foreach ($this->combine as $key => $combinators) {
152                $idx = array_search($key, $array);
153                if ($idx !== false) {
154                    unset($array[$idx]);
155                    $array = array_merge($array, $combinators);
156                }
157            }
158        }
159
160        return implode(',', array_unique($array));
161    }
162}
163