Disclaimer: Analysis done by CC, to be verified (could be wrong, but gives a good idea).
I had a look at the code to check how realistic this is. Short version: it’s doable and mostly a convergence job rather than a design problem — and we already have a partial precedent.
Where we are today
There are three parallel ways of displaying a user name, and none of them is authoritative:
XWiki#getUserName() / #getPlainUserName() / #getLocalUserName() in oldcore (XWiki.java), exposed as $xwiki.getUserName(). This is the de-facto standard and it’s used everywhere.
- The
#displayUser($arg $options) Velocity macro in templates/macros.vm, which does the nicer job (avatar, link, handles both users and groups) but is Velocity-only and is used in about 19 files.
- Direct reads of the
first_name/last_name xproperties, or of UserProperties#getFirstName()/getLastName(), scattered around.
Note that $services.user (UserScriptService) has no display method at all today, which is exactly the gap.
How much code is concerned
Roughly 215 matching lines across ~94 files in xwiki-platform-core + xwiki-platform-distribution (main code only, tests excluded):
- Java, ~28 files: oldcore (
XWiki, api/XWiki with 22 overloads, api/Document#getAuthorName()/getCreatorName()/getContentAuthorName(), UsersClass — the displayer of the Users property type —, XWikiLock, LockAction/EditAction/CancelAction/AdminAction), REST (ModelFactory, DomainObjectFactory, the search sources, UsersClassPropertyValuesProvider, DocumentUserReferenceModelSerializer), Solr (DocumentSolrMetadataExtractor, AttachmentSolrMetadataExtractor for author_display/creator_display/attachment_author_display), UserMentionsFormatter (which reimplements the name building with its own FIRST_NAME/LOGIN/FULL_NAME styles), the feed plugin, the notifications RSS renderer, the extension repository and the reset-password manager.
- Velocity and wiki pages, 66 files. The main ones:
macros.vm, flamingo’s contentheader.vm, commentsinline.vm, historyinline.vm, drawer.vm, restore.vm, editinline.vm, renameStatus.vm, copy.vm; getdocuments.vm, getusers.vm, getgroupmembers.vm, uorgsuggest.vm, recyclebinlist.vm, getdeleteddocuments.vm, diff_macros.vm, attachment_macros.vm, pdfcover.vm/pdffooter.vm, notification/macros.vm, notification/email/macros.vm; and wiki pages such as LiveTableResultsMacros.xml, SharePage.xml (16 occurrences on its own), WikiUsers.xml, UserDirectoryMacros.xml, XWikiUserProfileSheet.xml, PageAuthorsUIX.xml, SolrUserFacet.xml, Stats/Macros.xml, Invitation*, AnnotationCode/Macros.xml, Panels/Members.xml.
- And a handful of places that bypass both APIs and read
first_name/last_name directly for display: likers.vm (it builds a Live Data JSON with first_name/last_name columns), uorgsuggest.vm, getusers.vm, UserDirectoryMacros.xml, AdminUsersSheet.xml, SearchSuggestConfig.xml. These are precisely the ones that any display-level override would silently miss today.
What the API could look like
A new component role in xwiki-platform-user-api:
@Role
public interface UserDisplayer
{
Block display(UserReference user, Map<String, Object> parameters);
String displayPlain(UserReference user);
}
with the default implementation reading UserProperties and handling the guest/unknown/empty-name fallbacks, exposed as $services.user.display(...), with #displayUser delegating to it and XWiki#getUserName() and friends kept but reimplemented on top of it (they’re used by a large part of the extension ecosystem, so removing them is out of the question). Returning a Block instead of a String would also let us get rid of the current link/escapeXML boolean matrix, which is a recurring source of escaping bugs.
The tricky parts
- Solr is index-time, not display-time.
author_display and creator_display are stored fields, so a display-level API can’t retroactively hide anything there. We’d need to follow what we did for emails (exclude from indexing when obfuscation is enabled) or filter at query time. The same applies to any name already serialized into a cached JSON payload.
getUserName(user, format, ...) evaluates arbitrary Velocity with every XWikiUsers property bound to the context. That’s a public API contract that’s painful to reproduce in a clean component, and it’s a privacy hole in itself since a format script can read email. I’d deprecate it rather than port it.
- The JS consumers (Live Data, notifications, mentions autocomplete, the user picker, search suggest) receive names as JSON, so they have to be fed from the server-side API.
likers.vm shipping raw first_name/last_name columns is a good illustration of what breaks otherwise.
- Users versus groups:
#displayUser already unifies them, and the new API should too, otherwise it’ll be re-forked immediately.
- Volume: ~94 files is a lot of churn, much of it in wiki pages and templates that are hard to test. It has to be incremental — introduce the API, migrate oldcore, REST and the skin templates first, and leave the long tail for later.
One extra argument in favour, independent of privacy: getUserName() loads the full user document on every single call, so a single API is also the natural place to add caching, which would help on any page listing many authors.
So I’d suggest two issues: one for the UserDisplayer API and the migration of the call sites, and one for the privacy feature itself (opt-in “don’t display my name to unregistered users”), the latter depending on the former for anything beyond the obvious display points.