xref: /plugin/struct/types/Dropdown.php (revision 4a681ff98639a921d2453d562ebcf72939907a22)
1<?php
2namespace dokuwiki\plugin\struct\types;
3
4use dokuwiki\plugin\struct\meta\Column;
5use dokuwiki\plugin\struct\meta\QueryBuilder;
6use dokuwiki\plugin\struct\meta\Schema;
7use dokuwiki\plugin\struct\meta\Search;
8use dokuwiki\plugin\struct\meta\Value;
9
10class Dropdown extends AbstractBaseType {
11
12    protected $config = array(
13        'values' => 'one, two, three',
14        'schema' => '',
15        'field' => ''
16    );
17
18    /** @var Schema */
19    protected $schema = null;
20    /** @var Column */
21    protected $column = null;
22
23    /**
24     * Dropdown constructor.
25     *
26     * @param array|null $config
27     * @param string $label
28     * @param bool $ismulti
29     * @param int $tid
30     */
31    public function __construct($config = null, $label = '', $ismulti = false, $tid = 0) {
32        global $conf;
33
34        parent::__construct($config, $label, $ismulti, $tid);
35        $this->config['schema'] = Schema::cleanTableName($this->config['schema']);
36
37        if($this->usesLookup()) {
38            $this->schema = new Schema($this->config['schema']);
39            if(!$this->schema->getId()) {
40                // schema does not exist
41                msg(sprintf('Schema %s does not exist', $this->config['schema']), -1);
42                $this->schema = null;
43                $this->config['schema'] = '';
44                return;
45            }
46
47            // apply language replacement
48            $field = str_replace('$LANG', $conf['lang'], $this->config['field']);
49            $this->column = $this->schema->findColumn($field);
50            if(!$this->column) {
51                $field = str_replace('$LANG', 'en', $this->config['field']); // fallback to en
52                $this->column = $this->schema->findColumn($field);
53            }
54            if(!$this->column) {
55                // field does not exist
56                msg(sprintf('Field %s.%s does not exist', $this->config['schema'], $this->config['field']), -1);
57                $this->column = null;
58                $this->config['field'] = '';
59                return;
60            }
61
62            if($this->column->isMulti()) {
63                // field is multi
64                msg(sprintf('Field %s.%s is a multi field - not allowed for lookup', $this->config['schema'], $this->config['field']), -1);
65                $this->column = null;
66                $this->config['field'] = '';
67                return;
68            }
69        }
70    }
71
72    /**
73     * @return bool is this dropdown configured to use a lookup?
74     */
75    protected function usesLookup() {
76        return !blank($this->config['schema']) && !blank($this->config['field']);
77    }
78
79    /**
80     * Creates the options array
81     *
82     * @return array
83     */
84    protected function getOptions() {
85        if($this->usesLookup()) {
86            $options = $this->loadLookupData();
87        } else {
88            $options = explode(',', $this->config['values']);
89            $options = array_map('trim', $options);
90            $options = array_filter($options);
91            array_unshift($options, '');
92            $options = array_combine($options, $options);
93        }
94        return $options;
95    }
96
97    /**
98     * Loads all available lookup values
99     *
100     * @return array
101     */
102    protected function loadLookupData() {
103        $schema = $this->schema->getTable();
104        $field = $this->column->getLabel();
105
106        $search = new Search();
107        $search->addSchema($schema);
108        $search->addColumn($field);
109        $search->addSort($field);
110        $result = $search->execute();
111        $pids = $search->getPids();
112        $len = count($result);
113
114        /** @var Value[][] $result */
115        $options = array('' => '');
116        for($i = 0; $i < $len; $i++) {
117            $options[$pids[$i]] = $result[$i][0]->getDisplayValue();
118        }
119        return $options;
120    }
121
122    /**
123     * Render using linked field
124     *
125     * @param int|string $value
126     * @param \Doku_Renderer $R
127     * @param string $mode
128     * @return bool
129     */
130    public function renderValue($value, \Doku_Renderer $R, $mode) {
131        if(!$this->usesLookup()) {
132            return parent::renderValue($value, $R, $mode);
133        } else {
134            list(, $value) = json_decode($value);
135            return $this->column->getType()->renderValue($value, $R, $mode);
136        }
137    }
138
139    /**
140     * Render using linked field
141     *
142     * @param \int[]|\string[] $values
143     * @param \Doku_Renderer $R
144     * @param string $mode
145     * @return bool
146     */
147    public function renderMultiValue($values, \Doku_Renderer $R, $mode) {
148        if(!$this->usesLookup()) {
149            return parent::renderMultiValue($values, $R, $mode);
150        } else {
151            $values = array_map(
152                function ($val) {
153                    list(, $val) = json_decode($val);
154                    return $val;
155                }, $values
156            );
157            return $this->column->getType()->renderMultiValue($values, $R, $mode);
158        }
159    }
160
161    /**
162     * A Dropdown with a single value to pick
163     *
164     * @param string $name
165     * @param string $rawvalue
166     * @return string
167     */
168    public function valueEditor($name, $rawvalue) {
169        $class = 'struct_' . strtolower($this->getClass());
170
171        $name = hsc($name);
172        $html = "<select name=\"$name\" class=\"$class\">";
173        foreach($this->getOptions() as $opt => $val) {
174            if($opt == $rawvalue) {
175                $selected = 'selected="selected"';
176            } else {
177                $selected = '';
178            }
179
180            $html .= "<option $selected value=\"" . hsc($opt) . "\">" . hsc($val) . '</option>';
181        }
182        $html .= '</select>';
183
184        return $html;
185    }
186
187    /**
188     * A dropdown that allows to pick multiple values
189     *
190     * @param string $name
191     * @param \string[] $rawvalues
192     * @return string
193     */
194    public function multiValueEditor($name, $rawvalues) {
195        $class = 'struct_' . strtolower($this->getClass());
196
197        $name = hsc($name);
198        $html = "<select name=\"{$name}[]\" class=\"$class\" multiple=\"multiple\" size=\"5\">";
199        foreach($this->getOptions() as $opt) {
200            if(in_array($opt, $rawvalues)) {
201                $selected = 'selected="selected"';
202            } else {
203                $selected = '';
204            }
205
206            $html .= "<option $selected value=\"" . hsc($opt) . "\">" . hsc($opt) . '</option>';
207
208        }
209        $html .= '</select> ';
210        $html .= '<small>' . $this->getLang('multidropdown') . '</small>';
211        return $html;
212    }
213
214    /**
215     * @param string $value
216     * @return string
217     */
218    public function rawValue($value) {
219        if($this->usesLookup()) {
220            list($value) = json_decode($value);
221        }
222        return $value;
223    }
224
225    /**
226     * @param string $value
227     * @return string
228     */
229    public function displayValue($value) {
230        if($this->usesLookup()) {
231            list(, $value) = json_decode($value);
232            $value = $this->column->getType()->displayValue($value);
233        }
234        return $value;
235    }
236
237    /**
238     * Merge with lookup table
239     *
240     * @param QueryBuilder $QB
241     * @param string $tablealias
242     * @param string $colname
243     * @param string $alias
244     */
245    public function select(QueryBuilder $QB, $tablealias, $colname, $alias) {
246        if(!$this->usesLookup()) {
247            parent::select($QB, $tablealias, $colname, $alias);
248            return;
249        }
250
251        $schema = 'data_' . $this->schema->getTable();
252        $field = $this->column->getColName();
253
254        $rightalias = $QB->generateTableAlias();
255        $QB->addLeftJoin($tablealias, $schema, $rightalias, "$tablealias.$colname = $rightalias.pid");
256        $this->column->getType()->select($QB, $rightalias, $field, $alias);
257        $sql = $QB->getSelectStatement($alias);
258        $QB->addSelectStatement("JSON($tablealias.$colname, $sql)", $alias);
259    }
260
261    /**
262     * Compare against lookup table
263     *
264     * @param QueryBuilder $QB
265     * @param string $tablealias
266     * @param string $colname
267     * @param string $comp
268     * @param string|\string[] $value
269     * @param string $op
270     */
271    public function filter(QueryBuilder $QB, $tablealias, $colname, $comp, $value, $op) {
272        if(!$this->usesLookup()) {
273            parent::filter($QB, $tablealias, $colname, $comp, $value, $op);
274            return;
275        }
276
277        $schema = 'data_' . $this->schema->getTable();
278        $field = $this->column->getColName();
279
280        // compare against lookup field
281        $rightalias = $QB->generateTableAlias();
282        $QB->addLeftJoin($tablealias, $schema, $rightalias, "$tablealias.$colname = $rightalias.pid");
283        $this->column->getType()->filter($QB, $rightalias, $field, $comp, $value, $op);
284    }
285
286    /**
287     * Sort by lookup table
288     *
289     * @param QueryBuilder $QB
290     * @param string $tablealias
291     * @param string $colname
292     * @param string $order
293     */
294    public function sort(QueryBuilder $QB, $tablealias, $colname, $order) {
295        if(!$this->usesLookup()) {
296            parent::sort($QB, $tablealias, $colname, $order);
297            return;
298        }
299
300        $schema = 'data_' . $this->schema->getTable();
301        $field = $this->column->getColName();
302
303        $rightalias = $QB->generateTableAlias();
304        $QB->addLeftJoin($tablealias, $schema, $rightalias, "$tablealias.$colname = $rightalias.pid");
305        $this->column->getType()->sort($QB, $rightalias, $field, $order);
306    }
307
308}
309