Skip to content

Add hooks for feature plugins to inject users into the users listing#1183

Open
tecto wants to merge 1 commit into
virtualmin:masterfrom
tecto:dev/plugin-users-hooks
Open

Add hooks for feature plugins to inject users into the users listing#1183
tecto wants to merge 1 commit into
virtualmin:masterfrom
tecto:dev/plugin-users-hooks

Conversation

@tecto
Copy link
Copy Markdown

@tecto tecto commented Feb 24, 2026

Problem

Feature plugins that manage external user stores (remote mail relay accounts,
LDAP users, etc.) have no way to display their users in the standard domain
users listing or provide creation links. The only option today is a separate
plugin-specific page, which is inconsistent and harder for admins to discover.

Changes

This PR adds three small hooks to Virtualmin core, following the existing
plugin_call/plugin_defined pattern already used for database users
(list_domain_users line 1096) and web users (list_extra_web_users line 1127):

1. list_plugin_users hook in list_domain_users()

Feature plugins can define a list_plugin_users(&domain) function that returns
user hashes. These are merged into the standard users listing, just like
list_extra_web_users does for virtualmin-htpasswd.

# Include users from feature plugins that define list_plugin_users
if ($includeextra) {
    foreach my $f (&list_feature_plugins()) {
        if ($d->{$f} && &plugin_defined($f, "list_plugin_users")) {
            my @pu = &plugin_call($f, "list_plugin_users", $d);
            push(@users, @pu) if @pu;
        }
    }
}

2. edit_url support in users_table()

If a user hash contains an edit_url key, that URL is used for the user's
name link instead of the hardcoded edit_user.cgi. This lets plugin users
link to their own edit pages. Existing GPL behavior (plain text for extra
users) and Pro behavior (edit_user.cgi links) are preserved unchanged.

3. users_create_links hook in list_users.cgi

Feature plugins can define a users_create_links(&domain) function that
returns link arrays (same format as the existing @links entries). These
are placed outside the $mleft mailbox quota guard since plugin users
don't count against the hosting plan's mailbox limit.

Backward compatibility

All three changes are complete no-ops when no feature plugins define the
hooks. The existing behavior for standard users, database users, and web
users is entirely unchanged.

Motivation

The immediate use case is virtualmin-remote-mail,
a feature plugin that manages mail relay accounts for domains using an
external mail provider. With these hooks, remote mail users appear in the
standard users table alongside regular mailbox users, and admins can create
them from the same page.

The hooks are generic enough that any feature plugin managing external user
stores (LDAP directories, external authentication backends, etc.) could use
them.

Adds three hooks so feature plugins can display their users in the
standard domain users table and provide creation links:

1. list_plugin_users hook in list_domain_users() — plugins return user
   hashes that are merged into the users listing (follows the existing
   list_extra_web_users pattern for virtualmin-htpasswd)

2. edit_url support in users_table() — if a user hash has an edit_url
   key, it is used for the name link instead of edit_user.cgi

3. users_create_links hook in list_users.cgi — plugins can add "Add"
   buttons to the users page, placed outside the mailbox quota guard

All three are no-ops when no plugins define the hooks.
tecto added a commit to tecto/virtualmin-remote-mail that referenced this pull request Feb 24, 2026
- Fix shebang to #!/usr/local/bin/perl to match upstream Virtualmin
- Add PR link (virtualmin/virtualmin-gpl#1183) to patches/README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Include users from feature plugins that define list_plugin_users
if ($includeextra) {
foreach my $f (&list_feature_plugins()) {
if ($d->{$f} && &plugin_defined($f, "list_plugin_users")) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it comes from a plugin, shouldn’t the function name be feature_list_users for consistency?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to that

if (!$virtualmin_pro && $u->{'extra'}) {
my $col_val;
if ($u->{'edit_url'}) {
$col_val = "<a href='".&html_escape($u->{'edit_url'}).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that links need to be HTML-escaped? 🙂

"user=".&urlize($u->{'user'})."'>$col_text</a>";
if (!$virtualmin_pro && $u->{'extra'}) {
my $col_val;
if ($u->{'edit_url'}) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better way to make the PR with fewer changes is to leave the existing code as is and just add your new check if ($u->{'edit_url'}) {...} after the existing logic.

@iliaross
Copy link
Copy Markdown
Member

Jamie, I think it’s a nice addition. What do you think?

Comment thread list_users.cgi
foreach my $f (&list_feature_plugins()) {
if ($d->{$f} && &plugin_defined($f, "users_create_links")) {
my @plinks = &plugin_call($f, "users_create_links", $d);
push(@links, @plinks) if @plinks;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's technically no need for if @plinks here

@jcameron
Copy link
Copy Markdown
Collaborator

seems like a pretty cool idea to me, once the comments are addressed!

@iliaross
Copy link
Copy Markdown
Member

iliaross commented Mar 1, 2026

seems like a pretty cool idea to me, once the comments are addressed!

@tecto Could you provide an update on this?

@iliaross
Copy link
Copy Markdown
Member

seems like a pretty cool idea to me, once the comments are addressed!

@tecto Could you provide an update on this?

Do you want us to handle the patches as we see fit, or would you rather fix them in your PR?

@github-actions
Copy link
Copy Markdown

This pull request has been inactive for 3 weeks. It will be closed in 1 week if there is no follow-up.

@github-actions github-actions Bot added the Stale Inactive and needs review label May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Stale Inactive and needs review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants