# Pro ↔ Lite Bridge

How the Pro plugin overlays the Lite plugin: boot order, hook contract, element/extension injection, and the Extender trait pattern that lets Pro enhance Lite widgets without forking them.

## Context

Essential Addons Pro is not a standalone WordPress plugin. It's installed and activated separately, but at runtime it overlays the Lite plugin (`essential-addons-for-elementor-lite`). Pro depends on:

- Lite's class autoloader for shared base classes (`\Essential_Addons_Elementor\Elements\Login_Register`, `\Essential_Addons_Elementor\Traits\Helper`, etc.)
- Lite's `Asset_Builder` for per-page CSS/JS enqueuing
- Lite's hook surface for both initialization (`eael/before_init`) and feature extension (`apply_filters('eael_progressbar_layouts', ...)` and ~120 others)

This document captures the verified runtime contract — what Lite emits, what Pro consumes, and what invariants must hold so Lite continues to work when Pro is deactivated.

## Verified facts

### Boot order — Lite drives, Pro overlays

Lite's `plugins_loaded:100` hook fires `Lite\Classes\Bootstrap::__construct`, which contains the exact emission sequence (`includes/Classes/Bootstrap.php:100-114` in Lite):

```php
// init modules
$this->installer = new WPDeveloper_Plugin_Installer();

// before init hook
do_action('eael/before_init');                                                      // ← Pro hooks here

// search for pro version
$this->pro_enabled = apply_filters('eael/pro_enabled', false);                      // ← Pro returns true

// elements classmap
$this->registered_elements = apply_filters('eael/registered_elements',
    $GLOBALS['eael_config']['elements']);                                           // ← Pro merges

// extensions classmap
$this->registered_extensions = apply_filters('eael/registered_extensions',
    $GLOBALS['eael_config']['extensions']);                                         // ← Pro merges
```

### Pro entry — `eael/before_init` callback

`essential_adons_elementor.php:48-64`:

```php
add_action( 'eael/before_init', function () {
    // compatibility with lite
    if ( version_compare( EAEL_PLUGIN_VERSION, '4.6.3', '<=' ) ) {
        return;
    }

    $GLOBALS['eael_pro_config'] = require_once EAEL_PRO_PLUGIN_PATH . 'config.php';

    if ( class_exists( '\Essential_Addons_Elementor\Pro\Classes\Bootstrap' ) ) {
        \Essential_Addons_Elementor\Pro\Classes\Bootstrap::instance();
    }
} );
```

Three invariants:

1. **Version gate** — Pro bails if `EAEL_PLUGIN_VERSION <= 4.6.3` (the operator is `<=`, not `<`, so 4.6.3 itself fails the check). To support a newer Lite floor, bump this comparison **and** the `Requires Plugins` header.
2. **Config loaded into a separate global** — `$GLOBALS['eael_pro_config']`. Lite's config global is `$GLOBALS['eael_config']`. The two never merge except through the injection filters below.
3. **`class_exists` guard** — defends against partial autoload during error recovery. Removing it has caused fatal-on-deactivation tickets historically.

### Bootstrap singleton — what Pro registers

`includes/Classes/Bootstrap.php:46-58` (constructor):

```php
private function __construct()
{
    // mark pro version is enabled
    add_filter('eael/pro_enabled', '__return_true');

    // injecting pro elements
    add_filter('eael/registered_elements',  [$this, 'inject_new_elements']);
    add_filter('eael/registered_extensions', [$this, 'inject_new_extensions']);
    add_filter('eael/post_args',             [$this, 'eael_post_args']);

    // register hooks
    $this->register_hooks();
}
```

`register_hooks()` (lines 60+) registers ~121 Extender filter/action callbacks — see "Extender trait" below.

### Element injection — `array_merge_recursive`

`includes/Classes/Bootstrap.php:255-263`:

```php
public function inject_new_elements($elements)
{
    return array_merge_recursive($elements, $GLOBALS['eael_pro_config']['elements']);
}

public function inject_new_extensions($extensions)
{
    return array_merge_recursive($extensions, $GLOBALS['eael_pro_config']['extensions']);
}
```

**Why `array_merge_recursive` and not `array_merge`:** Lite's element entries have nested `dependency.css[]` and `dependency.js[]` arrays. A flat `array_merge` would replace these arrays when Pro's entry happens to have the same top-level slug. `array_merge_recursive` preserves both. (Today no slug overlaps occur, but the recursion is defensive.)

### Lite's pro-detection — `eael/pro_enabled`

Lite checks this filter in many widgets to decide whether to render upsell HEADING controls or expect Pro to inject the real controls. Sample call-sites (`grep apply_filters.*eael/pro_enabled` in Lite):

- `includes/Classes/Bootstrap.php:108` — sets `$this->pro_enabled` for the request
- `includes/Traits/Controls.php:2299` — gate around shared Pro-only control blocks
- `includes/Extensions/Image_Masking.php:283,502` — Pro feature gate
- `includes/Extensions/Promotion.php:13` — upsell-only extension; bails if Pro is enabled
- `includes/Extensions/Liquid_Glass_Effect.php:174,268,347` — three Pro feature gates
- `includes/Elements/Adv_Tabs.php:525`, `includes/Elements/Data_Table.php:111,530,1401` — widget-level gates

**Pro's job:** ensure this filter returns `true` for the whole request. Lite's default is `false`. The implementation is `__return_true` — never replace with a dynamic callback unless Pro genuinely needs to disable itself per-request (no current case justifies it).

### Extender trait — 121 callbacks enhancing Lite widgets

`includes/Traits/Extender.php` is the lynchpin of the bridge. It's a PHP trait used by `Bootstrap`. Hooks registered in `register_hooks()` include (sampled):

| Hook                                                  | Type   | What it does                                                |
| ----------------------------------------------------- | ------ | ----------------------------------------------------------- |
| `add_eael_progressbar_layout`                         | filter | Adds line rainbow / circle fill / half circle / box layouts |
| `eael_pricing_table_styles`                           | filter | Adds 5 Pro pricing table styles                             |
| `fancy_text_style_types`                              | filter | Adds Pro Fancy Text style types                             |
| `eael_ticker_options`                                 | filter | Adds Pro ticker layouts                                     |
| `eael/advanced-data-table/table_html/integration/database` | filter | DB-backed data source for Advanced Data Table          |
| `eael/advanced-data-table/table_html/integration/remote`   | filter | Remote DB-backed data source                           |
| `eael/advanced-data-table/table_html/integration/google`   | filter | Google Sheets data source                              |
| `eael/advanced-data-table/table_html/integration/tablepress` | filter | TablePress integration                               |
| `eael/event-calendar/integration`                     | filter | EventON integration for Event Calendar                      |
| `eael_team_member_style_presets_condition`            | filter | Team Member Pro presets                                     |
| `eael_liquid_glass_effect_*` (multiple)               | action | Pro additions to the Liquid Glass Effect extension          |
| `eael_adv_tab_styles`, `eael_adv_accordion_styles`    | filter | Pro tab/accordion styles                                    |
| `add_pricing_table_settings_control`                  | action | Pro controls injected into Lite's Pricing Table             |
| `add_filterable_gallery_style_block`                  | action | Pro style block for Filterable Gallery                      |
| `add_admin_license_markup`                            | action | Admin-side license UI markup                                |

Three dedicated `*_Extender` traits split out the larger surfaces, all composed into `Bootstrap`:

- `Filterable_Gallery_Extender.php` — Filterable Gallery Pro features
- `Business_Reviews_Extender.php` — Business Reviews + Google Business Profile integration
- `Login_Register_Extender.php` — Social login, password strength, Mailchimp

Pro also imports Lite's `Login_Registration` trait directly via `use \Essential_Addons_Elementor\Traits\Login_Registration;` — the trait composes into `Bootstrap` alongside Pro's own traits.

### Lite Trait imports inside Extender

`includes/Traits/Extender.php:23` aliases Lite's helper trait:

```php
use Essential_Addons_Elementor\Traits\Helper as HelperTrait;
trait Extender {
    use HelperTrait;
    // ...
}
```

This is the canonical pattern for re-using Lite helpers — alias and compose, never copy.

### Globals separation

| Plugin | Config global                      | Set in                                                                  |
| ------ | ---------------------------------- | ----------------------------------------------------------------------- |
| Lite   | `$GLOBALS['eael_config']`          | Lite's main file (loads its `config.php` at plugin entry)               |
| Pro    | `$GLOBALS['eael_pro_config']`      | Pro's main file inside the `eael/before_init` callback                  |

**Critical timing:** `$GLOBALS['eael_pro_config']` does not exist until the `eael/before_init` action fires. Any Pro code that needs config before that hook is too early — the only correct way is to hook into `eael/before_init` itself or anything that fires after.

### Side-channel hooks in Pro's entry file

Beyond `eael/before_init`, Pro's entry registers four other top-level hooks (`essential_adons_elementor.php:71-134`):

| Hook                       | Callback                                                          | When                                           |
| -------------------------- | ----------------------------------------------------------------- | ---------------------------------------------- |
| `wp_loaded`                | `Migration::migrator()`                                           | Every request, after WP is fully loaded        |
| `upgrader_process_complete`| `Migration::plugin_upgrade_hook($upgrader, $options)`             | After plugin update completes                  |
| `admin_notices`            | `Notice::failed_to_load()`                                        | Admin pages, when Lite is missing or too old   |
| `before_woocommerce_init`  | Declares HPOS compatibility via `FeaturesUtil`                    | WooCommerce init                               |
| `admin_init`               | `FigmaImageHandler::register()`                                   | Admin init                                     |

These do **not** depend on `Bootstrap::instance()` and run even if Lite is missing — which is why they're outside the `eael/before_init` callback. The Migration runner is particularly important: it must be safe to call before Lite is loaded.

### The Lite-standalone invariant

When Pro is deactivated:

- Lite's `apply_filters('eael/pro_enabled', false)` returns `false`
- Lite's `apply_filters('eael/registered_elements', ...)` returns Lite's own elements only
- Lite's Pro-feature checks (`if ( apply_filters('eael/pro_enabled', false ) )`) take the false branch, rendering upsell HEADING controls instead of Pro features

**Pro code must never write into Lite-side state that Lite would still need to read** when Pro is gone. The two safe Pro side-effects in Lite-visible scope are:

1. Registering filter/action callbacks (these auto-unregister when Pro is deactivated and its singleton goes out of scope between requests)
2. Writing to its own option-key namespace (Pro options must be self-managed, never reused by Lite)

Pro must **not**:

- Modify Lite options (`eael_save_data`, `eael_setup_wizard`, etc.) — those are Lite-owned
- Define constants Lite reads (Lite reads its own `EAEL_*` constants, not `EAEL_PRO_*`)
- Override Lite's class autoloader entries

## What's missing

These are gaps that affect the bridge contract but are not blockers for current PRs. Each should become its own follow-up issue when prioritized.

1. **No published list of `eael/*` hooks Lite exposes.** Pro's Extender consumes ~121 hooks but there's no single source of truth in Lite. When Lite removes or renames one, Pro silently breaks. Action: index Lite's `apply_filters` / `do_action` call sites in a per-hook table in Lite's `docs/architecture/`.
2. **No symmetric version gate Lite-side.** Lite never refuses to load when Pro is too old; the gate is one-directional. This means a feature that Pro 6.8 needs from Lite 4.6.4 could silently break when a user upgrades Lite while Pro stays at an older version. Action: add a Pro-version check inside Lite that surfaces a notice when Pro is below the supported floor.
3. **Extender file is monolithic.** ~121 callbacks plus 3 specialized sibling traits is a maintenance burden. Action: incrementally split by Lite widget (e.g. `Progressbar_Extender`, `Adv_Tabs_Extender`).
4. **`array_merge_recursive` corner case.** If a future Pro slug ever collides with a Lite slug, recursive merge will combine the entries in a surprising way (dependency arrays will append rather than replace). Action: assert no overlap at config load time.
5. **`eael/post_args` filter is undocumented.** Pro registers it (`Bootstrap.php:53`) but no Lite-side caller is documented. Action: trace and document or remove.
6. **No automated test of the Lite-standalone invariant.** Manual testing is the only safeguard against Pro-side changes that introduce a Lite breakage when Pro is deactivated. Action: add a Playwright scenario that runs the Lite suite with Pro deactivated, in CI.

## Acceptance

This doc accurately reflects:

- The `eael/before_init` callback in `essential_adons_elementor.php` lines 48–64 ✓
- The injection method bodies in `includes/Classes/Bootstrap.php` lines 255–263 ✓
- The `eael/pro_enabled` filter set to `__return_true` in `Bootstrap::__construct` ✓
- The version gate operator `<=` (not `<`) ✓
- The Bootstrap trait composition order (Library, Core, Extender, Filterable_Gallery_Extender, Business_Reviews_Extender, Login_Register_Extender, Enqueue, Helper, Instagram_Feed, Login_Registration from Lite) ✓
- The five side-channel hooks in the entry file ✓

If any of the above change in code, this doc must be updated in the same PR.

## Pairs with

- `.claude/rules/pro-lite-bridge.md` — actionable rules derived from this architecture doc
- `license-system.md` — license is admin-only, runs after the bridge handshake completes
- Lite's `docs/architecture/asset-loading.md` — `Asset_Builder` reads both `$GLOBALS['eael_config']` and (when Pro is active) `$GLOBALS['eael_pro_config']`

## Related

- `external-api-integrations.md` — outbound HTTP from inside Extender callbacks (Google Sheets, Mailchimp, etc.)
- `../../../essential-addons-for-elementor-lite/docs/architecture/extensions.md` — extension subsystem; Pro extensions are injected via the same filter as elements

## Out of scope

- Per-Extender-callback semantics — that's the per-widget docs in `../widgets/`
- The Extender refactor proposal — flagged in "What's missing", but the file is currently in production form
- Behaviour of Pro running against a Lite older than 4.6.3 — the version gate refuses to initialize, so no runtime characterization is needed
