xref: /plugin/usersettings/admin.php (revision 1ab406139ae52af26e6f9eaacaf1646e58521679)
1*1ab40613Stracker-user<?php
2*1ab40613Stracker-user
3*1ab40613Stracker-user/**
4*1ab40613Stracker-user * User Settings plugin — admin component.
5*1ab40613Stracker-user *
6*1ab40613Stracker-user * Provides an admin-only overview of every user's preferences as a flat,
7*1ab40613Stracker-user * sortable table: one row per (user x setting), with the columns
8*1ab40613Stracker-user *   Display name | Setting | Value | Changed by | Changed at.
9*1ab40613Stracker-user * A row's value is the user's explicit choice, or the toggle's default
10*1ab40613Stracker-user * (marked as such) when they never set one. A filter narrows the table to a
11*1ab40613Stracker-user * single setting; the table never grows wider as more toggles are added.
12*1ab40613Stracker-user *
13*1ab40613Stracker-user * Clicking a display name opens a per-user edit form (Model A+: an admin may
14*1ab40613Stracker-user * change anyone's preferences). Such a change is stored with the admin as the
15*1ab40613Stracker-user * recorded actor, and the user can still change it back themselves later.
16*1ab40613Stracker-user */
17*1ab40613Stracker-user
18*1ab40613Stracker-user// must be run within DokuWiki
19*1ab40613Stracker-userif (!defined('DOKU_INC')) die();
20*1ab40613Stracker-user
21*1ab40613Stracker-userclass admin_plugin_usersettings extends DokuWiki_Admin_Plugin
22*1ab40613Stracker-user{
23*1ab40613Stracker-user    /** @var string[] sort key => row field used for that sort */
24*1ab40613Stracker-user    protected $sortFields = [
25*1ab40613Stracker-user        'name'      => 'display_name',
26*1ab40613Stracker-user        'setting'   => 'setting_label',
27*1ab40613Stracker-user        'value'     => 'value_display',
28*1ab40613Stracker-user        'changedby' => 'changed_by_display',
29*1ab40613Stracker-user        'changedat' => 'changed_at',
30*1ab40613Stracker-user    ];
31*1ab40613Stracker-user
32*1ab40613Stracker-user    // ---------------------------------------------------------------------
33*1ab40613Stracker-user    //  Admin plugin metadata
34*1ab40613Stracker-user    // ---------------------------------------------------------------------
35*1ab40613Stracker-user
36*1ab40613Stracker-user    /** Only administrators may see other users' preferences. */
37*1ab40613Stracker-user    public function forAdminOnly()
38*1ab40613Stracker-user    {
39*1ab40613Stracker-user        return true;
40*1ab40613Stracker-user    }
41*1ab40613Stracker-user
42*1ab40613Stracker-user    /** Position in the admin menu. */
43*1ab40613Stracker-user    public function getMenuSort()
44*1ab40613Stracker-user    {
45*1ab40613Stracker-user        return 350;
46*1ab40613Stracker-user    }
47*1ab40613Stracker-user
48*1ab40613Stracker-user    /** Admin menu label — distinct from the user menu's "Preferences". */
49*1ab40613Stracker-user    public function getMenuText($language)
50*1ab40613Stracker-user    {
51*1ab40613Stracker-user        return $this->getLang('admin_menu');
52*1ab40613Stracker-user    }
53*1ab40613Stracker-user
54*1ab40613Stracker-user    // ---------------------------------------------------------------------
55*1ab40613Stracker-user    //  Component access
56*1ab40613Stracker-user    // ---------------------------------------------------------------------
57*1ab40613Stracker-user
58*1ab40613Stracker-user    /** @return helper_plugin_usersettings|null */
59*1ab40613Stracker-user    protected function getHelper()
60*1ab40613Stracker-user    {
61*1ab40613Stracker-user        return plugin_load('helper', 'usersettings');
62*1ab40613Stracker-user    }
63*1ab40613Stracker-user
64*1ab40613Stracker-user    /** @return action_plugin_usersettings|null */
65*1ab40613Stracker-user    protected function getActionPlugin()
66*1ab40613Stracker-user    {
67*1ab40613Stracker-user        return plugin_load('action', 'usersettings');
68*1ab40613Stracker-user    }
69*1ab40613Stracker-user
70*1ab40613Stracker-user    // ---------------------------------------------------------------------
71*1ab40613Stracker-user    //  Request handling
72*1ab40613Stracker-user    // ---------------------------------------------------------------------
73*1ab40613Stracker-user
74*1ab40613Stracker-user    /**
75*1ab40613Stracker-user     * Handle a submitted per-user edit form.
76*1ab40613Stracker-user     *
77*1ab40613Stracker-user     * Runs only for admins (DokuWiki's admin dispatcher enforces
78*1ab40613Stracker-user     * forAdminOnly() before this is called). Uses Post/Redirect/Get.
79*1ab40613Stracker-user     */
80*1ab40613Stracker-user    public function handle()
81*1ab40613Stracker-user    {
82*1ab40613Stracker-user        global $INPUT, $ID;
83*1ab40613Stracker-user
84*1ab40613Stracker-user        if (!$INPUT->post->bool('usersettings_adminsave')) {
85*1ab40613Stracker-user            return;
86*1ab40613Stracker-user        }
87*1ab40613Stracker-user        if (!checkSecurityToken()) {
88*1ab40613Stracker-user            return;
89*1ab40613Stracker-user        }
90*1ab40613Stracker-user
91*1ab40613Stracker-user        $this->processAdminSave();
92*1ab40613Stracker-user
93*1ab40613Stracker-user        // Post/Redirect/Get back to the overview
94*1ab40613Stracker-user        send_redirect(wl($ID, ['do' => 'admin', 'page' => 'usersettings'], true, '&'));
95*1ab40613Stracker-user    }
96*1ab40613Stracker-user
97*1ab40613Stracker-user    /**
98*1ab40613Stracker-user     * Validate the target user and store the submitted preferences for them,
99*1ab40613Stracker-user     * recording the acting admin as the actor. Kept redirect-free so it can
100*1ab40613Stracker-user     * be tested directly.
101*1ab40613Stracker-user     *
102*1ab40613Stracker-user     * @return bool
103*1ab40613Stracker-user     */
104*1ab40613Stracker-user    public function processAdminSave()
105*1ab40613Stracker-user    {
106*1ab40613Stracker-user        global $INPUT, $auth;
107*1ab40613Stracker-user
108*1ab40613Stracker-user        $target = $INPUT->post->str('edituser');
109*1ab40613Stracker-user        $admin  = $INPUT->server->str('REMOTE_USER');
110*1ab40613Stracker-user
111*1ab40613Stracker-user        $userData = ($auth !== null) ? $auth->getUserData($target) : false;
112*1ab40613Stracker-user        if ($userData === false) {
113*1ab40613Stracker-user            msg($this->getLang('badidentuser'), -1);
114*1ab40613Stracker-user            return false;
115*1ab40613Stracker-user        }
116*1ab40613Stracker-user
117*1ab40613Stracker-user        $action = $this->getActionPlugin();
118*1ab40613Stracker-user        $ok = ($action !== null) && $action->saveSubmittedPreferences($target, $admin);
119*1ab40613Stracker-user
120*1ab40613Stracker-user        $name = ($userData['name'] !== '') ? $userData['name'] : $target;
121*1ab40613Stracker-user        msg(
122*1ab40613Stracker-user            sprintf($this->getLang($ok ? 'adminsaved' : 'adminsavefail'), hsc($name)),
123*1ab40613Stracker-user            $ok ? 1 : -1
124*1ab40613Stracker-user        );
125*1ab40613Stracker-user        return $ok;
126*1ab40613Stracker-user    }
127*1ab40613Stracker-user
128*1ab40613Stracker-user    // ---------------------------------------------------------------------
129*1ab40613Stracker-user    //  Output
130*1ab40613Stracker-user    // ---------------------------------------------------------------------
131*1ab40613Stracker-user
132*1ab40613Stracker-user    /**
133*1ab40613Stracker-user     * Render either the overview table or, when an edituser parameter is
134*1ab40613Stracker-user     * present, the per-user edit form.
135*1ab40613Stracker-user     */
136*1ab40613Stracker-user    public function html()
137*1ab40613Stracker-user    {
138*1ab40613Stracker-user        global $INPUT;
139*1ab40613Stracker-user
140*1ab40613Stracker-user        $edituser = $INPUT->get->str('edituser');
141*1ab40613Stracker-user        if ($edituser !== '') {
142*1ab40613Stracker-user            echo $this->renderEditForm($edituser);
143*1ab40613Stracker-user        } else {
144*1ab40613Stracker-user            echo $this->renderTable();
145*1ab40613Stracker-user        }
146*1ab40613Stracker-user    }
147*1ab40613Stracker-user
148*1ab40613Stracker-user    // ---- overview table --------------------------------------------------
149*1ab40613Stracker-user
150*1ab40613Stracker-user    /**
151*1ab40613Stracker-user     * Build the rows of the overview: one per (user x toggle).
152*1ab40613Stracker-user     *
153*1ab40613Stracker-user     * @param array $users   [username => userdata] as from $auth->retrieveUsers()
154*1ab40613Stracker-user     * @param array $toggles registered toggle definitions
155*1ab40613Stracker-user     * @return array list of row arrays
156*1ab40613Stracker-user     */
157*1ab40613Stracker-user    public function buildRows(array $users, array $toggles)
158*1ab40613Stracker-user    {
159*1ab40613Stracker-user        $helper = $this->getHelper();
160*1ab40613Stracker-user        if ($helper === null) {
161*1ab40613Stracker-user            return [];
162*1ab40613Stracker-user        }
163*1ab40613Stracker-user
164*1ab40613Stracker-user        $rows = [];
165*1ab40613Stracker-user        foreach ($users as $username => $userData) {
166*1ab40613Stracker-user            $displayName = (!empty($userData['name'])) ? $userData['name'] : $username;
167*1ab40613Stracker-user            $stored = $helper->loadUserData($username);
168*1ab40613Stracker-user
169*1ab40613Stracker-user            foreach ($toggles as $key => $def) {
170*1ab40613Stracker-user                if (isset($stored[$key]) && array_key_exists('value', $stored[$key])) {
171*1ab40613Stracker-user                    $value     = $stored[$key]['value'];
172*1ab40613Stracker-user                    $changedBy = $stored[$key]['changed_by'] ?? '';
173*1ab40613Stracker-user                    $changedAt = (int) ($stored[$key]['changed_at'] ?? 0);
174*1ab40613Stracker-user                    $isDefault = false;
175*1ab40613Stracker-user                } else {
176*1ab40613Stracker-user                    $value     = $def['default'];
177*1ab40613Stracker-user                    $changedBy = '';
178*1ab40613Stracker-user                    $changedAt = 0;
179*1ab40613Stracker-user                    $isDefault = true;
180*1ab40613Stracker-user                }
181*1ab40613Stracker-user
182*1ab40613Stracker-user                $rows[] = [
183*1ab40613Stracker-user                    'user'               => $username,
184*1ab40613Stracker-user                    'display_name'       => $displayName,
185*1ab40613Stracker-user                    'setting_key'        => $key,
186*1ab40613Stracker-user                    'setting_label'      => $def['label'],
187*1ab40613Stracker-user                    'value_display'      => $this->displayValue($def, $value),
188*1ab40613Stracker-user                    'is_default'         => $isDefault,
189*1ab40613Stracker-user                    'changed_by_display' => $isDefault ? '' : $this->resolveActor($changedBy, $users),
190*1ab40613Stracker-user                    'changed_at'         => $changedAt,
191*1ab40613Stracker-user                ];
192*1ab40613Stracker-user            }
193*1ab40613Stracker-user        }
194*1ab40613Stracker-user        return $rows;
195*1ab40613Stracker-user    }
196*1ab40613Stracker-user
197*1ab40613Stracker-user    /**
198*1ab40613Stracker-user     * Sort overview rows by the given column and direction.
199*1ab40613Stracker-user     *
200*1ab40613Stracker-user     * @param array  $rows
201*1ab40613Stracker-user     * @param string $sort one of the keys of $this->sortFields
202*1ab40613Stracker-user     * @param string $dir  'asc' or 'desc'
203*1ab40613Stracker-user     * @return array
204*1ab40613Stracker-user     */
205*1ab40613Stracker-user    public function sortRows(array $rows, $sort, $dir)
206*1ab40613Stracker-user    {
207*1ab40613Stracker-user        $field = $this->sortFields[$sort] ?? 'display_name';
208*1ab40613Stracker-user
209*1ab40613Stracker-user        usort($rows, function ($a, $b) use ($field) {
210*1ab40613Stracker-user            if ($field === 'changed_at') {
211*1ab40613Stracker-user                return $a[$field] <=> $b[$field];
212*1ab40613Stracker-user            }
213*1ab40613Stracker-user            return strcasecmp((string) $a[$field], (string) $b[$field]);
214*1ab40613Stracker-user        });
215*1ab40613Stracker-user
216*1ab40613Stracker-user        if ($dir === 'desc') {
217*1ab40613Stracker-user            $rows = array_reverse($rows);
218*1ab40613Stracker-user        }
219*1ab40613Stracker-user        return $rows;
220*1ab40613Stracker-user    }
221*1ab40613Stracker-user
222*1ab40613Stracker-user    /**
223*1ab40613Stracker-user     * Human-readable value of a toggle: On/Off for a checkbox, the option
224*1ab40613Stracker-user     * label for a select.
225*1ab40613Stracker-user     *
226*1ab40613Stracker-user     * @param array $def
227*1ab40613Stracker-user     * @param mixed $value
228*1ab40613Stracker-user     * @return string
229*1ab40613Stracker-user     */
230*1ab40613Stracker-user    public function displayValue(array $def, $value)
231*1ab40613Stracker-user    {
232*1ab40613Stracker-user        if ($def['type'] === 'select') {
233*1ab40613Stracker-user            if (isset($def['options'][$value])) {
234*1ab40613Stracker-user                return (string) $def['options'][$value];
235*1ab40613Stracker-user            }
236*1ab40613Stracker-user            return (string) $value; // stored value no longer a defined option
237*1ab40613Stracker-user        }
238*1ab40613Stracker-user        return $this->getLang(empty($value) ? 'val_off' : 'val_on');
239*1ab40613Stracker-user    }
240*1ab40613Stracker-user
241*1ab40613Stracker-user    /**
242*1ab40613Stracker-user     * Resolve an actor username to a display name, falling back to the raw
243*1ab40613Stracker-user     * username when the actor is not (or no longer) a known user.
244*1ab40613Stracker-user     *
245*1ab40613Stracker-user     * @param string $actor
246*1ab40613Stracker-user     * @param array  $users [username => userdata]
247*1ab40613Stracker-user     * @return string
248*1ab40613Stracker-user     */
249*1ab40613Stracker-user    protected function resolveActor($actor, array $users)
250*1ab40613Stracker-user    {
251*1ab40613Stracker-user        if ($actor === '') {
252*1ab40613Stracker-user            return '';
253*1ab40613Stracker-user        }
254*1ab40613Stracker-user        if (isset($users[$actor]) && !empty($users[$actor]['name'])) {
255*1ab40613Stracker-user            return $users[$actor]['name'];
256*1ab40613Stracker-user        }
257*1ab40613Stracker-user        return $actor;
258*1ab40613Stracker-user    }
259*1ab40613Stracker-user
260*1ab40613Stracker-user    /**
261*1ab40613Stracker-user     * Render the overview table.
262*1ab40613Stracker-user     *
263*1ab40613Stracker-user     * @return string
264*1ab40613Stracker-user     */
265*1ab40613Stracker-user    protected function renderTable()
266*1ab40613Stracker-user    {
267*1ab40613Stracker-user        global $INPUT, $auth;
268*1ab40613Stracker-user
269*1ab40613Stracker-user        $helper  = $this->getHelper();
270*1ab40613Stracker-user        $toggles = $helper ? $helper->getRegisteredToggles() : [];
271*1ab40613Stracker-user
272*1ab40613Stracker-user        $html  = '<div class="plugin_usersettings_admin">';
273*1ab40613Stracker-user        $html .= '<h1>' . hsc($this->getLang('admin_heading')) . '</h1>';
274*1ab40613Stracker-user
275*1ab40613Stracker-user        if (empty($toggles)) {
276*1ab40613Stracker-user            return $html . '<p>' . hsc($this->getLang('notoggles')) . '</p></div>';
277*1ab40613Stracker-user        }
278*1ab40613Stracker-user
279*1ab40613Stracker-user        $users = ($auth !== null) ? $auth->retrieveUsers(0, 0) : [];
280*1ab40613Stracker-user        if (empty($users)) {
281*1ab40613Stracker-user            return $html . '<p>' . hsc($this->getLang('nousers')) . '</p></div>';
282*1ab40613Stracker-user        }
283*1ab40613Stracker-user
284*1ab40613Stracker-user        // request parameters (sort links and the filter form are GET)
285*1ab40613Stracker-user        $sort   = $INPUT->get->str('sort', 'name');
286*1ab40613Stracker-user        $dir    = ($INPUT->get->str('dir') === 'desc') ? 'desc' : 'asc';
287*1ab40613Stracker-user        $filter = $INPUT->get->str('filter');
288*1ab40613Stracker-user        if (!isset($this->sortFields[$sort])) {
289*1ab40613Stracker-user            $sort = 'name';
290*1ab40613Stracker-user        }
291*1ab40613Stracker-user        if ($filter !== '' && !isset($toggles[$filter])) {
292*1ab40613Stracker-user            $filter = '';
293*1ab40613Stracker-user        }
294*1ab40613Stracker-user
295*1ab40613Stracker-user        $html .= '<p>' . hsc($this->getLang('admin_intro')) . '</p>';
296*1ab40613Stracker-user        $html .= $this->renderFilter($toggles, $sort, $dir, $filter);
297*1ab40613Stracker-user
298*1ab40613Stracker-user        // rows
299*1ab40613Stracker-user        $rows = $this->buildRows($users, $toggles);
300*1ab40613Stracker-user        if ($filter !== '') {
301*1ab40613Stracker-user            $rows = array_values(array_filter($rows, function ($r) use ($filter) {
302*1ab40613Stracker-user                return $r['setting_key'] === $filter;
303*1ab40613Stracker-user            }));
304*1ab40613Stracker-user        }
305*1ab40613Stracker-user        $rows = $this->sortRows($rows, $sort, $dir);
306*1ab40613Stracker-user
307*1ab40613Stracker-user        // table
308*1ab40613Stracker-user        $html .= '<table class="inline plugin_usersettings_table">';
309*1ab40613Stracker-user        $html .= '<thead><tr>';
310*1ab40613Stracker-user        $html .= $this->sortHeader($this->getLang('th_name'), 'name', $sort, $dir, $filter);
311*1ab40613Stracker-user        $html .= $this->sortHeader($this->getLang('th_setting'), 'setting', $sort, $dir, $filter);
312*1ab40613Stracker-user        $html .= $this->sortHeader($this->getLang('th_value'), 'value', $sort, $dir, $filter);
313*1ab40613Stracker-user        $html .= $this->sortHeader($this->getLang('th_changedby'), 'changedby', $sort, $dir, $filter);
314*1ab40613Stracker-user        $html .= $this->sortHeader($this->getLang('th_changedat'), 'changedat', $sort, $dir, $filter);
315*1ab40613Stracker-user        $html .= '</tr></thead><tbody>';
316*1ab40613Stracker-user
317*1ab40613Stracker-user        foreach ($rows as $row) {
318*1ab40613Stracker-user            $rowClass = $row['is_default'] ? ' class="us-default-row"' : '';
319*1ab40613Stracker-user            $editUrl  = $this->pageURL(['edituser' => $row['user']]);
320*1ab40613Stracker-user
321*1ab40613Stracker-user            $html .= '<tr' . $rowClass . '>';
322*1ab40613Stracker-user            $html .= '<td><a href="' . $editUrl . '">' . hsc($row['display_name']) . '</a></td>';
323*1ab40613Stracker-user            $html .= '<td>' . hsc($row['setting_label']) . '</td>';
324*1ab40613Stracker-user            $html .= '<td>' . hsc($row['value_display']) . '</td>';
325*1ab40613Stracker-user            $html .= '<td>' . ($row['is_default']
326*1ab40613Stracker-user                        ? '<span class="us-default-mark">' . hsc($this->getLang('bydefault')) . '</span>'
327*1ab40613Stracker-user                        : hsc($row['changed_by_display'])) . '</td>';
328*1ab40613Stracker-user            $html .= '<td>' . ($row['changed_at'] > 0 ? hsc(dformat($row['changed_at'])) : '&mdash;') . '</td>';
329*1ab40613Stracker-user            $html .= '</tr>';
330*1ab40613Stracker-user        }
331*1ab40613Stracker-user
332*1ab40613Stracker-user        $html .= '</tbody></table>';
333*1ab40613Stracker-user        return $html . '</div>';
334*1ab40613Stracker-user    }
335*1ab40613Stracker-user
336*1ab40613Stracker-user    /**
337*1ab40613Stracker-user     * Render the setting filter (a small GET form).
338*1ab40613Stracker-user     *
339*1ab40613Stracker-user     * @param array  $toggles
340*1ab40613Stracker-user     * @param string $sort
341*1ab40613Stracker-user     * @param string $dir
342*1ab40613Stracker-user     * @param string $filter currently selected setting key
343*1ab40613Stracker-user     * @return string
344*1ab40613Stracker-user     */
345*1ab40613Stracker-user    protected function renderFilter(array $toggles, $sort, $dir, $filter)
346*1ab40613Stracker-user    {
347*1ab40613Stracker-user        global $ID;
348*1ab40613Stracker-user
349*1ab40613Stracker-user        // GET forms drop the action URL's query string, so every parameter
350*1ab40613Stracker-user        // travels as an explicit field — this also survives URL rewriting.
351*1ab40613Stracker-user        $html  = '<form class="us-filter" method="get" action="' . DOKU_BASE . DOKU_SCRIPT . '">';
352*1ab40613Stracker-user        $html .= '<input type="hidden" name="id" value="' . hsc($ID) . '" />';
353*1ab40613Stracker-user        $html .= '<input type="hidden" name="do" value="admin" />';
354*1ab40613Stracker-user        $html .= '<input type="hidden" name="page" value="usersettings" />';
355*1ab40613Stracker-user        $html .= '<input type="hidden" name="sort" value="' . hsc($sort) . '" />';
356*1ab40613Stracker-user        $html .= '<input type="hidden" name="dir" value="' . hsc($dir) . '" />';
357*1ab40613Stracker-user
358*1ab40613Stracker-user        $html .= '<label>' . hsc($this->getLang('filter_label')) . ' ';
359*1ab40613Stracker-user        $html .= '<select name="filter">';
360*1ab40613Stracker-user        $html .= '<option value="">' . hsc($this->getLang('filter_all')) . '</option>';
361*1ab40613Stracker-user        foreach ($toggles as $key => $def) {
362*1ab40613Stracker-user            $selected = ($filter === $key) ? ' selected="selected"' : '';
363*1ab40613Stracker-user            $html .= '<option value="' . hsc($key) . '"' . $selected . '>'
364*1ab40613Stracker-user                   . hsc($def['label']) . '</option>';
365*1ab40613Stracker-user        }
366*1ab40613Stracker-user        $html .= '</select></label> ';
367*1ab40613Stracker-user        $html .= '<button type="submit">' . hsc($this->getLang('filter_apply')) . '</button>';
368*1ab40613Stracker-user        return $html . '</form>';
369*1ab40613Stracker-user    }
370*1ab40613Stracker-user
371*1ab40613Stracker-user    /**
372*1ab40613Stracker-user     * Render one sortable column header.
373*1ab40613Stracker-user     *
374*1ab40613Stracker-user     * @param string $label
375*1ab40613Stracker-user     * @param string $col    sort key for this column
376*1ab40613Stracker-user     * @param string $sort   currently active sort key
377*1ab40613Stracker-user     * @param string $dir    currently active direction
378*1ab40613Stracker-user     * @param string $filter currently active filter (preserved in the link)
379*1ab40613Stracker-user     * @return string
380*1ab40613Stracker-user     */
381*1ab40613Stracker-user    protected function sortHeader($label, $col, $sort, $dir, $filter)
382*1ab40613Stracker-user    {
383*1ab40613Stracker-user        // clicking the active column flips direction; others start ascending
384*1ab40613Stracker-user        $newDir = ($sort === $col && $dir === 'asc') ? 'desc' : 'asc';
385*1ab40613Stracker-user        $arrow  = '';
386*1ab40613Stracker-user        if ($sort === $col) {
387*1ab40613Stracker-user            $arrow = ($dir === 'asc') ? " \u{25B2}" : " \u{25BC}";
388*1ab40613Stracker-user        }
389*1ab40613Stracker-user
390*1ab40613Stracker-user        $url = $this->pageURL(['sort' => $col, 'dir' => $newDir, 'filter' => $filter]);
391*1ab40613Stracker-user        return '<th><a href="' . $url . '">' . hsc($label) . $arrow . '</a></th>';
392*1ab40613Stracker-user    }
393*1ab40613Stracker-user
394*1ab40613Stracker-user    // ---- per-user edit form ---------------------------------------------
395*1ab40613Stracker-user
396*1ab40613Stracker-user    /**
397*1ab40613Stracker-user     * Render the edit form for one user's preferences.
398*1ab40613Stracker-user     *
399*1ab40613Stracker-user     * @param string $user
400*1ab40613Stracker-user     * @return string
401*1ab40613Stracker-user     */
402*1ab40613Stracker-user    protected function renderEditForm($user)
403*1ab40613Stracker-user    {
404*1ab40613Stracker-user        global $auth, $ID;
405*1ab40613Stracker-user
406*1ab40613Stracker-user        $html = '<div class="plugin_usersettings_admin">';
407*1ab40613Stracker-user
408*1ab40613Stracker-user        $userData = ($auth !== null) ? $auth->getUserData($user) : false;
409*1ab40613Stracker-user        if ($userData === false) {
410*1ab40613Stracker-user            $html .= '<h1>' . hsc($this->getLang('admin_heading')) . '</h1>';
411*1ab40613Stracker-user            $html .= '<p>' . hsc($this->getLang('badidentuser')) . '</p>';
412*1ab40613Stracker-user            $html .= '<p><a href="' . $this->pageURL() . '">'
413*1ab40613Stracker-user                   . hsc($this->getLang('edit_back')) . '</a></p>';
414*1ab40613Stracker-user            return $html . '</div>';
415*1ab40613Stracker-user        }
416*1ab40613Stracker-user
417*1ab40613Stracker-user        $displayName = ($userData['name'] !== '') ? $userData['name'] : $user;
418*1ab40613Stracker-user        $html .= '<h1>' . hsc(sprintf($this->getLang('edit_heading'), $displayName)) . '</h1>';
419*1ab40613Stracker-user
420*1ab40613Stracker-user        $helper  = $this->getHelper();
421*1ab40613Stracker-user        $action  = $this->getActionPlugin();
422*1ab40613Stracker-user        $toggles = $helper ? $helper->getRegisteredToggles() : [];
423*1ab40613Stracker-user
424*1ab40613Stracker-user        if (empty($toggles) || $action === null) {
425*1ab40613Stracker-user            $html .= '<p>' . hsc($this->getLang('notoggles')) . '</p>';
426*1ab40613Stracker-user            $html .= '<p><a href="' . $this->pageURL() . '">'
427*1ab40613Stracker-user                   . hsc($this->getLang('edit_back')) . '</a></p>';
428*1ab40613Stracker-user            return $html . '</div>';
429*1ab40613Stracker-user        }
430*1ab40613Stracker-user
431*1ab40613Stracker-user        $formAction = wl($ID, ['do' => 'admin', 'page' => 'usersettings'], false, '&amp;');
432*1ab40613Stracker-user        $html .= '<form method="post" action="' . $formAction . '" class="us-form">';
433*1ab40613Stracker-user        $html .= formSecurityToken(false);
434*1ab40613Stracker-user        $html .= '<input type="hidden" name="edituser" value="' . hsc($user) . '" />';
435*1ab40613Stracker-user        $html .= '<input type="hidden" name="usersettings_adminsave" value="1" />';
436*1ab40613Stracker-user
437*1ab40613Stracker-user        foreach ($toggles as $key => $def) {
438*1ab40613Stracker-user            $html .= $action->renderToggleRow($def, $helper->getPreference($key, $user));
439*1ab40613Stracker-user        }
440*1ab40613Stracker-user
441*1ab40613Stracker-user        $html .= '<div class="us-actions">';
442*1ab40613Stracker-user        $html .= '<button type="submit" class="button">'
443*1ab40613Stracker-user               . hsc($this->getLang('save')) . '</button> ';
444*1ab40613Stracker-user        $html .= '<a href="' . $this->pageURL() . '" class="us-back">'
445*1ab40613Stracker-user               . hsc($this->getLang('edit_back')) . '</a>';
446*1ab40613Stracker-user        $html .= '</div>';
447*1ab40613Stracker-user        $html .= '</form>';
448*1ab40613Stracker-user
449*1ab40613Stracker-user        return $html . '</div>';
450*1ab40613Stracker-user    }
451*1ab40613Stracker-user
452*1ab40613Stracker-user    // ---- helpers ---------------------------------------------------------
453*1ab40613Stracker-user
454*1ab40613Stracker-user    /**
455*1ab40613Stracker-user     * Build a URL back to this admin page with the given extra parameters.
456*1ab40613Stracker-user     *
457*1ab40613Stracker-user     * @param array $params
458*1ab40613Stracker-user     * @return string  HTML-attribute-safe URL
459*1ab40613Stracker-user     */
460*1ab40613Stracker-user    protected function pageURL(array $params = [])
461*1ab40613Stracker-user    {
462*1ab40613Stracker-user        global $ID;
463*1ab40613Stracker-user        $base = ['do' => 'admin', 'page' => 'usersettings'];
464*1ab40613Stracker-user        return wl($ID, array_merge($base, $params), false, '&amp;');
465*1ab40613Stracker-user    }
466*1ab40613Stracker-user}
467