Skip to content

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.

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.

PathWhat it does
wp-content/themes/supplychainschool/inc/routing.php:30Rewrite ^fetch-resources → sync control page
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:2530scss_show_status_page() — the HTML status/control UI
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1559init_resource_sync() — start
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1522stop_sync()
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1541reset_sync()
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1660process_resource_batch($offset) — the WP-Cron worker
wp-content/themes/supplychainschool/inc/classes/LMSClient.php:1783process_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:257The resources CPT

/fetch-resources is not a WordPress page — it is a rewrite handled in routing.php:75:

RequestAction
GET /fetch-resourcesRender the status page (log, stats, progress, buttons)
POST /fetch-resources?start_syncinit_resource_sync()
POST /fetch-resources?stop_syncstop_sync()
POST /fetch-resources?reset_syncreset_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.

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:

OptionContents
scss_resource_sync_statusstatus, started_at, total_resources, processed, current_offset, last_error
scss_resource_sync_logRolling log, last 500 entries
scss_resource_sync_statsupdated, skipped, errors

Plus ACF options last_imported_id (the resume offset) and resources_last_updated.

process_single_resource() (:1783):

  1. The Moodle id is $resource->rlid on Moodle 4, $resource->cmid on Moodle 3 (:1785).
  2. Find the existing post by the cmid meta value, newest first.
  3. Skip if $resource->timemodified <= saved timemodified — this is what makes re-runs cheap.
  4. 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.

  • LMS_URL and 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.php is triggered externally; if cron is disabled the sync starts and never progresses past the first batch.
  • reset_sync() is destructive: it clears the log, the status and the stats, and sets last_imported_id back 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.
  • $offset is undefined inside process_single_resource() (LMSClient.php:1805, if ($offset % 50 === 0)), so the skip-logging branch evaluates against null. 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_resources is 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 for init_resource_sync().
  • There is no scheduled recurring resource sync. scss_process_resource_batch is 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]].)
  • New resource metadata: extend process_single_resource() and add the taxonomy through insert_or_update_term() so terms are created on demand. Register any new taxonomy in inc/cpt.php:372 alongside topics, level, markets, types.
  • Changing batch size: $resource_request_limit at LMSClient.php:242. Larger batches mean fewer HTTP calls but a longer single PHP request — watch the host’s max_execution_time.
  • All four /fetch-resources actions dispatch from the single case 'resources': block in routing_requests() (routing.php:75), so one guard there covers all of them. The status page’s inline JS (LMSClient.php:2830 onward) posts with fetch() 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 watch processed climb and log entries appear. Confirm a second run reports mostly skipped.
  • 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]].