Skip to content

Latest commit

Β 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Online Users for Ultimate Member

WordPress Ultimate Member PHP License

Shows which Ultimate Member users are online β€” as a block, a widget or the [whos_online] shortcode, as a status dot on profiles and member directories, and as an Online/Offline directory filter.

This is an independent fork of Ultimate Member - Online (v2.2.2) with the presence layer rebuilt. It is not affiliated with, endorsed by, or supported by Ultimate Member.


✨ Features

  • Online users list as a block, a sidebar widget, or [whos_online max="11" roles="all"]
  • Status dot beside a member's name on their profile and in every member directory view
  • Online Status profile field and an Online / Offline member directory filter
  • Per-user opt-out on the account page's Privacy tab, honoured everywhere including the Private Messages integration
  • Online count in Ultimate Member's REST stats; integrates with the Private Messages and Friends extensions
  • Configurable offline timeout, plus an optional ping for sites behind full-page caching
  • WordPress personal-data exporter and eraser, and suggested privacy policy text
  • Overridable templates, screen-reader-text labels on every status marker

πŸ“¦ Installation

  1. Copy the um-online-users folder into /wp-content/plugins/
  2. Activate it β€” Ultimate Member 2.7.0+ must be active
  3. Configure at Ultimate Member β†’ Settings β†’ Extensions β†’ Online

Settings

Setting Default What it does
Show online stats in member directory off Enables the status dot in member directories
Minutes until a user counts as offline 15 How long after a page view a user stops being shown as online
Enable cache-compatible presence ping off Turn on for sites with full-page caching (see below)

⚑ Presence storage

The original extension kept every online user in a single autoloaded option (um_online_users) and rewrote the whole option on every authenticated page view. That meant:

  • one update_option() write per page view per logged-in user;
  • a read-modify-write race β€” two concurrent requests each wrote back their own stale copy of the array, so one user's presence was silently lost;
  • the full online-user map unserialized on every request, including admin, AJAX and REST.

This fork stores one integer per user in usermeta:

  • writes are per-row, so there is no contention and nothing is lost;
  • writes are throttled to at most one per user per minute (um_online_users_write_throttle);
  • reads come out of WordPress's existing user-meta cache;
  • list queries run through WP_User_Query behind a 60-second transient (um_online_users_cache_ttl);
  • an hourly cron trims expired rows β€” housekeeping only, since is_online() checks the timestamp itself.

Timestamps are always GMT. The original mixed time() and current_time( 'timestamp' ), so changing the site's timezone either flushed everyone offline or pinned them online permanently.

Presence is recorded on both template_redirect and admin_init, so a member working in the dashboard stays online. The original bailed on is_admin() and silently timed them out.

Multisite

Presence is stored in usermeta, which is shared across a network, but the visible list is filtered by WP_User_Query and therefore scoped to the users of the current site. On a network with shared users, someone active on site A is not listed as online on site B unless they are a member of site B as well. There is no cross-network aggregate view.

Full-page caching

Presence is normally recorded on template_redirect. A cached page never reaches PHP, so on sites using WP Rocket, LiteSpeed, Varnish or Cloudflare APO nobody would ever appear online. Turning on Enable cache-compatible presence ping loads a ~1 KB script that reports presence with one admin-ajax request per logged-in visitor per interval (a third of the timeout), throttled through sessionStorage and paused while the tab is hidden.


🧩 Extending

Filters

um_online_interval                 // int   Offline timeout in minutes (default: the setting, 15)
um_online_users_write_throttle     // int   Seconds between presence writes per user (default 60)
um_online_users_cache_ttl          // int   List-query cache lifetime in seconds (default 60)
um_online_users_max_results        // int   Hard ceiling on users returned per query (default 500)
um_online_users_query_args         // array WP_User_Query arguments for the online list
um_online_users_is_hidden          // bool  Whether a user's status is hidden
um_online_users_can_view           // bool  Whether a user may appear in the list
um_online_users_avatar             // string Avatar markup used in the list
um_online_users_purge_batch        // int   Rows the cleanup cron trims per run (default 1000)

Actions

um_online_users_touched            // ( int $user_id, int $timestamp ) after presence is recorded
um_online_users_cleanup            // hourly cron event

API

UM()->Online()->is_online( $user_id );          // bool, false for users who opted out
UM()->Online()->count();                        // int
UM()->Online()->get_user_ids( array(            // int[], most recently active first
    'roles'  => array( 'um_member' ),
    'number' => 20,
) );
UM()->Online()->store()->last_seen( $user_id ); // int GMT timestamp, 0 if never seen

UM()->Online()->get_users() and UM()->Online()->users still return the original id => last seen array (or false) for backward compatibility, but they cannot be paginated β€” prefer get_user_ids().

Shortcodes

[whos_online max="11" roles="all"]     the documented tag
[um_whos_online]                       prefixed, guaranteed collision-free
[ultimatemember_online]                the original extension's tag, still honoured

All three render identically. max is how many members show before the "+N" toggle; roles is a comma-separated list of role slugs, or all.

A tag is only claimed if nothing else has registered it β€” the check runs on init at priority 20, after other plugins have registered theirs. If another plugin already owns whos_online or ultimatemember_online, this plugin leaves it alone rather than silently breaking it, and um_whos_online still works.

Templates

Copy any of these into yourtheme/ultimate-member/um-online-users/:

Template Purpose
online.php List wrapper and the "+N" toggle
online-users.php The user tiles β€” also rendered on its own for the "+N" AJAX response
online-marker.php The status dot
online-text.php The status as text
nobody.php Shown when nobody is online

Overrides in the original yourtheme/ultimate-member/um-online/ path are still honoured.


πŸ”„ Migrating from Ultimate Member - Online

Deactivate the original, activate this one. Nothing else is required.

  • Presence data is migrated out of um_online_users into usermeta on the first admin page load, in batches of 500. Nobody is logged out or shown as offline as a side effect.
  • The online_show_stats setting, the _hide_online_status user preference, the [ultimatemember_online] shortcode, the um_online_users widget and its stored instances, UM()->Online(), and the um_online_interval filter all carry over unchanged.
  • Theme overrides in ultimate-member/um-online/ keep resolving.
  • online.php was rewritten and online-users.php is new. If you override online.php, re-copy it β€” the old one expects an $online array of id => timestamp and no longer receives it.

πŸ› What was fixed

Architecture

  • Presence moved from one autoloaded option, rewritten on every page view, to per-user throttled usermeta (see above).
  • is_online() never compared the timestamp against the timeout β€” it only checked that a key existed in the option. Between the 15-minute purges, every user who had ever loaded a page reported as online.
  • clear_online_user() reset um_online_users_last_updated to "now", so on a site where anyone logs out every 15 minutes the stale-user purge could never run and users stayed "online" indefinitely.
  • The list rendered every online user server-side β€” one get_userdata() call and up to fifteen file_exists() probes each β€” then used JavaScript to hide everything past max. Only max users are fetched now; the rest load on demand.
  • get_plugin_data() ran at file scope, pulling wp-admin/includes/plugin.php into every front-end request just to read the version out of the plugin header.
  • Presence logging ran on init, so it also fired for AJAX, cron and REST requests. It runs on template_redirect now.

Privacy and correctness

  • Status leak: the Private Messages integration reported a user's real online status without checking whether they had opted out.
  • Inverted preference: for the new-UI boolean field, the checked value (1) β€” meaning "show my online status" β€” was treated as hidden. The same comparison ('yes' == 1) is true on PHP 7, so opting in could opt you out.
  • Filtering a member directory by Online when nobody was online applied no filter at all and listed every member. Same for the UM-metadata backend.
  • The directory filter assigned query_args['include'] / ['exclude'] outright, discarding anything another filter had already set. Both are merged now.
  • Filter values arriving from the query string were compared with in_array( 1, $value ), which resolves differently on PHP 7 and PHP 8.
  • Avatars were built by probing wp-content/uploads/ultimatemember/<id>/ with up to fifteen file_exists() calls, which broke on offloaded media (S3, Cloudinary, CDN rewrites) and ignored every avatar filter. Ultimate Member's own avatar handling is used instead.
  • The list ignored UM profile privacy, exposing members whose profiles are private or role-restricted.
  • A user changing their visibility preference stayed listed until the next purge.
  • roles="a, b" silently never matched b β€” the role list was never trimmed.
  • roles="does-not-exist" fell through to "all roles" instead of returning nobody.
  • The per-directory "Hide online stats" logic could never evaluate to "show", and read a key the directory never set.
  • Timestamps mixed local and GMT time.

Security and hardening

  • Widget values were interpolated into a [ultimatemember_online max="…" roles="…"] string, so a stored value containing a quote broke out of the attribute. The renderer is called directly now, and max/role are cast and whitelisted.
  • Unescaped output: the status text, role labels in the widget form, and _e() where esc_html_e() belonged.
  • Role slugs from the shortcode and AJAX are checked against the roles that actually exist.
  • Text domain loading moved off plugins_loaded (the WordPress 6.7 "just in time" notice, which 2.2.2's changelog claimed to have fixed).

PHP compatibility

  • The header declared Requires PHP: 5.6 while the code used UM()->frontend()->enqueue()::get_suffix(), which needs PHP 7.0. The suffix is computed locally now and the floor is honestly stated as 7.4.
  • The widget read $instance['title'], ['max'] and ['role'] with no isset() guard β€” undefined-index warnings on PHP 8 for a freshly dropped widget or a legacy-widget block preview.
  • function um_online_dependencies() was declared three times inside different branches of one function β€” a redeclare fatal waiting for two branches to be reachable.
  • &$this passed by reference to add_action().

Added

  • Online Users block (um-online-users/online-users) with a server-side render and an inspector panel. No build step: the editor script is plain ES5 using wp.element.createElement.
  • Offline timeout as a setting instead of filter-only.
  • Cache-compatible presence ping.
  • WordPress personal-data exporter, eraser and suggested privacy policy text.
  • screen-reader-text labels on every status marker, which previously conveyed state through a coloured icon and a title alone.
  • Stylesheet rebuilt on flexbox with CSS custom properties, plus selectors for Ultimate Member 2.9's directory markup β€” the original only knew the older uimob* classes.
  • index.php guards, composer.json, phpcs.xml.dist, .editorconfig and a regenerated .pot.
  • Deactivation now clears the cron event; uninstall removes presence metadata too.

🧾 Requirements

  • WordPress 5.6+
  • PHP 7.4+
  • Ultimate Member 2.7.0+

πŸ“„ License

GPL-2.0-or-later. See LICENSE.

Forked from Ultimate Member - Online by Ultimate Member, distributed under the GPL-2.0-or-later; the original copyright is retained. "Ultimate Member" is a trademark of its owner and is used here only to describe compatibility.

About

Shows which Ultimate Member users are online - as a block, widget or shortcode, as a status dot on profiles and member directories, and as an Online Status directory filter. Fork of Ultimate Member - Online with the presence layer rebuilt.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages