row field used for that sort */ protected $sortFields = [ 'name' => 'display_name', 'setting' => 'setting_label', 'value' => 'value_display', 'changedby' => 'changed_by_display', 'changedat' => 'changed_at', ]; // --------------------------------------------------------------------- // Admin plugin metadata // --------------------------------------------------------------------- /** Only administrators may see other users' preferences. */ public function forAdminOnly() { return true; } /** Position in the admin menu. */ public function getMenuSort() { return 350; } /** Admin menu label — distinct from the user menu's "Preferences". */ public function getMenuText($language) { return $this->getLang('admin_menu'); } // --------------------------------------------------------------------- // Component access // --------------------------------------------------------------------- /** @return helper_plugin_usersettings|null */ protected function getHelper() { return plugin_load('helper', 'usersettings'); } /** @return action_plugin_usersettings|null */ protected function getActionPlugin() { return plugin_load('action', 'usersettings'); } // --------------------------------------------------------------------- // Request handling // --------------------------------------------------------------------- /** * Handle a submitted per-user edit form. * * Runs only for admins (DokuWiki's admin dispatcher enforces * forAdminOnly() before this is called). Uses Post/Redirect/Get. */ public function handle() { global $INPUT, $ID; if (!$INPUT->post->bool('usersettings_adminsave')) { return; } if (!checkSecurityToken()) { return; } $this->processAdminSave(); // Post/Redirect/Get back to the overview send_redirect(wl($ID, ['do' => 'admin', 'page' => 'usersettings'], true, '&')); } /** * Validate the target user and store the submitted preferences for them, * recording the acting admin as the actor. Kept redirect-free so it can * be tested directly. * * @return bool */ public function processAdminSave() { global $INPUT, $auth; $target = $INPUT->post->str('edituser'); $admin = $INPUT->server->str('REMOTE_USER'); $userData = ($auth !== null) ? $auth->getUserData($target) : false; if ($userData === false) { msg($this->getLang('badidentuser'), -1); return false; } $action = $this->getActionPlugin(); $ok = ($action !== null) && $action->saveSubmittedPreferences($target, $admin); $name = ($userData['name'] !== '') ? $userData['name'] : $target; msg( sprintf($this->getLang($ok ? 'adminsaved' : 'adminsavefail'), hsc($name)), $ok ? 1 : -1 ); return $ok; } // --------------------------------------------------------------------- // Output // --------------------------------------------------------------------- /** * Render either the overview table or, when an edituser parameter is * present, the per-user edit form. */ public function html() { global $INPUT; $edituser = $INPUT->get->str('edituser'); if ($edituser !== '') { echo $this->renderEditForm($edituser); } else { echo $this->renderTable(); } } // ---- overview table -------------------------------------------------- /** * Build the rows of the overview: one per (user x toggle). * * @param array $users [username => userdata] as from $auth->retrieveUsers() * @param array $toggles registered toggle definitions * @return array list of row arrays */ public function buildRows(array $users, array $toggles) { $helper = $this->getHelper(); if ($helper === null) { return []; } $rows = []; foreach ($users as $username => $userData) { $displayName = (!empty($userData['name'])) ? $userData['name'] : $username; $stored = $helper->loadUserData($username); foreach ($toggles as $key => $def) { if (isset($stored[$key]) && array_key_exists('value', $stored[$key])) { $value = $stored[$key]['value']; $changedBy = $stored[$key]['changed_by'] ?? ''; $changedAt = (int) ($stored[$key]['changed_at'] ?? 0); $isDefault = false; } else { $value = $def['default']; $changedBy = ''; $changedAt = 0; $isDefault = true; } $rows[] = [ 'user' => $username, 'display_name' => $displayName, 'setting_key' => $key, 'setting_label' => $def['label'], 'value_display' => $this->displayValue($def, $value), 'is_default' => $isDefault, 'changed_by_display' => $isDefault ? '' : $this->resolveActor($changedBy, $users), 'changed_at' => $changedAt, ]; } } return $rows; } /** * Sort overview rows by the given column and direction. * * @param array $rows * @param string $sort one of the keys of $this->sortFields * @param string $dir 'asc' or 'desc' * @return array */ public function sortRows(array $rows, $sort, $dir) { $field = $this->sortFields[$sort] ?? 'display_name'; usort($rows, function ($a, $b) use ($field) { if ($field === 'changed_at') { return $a[$field] <=> $b[$field]; } return strcasecmp((string) $a[$field], (string) $b[$field]); }); if ($dir === 'desc') { $rows = array_reverse($rows); } return $rows; } /** * Human-readable value of a toggle: On/Off for a checkbox, the option * label for a select. * * @param array $def * @param mixed $value * @return string */ public function displayValue(array $def, $value) { if ($def['type'] === 'select') { if (isset($def['options'][$value])) { return (string) $def['options'][$value]; } return (string) $value; // stored value no longer a defined option } return $this->getLang(empty($value) ? 'val_off' : 'val_on'); } /** * Resolve an actor username to a display name, falling back to the raw * username when the actor is not (or no longer) a known user. * * @param string $actor * @param array $users [username => userdata] * @return string */ protected function resolveActor($actor, array $users) { if ($actor === '') { return ''; } if (isset($users[$actor]) && !empty($users[$actor]['name'])) { return $users[$actor]['name']; } return $actor; } /** * Render the overview table. * * @return string */ protected function renderTable() { global $INPUT, $auth; $helper = $this->getHelper(); $toggles = $helper ? $helper->getRegisteredToggles() : []; $html = '
' . hsc($this->getLang('notoggles')) . '
' . hsc($this->getLang('nousers')) . '
'; } // request parameters (sort links and the filter form are GET) $sort = $INPUT->get->str('sort', 'name'); $dir = ($INPUT->get->str('dir') === 'desc') ? 'desc' : 'asc'; $filter = $INPUT->get->str('filter'); if (!isset($this->sortFields[$sort])) { $sort = 'name'; } if ($filter !== '' && !isset($toggles[$filter])) { $filter = ''; } $html .= '' . hsc($this->getLang('admin_intro')) . '
'; $html .= $this->renderFilter($toggles, $sort, $dir, $filter); // rows $rows = $this->buildRows($users, $toggles); if ($filter !== '') { $rows = array_values(array_filter($rows, function ($r) use ($filter) { return $r['setting_key'] === $filter; })); } $rows = $this->sortRows($rows, $sort, $dir); // table $html .= '| ' . hsc($row['display_name']) . ' | '; $html .= '' . hsc($row['setting_label']) . ' | '; $html .= '' . hsc($row['value_display']) . ' | '; $html .= '' . ($row['is_default'] ? '' . hsc($this->getLang('bydefault')) . '' : hsc($row['changed_by_display'])) . ' | '; $html .= '' . ($row['changed_at'] > 0 ? hsc(dformat($row['changed_at'])) : '—') . ' | '; $html .= '
' . hsc($this->getLang('badidentuser')) . '
'; $html .= ''; return $html . '' . hsc($this->getLang('notoggles')) . '
'; $html .= ''; return $html . ''; } $formAction = wl($ID, ['do' => 'admin', 'page' => 'usersettings'], false, '&'); $html .= ''; return $html . ''; } // ---- helpers --------------------------------------------------------- /** * Build a URL back to this admin page with the given extra parameters. * * @param array $params * @return string HTML-attribute-safe URL */ protected function pageURL(array $params = []) { global $ID; $base = ['do' => 'admin', 'page' => 'usersettings']; return wl($ID, array_merge($base, $params), false, '&'); } }