1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\plugin\struct\meta\Column;
6use dokuwiki\plugin\struct\meta\PageColumn;
7use dokuwiki\plugin\struct\meta\QueryBuilder;
8use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
9use dokuwiki\plugin\struct\meta\RevisionColumn;
10use dokuwiki\plugin\struct\meta\RowColumn;
11use dokuwiki\plugin\struct\meta\Schema;
12use dokuwiki\plugin\struct\meta\Search;
13use dokuwiki\plugin\struct\meta\SummaryColumn;
14use dokuwiki\plugin\struct\meta\UserColumn;
15use dokuwiki\plugin\struct\meta\Value;
16
17class Lookup extends Dropdown
18{
19    protected $config = ['schema' => '', 'field' => ''];
20
21    /** @var  Column caches the referenced column */
22    protected $column;
23
24    /**
25     * Dropdown constructor.
26     *
27     * @param array|null $config
28     * @param string $label
29     * @param bool $ismulti
30     * @param int $tid
31     */
32    public function __construct($config = null, $label = '', $ismulti = false, $tid = 0)
33    {
34        parent::__construct($config, $label, $ismulti, $tid);
35        $this->config['schema'] = Schema::cleanTableName($this->config['schema']);
36    }
37
38    /**
39     * Get the configured loojup column
40     *
41     * @return Column|false
42     */
43    protected function getLookupColumn()
44    {
45        if ($this->column instanceof Column) return $this->column;
46        $this->column = $this->getColumn($this->config['schema'], $this->config['field']);
47        return $this->column;
48    }
49
50    /**
51     * Gets the given column, applies language place holder
52     *
53     * @param string $table
54     * @param string $infield
55     * @return Column|false
56     */
57    protected function getColumn($table, $infield)
58    {
59        global $conf;
60
61        $table = new Schema($table);
62        if (!$table->getId()) {
63            // schema does not exist
64            msg(sprintf('Schema %s does not exist', $table), -1);
65            return false;
66        }
67
68        // apply language replacement
69        $field = str_replace('$LANG', $conf['lang'], $infield);
70        $column = $table->findColumn($field);
71        if (!$column) {
72            $field = str_replace('$LANG', 'en', $infield); // fallback to en
73            $column = $table->findColumn($field);
74        }
75        if (!$column) {
76            if ($infield == '%pageid%') {
77                $column = new PageColumn(0, new Page(), $table);
78            }
79            if ($infield == '%title%') {
80                $column = new PageColumn(0, new Page(['usetitles' => true]), $table);
81            }
82            if ($infield == '%lastupdate%') {
83                $column = new RevisionColumn(0, new DateTime(), $table);
84            }
85            if ($infield == '%lasteditor%') {
86                $column = new UserColumn(0, new User(), $table);
87            }
88            if ($infield == '%lastsummary%') {
89                return new SummaryColumn(0, new AutoSummary(), $table);
90            }
91            if ($infield == '%rowid%') {
92                $column = new RowColumn(0, new Decimal(), $table);
93            }
94        }
95        if (!$column) {
96            // field does not exist
97            msg(sprintf('Field %s.%s does not exist', $table, $infield), -1);
98            return false;
99        }
100
101        if ($column->isMulti()) {
102            // field is multi
103            msg(sprintf('Field %s.%s is a multi field - not allowed for lookup', $table, $field), -1);
104            return false;
105        }
106
107        return $column;
108    }
109
110    /**
111     * Creates the options array
112     *
113     * @return array
114     */
115    protected function getOptions()
116    {
117        $schema = $this->config['schema'];
118        $column = $this->getLookupColumn();
119        if (!$column) return [];
120        $field = $column->getLabel();
121
122        $search = new Search();
123        $search->addSchema($schema);
124        $search->addColumn($field);
125        $search->addSort($field);
126
127        $result = $search->getRows();
128        $pids = $search->getPids();
129        $rids = $search->getRids();
130        $len = count($result);
131
132        $options = ['' => ''];
133        for ($i = 0; $i < $len; $i++) {
134            $val = json_encode([$pids[$i], (int)$rids[$i]], JSON_THROW_ON_ERROR);
135            $options[$val] = $result[$i][0]->getDisplayValue();
136        }
137        return $options;
138    }
139
140
141    /**
142     * Render using linked field
143     *
144     * @param int|string $value
145     * @param \Doku_Renderer $R
146     * @param string $mode
147     * @return bool
148     */
149    public function renderValue($value, \Doku_Renderer $R, $mode)
150    {
151        [, $value] = \helper_plugin_struct::decodeJson($value);
152        $column = $this->getLookupColumn();
153        if (!$column) return false;
154        return $column->getType()->renderValue($value, $R, $mode);
155    }
156
157    /**
158     * Render using linked field
159     *
160     * @param \int[]|\string[] $values
161     * @param \Doku_Renderer $R
162     * @param string $mode
163     * @return bool
164     */
165    public function renderMultiValue($values, \Doku_Renderer $R, $mode)
166    {
167        $values = array_map(
168            function ($val) {
169                [, $val] = \helper_plugin_struct::decodeJson($val);
170                return $val;
171            },
172            $values
173        );
174        $column = $this->getLookupColumn();
175        if (!$column) return false;
176        return $column->getType()->renderMultiValue($values, $R, $mode);
177    }
178
179    /**
180     * @param string $value
181     * @return string
182     */
183    public function rawValue($value)
184    {
185        [$value] = \helper_plugin_struct::decodeJson($value);
186        return $value;
187    }
188
189    /**
190     * @param string $value
191     * @return string
192     */
193    public function displayValue($value)
194    {
195        [, $value] = \helper_plugin_struct::decodeJson($value);
196        $column = $this->getLookupColumn();
197        if ($column) {
198            return $column->getType()->displayValue($value);
199        } else {
200            return '';
201        }
202    }
203
204    /**
205     * This is the value to be used as argument to a filter for another column.
206     *
207     * In a sense this is the counterpart to the @param string $value
208     *
209     * @return string
210     * @see filter() function
211     *
212     */
213    public function compareValue($value)
214    {
215        [, $value] = \helper_plugin_struct::decodeJson($value);
216        $column = $this->getLookupColumn();
217        if ($column) {
218            return $column->getType()->rawValue($value);
219        } else {
220            return '';
221        }
222    }
223
224
225    /**
226     * Merge with lookup table
227     *
228     * @param QueryBuilder $QB
229     * @param string $tablealias
230     * @param string $colname
231     * @param string $alias
232     */
233    public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
234    {
235        $schema = 'data_' . $this->config['schema'];
236        $column = $this->getLookupColumn();
237        if (!$column) {
238            parent::select($QB, $tablealias, $colname, $alias);
239            return;
240        }
241
242        $field = $column->getColName();
243        $rightalias = $QB->generateTableAlias();
244        $QB->addLeftJoin(
245            $tablealias,
246            $schema,
247            $rightalias,
248            "STRUCT_LOOKUP($tablealias.$colname, 0) = $rightalias.pid " .
249            "AND STRUCT_LOOKUP($tablealias.$colname, 1) = $rightalias.rid " .
250            "AND $rightalias.latest = 1"
251        );
252        $column->getType()->select($QB, $rightalias, $field, $alias);
253        $sql = $QB->getSelectStatement($alias);
254        $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $sql)", $alias);
255    }
256
257    /**
258     * Compare against lookup table
259     *
260     * @param QueryBuilderWhere $add
261     * @param string $tablealias
262     * @param string $colname
263     * @param string $comp
264     * @param string|\string[] $value
265     * @param string $op
266     */
267    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
268    {
269        $schema = 'data_' . $this->config['schema'];
270        $column = $this->getLookupColumn();
271        if (!$column) {
272            parent::filter($add, $tablealias, $colname, $comp, $value, $op);
273            return;
274        }
275        $field = $column->getColName();
276
277        // compare against lookup field
278        $QB = $add->getQB();
279        $rightalias = $QB->generateTableAlias();
280        $QB->addLeftJoin(
281            $tablealias,
282            $schema,
283            $rightalias,
284            "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " .
285            "$rightalias.latest = 1"
286        );
287        $column->getType()->filter($add, $rightalias, $field, $comp, $value, $op);
288    }
289
290    /**
291     * Sort by lookup table
292     *
293     * @param QueryBuilder $QB
294     * @param string $tablealias
295     * @param string $colname
296     * @param string $order
297     */
298    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
299    {
300        $schema = 'data_' . $this->config['schema'];
301        $column = $this->getLookupColumn();
302        if (!$column) {
303            parent::sort($QB, $tablealias, $colname, $order);
304            return;
305        }
306        $field = $column->getColName();
307
308        $rightalias = $QB->generateTableAlias();
309        $QB->addLeftJoin(
310            $tablealias,
311            $schema,
312            $rightalias,
313            "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " .
314            "$rightalias.latest = 1"
315        );
316        $column->getType()->sort($QB, $rightalias, $field, $order);
317    }
318}
319