LMS resource sync
The resource library — training materials, e-learning modules, events —
lives in Moodle. This sync pulls it into the resources custom post type in
batches so WordPress can list, filter and search it, while the resource
itself still opens in Moodle.
Why it exists
Section titled “Why it exists”Members browse and search the library on the marketing site, filtered by topic, competency level, market and format. Moodle cannot render those pages, and querying it live on every page load would be far too slow and would break under page caching. So the metadata is mirrored into WordPress and refreshed by a manually triggered background sync.
Entry points
Section titled “Entry points”| Path | What it does |
|---|---|
wp-content/themes/supplychainschool/inc/routing.php:30 | Rewrite ^fetch-resources → sync control page |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:2530 | scss_show_status_page() — the HTML status/control UI |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1559 | init_resource_sync() — start |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1522 | stop_sync() |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1541 | reset_sync() |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1660 | process_resource_batch($offset) — the WP-Cron worker |
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1783 | process_single_resource() |
wp-content/themes/supplychainschool/inc/admin/resources.php:57 | “Fetch Resources” button on the Resources list screen |
wp-content/themes/supplychainschool/inc/cpt.php:257 | The resources CPT |
How it works
Section titled “How it works”Control surface
Section titled “Control surface”/fetch-resources is not a WordPress page — it is a rewrite handled in
routing.php:75:
| Request | Action |
|---|---|
GET /fetch-resources | Render the status page (log, stats, progress, buttons) |
POST /fetch-resources?start_sync | init_resource_sync() |
POST /fetch-resources?stop_sync | stop_sync() |
POST /fetch-resources?reset_sync | reset_sync() |
Each POST returns JSON. Access control for these routes is handled in the
case 'resources': block of routing_requests() (routing.php:75) — that
block is the single place to add or change it.
inc/admin/resources.php:57 adds a “Fetch Resources” button to the CPT list
screen that just links to /fetch-resources.
The batch loop
Section titled “The batch loop”flowchart TD
A[init_resource_sync] --> B[clear status/stats, reset last_imported_id]
B --> C[wp_schedule_single_event +5s scss_process_resource_batch 0]
C --> D[process_resource_batch offset]
D --> E[POST Moodle action=get_filtered_resources limit offset]
E -->|error| F[status=error, retry same offset in 30s]
E --> G[process_single_resource for each]
G --> H{next_offset < total}
H -->|yes| I[schedule next batch in 10s]
I --> D
H -->|no| J[status=completed, resources_last_updated set]
Batch size is 25 by default ($resource_request_limit, LMSClient.php:242)
but 200 on blogs 6 and 7 (LMSClient.php:1663).
State is kept in options, not transients, so it survives cache flushes:
| Option | Contents |
|---|---|
scss_resource_sync_status | status, started_at, total_resources, processed, current_offset, last_error |
scss_resource_sync_log | Rolling log, last 500 entries |
scss_resource_sync_stats | updated, skipped, errors |
Plus ACF options last_imported_id (the resume offset) and
resources_last_updated.
Per resource
Section titled “Per resource”process_single_resource() (:1783):
- The Moodle id is
$resource->rlidon Moodle 4,$resource->cmidon Moodle 3 (:1785). - Find the existing post by the
cmidmeta value, newest first. - Skip if
$resource->timemodified <= saved timemodified— this is what makes re-runs cheap. - Otherwise insert or update the post and its taxonomy terms.
Taxonomy helpers: process_departments(), process_level(),
process_markets(), process_issues(), process_resource_types()
(:1860–:1968), all funnelling through insert_or_update_term()
(:2168) which creates terms on demand. process_issues() takes a parent
department id, so issues are nested under departments in topics.
The admin list screen also gets a resource_id column showing the cmid
(functions.php:396) and search is extended to match post meta
(inc/admin/resources.php) so you can find a resource by its Moodle id.
Configuration
Section titled “Configuration”LMS_URLand the Moodle REST endpoint — derived per blog, see [[moodle-sso-and-sessions]].- ACF option
moodle_version(3 or 4) decides which id field is read. - ACF options
last_imported_id,resources_last_updated. - WP-Cron must be running. On WP Engine,
wp-cron.phpis triggered externally; if cron is disabled the sync starts and never progresses past the first batch.
Invariants and gotchas
Section titled “Invariants and gotchas”reset_sync()is destructive: it clears the log, the status and the stats, and setslast_imported_idback to 0. Prefer start over reset.reset_sync()deletes the log,init_resource_sync()deliberately does not (comment at:1567). Use start, not reset, when you want history.$offsetis undefined insideprocess_single_resource()(LMSClient.php:1805,if ($offset % 50 === 0)), so the skip-logging branch evaluates againstnull. It logs on every skip in practice rather than every 50th — the opposite of the intent in the comment above it.- A failed batch retries the same offset forever, every 30 seconds, with
status = 'error'. There is no attempt cap. A permanently failing offset is an infinite cron loop; watch for it in the log. - The sync writes
update_option('scss_resource_sync_status', …)once per resource (:1710), so a 200-item batch does 200 option writes. total_resourcesis only captured on the first batch (offset === 0). If the library grows mid-sync the loop stops early or over-runs.- Batch size 200 on blogs 6 and 7 is hard-coded with no comment. Blog 6 is the Carbon Calculator site, which has no resource library — that branch is probably vestigial.
update_resources()(:1970) is just an alias forinit_resource_sync().- There is no scheduled recurring resource sync.
scss_process_resource_batchis only ever scheduled as a single event by a running sync. Resources go stale until someone visits/fetch-resources. (Members sync is daily — see [[member-directory-and-stats]].)
Changing it safely
Section titled “Changing it safely”- New resource metadata: extend
process_single_resource()and add the taxonomy throughinsert_or_update_term()so terms are created on demand. Register any new taxonomy ininc/cpt.php:372alongsidetopics,level,markets,types. - Changing batch size:
$resource_request_limitatLMSClient.php:242. Larger batches mean fewer HTTP calls but a longer single PHP request — watch the host’smax_execution_time. - All four
/fetch-resourcesactions dispatch from the singlecase 'resources':block inrouting_requests()(routing.php:75), so one guard there covers all of them. The status page’s inline JS (LMSClient.php:2830onward) posts withfetch()and would need a nonce threaded through if you add one. - Verify by hand:
POST /fetch-resources?start_sync, then reload the status page and watchprocessedclimb and log entries appear. Confirm a second run reports mostlyskipped. - Deliberately not abstracted: options rather than transients for state. Transients can be evicted by an object cache mid-sync, which would lose the offset.
None.
Related: [[member-directory-and-stats]], [[moodle-sso-and-sessions]], [[site-search]], [[acf-flexible-content-blocks]].