Member directory and stats
Two smaller Moodle syncs: the member/partner directory (companies in the School, with logos, levels, topics and markets) and the headline statistics shown on marketing pages. Unlike the resource sync, the member sync runs daily on WP-Cron.
Why it exists
Section titled “Why it exists”The “our members” and partner grids are a sales asset and change as companies join or move between membership levels, so they need to track Moodle without an editor copying logos across. The stats (“X members, Y resources, Z users trained”) are the same data summarised, and are cached hard because they appear on the homepage.
Entry points
Section titled “Entry points”| Path | What it does |
|---|---|
wp-content/themes/supplychainschool/inc/routing.php:38 | Rewrite ^update-members → LMSClient::update_members() |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1977 | update_members() — resumes from the last page |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1989 | iterate_members($page, $count) — the paging worker |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1483 | get_stats() — cached stats fetch |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:2230 | lms_get_stats() |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:2360 | lms_refresh_stats_cache() — the nightly refresh |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:2934 | Cron hook registrations |
wp-content/themes/supplychainschool/inc/cpt.php:29 | The member CPT plus member_level and member_tag taxonomies |
wp-content/themes/supplychainschool/inc/components/fc-member-grid.php | The member grid block |
wp-content/themes/supplychainschool/inc/components/fc-partners.php | The partners block |
wp-content/themes/supplychainschool/partials/member-card.php | Card markup |
How it works
Section titled “How it works”Scheduling
Section titled “Scheduling”Registered at LMSClient.php:509-514 and hooked at :2934:
| Hook | Schedule | Callback |
|---|---|---|
scss_import_members | daily | LMSClient::update_members |
scss_process_resource_batch | single events only | LMSClient::process_resource_batch |
lms_refresh_stats_cache | daily at 03:30 local | lms_refresh_stats_cache |
GET /update-members triggers the member sync on demand and returns JSON
(routing.php:134).
Member sync
Section titled “Member sync”update_members() reads the ACF option members_last_imported and starts
from the next page, so a partial sync resumes rather than restarting.
iterate_members() then, per page:
- Check the failure backoff transient
lms_members_failure_backoff. If set, reschedule for +300s and bail. GETMoodleaction=get_directory&page=N.- On failure, increment
lms_members_consecutive_failures; at 3 consecutive failures set a 15-minute backoff. Save the current page asmembers_last_importedand stop. - On success clear both transients.
- Stop after 10 pages in one run (
$count > 10,:2033). - Store
members_last_imported—0when the last page is reached, so the next daily run starts over from page 1. - For each member: find the
memberpost by theapi_idmeta, update or insert, then write ACF fieldsapi_id,description,member_logo,website—array_filter()ed first, so empty values are never written and a cleared field in Moodle keeps its old WordPress value. - Terms:
member_levelfrom$member->level(created if missing), thentopicsandmarketsfrom$member->topic_market, andmember_tag, each viainsert_or_update_term().
Helpers array_flatten() and process_api_arrays() (:2136, :2149)
normalise the nested arrays Moodle returns.
Three layers of caching:
get_stats()(:1483) returns thelms_statstransient if present.- On a miss it calls Moodle
action=get_statsand caches for 24 hours. - On failure it sets
lms_stats_failure_backofffor 15 minutes and returns the stale transient (ornull), so a Moodle outage does not mean a hammering loop or a fatal on the homepage.
lms_refresh_stats_cache() (:2360) is the nightly warm-up at 03:30, and
lms_schedule_stats_refresh() (:2495) also schedules a one-off refresh
+60s in some paths. Both contain a comment about “registered users which has
been moved via array_swap function above” (:2326, :2464), suggesting the
stat ordering is massaged for display.
lms_user_info() (:2192) is the helper the front end uses for the
logged-in user’s own details.
Configuration
Section titled “Configuration”LMS_URLper blog — see [[moodle-sso-and-sessions]].- ACF option
members_last_imported— the resume cursor. - Transients:
lms_stats,lms_stats_failure_backoff,lms_members_failure_backoff,lms_members_consecutive_failures. - WP-Cron must be running for the daily syncs.
Invariants and gotchas
Section titled “Invariants and gotchas”iterate_members()stops after 10 pages per run. With a daily schedule, a directory of more than 10 pages takes several days to fully refresh. If members appear stale, count the pages before suspecting a bug.$countis passed as0fromupdate_members()and incremented per recursion, but the recursive call happens after term assignment — check the tail ofiterate_members()before assuming the paging depth is what you expect.array_filter($fields)drops empty values (:2058), so clearing a description or logo in Moodle does not clear it in WordPress. Fields only ever get overwritten with non-empty values.- On request failure the current page is saved as the cursor
(
:2023), so the next run retries that page rather than skipping it — but it also means a permanently failing page blocks all later pages indefinitely. wp_insert_term()returns an array, not an object;iterate_members()reads$level->term_idfrom it (:2093), which works forget_term_by()but not for a freshly inserted term. A brand new membership level may not be assigned on the run that creates it.- Stats are cached for 24 hours and refreshed nightly, so a stats change in
Moodle can take a day to appear. Clearing the
lms_statstransient is the fast path. - The
memberCPT is'public' => false, so members have no front-end URL — they only render inside blocks. - Member logos are stored as ACF values from a Moodle URL, not sideloaded into the media library.
Changing it safely
Section titled “Changing it safely”- New member field: add it to the
$fieldsarray initerate_members()(:2051) and topartials/member-card.php. Remember thearray_filter()behaviour — if the field must be clearable, write it outside that array. - New member taxonomy: register it in
inc/cpt.php:29alongsidemember_level/member_tag, then assign withinsert_or_update_term(). - Changing the page cap:
$count > 10at:2033. Raise it only if the host will tolerate a longer request; the sync is synchronous within one cron run. - Stats: change the cache duration at
:1510and the backoff at:1487together — a short cache with a long backoff means the homepage shows nothing during an outage. - Verify by hand:
GET /update-members, then check thememberposts andmembers_last_imported. For stats, delete thelms_statstransient and reload a page that shows them. - Deliberately not abstracted: the backoff/consecutive-failure pattern is
duplicated between the member sync and
get_stats(). They tolerate failure differently — members must not skip a page, stats must not block a page render.
None.
Related: [[lms-resource-sync]], [[moodle-sso-and-sessions]], [[acf-flexible-content-blocks]].