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\SearchConfigParameters;
9use dokuwiki\Utf8\PhpString;
10
11class Tag extends AbstractMultiBaseType
12{
13    protected $config = [
14        'page' => '',
15        'autocomplete' => [
16            'mininput' => 2,
17            'maxresult' => 5
18        ]
19    ];
20
21    /**
22     * @param int|string $value
23     * @param \Doku_Renderer $R
24     * @param string $mode
25     * @return bool
26     */
27    public function renderValue($value, \Doku_Renderer $R, $mode)
28    {
29        $context = $this->getContext();
30        $filter = SearchConfigParameters::$PARAM_FILTER .
31            '[' . $context->getTable() . '.' . $context->getLabel() . '*~]=' . $value;
32
33        $page = trim($this->config['page']);
34        if (!$page) $page = cleanID($context->getLabel());
35
36        $R->internallink($page . '?' . $filter, $value);
37        return true;
38    }
39
40    /**
41     * Autocomplete from existing tags
42     *
43     * @return array
44     */
45    public function handleAjax()
46    {
47        global $INPUT;
48
49        // check minimum length
50        $lookup = trim($INPUT->str('search'));
51        if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return [];
52
53        // results wanted?
54        $max = $this->config['autocomplete']['maxresult'];
55        if ($max <= 0) return [];
56
57        $context = $this->getContext();
58        $sql = $this->buildSQLFromContext($context);
59        $opt = ["%$lookup%"];
60
61        /** @var \helper_plugin_struct_db $hlp */
62        $hlp = plugin_load('helper', 'struct_db');
63        $sqlite = $hlp->getDB();
64        $rows = $sqlite->queryAll($sql, $opt);
65
66        $result = [];
67        foreach ($rows as $row) {
68            $result[] = [
69                'label' => $row['value'],
70                'value' => $row['value']
71            ];
72        }
73
74        return $result;
75    }
76
77    /**
78     * Create the sql to query the database for tags to do autocompletion
79     *
80     * This method both handles multi columns and page schemas that need access checking
81     *
82     * @param Column $context
83     *
84     * @return string The sql with a single "?" placeholde for the search value
85     */
86    protected function buildSQLFromContext(Column $context)
87    {
88        $sql = '';
89        if ($context->isMulti()) {
90            /** @noinspection SqlResolve */
91            $sql .= "SELECT DISTINCT value
92                      FROM multi_{$context->getTable()} AS M, data_{$context->getTable()} AS D
93                     WHERE M.pid = D.pid
94                       AND M.rev = D.rev
95                       AND M.colref = {$context->getColref()}\n";
96        } else {
97            /** @noinspection SqlResolve */
98            $sql .= "SELECT DISTINCT col{$context->getColref()} AS value
99                      FROM data_{$context->getTable()} AS D
100                     WHERE 1 = 1\n";
101        }
102
103        $sql .= "AND ( D.pid = '' OR (";
104        $sql .= "PAGEEXISTS(D.pid) = 1\n";
105        $sql .= "AND GETACCESSLEVEL(D.pid) > 0\n";
106        $sql .= ")) ";
107
108        $sql .= "AND D.latest = 1\n";
109        $sql .= "AND value LIKE ?\n";
110        $sql .= 'ORDER BY value';
111
112        return $sql;
113    }
114
115    /**
116     * Normalize tags before comparing
117     *
118     * @param QueryBuilder $QB
119     * @param string $tablealias
120     * @param string $colname
121     * @param string $comp
122     * @param string|string[] $value
123     * @param string $op
124     */
125    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
126    {
127        /** @var QueryBuilderWhere $add Where additionional queries are added to */
128        if (is_array($value)) {
129            $add = $add->where($op); // sub where group
130            $op = 'OR';
131        }
132        foreach ((array)$value as $item) {
133            $pl = $add->getQB()->addValue($item);
134            $add->where($op, "LOWER(REPLACE($tablealias.$colname, ' ', '')) $comp LOWER(REPLACE($pl, ' ', ''))");
135        }
136    }
137}
138