Skip to content

Commit c133c1f

Browse files
wip
1 parent cbc7db9 commit c133c1f

3 files changed

Lines changed: 768 additions & 0 deletions

File tree

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# Global Classes — Extension Points
2+
3+
Elementor Core Intermediate
4+
5+
This page documents the integration surface 3rd-party code can use to read, write, observe, and extend Global Classes in Elementor 4.1+. For background, see [Architecture Overview](./01-overview.md). For breaking changes from prior versions, see [Migration Guide](./02-migration-guide.md).
6+
7+
> **Note.** Anything not listed here — internal classes, private methods, post meta written by `Global_Classes_Relations`, the `e_global_class` post type internals — is implementation detail and may change between releases. Read meta keys at your own risk; do not write to them.
8+
9+
## # REST endpoints
10+
11+
All endpoints live under the `elementor/v1` namespace. All read endpoints accept an optional `context` argument: `frontend` (default) or `preview`.
12+
13+
### # `GET /elementor/v1/global-classes`
14+
15+
Lightweight index of all global classes for the active kit, in display order.
16+
17+
| Argument | Type | Required | Default | Notes |
18+
| -------- | ---- | -------- | ------- | ----- |
19+
| `context` | string | no | `frontend` | One of `frontend`, `preview`. |
20+
21+
**Permission:** `is_user_logged_in()`.
22+
23+
**Response:**
24+
25+
```json
26+
{
27+
"data": [
28+
{ "id": "g-abc", "label": "Card" },
29+
{ "id": "g-def", "label": "Button" }
30+
]
31+
}
32+
```
33+
34+
This endpoint returns labels only. Use `/global-classes/styles` or `/global-classes/post` for full class data.
35+
36+
### # `GET /elementor/v1/global-classes/post`
37+
38+
Full class definitions for the classes referenced by a specific document, plus their order.
39+
40+
| Argument | Type | Required | Default | Notes |
41+
| -------- | ---- | -------- | ------- | ----- |
42+
| `post_id` | integer | yes || The Elementor document ID. |
43+
| `context` | string | no | `frontend` | One of `frontend`, `preview`. |
44+
45+
**Permission:** `is_user_logged_in()`.
46+
47+
**Response:**
48+
49+
```json
50+
{
51+
"data": {
52+
"g-abc": { "id": "g-abc", "label": "Card", "type": "class", "variants": [ ] }
53+
},
54+
"meta": { "order": [ "g-abc" ] }
55+
}
56+
```
57+
58+
`meta.order` is the intersection of the global class order with the IDs used by the document.
59+
60+
### # `GET /elementor/v1/global-classes/styles`
61+
62+
Bulk fetch of class definitions by ID list. Used by the editor for lazy loading classes not in the initial document index.
63+
64+
| Argument | Type | Required | Default | Notes |
65+
| -------- | ---- | -------- | ------- | ----- |
66+
| `ids` | string | yes || Comma-separated list of class IDs. |
67+
| `context` | string | no | `frontend` | One of `frontend`, `preview`. |
68+
69+
**Permission:** `is_user_logged_in()`.
70+
71+
**Response:** Same shape as `/global-classes/post`. IDs that don't exist resolve to `null` in `data` and are omitted from `meta.order`.
72+
73+
```json
74+
{
75+
"data": {
76+
"g-abc": { "id": "g-abc", "label": "Card", "type": "class", "variants": [ ] },
77+
"g-missing": null
78+
},
79+
"meta": { "order": [ "g-abc" ] }
80+
}
81+
```
82+
83+
> **Security note.** `is_user_logged_in()` allows any authenticated user to read class IDs and definitions. If your site needs stricter visibility, layer your own capability check via a `rest_pre_dispatch` filter scoped to these routes.
84+
85+
### # `PUT /elementor/v1/global-classes`
86+
87+
Create, update, delete, and reorder classes.
88+
89+
| Argument | Type | Required | Default | Notes |
90+
| -------- | ---- | -------- | ------- | ----- |
91+
| `context` | string | no | `frontend` | One of `frontend`, `preview`. |
92+
| `changes` | object | yes || `{ added: string[], deleted: string[], modified: string[], order?: boolean }`. `additionalProperties: false`. |
93+
| `items` | object | yes || Map of `class_id → { id, label, type, variants }` for **touched** items only (added and modified). |
94+
| `order` | string[] | yes || The full ordered list of class IDs after the operation. |
95+
96+
**Permission:** `current_user_can( Add_Capabilities::UPDATE_CLASS )`.
97+
98+
**Responses:**
99+
100+
- `204 No Content` on success.
101+
- `200 OK` with `{ "code": "DUPLICATED_LABEL", "modifiedLabels": { … } }` when the server auto-renamed duplicate labels.
102+
- `400 Bad Request` with `code: "invalid_items" | "invalid_order" | "global_classes_limit_exceeded"`. The limit error carries `meta.current_count` and `meta.max_allowed`.
103+
104+
The server enforces a hard limit of **1000 classes** (`Global_Classes_REST_API::MAX_ITEMS`).
105+
106+
Set `changes.order = true` when the order changes, otherwise document-wide style caches for the context will not be invalidated.
107+
108+
### # `GET /elementor/v1/global-classes/usage`
109+
110+
Detailed usage report of applied global classes across the site.
111+
112+
| Argument | Type | Required | Default | Notes |
113+
| -------- | ---- | -------- | ------- | ----- |
114+
| `context` | string | no | `frontend` | One of `frontend`, `preview`. |
115+
116+
**Permission:** `current_user_can( 'manage_options' )`.
117+
118+
## # PHP actions
119+
120+
### # `elementor/global_classes/update`
121+
122+
Fired after every mutation of the global classes set (via `apply_changes()` or `put()`).
123+
124+
**Signature:**
125+
126+
```php
127+
do_action(
128+
'elementor/global_classes/update',
129+
string $context, // 'frontend' | 'preview'
130+
array $changes // [ 'added' => string[], 'deleted' => string[], 'modified' => string[], 'order' => bool ]
131+
);
132+
```
133+
134+
**Use it to:** invalidate your own caches, mirror class data to an external system, track usage analytics. Listeners must register with `accepted_args = 2`.
135+
136+
```php
137+
add_action( 'elementor/global_classes/update', function( $context, $changes ) {
138+
if ( 'frontend' !== $context ) {
139+
return;
140+
}
141+
142+
foreach ( $changes['deleted'] as $class_id ) {
143+
my_plugin_drop_cached_css( $class_id );
144+
}
145+
}, 10, 2 );
146+
```
147+
148+
### # `elementor/atomic-widgets/styles/register`
149+
150+
Fired during style registration. Lets you register custom style sources into `Atomic_Styles_Manager` alongside the built-in global styles.
151+
152+
**Signature:**
153+
154+
```php
155+
do_action(
156+
'elementor/atomic-widgets/styles/register',
157+
\Elementor\Modules\AtomicWidgets\Styles\Atomic_Styles_Manager $manager,
158+
array $post_ids
159+
);
160+
```
161+
162+
Listeners must register with `accepted_args = 2`. Use the `[ <your_key>, $post_id, $context ]` tuple convention when registering per-document styles.
163+
164+
### # `elementor/atomic-widgets/styles/clear`
165+
166+
Fired when style caches should be invalidated. The `$key` argument is a hierarchical tuple — listeners may receive any prefix of `[ <key>, $post_id, $context ]`.
167+
168+
**Signature:**
169+
170+
```php
171+
do_action( 'elementor/atomic-widgets/styles/clear', array $key );
172+
```
173+
174+
For Global Classes specifically, the key forms are:
175+
176+
| Key | Meaning |
177+
| --- | ------- |
178+
| `[ 'global' ]` | Full clear, all documents and contexts. |
179+
| `[ 'global', $context ]` | Clear one context, all documents. |
180+
| `[ 'global', $post_id ]` | Clear one document, both contexts. |
181+
| `[ 'global', $post_id, $context ]` | Clear one document and context. |
182+
183+
If your plugin caches anything keyed on the old `[ 'global', $context ]` form, also handle the per-document forms.
184+
185+
## # PHP filters
186+
187+
### # `elementor/atomic-widgets/settings/transformers/classes`
188+
189+
Unchanged contract. Internally now resolves labels via `Global_Classes_Repository::all_labels()` instead of `all()->get_items()` (faster, transparent to consumers).
190+
191+
### # `elementor/kit/meta_to_preserve_on_kit_import`
192+
193+
Used to preserve kit-level metadata across kit imports. Global Classes registers four keys here (order, frontend labels, preview labels, sync map). 3rd-party modules that store kit-level metadata they want preserved across kit imports can add their own keys:
194+
195+
```php
196+
add_filter( 'elementor/kit/meta_to_preserve_on_kit_import', function( array $meta_keys ) {
197+
$meta_keys[] = 'my_plugin_kit_meta_key';
198+
return $meta_keys;
199+
} );
200+
```
201+
202+
### # `elementor/template_library/export/build_snapshots`, `…/extract_snapshots`, `…/import/process_content`
203+
204+
Existing template-library filters. Global Classes hooks into all three to participate in template export/import. No signature changes here — the contracts are documented elsewhere. Mentioned here only because Global Classes is now one of their consumers.
205+
206+
## # PHP API: `Global_Classes_Repository`
207+
208+
```php
209+
use Elementor\Modules\GlobalClasses\Global_Classes_Repository;
210+
211+
$repository = Global_Classes_Repository::make();
212+
// or, scoped to a specific kit:
213+
$repository = Global_Classes_Repository::make( $kit );
214+
```
215+
216+
### # Context
217+
218+
```php
219+
$repository->set_preview( true ); // operate on preview/draft state
220+
$repository->set_preview( false ); // operate on published state (default)
221+
$repository->set_kit( $kit ); // bind to a non-active kit
222+
```
223+
224+
### # Reads
225+
226+
| Method | Returns | Notes |
227+
| ------ | ------- | ----- |
228+
| `all_labels()` | `array<string, string>` | `class_id → label`, in display order. Cheap. Prefer over `all()`. |
229+
| `get( string $class_id )` | `?array` | Full item or `null`. |
230+
| `get_by_ids( array $class_ids )` | `array<string, array>` | `id → item` map. Missing IDs are absent. |
231+
| `get_order()` | `string[]` | The current ordered ID list. |
232+
| `each_item( callable $cb, bool $skip_migration = false, int $batch_size = 100 )` | `void` | Streams every class through `$cb` in display order. Use for bulk processing. |
233+
| `all( bool $force = false )` | `Global_Classes` | **Heavy.** Loads every class into memory. Avoid on hot paths. |
234+
235+
### # Writes
236+
237+
| Method | Notes |
238+
| ------ | ----- |
239+
| `apply_changes( array $touched_items, array $changes, array $order ) : void` | Used by the REST `PUT` path. `$changes` shape matches `elementor/global_classes/update`. Persists only touched items and the order/labels diff. |
240+
| `put( array $items, array $order )` | "Replace everything" semantics. Diffs internally against the CPT, persists, and fires `elementor/global_classes/update` with the computed `$changes`. |
241+
| `update_order_and_labels( array $order, array $new_labels ) : void` | Updates only the kit-level order and label map. Does not touch class data. |
242+
| `delete_all() : void` | Removes every class for the current context. |
243+
244+
Both `apply_changes()` and `put()` fire `elementor/global_classes/update` at the end.
245+
246+
## # Post type and post meta (read-only)
247+
248+
These exist as implementation detail. You may read them, but do not write to them — go through `Global_Classes_Repository` or `Global_Classes_Relations` instead. Treat the exact key names as an implementation detail that may change.
249+
250+
### # The `e_global_class` post type
251+
252+
```php
253+
\Elementor\Modules\GlobalClasses\Global_Class_Post_Type::CPT; // 'e_global_class'
254+
```
255+
256+
Registered with `public => false`, `supports => [ 'title' ]`. The post `title` is the class label.
257+
258+
### # Meta on `e_global_class` posts
259+
260+
| Constant | Key | Purpose |
261+
| -------- | --- | ------- |
262+
| `Global_Class_Post::META_KEY_ID` | `_elementor_global_class_id` | The user-facing class ID (`g-abc`). |
263+
| `Global_Class_Post::META_KEY_DATA` | `_elementor_global_class_data` | `{ type, variants, … }` for frontend. |
264+
| `Global_Class_Post::META_KEY_DATA_PREVIEW` | `_elementor_global_class_data_preview` | Same, for preview. |
265+
| `Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_FRONTEND` | `_elementor_global_class_using_documents` | Reverse index: document IDs that use this class (frontend). |
266+
| `Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_PREVIEW` | `_elementor_global_class_using_documents_preview` | Same, for preview. |
267+
268+
### # Meta on document posts
269+
270+
| Constant | Key | Purpose |
271+
| -------- | --- | ------- |
272+
| `Global_Classes_Relations::META_KEY_FRONTEND` | `_elementor_used_global_class` | Multi-value: class IDs the document references (frontend). |
273+
| `Global_Classes_Relations::META_KEY_PREVIEW` | `_elementor_used_global_class_preview` | Same, for preview. |
274+
| `Global_Classes_Relations::META_KEY_USAGE_INDEXED_FRONTEND` | `_elementor_global_class_usage_indexed` | `'1'` once the document has been indexed. |
275+
| `Global_Classes_Relations::META_KEY_USAGE_INDEXED_PREVIEW` | `_elementor_global_class_usage_indexed_preview` | Same, for preview. |
276+
277+
## # Kit-level meta keys (read-only)
278+
279+
These live on the active Kit post. Again — read only.
280+
281+
| Constant | Purpose |
282+
| -------- | ------- |
283+
| `Global_Classes_Order::META_KEY` | The global class display order. |
284+
| `Global_Classes_Labels::META_KEY_FRONTEND` | `class_id → label` map (frontend). |
285+
| `Global_Classes_Labels::META_KEY_PREVIEW` | Same, for preview. |
286+
| `Global_Classes_Sync_Map::META_KEY` | Design-system sync map. |
287+
| `Global_Classes_Post_IDs` storage | `class_id → e_global_class post ID` lookup. |
288+
289+
All four are preserved on kit import via the `elementor/kit/meta_to_preserve_on_kit_import` filter — see [above](#kit-import-meta-preservation).
290+
291+
## # Notes and constraints
292+
293+
- The hard ceiling on global classes is `Global_Classes_REST_API::MAX_ITEMS = 1000`. Server returns HTTP 400 / `global_classes_limit_exceeded` if the post-mutation count would exceed it.
294+
- The DB migration `Migrate_To_Posts` runs once on upgrade (DB version `1 → 2`). It is idempotent (skipped if any `e_global_class` post exists), and it intentionally leaves the legacy kit meta in place — that meta is not the source of truth after migration, and reading it will return stale data.
295+
- `Global_Classes_Repository::all()` is heavy. Use `all_labels()` + `get_by_ids()` or `each_item()` instead.
296+
- The reverse index on `e_global_class` posts is maintained by `Global_Classes_Relations`. Code that writes `_elementor_used_global_class` directly will desync it until the document is next saved through the editor.

0 commit comments

Comments
 (0)