1<?php
2
3namespace dokuwiki\plugin\struct\types;
4
5use dokuwiki\Extension\AuthPlugin;
6use dokuwiki\plugin\struct\meta\QueryBuilder;
7use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
8use dokuwiki\plugin\struct\meta\StructException;
9use dokuwiki\plugin\struct\meta\ValidationException;
10use dokuwiki\Utf8\PhpString;
11
12class User extends AbstractMultiBaseType
13{
14    protected $config = [
15        'existingonly' => true,
16        'autocomplete' => [
17            'fullname' => true,
18            'mininput' => 2,
19            'maxresult' => 5
20        ]
21    ];
22
23    /**
24     * @param string $rawvalue the user to validate
25     * @return int|string
26     */
27    public function validate($rawvalue)
28    {
29        $rawvalue = parent::validate($rawvalue);
30
31        if ($this->config['existingonly']) {
32            /** @var AuthPlugin $auth */
33            global $auth;
34            $info = $auth->getUserData($rawvalue, false);
35            if ($info === false) throw new ValidationException('User not found', $rawvalue);
36        }
37
38        return $rawvalue;
39    }
40
41    /**
42     * @param string $value the user to display
43     * @param \Doku_Renderer $R
44     * @param string $mode
45     * @return bool
46     */
47    public function renderValue($value, \Doku_Renderer $R, $mode)
48    {
49        if ($mode == 'xhtml') {
50            $name = userlink($value);
51            $R->doc .= $name;
52        } else {
53            $name = userlink($value, true);
54            $R->cdata($name);
55        }
56        return true;
57    }
58
59    /**
60     * Autocompletion for user names
61     *
62     * @return array
63     * @todo should we have any security mechanism? Currently everybody can look up users
64     */
65    public function handleAjax()
66    {
67        /** @var AuthPlugin $auth */
68        global $auth;
69        global $INPUT;
70
71        if (!$auth->canDo('getUsers')) {
72            return [];
73        }
74
75        // check minimum length
76        $lookup = trim($INPUT->str('search'));
77        if (PhpString::strlen($lookup) < $this->config['autocomplete']['mininput']) return [];
78
79        // results wanted?
80        $max = $this->config['autocomplete']['maxresult'];
81        if ($max <= 0) return [];
82
83        // find users by login, fill up with names if wanted
84        // Because a value might be interpreted as integer in the
85        // array key, we temporarily pad each key with a space at the
86        // end to enforce string keys.
87        $pad_keys = function ($logins) {
88            $result = [];
89            foreach ($logins as $login => $info) {
90                $result["$login "] = $info;
91            }
92            return $result;
93        };
94        $logins = $pad_keys($auth->retrieveUsers(0, $max, ['user' => $lookup]));
95        if ((count($logins) < $max) && $this->config['autocomplete']['fullname']) {
96            $logins = array_merge(
97                $logins,
98                $pad_keys($auth->retrieveUsers(0, $max, ['name' => $lookup]))
99            );
100        }
101
102        // reformat result for jQuery UI Autocomplete
103        $users = [];
104        foreach ($logins as $login => $info) {
105            $true_login = substr($login, 0, -1);
106            $users[] = [
107                'label' => $info['name'] . ' [' . $true_login . ']',
108                'value' => $true_login
109            ];
110        }
111
112        return $users;
113    }
114
115    /**
116     * When handling `%lasteditor%` get the data from the `titles` table instead the `data_` table.
117     *
118     * @param QueryBuilder $QB
119     * @param string $tablealias
120     * @param string $colname
121     * @param string $alias
122     */
123    public function select(QueryBuilder $QB, $tablealias, $colname, $alias)
124    {
125        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\UserColumn')) {
126            $rightalias = $QB->generateTableAlias();
127            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
128            $QB->addSelectStatement("$rightalias.lasteditor", $alias);
129            return;
130        }
131
132        parent::select($QB, $tablealias, $colname, $alias);
133    }
134
135    /**
136     * When sorting `%lasteditor%`, then sort the data from the `titles` table instead the `data_` table.
137     *
138     * @param QueryBuilder $QB
139     * @param string $tablealias
140     * @param string $colname
141     * @param string $order
142     */
143    public function sort(QueryBuilder $QB, $tablealias, $colname, $order)
144    {
145        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\UserColumn')) {
146            $rightalias = $QB->generateTableAlias();
147            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
148            $QB->addOrderBy("$rightalias.lasteditor $order");
149            return;
150        }
151
152        $QB->addOrderBy("$tablealias.$colname $order");
153    }
154
155    /**
156     * When using `%lasteditor%`, we need to compare against the `title` table.
157     *
158     * @param QueryBuilderWhere $add
159     * @param string $tablealias
160     * @param string $colname
161     * @param string $comp
162     * @param string|string[] $value
163     * @param string $op
164     */
165    public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op)
166    {
167        if (is_a($this->context, 'dokuwiki\plugin\struct\meta\UserColumn')) {
168            $QB = $add->getQB();
169            $rightalias = $QB->generateTableAlias();
170            $QB->addLeftJoin($tablealias, 'titles', $rightalias, "$tablealias.pid = $rightalias.pid");
171
172            // compare against page and title
173            $sub = $add->where($op);
174            $pl = $QB->addValue($value);
175            $sub->whereOr("$rightalias.lasteditor $comp $pl");
176            return;
177        }
178
179        parent::filter($add, $tablealias, $colname, $comp, $value, $op);
180    }
181}
182