PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / block-patterns / module.php

module.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/block-patterns/module.php

189 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block Patterns module (spec 051-block-patterns).
4 *
5 * Exposes the Templately Gutenberg catalog as native WordPress block patterns:
6 * plan-keyed remote sync into a local cache (transient list + inert content
7 * files), metadata-only registration on guarded `init` with WP 6.5 filePath
8 * lazy content, start-page/post pattern keys for the native new-page chooser,
9 * a save-time media localizer with persistent source-URL dedup, and a
10 * cache-fed dashboard widget. v1 is connected-sites-only and WP >= 6.5 only
11 * (spec 051 Clarifications, 2026-07-28). Design decisions: specs/051-block-
12 * patterns/research.md (D1-D9).
13 *
14 * @package Templately
15 */
16
17 namespace Templately\Modules\BlockPatterns;
18
19 use Templately\Core\Capabilities;
20 use Templately\Core\Module_Base;
21 use Templately\Modules\BlockPatterns\Cleanup\PatternCacheTask;
22 use Templately\Modules\BlockPatterns\REST\Content as ContentEndpoint;
23 use Templately\Modules\BlockPatterns\REST\Search as SearchEndpoint;
24 use Templately\Modules\BlockPatterns\REST\Sync as SyncEndpoint;
25
26 class Module extends Module_Base {
27
28 public function get_name(): string {
29 return 'block-patterns';
30 }
31
32 /**
33 * WP >= 6.5 only: filePath lazy pattern content (the whole registration
34 * design) does not exist below 6.5, and v1 ships no inline fallback
35 * (research D8 / spec FR-006).
36 *
37 * Since spec 053 the check is the `pattern-file-path` capability. Its
38 * declaration (Capability_Seed) still routes the version through the
39 * legacy `templately_block_patterns_wp_version` filter, so existing tests
40 * and third-party code keep working; the documented override going
41 * forward is `templately_capability_pattern-file-path`.
42 */
43 public function is_active(): bool {
44 return Capabilities::get_instance()->has( 'pattern-file-path' );
45 }
46
47 public function get_dependencies(): array {
48 // full-site-import: Utils::import_and_replace_attachments (MediaLocalizer);
49 // gutenberg-integration: registers the gutenberg json helper that util resolves
50 // through the templately_fsi_json_helper seam;
51 // utilities: the guarded remover this module's cache-clear deletes through.
52 return [ 'full-site-import', 'gutenberg-integration', 'utilities' ];
53 }
54
55 protected function init_hooks(): void {
56 // Manual-only contribution to the shared cleanup service (spec 052): a
57 // "clear the pattern library" action for the Utilities tab. Never
58 // scheduled — every cleared pattern is re-fetched from the cloud.
59 PatternCacheTask::register();
60
61 // REGISTER ON `init`, LIKE CORE DOES — and never on any later action.
62 //
63 // This used to run on `rest_api_init` to keep the catalog out of the
64 // editor's bootstrap payload. It achieved the exact opposite. WordPress
65 // keeps a SECOND copy of every pattern registered outside `init`:
66 //
67 // // class-wp-block-patterns-registry.php
68 // if ( current_action() && 'init' !== current_action() ) {
69 // $this->registered_patterns_outside_init[ $pattern_name ] = $pattern;
70 // }
71 //
72 // and `wp-admin/edit-form-blocks.php` inlines that bucket wholesale into
73 // the page as `__experimentalAdditionalBlockPatterns`. Core keeps it only
74 // to detect DEPRECATED registrations from `admin_init`/`current_screen` —
75 // its own comment says so — and we were feeding it as our main path.
76 // Measured on a connected site: 100 of 100 of our patterns in that bucket,
77 // 87,630 bytes inlined into every single editor page load.
78 //
79 // Registered on `init` we are in the ordinary registry only, which nothing
80 // inlines, and core-data's `getBlockPatterns` resolver fetches
81 // `/wp/v2/block-patterns/patterns` when the editor actually wants patterns.
82 //
83 // The original fear — that `init` registration forces content resolution —
84 // belonged to the eager `filePath` era, when one pattern averaged ~50KB and
85 // the whole catalog inlined megabytes. In lazy mode a pattern's content IS
86 // the ~865-byte placeholder, passed inline, so there is no file to resolve
87 // and nothing to defer.
88 //
89 // PRIORITY 20 IS LOAD-BEARING. Core registers its own pattern categories on
90 // `init` at the default 10 (`_register_core_block_patterns_and_categories`,
91 // `_register_theme_block_patterns`). `PatternRegistrar::core_category_for()`
92 // asks the live registry whether a core equivalent exists, so running before
93 // core would find none and file every design into a parallel `templately-*`
94 // bucket instead of core's "Call to action", "Header" and friends.
95 add_action( 'init', function () {
96 PatternRegistrar::get_instance()->register_all();
97 }, 20 );
98
99 // Cron: self-chaining sync events (research D5).
100 add_action( PatternSync::EVENT_SYNC_LIST, [ PatternSync::get_instance(), 'sync_list' ] );
101 add_action( PatternSync::EVENT_SYNC_CONTENT, [ PatternSync::get_instance(), 'sync_content_batch' ] );
102
103 // Plan/connection boundary invalidation: the stored account lives in
104 // `_templately_user` user meta (written via update_user_option on login/
105 // logout/plan refresh) — deleting the plan-keyed caches on any write to it
106 // covers connect, disconnect and plan change (plan.md cross-cutting rules).
107 $invalidate = function ( $meta_id, $object_id, $meta_key ) {
108 PatternSync::get_instance()->maybe_invalidate_for_meta( (string) $meta_key );
109 };
110 add_action( 'updated_user_meta', $invalidate, 10, 3 );
111 add_action( 'added_user_meta', $invalidate, 10, 3 );
112 add_action( 'deleted_user_meta', $invalidate, 10, 3 );
113
114 // Save-time media localization (research D6).
115 add_action( 'wp_after_insert_post', [ MediaLocalizer::get_instance(), 'maybe_localize' ], 10, 2 );
116 add_action( MediaLocalizer::EVENT_LOCALIZE_REST, [ MediaLocalizer::get_instance(), 'localize_deferred' ] );
117
118 // Media is imported when a pattern is INSERTED, so a discarded draft would
119 // otherwise leave its images orphaned in the library.
120 add_action( 'before_delete_post', [ MediaLocalizer::get_instance(), 'cleanup_for_post' ] );
121
122 // Dashboard widget (research D9: server-rendered, cache-only).
123 add_action( 'wp_dashboard_setup', [ DashboardWidget::get_instance(), 'register_widget' ] );
124
125 // First sync: schedule (never fetch inline) when no list has ever synced.
126 add_action( 'admin_init', [ PatternSync::get_instance(), 'ensure_scheduled' ] );
127
128 // The lazy-pattern placeholder block. Registered server-side so the block
129 // type exists for parsing/validation; its editor UI ships in this module's
130 // JS, which compiles into the `templately` bundle via the boot manifest
131 // (react-src/generated/moduleManifest.ts) and rides that bundle's
132 // unconditional enqueue in Admin::enqueue_scripts() onto the editor screen.
133 // That is the same reach the retired enqueue_module_assets() glob had.
134 add_action( 'init', function () {
135 if ( ! PatternRegistrar::lazy_mode() ) {
136 return;
137 }
138 if ( ! \WP_Block_Type_Registry::get_instance()->is_registered( 'templately/lazy-pattern' ) ) {
139 register_block_type( 'templately/lazy-pattern', [
140 'api_version' => 3,
141 'attributes' => [
142 'patternId' => [ 'type' => 'number', 'default' => 0 ],
143 'previewUrl' => [ 'type' => 'string', 'default' => '' ],
144 'title' => [ 'type' => 'string', 'default' => '' ],
145 'kind' => [ 'type' => 'string', 'default' => '' ],
146 ],
147 // Never renders on the front end: on the canvas it replaces
148 // itself with the real blocks, so it cannot reach saved content.
149 'render_callback' => '__return_empty_string',
150 ] );
151 }
152 }, 5 );
153
154 // The placeholder renders inside the editor CANVAS IFRAME, which the
155 // module's admin stylesheet never reaches — enqueue_block_assets is the
156 // hook WordPress mirrors into that iframe, so the loading/error state
157 // keeps its Templately styling instead of rendering unstyled.
158 add_action( 'enqueue_block_assets', function () {
159 if ( ! is_admin() || ! PatternRegistrar::lazy_mode() ) {
160 return;
161 }
162
163 // Emitted by this module's OWN stylesheet entry, not by the retired module-CSS glob
164 // that used to write assets/css/modules/style-block-patterns.css. Every module's
165 // JS-imported CSS now lands in style-templately.css, which is enqueued on the admin
166 // page and never reaches the canvas iframe — hence the separate entry. The
167 // `style-` prefix is @wordpress/scripts': a source file named `style.scss` emits
168 // `style-{entry}.css`. See webpack.config.partial.js.
169 $relative = 'assets/css/style-block-patterns.css';
170 if ( ! file_exists( TEMPLATELY_PATH . $relative ) ) {
171 return;
172 }
173
174 wp_enqueue_style(
175 'templately-block-patterns',
176 TEMPLATELY_URL . $relative,
177 [],
178 (string) filemtime( TEMPLATELY_PATH . $relative )
179 );
180 } );
181 }
182
183 public function register_rest_routes(): void {
184 ContentEndpoint::get_instance()->register_routes();
185 SyncEndpoint::get_instance()->register_routes();
186 SearchEndpoint::get_instance()->register_routes();
187 }
188 }
189