PluginProbe
DesignSetGo / trunk
DesignSetGo vtrunk
2.7.3 2.7.1 2.7.2 2.7.0 2.6.3 2.6.2 2.6.1 2.6.0 2.5.1 2.4.1-test 2.5.0 2.4.0 2.3.0 2.2.0 2.1.2 2.1.1 2.1.0 trunk 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 All 67 releases
designsetgo / includes / blocks / class-loader.php

class-loader.php in DesignSetGo trunk, at includes/blocks/class-loader.php

429 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blocks Loader Class
4 *
5 * @package DesignSetGo
6 * @since 1.0.0
7 */
8
9 namespace DesignSetGo\Blocks;
10
11 // Exit if accessed directly.
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Blocks Loader Class - Registers all blocks
18 */
19 class Loader {
20 /**
21 * Constructor.
22 */
23 public function __construct() {
24 add_action( 'init', array( $this, 'register_shared_chunks' ), 5 ); // Register shared chunks first.
25 add_action( 'init', array( $this, 'register_blocks' ) );
26 add_action( 'init', array( $this, 'register_block_styles' ) );
27 add_action( 'init', array( $this, 'setup_script_translations' ), 15 ); // After blocks are registered.
28 add_action( 'init', array( $this, 'fix_view_module_dependencies' ), 20 ); // After blocks register their modules.
29 add_filter( 'block_type_metadata', array( $this, 'add_shared_dependencies' ) );
30 add_filter( 'block_type_metadata', array( $this, 'sync_asset_version' ) );
31 }
32
33 /**
34 * Version every block's own CSS/JS with the PLUGIN version.
35 *
36 * WordPress uses a block's `version` field from block.json as the
37 * cache-busting `?ver=` on the assets that block.json declares —
38 * `$block_version = isset( $metadata['version'] ) ? $metadata['version'] : false;`
39 * in wp-includes/blocks.php. Nearly every block here still carries the
40 * scaffolded `"version": "1.0.0"`, so `build/blocks/{block}/index.css?ver=1.0.0`
41 * is byte-identical across every release: browsers and CDNs keep serving the
42 * copy they cached from an older version of the plugin, forever. The plugin's
43 * global stylesheet does not have this problem — it is enqueued with
44 * DESIGNSETGO_VERSION and busts correctly — which is what masked it.
45 *
46 * That means a CSS-only fix to any block silently fails to reach existing
47 * users. It bit us in 2.4: Icon Button's icon layout moved out of the saved
48 * markup and into the stylesheet, so the markup began depending on CSS that
49 * cached browsers never received — the icon lost its box and its SVG expanded
50 * to fill the button.
51 *
52 * Overriding `version` here fixes it for every block at once and removes the
53 * need to remember to bump 66 block.json files on each release. The override
54 * is UNCONDITIONAL: a version a block.json pins for itself is discarded too
55 * (`scroll-marquee` pins 1.2.0), because cache-busting needs a single source
56 * of truth. A per-block pin cannot serve that purpose — it only busts when
57 * someone remembers to bump it, which is the bug this exists to remove.
58 *
59 * @since 2.4.0
60 *
61 * @param array $metadata Parsed block.json metadata.
62 * @return array Metadata with the asset version synced to the plugin version.
63 */
64 public function sync_asset_version( $metadata ) {
65 if ( ! isset( $metadata['name'] ) || 0 !== strpos( $metadata['name'], 'designsetgo/' ) ) {
66 return $metadata;
67 }
68
69 $metadata['version'] = DESIGNSETGO_VERSION;
70
71 return $metadata;
72 }
73
74 /**
75 * Re-register view script modules with the correct @wordpress/interactivity
76 * dependency name.
77 *
78 * @wordpress/scripts' webpack build emits `wp-interactivity` (the old script
79 * handle) as the dependency in view.asset.php, but WP 6.5+ registers the
80 * module as `@wordpress/interactivity`. Sorting fails silently during
81 * `print_enqueued_script_modules` when a module references an unregistered
82 * dependency, so the view script never reaches the page.
83 *
84 * @since 2.1.0
85 */
86 public function fix_view_module_dependencies() {
87 if ( ! function_exists( 'wp_register_script_module' ) ) {
88 return;
89 }
90
91 $modules_to_fix = array(
92 'designsetgo-query-view-script-module' => DESIGNSETGO_PATH . 'build/blocks/query/view.js',
93 );
94
95 foreach ( $modules_to_fix as $module_id => $src_path ) {
96 if ( ! file_exists( $src_path ) ) {
97 continue;
98 }
99 $asset_file = str_replace( '.js', '.asset.php', $src_path );
100 $version = file_exists( $asset_file )
101 ? ( include $asset_file )['version'] ?? false // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- build artifact; path resolved from plugin directory
102 : filemtime( $src_path );
103
104 // Deregister first — wp_register_script_module is a no-op if the id
105 // is already registered, and block.json auto-registration already
106 // ran with the wrong dep name.
107 wp_deregister_script_module( $module_id );
108
109 // Re-register with `@wordpress/interactivity` (the current module id)
110 // instead of `wp-interactivity` (the old script handle). Dep is
111 // declared as a shaped array so the `import` type is explicit
112 // (matches WP's `register_script_module` signature).
113 wp_register_script_module(
114 $module_id,
115 plugins_url( 'build/blocks/query/view.js', DESIGNSETGO_PATH . 'designsetgo.php' ),
116 array(
117 array(
118 'id' => '@wordpress/interactivity',
119 'import' => 'static',
120 ),
121 ),
122 $version
123 );
124 }
125 }
126
127 /**
128 * Register shared webpack chunks (code-split dependencies).
129 *
130 * These are extracted by webpack's splitChunks optimization and need to be
131 * manually registered so they can be used as dependencies by blocks.
132 *
133 * @since 1.0.0
134 */
135 public function register_shared_chunks() {
136 $shared_chunks = array(
137 'shared-icon-library' => array(
138 'path' => DESIGNSETGO_PATH . 'build/shared-icon-library.js',
139 ),
140 'shared-icon-picker' => array(
141 'path' => DESIGNSETGO_PATH . 'build/shared-icon-picker.js',
142 ),
143 );
144
145 foreach ( $shared_chunks as $handle => $config ) {
146 if ( ! file_exists( $config['path'] ) ) {
147 continue;
148 }
149
150 // Get asset file with dependencies and version.
151 $asset_file = str_replace( '.js', '.asset.php', $config['path'] );
152 if ( ! file_exists( $asset_file ) ) {
153 continue;
154 }
155
156 $asset_data = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- build artifact; path resolved from plugin directory
157
158 // Register the script.
159 wp_register_script(
160 'designsetgo-' . $handle,
161 DESIGNSETGO_URL . 'build/' . $handle . '.js',
162 $asset_data['dependencies'],
163 $asset_data['version'],
164 true
165 );
166 }
167 }
168
169 /**
170 * Add shared chunk dependencies to blocks that use them.
171 *
172 * This filter runs during block registration and injects webpack-split
173 * shared chunks as dependencies for blocks that use them.
174 *
175 * @param array $metadata Block metadata from block.json.
176 * @return array Modified metadata.
177 */
178 public function add_shared_dependencies( $metadata ) {
179 if ( ! isset( $metadata['name'] ) ) {
180 return $metadata;
181 }
182
183 // Blocks that use the icon library.
184 $icon_blocks = array(
185 'designsetgo/icon',
186 'designsetgo/icon-button',
187 'designsetgo/icon-list',
188 'designsetgo/icon-list-item',
189 'designsetgo/divider',
190 'designsetgo/star-rating',
191 );
192
193 if ( in_array( $metadata['name'], $icon_blocks, true ) ) {
194 // Add shared icon library dependencies.
195 if ( ! isset( $metadata['editorScript'] ) ) {
196 $metadata['editorScript'] = array();
197 } elseif ( ! is_array( $metadata['editorScript'] ) ) {
198 $metadata['editorScript'] = array( $metadata['editorScript'] );
199 }
200
201 // Add shared chunks as dependencies.
202 $metadata['editorScript'][] = 'designsetgo-shared-icon-library';
203 $metadata['editorScript'][] = 'designsetgo-shared-icon-picker';
204 }
205
206 return $metadata;
207 }
208
209 /**
210 * Register all blocks.
211 *
212 * Derives the block name from the directory name to avoid reading
213 * block.json twice (once here and once inside register_block_type).
214 */
215 public function register_blocks() {
216 // Get all block directories from build folder.
217 $blocks_dir = DESIGNSETGO_PATH . 'build/blocks/';
218
219 if ( ! file_exists( $blocks_dir ) ) {
220 return;
221 }
222
223 // Register each block.
224 $blocks = array_filter( glob( $blocks_dir . '*' ), 'is_dir' );
225
226 foreach ( $blocks as $block_dir ) {
227 if ( ! file_exists( $block_dir . '/block.json' ) ) {
228 continue;
229 }
230
231 // Derive block name from directory name — matches the slug in block.json
232 // and avoids a redundant file_get_contents + json_decode per block.
233 $block_name = 'designsetgo/' . basename( $block_dir );
234
235 // In debug mode, verify the derived name matches block.json to catch
236 // directory/name mismatches during development.
237 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
238 $json_data = wp_json_file_decode( $block_dir . '/block.json', array( 'associative' => true ) );
239 $json_name = isset( $json_data['name'] ) ? $json_data['name'] : '';
240 if ( $block_name !== $json_name ) {
241 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
242 trigger_error(
243 sprintf(
244 'DesignSetGo: Block directory name "%s" does not match block.json name "%s" in %s',
245 esc_html( $block_name ),
246 esc_html( $json_name ),
247 esc_html( $block_dir )
248 ),
249 E_USER_WARNING
250 );
251 }
252 }
253
254 // Check if block should be registered (allows filtering via Block_Manager).
255 $should_register = apply_filters( 'designsetgo_register_block', true, $block_name );
256
257 if ( $should_register ) {
258 register_block_type( $block_dir );
259 }
260 }
261 }
262
263 /**
264 * Setup script translations for blocks with viewScript.
265 *
266 * Enables i18n for frontend JavaScript files.
267 *
268 * @since 1.1.5
269 */
270 public function setup_script_translations() {
271 // Table of Contents block has translatable frontend strings.
272 if ( wp_script_is( 'designsetgo-table-of-contents-view-script', 'registered' ) ) {
273 wp_set_script_translations(
274 'designsetgo-table-of-contents-view-script',
275 'designsetgo',
276 DESIGNSETGO_PATH . 'languages'
277 );
278 }
279
280 // Form Builder block has translatable frontend strings.
281 if ( wp_script_is( 'designsetgo-form-builder-view-script', 'registered' ) ) {
282 wp_set_script_translations(
283 'designsetgo-form-builder-view-script',
284 'designsetgo',
285 DESIGNSETGO_PATH . 'languages'
286 );
287 }
288
289 // Scroll Slides block has translatable frontend strings.
290 if ( wp_script_is( 'designsetgo-scroll-slides-view-script', 'registered' ) ) {
291 wp_set_script_translations(
292 'designsetgo-scroll-slides-view-script',
293 'designsetgo',
294 DESIGNSETGO_PATH . 'languages'
295 );
296 }
297 }
298
299 /**
300 * Register block style variations.
301 *
302 * These appear in the Site Editor under Styles → Blocks → [Block Name].
303 * Styles are JSON-based (WordPress 6.7+ theme.json format) and
304 * auto-discovered from each block's /styles/ directory. Drop a JSON file
305 * (with `slug`, `title`, `blockTypes` and `styles`) into
306 * `src/blocks/{block}/styles/` to add a variation; none ship by default.
307 *
308 * Theme/plugin section styles for the layout blocks are handled separately
309 * by {@see \DesignSetGo\Section_Styles}.
310 */
311 public function register_block_styles() {
312 // Register JSON-based block style variations from the /styles/ directory.
313 // This will auto-discover all *.json files in build/blocks/*/styles/ directories.
314 $this->register_json_block_styles();
315 }
316
317 /**
318 * Register JSON-based block style variations.
319 *
320 * Discovers and registers block style variations from JSON files in the
321 * block's /styles/ subdirectory. These are theme.json-formatted style
322 * variations that provide comprehensive styling (colors, typography, etc.).
323 *
324 * @since 1.0.0
325 */
326 private function register_json_block_styles() {
327 $blocks_dir = DESIGNSETGO_PATH . 'build/blocks/';
328
329 if ( ! file_exists( $blocks_dir ) ) {
330 return;
331 }
332
333 // Find all block directories.
334 $blocks = array_filter( glob( $blocks_dir . '*' ), 'is_dir' );
335
336 // Collect all style variation data to register in a single filter callback.
337 $all_style_variations = array();
338
339 foreach ( $blocks as $block_dir ) {
340 $styles_dir = $block_dir . '/styles/';
341
342 // Check if the block has a styles directory.
343 if ( ! is_dir( $styles_dir ) ) {
344 continue;
345 }
346
347 // Find all JSON files in the styles directory.
348 $style_files = glob( $styles_dir . '*.json' );
349
350 foreach ( $style_files as $style_file ) {
351 // Read and decode the JSON file.
352 $style_data = wp_json_file_decode( $style_file, array( 'associative' => true ) );
353
354 if ( ! $style_data || ! isset( $style_data['slug'] ) || ! isset( $style_data['title'] ) ) {
355 continue;
356 }
357
358 // Get the block types this style applies to.
359 $block_types = isset( $style_data['blockTypes'] ) ? $style_data['blockTypes'] : array();
360
361 // Register the style for each block type.
362 foreach ( $block_types as $block_type ) {
363 // Register the block style variation.
364 // Note: No separate stylesheet needed, so style_handle is omitted.
365 register_block_style(
366 $block_type,
367 array(
368 'name' => $style_data['slug'],
369 'label' => $style_data['title'],
370 )
371 );
372
373 // Collect the style variation data for batched registration.
374 if ( isset( $style_data['styles'] ) ) {
375 $all_style_variations[] = array(
376 'block_type' => $block_type,
377 'slug' => $style_data['slug'],
378 'styles' => $style_data['styles'],
379 );
380 }
381 }
382 }
383 }
384
385 // Register all style variations with a single filter callback.
386 if ( ! empty( $all_style_variations ) ) {
387 add_filter(
388 'wp_theme_json_data_default',
389 function ( $theme_json ) use ( $all_style_variations ) {
390 $new_data = array(
391 'version' => 3,
392 'styles' => array(
393 'blocks' => array(),
394 ),
395 );
396
397 foreach ( $all_style_variations as $variation ) {
398 if ( ! isset( $new_data['styles']['blocks'][ $variation['block_type'] ] ) ) {
399 $new_data['styles']['blocks'][ $variation['block_type'] ] = array();
400 }
401
402 $new_data['styles']['blocks'][ $variation['block_type'] ]['variations'][ $variation['slug'] ] = $variation['styles'];
403 }
404
405 return $theme_json->update_with( $new_data );
406 }
407 );
408 }
409 }
410
411 /**
412 * Get registered DesignSetGo blocks.
413 *
414 * @return array
415 */
416 public static function get_registered_blocks() {
417 $blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
418 $dsg_blocks = array();
419
420 foreach ( $blocks as $name => $block ) {
421 if ( strpos( $name, 'designsetgo/' ) === 0 ) {
422 $dsg_blocks[] = $name;
423 }
424 }
425
426 return $dsg_blocks;
427 }
428 }
429