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 = [
20        'schema' => '',
21        'field' => '',
22        'combobox' => false,
23    ];
24
25    /** @var  Column caches the referenced column */
26    protected $column;
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 instanceof Column) 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(['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 [];
124        $field = $column->getLabel();
125
126        $search = new Search();
127        $search->addSchema($schema);
128        $search->addColumn($field);
129        $search->addSort($field);
130
131        $result = $search->getRows();
132        $pids = $search->getPids();
133        $rids = $search->getRids();
134        $len = count($result);
135
136        $options = ['' => ''];
137        for ($i = 0; $i < $len; $i++) {
138            $val = json_encode([$pids[$i], (int)$rids[$i]], JSON_THROW_ON_ERROR);
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        [, $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                [, $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        [$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        [, $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 @param string $value
212     *
213     * @return string
214     * @see filter() function
215     *
216     */
217    public function compareValue($value)
218    {
219        [, $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            "STRUCT_LOOKUP($tablealias.$colname, 0) = $rightalias.pid " .
253            "AND STRUCT_LOOKUP($tablealias.$colname, 1) = $rightalias.rid " .
254            "AND $rightalias.latest = 1"
255        );
256        $column->getType()->select($QB, $rightalias, $field, $alias);
257        $sql = $QB->getSelectStatement($alias);
258        $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $sql)", $alias);
259    }
260
261    /**
262     * Compare against lookup table
263     *
264     * @param QueryBuilderWhere $add
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(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
272    {
273        $schema = 'data_' . $this->config['schema'];
274        $column = $this->getLookupColumn();
275        if (!$column) {
276            parent::filter($add, $tablealias, $colname, $comp, $value, $op);
277            return;
278        }
279        $field = $column->getColName();
280
281        // compare against lookup field
282        $QB = $add->getQB();
283        $rightalias = $QB->generateTableAlias();
284        $QB->addLeftJoin(
285            $tablealias,
286            $schema,
287            $rightalias,
288            "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " .
289            "$rightalias.latest = 1"
290        );
291        $column->getType()->filter($add, $rightalias, $field, $comp, $value, $op);
292    }
293
294    /**
295     * Sort by lookup table
296     *
297     * @param QueryBuilder $QB
298     * @param string $tablealias
299     * @param string $colname
300     * @param string $order
301     */
302    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
303    {
304        $schema = 'data_' . $this->config['schema'];
305        $column = $this->getLookupColumn();
306        if (!$column) {
307            parent::sort($QB, $tablealias, $colname, $order);
308            return;
309        }
310        $field = $column->getColName();
311
312        $rightalias = $QB->generateTableAlias();
313        $QB->addLeftJoin(
314            $tablealias,
315            $schema,
316            $rightalias,
317            "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " .
318            "$rightalias.latest = 1"
319        );
320        $column->getType()->sort($QB, $rightalias, $field, $order);
321    }
322}
323