Summary
mam-main organizes into 22 subsystems across ~145 PHP files. The shape splits cleanly into three layers:
- Cross-cutting infrastructure (Phase 1 of the refactor) —
request-context,account-code,migrations,repositories. Load-bearing for every other subsystem. - Pipelines (Phase 2) —
phone-data(mobile JSON),notifications-manager/dispatcher(email/SMS/push). Single entry points for the platform’s two highest-traffic operations. - Feature subsystems —
app-settings,forms-manager,content-classes,user-roles,push-notification-manager,setup-wizard,publish-app,setcron-manager,plugin-update-manager, plus the smaller subsystems.
Every sibling plugin extends mam-main exclusively through filters and actions. mam-main never imports sibling-plugin classes.
Cross-cutting abstractions
MAM_Current_Request (includes/request-context/)
Per-request context object. Replaces the legacy $mam_user / $mam_user_id / $mam_user_role / $is_cloning globals. Accessed via mam_current_request(). Initialized once per request near the top of the bootstrap; subsystems read identity state from it instead of mutating shared globals.
$req = mam_current_request();
$user_id = $req->user_id();
$role = $req->user_role();
$is_cloning = $req->is_cloning();
MAM_Account_Code_Manager (includes/account-code/)
Centralized owner of the local-app-account_code option (frozen public contract — customer enrollment ID). All reads/writes route through this class. Includes a transient cache and graceful-degradation hooks for enrollment-server unreachability.
Encryption-at-rest is not yet implemented; the option remains plaintext.
MAM_Migration_Tasks (includes/migrations/)
Framework for safe option/table renames. Used at boot time only to keep legacy callers (in unmaintained customer-site plugin copies) reading the old keys via alias filters.
| Helper | Purpose |
|---|---|
register_option_alias($old, $new) |
pre_option + pre_update_option filter pair so legacy reads continue to work |
register_option_renames($pairs) |
Batch alias registration |
rename_option($old, $new) |
Copy + delete with idempotency guard |
rename_table($old, $new) |
ALTER TABLE RENAME with collision check |
rename_postmeta_key($old, $new) |
Bulk postmeta UPDATE |
Repositories (includes/repositories/)
DB abstraction layer. One repository per owned table. All $wpdb calls against owned tables route through repositories.
| Repository | Owned table |
|---|---|
MAM_Mail_Queue_Repository |
wp_mam_mail_queue |
MAM_Mail_Attachments_Repository |
wp_mam_mail_attachments |
MAM_Sms_Queue_Repository |
wp_mam_sms_queue (unified PN+SMS) |
MAM_Notification_History_Repository |
wp_mam_notification_history |
MAM_Phone_Token_Repository |
wp_mam_phone_tokens |
MAM_Debug_Items_Repository |
wp_mam_debug_items |
Pipelines
Phone Data Pipeline (includes/phone-data/)
Entry points:
- AJAX:
local_app_get_phone_data(frozen public contract) - Function:
mam_get_phone_data()(delegates toMAM_Phone_Data_Pipeline::run())
Phases:
phase_auth → phase_settings → phase_content → phase_finalize
identity load settings, fire home_cats injection,
hydration cache decision, mam_get_ output cleanup,
short-circuit phone_data_ apply final-block
check before_send, overrides
inject form_data,
nonce, etc.
State is carried by MAM_Phone_Data_Context. At the legacy filter boundary it converts to/from an array so existing mam_get_phone_data_before_send subscribers keep working untouched.
See Phone data pipeline overview and Phone data pipeline phases.
Notification Dispatcher (includes/notifications-manager/dispatcher/)
do_action('mam_notification_send_message', $msg)
│
▼
MAM_Notification_Dispatcher::dispatch()
│
┌────────────┼────────────┐
▼ ▼ ▼
Email SMS Push
Sender Sender Sender
│ │ │
wp_mail() Twilio APNs / FCM
or queue or queue via MAM_PushDispatcher
Channel routing is driven by the mam_notification_list filter (68+ subscribers). Each registered type declares which channels it supports plus per-channel templates. Queueing decision: count > 10 OR global cron enabled.
See Notifications overview.
Boot order
MAM_Main_Manager::__construct() calls 15 register_*() methods in a fixed order (mam-main.php:503–517):
register_core— request context, account code, migrationsregister_database— repositories, table-existence checksregister_app_users— user-roles bootstrapregister_user_management— login, profile, phone-coderegister_forms— Gravity Forms integrationregister_tab_bar— tab manager filter wiringregister_app_settings— admin UIregister_push_notifications— APNs/FCM senders, token registrationregister_notifications— dispatcher hook + admin UIregister_publishing— app-submit pipelineregister_cron— mail/PN/SMS cron + custom cron registrationregister_setup_wizard— onboarding + enrollmentregister_admin_ui— top-level menu + dashboard widgetsregister_plugin_updates— software entitlements + update checksregister_entitlements— per-plugin licensing
The constructor itself contains zero direct add_action/add_filter calls. The crash-reporter loads earlier still, before plugins_loaded, via a top-level include in mam-main.php so it can capture errors during boot.
Dependency graph
─── Cross-cutting (Phase 1) ─────────────────────────────
request-context ← used by ~everything
account-code ← enrollment-aware code
migrations ← boot-time only
repositories ← consumed by mail-cron, notifications, push,
helper-classes, grid-classes
─── Pipelines (Phase 2) ─────────────────────────────────
phone-data ← called by app-connect; depends on request-context,
app-settings, content-classes, forms-manager, user-roles
notifications-manager → repositories, push-notification-manager,
mail-cron-manager
─── Feature subsystems ──────────────────────────────────
app-connect → phone-data
app-settings → content-classes, helper-classes
user-roles → app-connect, app-settings, notifications,
forms-manager
push-notification-manager → request-context, MAM_Phone_Token_Repository
forms-manager → app-settings, user-roles, content-classes
setup-wizard → app-settings, user-roles, plugin-update-manager,
account-code
plugin-update-manager → setup-wizard, support-manager, account-code
publish-app → app-settings, helper-classes
grid-classes → repositories, request-context
helper-classes → app-settings, user-roles, content-classes
content-classes → app-settings, app-connect, user-roles
Sibling-plugin extension surface
The primary surfaces a sibling plugin reaches for:
| Hook | Purpose | Typical subscriber count |
|---|---|---|
mam_get_phone_data_before_send |
Inject content into the mobile JSON payload | ~70 |
mam_notification_send_message |
Fire a notification (email/SMS/push) | ~30 fire sites |
mam_notification_list |
Register a notification type with templates | 68+ |
mam_tab_manager |
Enrich a button definition | ~43 |
mam_app_settings_get_setting |
Read a setting (with fallback chain) | 100+ call sites |
mam_cron_manager |
Register a custom cron task | 25+ |
Priority conventions for mam_get_phone_data_before_send:
- 1 — base settings
- 10 — default cohort (most sibling plugins)
- 99–100 — late-running enrichments (faceted-search, localize, payment gateways)
- 199 — form-state validation
- 999+ — niche overrides (weather, delivery, payment processors)
- 1000 —
MAM_Main_Manager::manage_phone_data(home_catsinjection); must run after all priority-<1000 subscribers - 1001+ — use-case-specific total overrides
See Priority conventions for phone-data subscribers.
Where to add a feature
| Adding… | Goes in… |
|---|---|
| A new content-type handler (button class) | includes/content-classes/ + register in content_class_manager |
| A new notification type | Hook mam_notification_list — templates auto-loaded |
| A new mobile-app screen element | Hook mam_get_phone_data_before_send at appropriate priority |
| A new admin grid | Extend WP_List_Table in includes/grid-classes/ |
| A new DB table mam-main owns | Add a repository under includes/repositories/ |
| A new global identity field | Add accessor to MAM_Current_Request |
| A new cross-cutting setting | Access via the mam_app_settings_get_setting filter |
If your feature crosses subsystem boundaries, the answer is almost always “add a new filter and let subscribers register against it” — that’s how the entire plugin scales.
Verification
This article was last verified against:
- Plugin:
mam-mainv2.1.11 mam-main/ARCHITECTURE.mdmam-main/REFACTOR_PLAN.mdmam-main/mam-main.php(boot order)
Re-verify whenever a new subsystem is added, a register_*() method is added or reordered in MAM_Main_Manager::__construct(), or a cross-cutting abstraction’s public interface changes.
Related articles
- Plugin: mam-main
- Phone data pipeline overview
- Notifications overview
- Settings cascade overview
- Frozen public contracts reference
- Extending mam-main: developer guide
Metadata
| Field | Value |
|---|---|
| Article type | Plugin Overview |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Category | Getting Started |
| Audience | PHP developer |
| Last verified | 2026-05-02 |
