# Extensions (Pro Overlay)

Pro contributes 8 extensions (+ 1 DynamicTags subsystem) into Lite's extension registry. This doc enumerates them, captures their registration pattern, and flags integration points worth knowing before editing.

## Context

In Essential Addons terminology, **extensions** augment Elementor's built-in elements (Section, Column, Container, Common controls) rather than adding standalone widgets. The Lite plugin's subsystem doc — `../../../essential-addons-for-elementor-lite/docs/architecture/extensions.md` — covers the registration loop, the `'context' => 'edit'` vs `'view'` semantic, and the canonical extension authoring pattern.

Pro adds its extensions by merging into Lite's registry via the `eael/registered_extensions` filter. Once merged, Lite's `register_extensions()` loop (in Lite's `Traits/Elements.php`) instantiates each enabled extension class with `new $extension['class']` — no different from a Lite-owned extension.

This Pro doc captures **only Pro-specific extension surface** — what each Pro extension does, what Elementor hooks it consumes, and known gaps. For per-extension deep dives (controls catalogue, settings persistence, frontend behaviour), see `../extensions/`.

## Verified facts

### Inventory

| Slug                      | Class                                                         | File                                                              | Hooks consumed                                                                       |
| ------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `custom-cursor`           | `Custom_Cursor`                                               | `includes/Extensions/Custom_Cursor.php`                           | `elementor/documents/register_controls`, common/column/section/container `_section_style/after_section_end`, `elementor/frontend/before_render`, custom `eael/custom_cursor/page_render` |
| `section-particles`       | `EAEL_Particle_Section`                                       | `includes/Extensions/EAEL_Particle_Section.php`                   | section + container `before_render` / `after_render`, `section_layout/after_section_end` |
| `section-parallax`        | `EAEL_Parallax_Section`                                       | `includes/Extensions/EAEL_Parallax_Section.php`                   | section + container `after_render`, `section_layout/after_section_end`               |
| `smooth-animation`        | `Smooth_Animation`                                            | `includes/Extensions/Smooth_Animation.php`                        | (verify in code — likely common/section/container control + frontend before_render)  |
| `tooltip-section`         | `EAEL_Tooltip_Section`                                        | `includes/Extensions/EAEL_Tooltip_Section.php`                    | common `_section_style/after_section_end`, widget `before_render_content`            |
| `content-protection`      | `Content_Protection`                                          | `includes/Extensions/Content_Protection.php`                      | common/section/container `after_section_end`, `elementor/widget/render_content`, `elementor/frontend/section/should_render`, `elementor/frontend/container/should_render` |
| `conditional-display`     | `Conditional_Display`                                         | `includes/Extensions/Conditional_Display.php`                     | common/section/container/column `after_section_end`                                  |
| `advanced-dynamic-tags`   | `Advanced_Dynamic_Tags`                                       | `includes/Extensions/Advanced_Dynamic_Tags.php`                   | `elementor/dynamic_tags/register`                                                    |

Plus the **`DynamicTags/` subsystem** — five tag classes living under `includes/Extensions/DynamicTags/`:

| Class                | File                                                    | Purpose                                                              |
| -------------------- | ------------------------------------------------------- | -------------------------------------------------------------------- |
| `Posts`              | `includes/Extensions/DynamicTags/Posts.php`             | Posts loop dynamic tag (`eael-dynamic-tags-posts`)                   |
| `Custom_Post_Types`  | `includes/Extensions/DynamicTags/Custom_Post_Types.php` | CPT loop tag                                                         |
| `Terms`              | `includes/Extensions/DynamicTags/Terms.php`             | Taxonomy terms tag                                                   |
| `Woo_Products`       | `includes/Extensions/DynamicTags/Woo_Products.php`      | WooCommerce products tag                                             |
| `Acf_Relationship`   | `includes/Extensions/DynamicTags/Acf_Relationship.php`  | ACF relationship field tag (extends `Posts`)                         |

These are not registered as Pro extensions in `config.php`. They are registered through Elementor's `elementor/dynamic_tags/register` hook from inside `Advanced_Dynamic_Tags::register_dynamic_widgets`. See [`dynamic-data.md`](dynamic-data.md) for the registration flow.

### Non-class file

`includes/Extensions/particle-themes.php` — pure PHP file that `return`s an array of preset JSON theme strings (default, nasa, bubble, snow, nyan_cat). Consumed by `Traits/Enqueue.php` to localize `ParticleThemesData` for `section-particles`. **Today this file is not loaded** — `Enqueue.php` has a hardcoded duplicate of the same data. See `asset-loading.md` § "What's missing" #1.

### Registration pattern

Each Pro extension class follows the same shape:

```php
namespace Essential_Addons_Elementor\Pro\Extensions;

class Custom_Cursor {
    public function __construct() {
        add_action( 'elementor/element/common/_section_style/after_section_end', [ $this, 'register_controls' ] );
        add_action( 'elementor/element/section/section_advanced/after_section_end', [ $this, 'register_controls' ] );
        add_action( 'elementor/element/column/section_advanced/after_section_end', [ $this, 'register_controls' ] );
        add_action( 'elementor/element/container/section_layout/after_section_end', [ $this, 'register_controls' ] );
        add_action( 'elementor/frontend/before_render', [ $this, 'before_render' ], 100 );
        add_action( 'eael/custom_cursor/page_render', [ $this, 'render_custom_cursor_html' ], 10, 2 );
    }
    // ...register_controls, before_render, render_custom_cursor_html
}
```

Three invariants:

1. **No constructor args.** Lite's `register_extensions()` loop calls `new $extension['class']` with no parameters.
2. **All hook registration happens in the constructor.** Extensions don't have lifecycle hooks beyond instantiation.
3. **Controls register at multiple element types.** Most extensions register controls under both **section** AND **container** (Elementor 3.x Flexbox Container is a separate element type from Section, but Pro extensions need to apply to both).

### `'context'` field — `'edit'` vs `'view'`

The `dependency.css[].context` and `dependency.js[].context` keys in `config.php` control when `Asset_Builder` enqueues the asset. `'view'` means front-end render; `'edit'` means Elementor editor.

For Pro extensions specifically:

- `custom-cursor` — both edit AND view JS (editor needs a preview, frontend needs the actual cursor)
- `tooltip-section` — `'context' => 'view'` only (note: file `advanced-tooltip.min.js` in `src/js/edit/` is wired to `'view'` context in config — likely a config error worth verifying)
- `section-particles`, `section-parallax`, `smooth-animation` — `'view'` only
- `content-protection` — CSS only, `'view'` only

### How a Pro extension reaches the registration loop

```
1. Pro Bootstrap::inject_new_extensions() merges Pro extensions into Lite's array
2. Lite Bootstrap::__construct invokes apply_filters('eael/registered_extensions', ...)
3. Lite Bootstrap::register_extensions() iterates the merged array:
       foreach ( $this->registered_extensions as $key => $extension ) {
           if ( ! in_array( $key, $active_elements ) ) { continue; }  // ← Pro slug must be in the EA dashboard "active" list
           if ( class_exists( $extension['class'] ) ) {
               new $extension['class'];
           }
       }
```

**Pro extension activation gates:**

- The slug must appear in the EA dashboard "active" widgets/extensions option (Lite stores user enablement here)
- For Pro extensions specifically, **Pro `includes/Traits/Core.php → set_default_values()`** appends every Pro slug to the `$defaults` array on first install — so all Pro extensions are enabled by default
- A user can disable a Pro extension from the EA dashboard at any time; the slug then drops out of the active list and `register_extensions` skips it

If you add a new Pro extension and skip the `set_default_values` update, it won't be active on fresh installs.

### Promotion-style upsell pattern is Lite-only

Lite's `Extensions/Promotion.php` shows upsell HEADING controls in Lite widgets when `apply_filters('eael/pro_enabled', false)` returns false. Pro does NOT contain a `Promotion` extension — Pro is the thing being promoted. The upsell controls bail when Pro is active (Lite's `Promotion.php` does `if ( ! apply_filters('eael/pro_enabled', false) ) { /* register */ }`).

### Extender hooks vs. extensions

Some Pro features are exposed via the Extender trait (filter callbacks injected into Lite widgets) and some via standalone extensions. The split today is:

- **Standalone extension** when the feature applies to elements broadly (any section, any widget) — `conditional-display`, `content-protection`, `custom-cursor`, `tooltip-section`, particle/parallax effects
- **Extender callback** when the feature is widget-specific — Progress Bar Pro layouts, Pricing Table Pro styles, Login/Register social login, etc.

There's no hard rule but the current split is roughly "cross-cutting → extension; widget-specific → extender". A new Pro feature should follow the same heuristic.

## What's missing

1. **Hook documentation is per-class only.** No single index of which Elementor hooks Pro extensions consume. A change in Elementor (e.g. renaming `_section_style` for the common element) breaks multiple Pro extensions silently. Action: maintain a Pro-side hook index in this doc.
2. **`Smooth_Animation` hook surface is undocumented** in this enumeration — verify in code before claiming it's "section before_render"-style.
3. **`tooltip-section` context flag is suspicious** — the JS file is in `src/js/edit/` but config marks it `'context' => 'view'`. Either the path is wrong or the context is wrong. Verify whether tooltips render in the editor or only on the front end.
4. **Container vs Section duplication.** Every Pro section-level extension registers the same control set against `section_layout/after_section_end` (legacy Section element) AND `container/section_layout/after_section_end` (Elementor 3.x Container). When a control config changes, both registrations need updating. Refactor to a shared registration helper.
5. **Per-extension docs.** `docs/extensions/<slug>.md` ships one doc per Pro extension. Architectural cross-cuts like `Conditional_Display`'s IP geolocation behaviour live in `external-api-integrations.md`.
6. **`particle-themes.php` is dead code** — defined but not loaded. Either wire it up (so `Enqueue.php` reads from the file) or delete it.
7. **DynamicTags registration runs at admin too.** `Advanced_Dynamic_Tags::__construct` hooks `elementor/dynamic_tags/register` without an `is_admin()` gate. Verify that's intentional — Elementor only fires that hook in editor context, so the gate would be redundant, but the constructor itself runs every request.
8. **Extension class instantiation has no error handling.** Lite's `register_extensions()` uses `class_exists` but if instantiation throws, the failure propagates and breaks the whole loop. Wrap in try/catch in Lite to make per-extension failure isolated.

## Acceptance

This doc accurately reflects:

- 8 Pro extension slugs in `config.php` ✓
- 5 DynamicTags classes under `Extensions/DynamicTags/` ✓
- `particle-themes.php` is a non-class data file, not loaded today ✓
- Each extension class registers all hooks in its constructor with no args ✓
- Pro extensions register controls against BOTH section and container element types ✓
- Slug must be in EA dashboard "active" list to instantiate (per Lite's register_extensions loop) ✓
- Pro's `set_default_values()` ensures all Pro slugs are active on fresh installs ✓

If a Pro extension is added, removed, or renamed, update this doc + the table in `CLAUDE.md` in the same PR.

## Pairs with

- `pro-lite-bridge.md` — explains the `eael/registered_extensions` filter contract
- `asset-loading.md` — extension assets flow through `Asset_Builder` via the same `'dependency'` shape
- `dynamic-data.md` — DynamicTags subsystem under `Extensions/DynamicTags/`

## Related

- Lite's `docs/architecture/extensions.md` — the subsystem this overlays on
- `../../includes/Traits/Extender.php` — the alternative pattern (Extender callbacks vs. standalone extensions)

## Out of scope

- Per-extension control catalogue, frontend rendering, settings persistence — that's the per-extension docs in `../extensions/`
- The Lite-side extension subsystem (Promotion, Image_Masking, Liquid_Glass_Effect, etc.) — Lite's doc
- DynamicTags individual tag classes (Posts, CPT, Terms, etc.) — captured separately in `dynamic-data.md`
