# User Settings plugin for DokuWiki

A self-contained, server-side store of per-user preference toggles, with a
self-service settings page for every logged-in user and an admin overview of
everyone's choices.

It is **infrastructure for other plugins**: a feature plugin (or a template's
companion plugin) registers its own toggle with a single event handler, and
this plugin renders it, stores it, and exposes it — without ever needing to be
edited itself.

![User Preferences page with toggles and language selector](images/usersettings-screen1.png)
![Admin overview table showing all users' effective preferences](images/usersettings-screen2.png)
![Per-user edit form for changing another user's preferences](images/usersettings-screen3.png)

## Why this plugin exists

DokuWiki core has no server-side per-user preference store. The auth backend
holds only name, e-mail, password and groups, and DokuWiki's built-in
interface preferences live in a browser **cookie** — so they do not follow a
user from one browser or device to another.

This plugin fills that gap. Preferences are stored on the server, keyed by
user, so they apply wherever the person is logged in.

## What a user sees

A **"Preferences"** item appears in the user menu, just to the left of
"Update Profile". It opens a settings page (`do=usersettings`) listing every
registered toggle as a checkbox or a drop-down. The page is a plain HTML form —
no JavaScript — so it works in any browser.

## What an admin sees

Admin → **User Settings** shows a flat, sortable table — one row per
(user × setting): *Username · Display name · Email · Groups · Setting · Value ·
Changed by · Changed at*. The Email and Groups columns can each be hidden from
the configuration. A row shows the user's explicit choice, or the toggle's
default (marked as such) when they never set one.

Every column except *Changed at* is searchable: a per-column text-filter row
(case-insensitive substring, modelled on the User Manager's search row)
narrows the table, while the *Setting* column keeps its drop-down for picking a
single setting. The table is paginated with numbered page links and a
configurable page size. Filtering, sorting and the current page all travel in
the URL, so the view is bookmarkable and needs no JavaScript.

Clicking a display name opens an edit form for that user. This is **Model A+**:
an admin may change anyone's preferences, and the change is recorded under the
admin's name — but it is *not* enforced. The user can always change it back.
This is how you roll out a new feature as default-on while pre-setting it off
for specific people (for example, keeping two conservative admins on the old
theme).

## Storage

One JSON file per user, under `{metadir}/usersettings/`, holding
`{key: {value, changed_at, changed_by}}`. JSON and pretty-printed, so the files
are easy to inspect or back up. The page text and the wiki changelog are never
touched. `changed_by` records whoever made the change — the user, or an admin
acting on their behalf — which is what the overview's "Changed by" column
shows.

## Registering a toggle from another plugin

This is the integration point. Your plugin hooks the
`PLUGIN_USERSETTINGS_REGISTER` event and appends one or more toggle
definitions:

```php
class action_plugin_myfeature extends DokuWiki_Action_Plugin
{
    public function register(Doku_Event_Handler $controller)
    {
        $controller->register_hook(
            'PLUGIN_USERSETTINGS_REGISTER', 'BEFORE', $this, 'registerToggles'
        );
    }

    public function registerToggles(Doku_Event $event)
    {
        // a simple on/off toggle
        $event->data[] = [
            'key'     => 'myfeature_enabled',
            'label'   => 'Enable my feature',
            'type'    => 'checkbox',
            'default' => 1,
            'desc'    => 'Show my feature on wiki pages.',
            'plugin'  => 'myfeature',
        ];

        // a choice from a fixed list
        $event->data[] = [
            'key'     => 'myfeature_mode',
            'label'   => 'My feature mode',
            'type'    => 'select',
            'options' => ['compact' => 'Compact', 'full' => 'Full view'],
            'default' => 'full',
            'plugin'  => 'myfeature',
        ];
    }
}
```

### Toggle definition fields

| Field | Required | Notes |
| --- | --- | --- |
| `key` | yes | Unique identifier; `A-Z a-z 0-9 _` only. Also the storage key and HTML field name. Prefix it with your plugin name to avoid collisions. |
| `label` | yes | Shown on the settings page and in the admin table. |
| `type` | — | `checkbox` (default) or `select`. |
| `default` | — | Default value. Checkbox: `0`/`1`. Select: one of the option keys. |
| `options` | for `select` | A `value => label` map. Required and non-empty for selects. |
| `desc` | — | Optional help text shown under the toggle. |
| `plugin` | — | Optional identifier of the registering plugin/template. |

Invalid definitions (missing key, illegal key characters, a select with no
options) are silently dropped. If two plugins register the same `key`, the
first registration wins.

A toggle definition is plain data, so a **template** can have a toggle too —
ship a tiny companion action plugin that does nothing but register it.

## Built-in toggles

### Interface language

This plugin ships with a built-in **Interface language** toggle that allows
each logged-in user to select their preferred language for DokuWiki's menus
and messages, overriding the site-wide default (`$conf['lang']`).

- **Key:** `lang`
- **Type:** `select`
- **Options:** All installed languages found in `inc/lang/`, displayed by their
  native name (endonym) — e.g. *Deutsch*, *日本語*, *Français*. Unknown codes
  fall back to the bare code.
- **Default:** The site's configured default language

The language preference is applied as early as possible in the request
lifecycle (during `ACTION_ACT_PREPROCESS`), so all rendering — template hooks,
plugins, the wiki text itself — sees the user's chosen language immediately.

Users can change it in the Preferences page. Admins can set it per-user in the
User Settings admin table. The language list is scanned from `inc/lang/` at
registration time, which fires only once per request (when the settings page or
admin table is actually visited), not on every page load.

## Reading a preference

Your plugin reads the effective value through the helper. `getPreference()`
returns the user's stored value, or the registered default if they never set
one:

```php
$prefs = plugin_load('helper', 'usersettings');
$enabled = $prefs ? $prefs->getPreference('myfeature_enabled', null) : 1;
// pass a username as the 2nd argument, or null for the current user
```

Always provide your own sensible fallback (`? ... : 1` above) in case the
User Settings plugin is not installed.

## Configuration

Admin → Configuration Settings. These affect only the admin overview table;
the self-service Preferences page is unchanged.

| Setting | Default | Effect |
| --- | --- | --- |
| `show_mail` | `1` (on) | Show the Email column in the admin overview. |
| `show_grps` | `1` (on) | Show the Groups column in the admin overview. |
| `entries_per_page` | `20` | Rows per page in the admin overview. Set to `0` to show all rows on one page (no pagination). |

## Components

| File | Role |
| --- | --- |
| `helper.php` | Storage, the registration event, the read/write API. |
| `action.php` | The user-menu item, the `do=usersettings` settings page, and the built-in interface language toggle. |
| `admin.php` | The admin overview table and per-user edit form. |
| `MenuItem.php` | The user-menu item class. |

## Install

Drop the folder into `lib/plugins/usersettings/`, or use Admin → Extension
Manager → Manual Install. The admin-overview columns and page size can be
tuned under **Configuration** (above), but the defaults are sensible.

## Translations

Included: English (`en`), German (`de`), Russian (`ru`), Japanese (`ja`).

## Notes

- The settings page and admin pages are plain HTML forms — no JavaScript — so
  there are no old-browser concerns.
- The **built-in interface language toggle** is registered automatically by
  `action.php` and requires no configuration. It scans `inc/lang/` at
  registration time to populate the option list, so all installed languages
  are immediately available to users. The toggle applies the selected language
  during `ACTION_ACT_PREPROCESS`, before any output is produced, ensuring
  consistency throughout the request.
- This is a new, locally-developed plugin with no upstream, so — unlike the
  forked plugins on this wiki — it carries no update-suppression date.

## License

GPL 2, matching DokuWiki.
