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