Phone data pipeline overview

What it is

MAM_Phone_Data_Pipeline (includes/phone-data/) is the canonical builder of the JSON payload returned to the mobile app. It replaces the legacy 1900-line mam_build_phone_data_array() god function (eliminated in PR #27).

The pipeline runs in 4 phases. State is carried by MAM_Phone_Data_Context — a value object passed between phases. At the legacy filter boundary the context converts to/from an array so existing mam_get_phone_data_before_send subscribers (~70 of them) keep working untouched.


Entry points

Entry Use
AJAX action local_app_get_phone_data Primary — what the mobile app calls
AJAX action mam_get_phone_data Modern alias — same handler
Function mam_get_phone_data($return_data, $single_button_id) Direct PHP call (used by some sibling plugins)
Function local_app_get_phone_data($return_data, $single_button_id) Legacy alias — same handler

When called via AJAX: send a JSON response and exit. When called as PHP with $return_data = true: return the assembled array.

⚠️ mam_get_phone_data() is the most-frozen function in the codebase. Don’t rename it, don’t change its signature, don’t change its return shape. Frozen contract — do not rename.


The four phases

phase_auth

Resolves identity, hydrates MAM_Current_Request. After this phase the pipeline knows who the user is, what role they have, whether they’re cloning, and whether the request is an authenticated app request.

Default subscriber: mam_user_roles_manager::manage_phone_data (priority 10).

phase_settings

Loads app settings (theme, layout, button arrays). Evaluates the cache-bypass decision (cloning admins and users with disable_caching get a fresh build). Fires the mam_local_app_data short-circuit hook — if any subscriber returns a non-empty array, the pipeline returns it immediately and skips the remaining phases.

Default subscriber: mam_app_settings::manage_phone_data (priority 1).

phase_content

The biggest phase. Builds page/button/section data; fires the legacy mam_get_phone_data_before_send filter so ~70 sibling-plugin subscribers can inject their data; injects form_data, nonce, sync_user_meta. The button-rendering loop runs here, calling each content class’s get_data_for_app().

This is where most sibling plugins do their work.

phase_finalize

home_cats injection (the home-screen category structure), output-shaping cleanup (clean_notifications, mam_replace_null_with_empty_string), and final-block overrides (use-case priority-9999 subscribers run here).

Default subscriber: MAM_Main_Manager::manage_phone_data (priority 1000) injects home_cats. Critical contract: must run after every priority < 1000 subscriber.


State carrier — MAM_Phone_Data_Context

A value object holding:

  • The accumulated $data_array
  • The current request context (user, role)
  • Cache flags (bypass_caching, cursor_value)
  • Performance tracers
  • Final-block overrides

At the legacy mam_get_phone_data_before_send boundary, the context converts to/from an array. Subscribers see exactly the same $data_array shape they always did.


Frozen public contracts in this pipeline

Contract Why
AJAX local_app_get_phone_data Mobile apps call it directly
AJAX mam_get_phone_data Modern alias, also in the field
Function mam_get_phone_data() Direct PHP callers across the suite
Function local_app_get_phone_data() Older mobile clients
Filter mam_get_phone_data_before_send ~70 subscribers; the main extension point
Filter mam_local_app_data Short-circuit hook — preserved even with no active subscribers
Filter mam_main_check_initial_form Form-state validation pass

Cache: cursor mechanism

The phone-data response is large (50–200 KB common). To avoid re-sending unchanged data on every app launch, each section is timestamp-tagged. The mobile app stores a cursor (the last timestamp it has) and sends it on the next request. Server compares against per-section timestamps; sections newer than the cursor are sent in full, older sections are sent as null (mobile keeps its cached copy).

JSON_Cursor_Manager::reset_cursor() invalidates the entire per-user cache when a setting change requires a full rebuild (admin saves a button, image upload, etc.).

See Cursor cache mechanism.


Subscriber priority conventions

For mam_get_phone_data_before_send:

  • 1 — base settings
  • 10 — default cohort (most sibling plugins)
  • 99–100 — late-running enrichments
  • 199 — form-state validation
  • 999+ — niche overrides
  • 1000home_cats injection (MAM_Main_Manager::manage_phone_data) — must run after all <1000
  • 1001+ — use-case-specific total overrides

See Priority conventions for phone-data subscribers.


Verification harness

tests/snapshot/snapshot-phone-data.php (Phase 1.4 tool) captures JSON fixtures for diff-based regression checks. Run on every PR that touches the pipeline; non-zero diff means the contract changed.


Gotchas

  • Don’t rename anything frozen. Renaming the AJAX action, the function, or the filter immediately breaks customer apps in the field.
  • Cache invalidation is hard. Calling JSON_Cursor_Manager::reset_cursor() defeats the cache for that user; calling it on every update_option defeats it globally. Use sparingly.
  • mam_local_app_data short-circuit has no active subscribers today, but the contract must remain — a future use-case plugin might need to fully replace the response.
  • Priority 1000 contracthome_cats must run last among standard subscribers. Don’t register at priority 999 something that depends on home_cats.

  • Phone data pipeline phases
  • Cursor cache mechanism
  • Mobile JSON shape
  • Priority conventions for phone-data subscribers
  • AJAX action: local_app_get_phone_data
  • Hook: mam_get_phone_data_before_send
  • Hook: mam_local_app_data
  • Hook: mam_main_check_initial_form
  • Frozen public contracts reference

Metadata

Field Value
Article type Plugin Overview
Plugin slug mam-main
Applies to plugin version 2.1.11+
Category Plugin Reference
Audience PHP developer
Last verified 2026-05-02
Contents

    Need Support?

    Can’t find the answer you’re looking for? Don’t worry we’re here to help!