For the complete documentation index, see llms.txt. This page is also available as Markdown.

'perm'` inside `$adminMenu`

Permissions: $adminMenu does not gate anything

The trap. Setting 'perm' inside $adminMenu feels like it restricts the route. It does not. It only hides the menu link.

// hides the link. does NOT block the route.
protected $adminMenu = array(
    'main/list'   => array('caption' => LAN_MANAGE, 'perm' => 'P'),
    'main/create' => array('caption' => LAN_CREATE, 'perm' => '0'),
);

A user with P rights will not see the "Create" link — and can still reach admin_config.php?mode=main&action=create by typing the URL. Hiding a link is not authorisation.

Where the gate actually is

$adminMenu is read in exactly four places in admin_ui.php: resolving the default mode, the getter/setter, renderMenu(), and restrictMenuAccess(). It never appears in checkAccess(), hasModeAccess() or hasRouteAccess().

The real gate is the dispatcher's $perm property:

protected $perm = '0';                    // string: gates the whole dispatcher
                                          //   -> hasModeAccess()

protected $perm = array(                  // array: gates per route
    'main/prefs' => '0',                  //   -> hasRouteAccess()
);

The core docblock above $adminMenu confirms it: 'perm' and 'userclass' restrictions are inherited from $modes, $access and $perm — you do not need to set them in the menu. And because restrictMenuAccess() itself calls hasRouteAccess(), setting $perm also hides the links. So $perm alone does both jobs; 'perm' keys in $adminMenu are redundant.

Which route keys are real

Only these are actual mode/action routes and can be gated by a $perm array:

Key
Method

main/list

ListPage()

main/create

CreatePage()

main/edit

EditPage()

main/prefs

PrefsPage()

main/inline

inline action

These are not routes — they are triggers that POST to main/list:

Key
Reality

main/delete

ListDeleteTrigger()

main/batch

ListBatchTrigger()

main/copy

a batch trigger

Putting 'main/delete' => '0' in $perm is dead configuration. It never matches a route. Worse, it reads as if delete is protected when it is not: delete, copy and batch all run under whatever permission main/list has.

Consequence. A clean read/write split (list at P, writing at 0) is not achievable through $perm alone — you would have to override ListDeleteTrigger() and ListBatchTrigger(). For a small plugin it is usually better to gate the whole thing:


Last updated