Skip to content

Referees

The club's referee list — who they are and what their number is — kept in the WordPress admin and published at /referees/referee-list/ to the people the club has said may read it.

It is a companion to Club Player Manager, not part of it. It stores nothing about players, guardians or squads, and it keeps its tables entirely to itself.

What it does

  1. The club keeps a list of referees. A name and a phone number each, under Referees in the admin. That is the whole record: this list exists so that somebody can reach a referee on a Saturday morning — by phone, text or WhatsApp, in one tap from the list.
  2. The club says who may read it, by email address, under Referees → Referee access.
  3. Those people read it on the site, at /referees/referee-list/, after signing in with the sign-in Club Player Manager already runs for the club's parents — an emailed link, and a passkey they can save so the next visit needs no email at all.

Requirements

  • WordPress 6.5+, PHP 8.2+
  • Club Player Manager 1.14 or newer (platform 3.8 — see the platform layer)
  • No Composer dependencies, and no build step

Access, and why there is no second sign-in

This plugin has no sign-in of its own, on purpose. Club Player Manager already runs one for the club's parents, and most of the people reading the referee list are those same parents, on the same phone, with a passkey already saved. A second sign-in beside it would mean a second set of emailed links, a second session cookie and a second passkey for the club to hold and for a parent to tell apart from the first.

So the split is: core says who somebody is, this plugin says what they may see.

Who decides Where
Proving the address is yours Club Player Manager /account/ — emailed link, then a passkey
Whether you may read the list This plugin Referees → Referee access

A referee who is not also a parent has no children on file, so core would not have recognised their address at all. This plugin vouches for the addresses on its own list through the cpm_account_recognised_email filter, and core then does the rest — link, session, passkey and all. Their account page leaves out the "Your children at the club" panel, since they are not there as a parent.

Being signed in is never being allowed. Every parent at the club has a session; the referee list asks its own list again on every page load, so somebody the club removes loses the page on their next request rather than when their session happens to expire.

What a referee user sees

On the list, signed in The referees and their numbers, each one a call, a text and a WhatsApp link
Signed in, not on the list Which address they are signed in as, and to ask the club — the usual cause is a parent with two addresses, signed in as the other one
Not signed in A link to the account page to sign in

The page is never cached: it is one URL whose content depends on who is asking, and what a shared cache would hand to the wrong person is a list of people's phone numbers.

Where a referee came from

Every referee carries two separate facts, shown on the Referees screen and on each referee's page:

What it is Set by
Source Where the details came from, in free text — "County FA list", "refereed our U10s" Whoever adds them; editable by an admin afterwards
Added by The email address of whoever added them Recorded automatically, never edited

Added by is the signed-in club user for a suggestion, and the admin's own address for a referee added by hand or from a CSV. Both are blank for a referee added before these fields existed.

Where a referee is based

Each referee has an optional Location: a town or area, in the club's own words, so whoever is finding a referee for an away game can see who is nearby. An admin sets it when adding a referee or on their page, a club user can give it (optionally) when suggesting one, and a CSV can carry it in a Location column.

It shows in the admin lists, and under the referee's name on the published list and the fixture send list. The public page a referee opens from a fixture request does not show it: that page carries nothing from the referee list.

Confirmed 18 or over

Each referee carries a Confirmed 18 or over flag. Only an admin can set it: tick the box when adding a referee, or on the referee's own page. It records which admin confirmed it and when, and the referee's page shows "Confirmed by jo@example.com on 4 Oct 2026".

  • Unticked means not yet confirmed, not under 18. Every referee who was already on the list, every suggestion from a club user and every CSV row starts unconfirmed.
  • Saving a referee again with the box still ticked keeps the original admin and date. Unticking clears both.
  • The admin lists show an 18+ confirmed or Not confirmed badge. The published list and the fixture send list mark an unconfirmed referee Not confirmed 18+ under their name, so whoever is picking a referee can see it. Nobody is hidden or blocked because of it.

Suggestions from club users

Anybody signed in to the club — a parent as much as a referee user — gets a Suggest a referee form at the foot of /referees/referee-list/: a name, a phone number, and where they got the details. That includes someone signed in but not allowed to read the list, since knowing a good referee has nothing to do with it.

A suggestion is never published by itself. It is held as pending, off the list, and shows up at the top of Referees under Waiting for approval, with who suggested it and a count on the admin menu. An admin can correct it on its own page, then Approve it (it goes on the list) or Turn down it (it is deleted).

What the form refuses, and says so on the page:

  • a blank name, number or source, or a number with no digits in it;
  • a referee already on the list or already waiting — the same phone typed another way (07700 900123, +44 7700 900123) is the same referee;
  • more than 10 suggestions from one person waiting at once.

The form carries no nonce, the same way English FA's signed-in answers don't: the person has no WordPress account to tie one to, and core's session cookie is SameSite=Lax, so a cross-site post arrives with nobody signed in and writes nothing. Who made a suggestion is read from the session, never from the form.

Notes about referees

Everybody who can read the list can write notes about the referees on it, and read everybody else's. Under each referee is a folded N notes row; opening it shows the notes, oldest first, each with the email address of whoever wrote it and when, and a box to add one.

  • Attribution is the signed-in address, taken from core's session and never from the form — it is the only identity anybody has here, being what they signed in with.
  • Only the people on Referee access see or write notes, the same as the list itself. Somebody taken off it loses both on their next page load; their notes stay, still attributed to them.
  • Authors can delete their own notes, and nobody else's. Notes are not edited: delete and write again. An admin can delete any note from the referee's page under Referees.
  • Notes are plain text, up to 2,000 characters, and go when their referee is removed.

Uploading a CSV

Referees → Upload a CSV adds referees in bulk from a spreadsheet saved as CSV:

  • one referee per row, the name in the first column, the number in the second and optionally the source in the third — or a header row, in which case columns headed Name/Referee, Phone/Mobile/Telephone and Source are found wherever they are, as is a Location/Area/ Town column, and any other columns are ignored. A location is only read from a headed column: without a header the third column is the source, and nothing is guessed from a fourth;
  • a Source box on the upload form covers every row without a source of its own; left blank, the file's name is recorded (CSV upload: referees.csv);
  • comma, semicolon or tab delimited, with or without Excel's byte-order mark;
  • up to 1 MB.

Imported referees go straight on the list. Nothing already there is added twice, including a suggestion still waiting, so uploading an updated copy of the same file only adds the new rows. Rows without a name are skipped and reported by line number. An import never changes or removes a referee already on the list.

Calling, texting and WhatsApp

Each number carries three links, on the published list and on the admin's own Referees screen: the number itself dials it, Text opens a new message to it, and WhatsApp opens a chat with it. On the plain list none of them writes the message; the one place a message is written for you is a fixture request, below.

The stored number is never rewritten. A club types a referee's number the way that referee gives it out, and that is what both screens show; the links are built from it at render time, so correcting a number corrects all three.

Calling and texting take the number much as it was typed, which is why they always work: the handset is in the same country as the club. WhatsApp cannot — it identifies a person by their number in full international form, so 07700 900123 has to become 447700900123 or the link opens a chat with nobody.

A number typed the national way is always read as a UK one. 44 is a constant, not a setting: this club's referees are in the UK, which is the same flat assumption the plugin already makes about pounds. A number typed in full keeps its own country, so a referee abroad is reached by typing their number in full:

Typed Read as
07700 900123 UK — the trunk 0 becomes 44
7700 900123 UK with the trunk 0 left off
447700900123 UK, + left off
+44 7700 900123 already international
0044 7700 900123 already international, dialled the long way
+353 87 123 4567 already international — kept as typed, not given a 44

A number that comes out too short or too long to be anybody's gets no WhatsApp link at all, rather than a wrong one. It keeps its call and text links, which need no country.

Finding a referee for a fixture

A referee user can ask the club's referees to cover a fixture, from the same referee list page:

  1. They enter the fixture — team (suggested from the club's squads), opponent, date, kick-off, venue and any notes — under Find a referee for a fixture.
  2. They send it. The fixture's own view lists the referees again, each with a Text and a WhatsApp link that open a message already written — "Hi Sam, are you free to referee U12 Reds v Rovers on Saturday 4 October, 10:30 kick-off? … Tap here to accept: …" — ready to send and still editable. There is also a version of the message for a group chat.
  3. The first referee to tap Accept gets it. The link goes to /referees/fixture/, a public page (the referee has no account, and is never asked to sign in) showing only what the message already said, with an Accept button.
  4. Everybody else is told it has gone. Any other referee opening their link afterwards is thanked and told another referee has accepted — not who.
  5. The person who asked is emailed with who accepted, and the number to reach them on.
Sent to one referee Their link names them, signed with an HMAC, so tapping Accept records them without typing anything — and editing the number in the link does not accept in somebody else's name
Posted to a group The link names nobody, and the page asks whoever opens it for their name (and, optionally, a number)
Two referees at once The accept is a single UPDATE … WHERE status = 'open', so exactly one of them changes the row — on MySQL and on SQLite alike. The other is told it has been covered
Changed their mind The person who asked can withdraw an open request; anyone opening the link is then told it is no longer needed. An accepted one cannot be withdrawn from the page — that is a conversation with the referee

The link is the credential: every fixture has a 128-bit random token, and nothing else identifies it publicly. A referee user sees only the fixtures they asked for, and loses them along with the list if the club removes their address. Fixtures dated before today drop off their list.

The wording of the message can be changed with the crf_fixture_message filter.

The page

Ordinary WordPress pages are provisioned on activation — referees, with referee-list and fixture beneath it — because a hierarchical page's permalink already is the path that was asked for. Everything follows from them being real pages: the theme draws the header, the footer and the navigation as it does everywhere else, and the club can write whatever it likes above the list in the block editor. The list itself is appended at render time rather than pasted into the content, so it cannot be deleted by accident.

The plugin ships no front-end stylesheet. Both pages are plain HTML — tables, forms, lists, <details> — styled by the club's theme like any other page content, so they look like the rest of the site and there is nothing of ours to clash with it. Elements keep their crf- class names, so a theme or the Customizer's Additional CSS can still target them.

A page already sitting on either slug is adopted rather than duplicated, so a club that has already built its own /referees/ page keeps it.

Deleting the plugin drops its tables and leaves those pages alone — they are content, and content is the club's.

Admin

Referees, gated on crf_manage, which is granted to WordPress administrators and to Club Player Manager's club admins (cpm_manage_all). Two screens:

  • Referees — suggestions waiting for approval, the list, an add form and a CSV upload under it, and a page per referee to edit, approve or remove one, and to read or delete the notes club members have written about them.
  • Referee access — the addresses allowed to read the published list.

Offering the list on the account page

The link to the referee list is contributed to core's account page through cpm_account_links, so a referee finds it where they sign in rather than having to keep the URL. It is offered only to addresses on the referee list.

That panel is off by default: tick More from the club under Club Management → Settings → Guardian account page for it to appear at all.

Development

Tests and linting run from the repo root, like every plugin here:

composer test:refs      # unit + integration
composer lint:refs      # phpcs

The repository's own README and docs/MONOREPO.md cover the toolchain, the release process and how the plugins here share code.