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