PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.4.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.4.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / blocks / register.php

register.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.4.0, at inc/blocks/register.php

665 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blocks Register
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\Blocks;
9
10 use SureDonation\Inc\Assets\Register as Assets_Register;
11 use SureDonation\Inc\Helper;
12 use SureDonation\Inc\Payments\Offline\Offline_Helper;
13 use SureDonation\Inc\Payments\PayPal\PayPal_Helper;
14 use SureDonation\Inc\Payments\Payment_Helper;
15 use SureDonation\Inc\Payments\Stripe\Stripe_Helper;
16 use SureDonation\Inc\Post_Types\Donation_Form;
17 use SureDonation\Inc\Traits\Get_Instance;
18
19 // Exit if accessed directly.
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23
24 /**
25 * Register class for blocks.
26 *
27 * @since 0.0.1
28 */
29 class Register {
30 use Get_Instance;
31
32 /**
33 * Constructor.
34 *
35 * @since 0.0.1
36 */
37 public function __construct() {
38 add_action( 'init', [ $this, 'register_embed_block_script' ], 5 );
39 add_action( 'init', [ $this, 'register_blocks' ] );
40 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
41 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_campaign_editor_assets' ] );
42 add_filter( 'block_categories_all', [ $this, 'register_block_category' ], 10, 2 );
43 add_filter( 'block_editor_settings_all', [ $this, 'add_campaign_iframe_styles' ], 10, 2 );
44 add_filter( 'block_editor_settings_all', [ $this, 'add_donation_form_iframe_styles' ], 10, 2 );
45 add_filter( 'block_editor_settings_all', [ $this, 'add_phone_iframe_styles' ], 10, 2 );
46 add_action( 'enqueue_block_assets', [ $this, 'enqueue_preview_field_scripts' ] );
47 }
48
49 /**
50 * Load the dropdown and phone field libraries into the block editor canvas.
51 *
52 * The donation form embed block previews the real form through the block's PHP
53 * render_callback (ServerSideRender), which returns markup only — a REST render
54 * emits no wp_footer(), so nothing the render callback enqueues ever reaches the
55 * page. Without their libraries the dropdown stays an unstyled native <select>
56 * and the phone field renders with no country flag or dial code.
57 *
58 * `enqueue_block_assets` is the hook WordPress replays when it collects assets
59 * for the iframed canvas: _wp_get_iframed_editor_assets() fires it and returns
60 * both the printed styles AND scripts, which the canvas injects into its own
61 * document. It is explicitly the hook for front-end assets that need to run
62 * against editor content.
63 *
64 * Only the two field libraries are loaded. The payment gateways are excluded on
65 * purpose: mounting Stripe Elements or the PayPal SDK would pull third-party
66 * scripts into wp-admin on every editor load and open live gateway connections
67 * for a preview the author cannot interact with. Those keep their static
68 * placeholders (see _editor-preview.scss).
69 *
70 * Both initialisers are safe here — each is ready-state aware, guards against
71 * double-initialising via a dataset flag, and exposes a re-init hook the editor
72 * calls once ServerSideRender has injected the markup (see the block's edit
73 * component).
74 *
75 * @return void
76 * @since 1.4.0
77 */
78 public function enqueue_preview_field_scripts() {
79 // Front end already enqueues these per-block from the field render; this is
80 // the editor-only path.
81 if ( ! is_admin() ) {
82 return;
83 }
84
85 // The form builder mounts its own React controls for these fields.
86 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
87 if ( $screen && 'suredonation_form' === $screen->post_type ) {
88 return;
89 }
90
91 // The handles are registered on wp_enqueue_scripts, which never fires in
92 // admin. Registration is side-effect free (wp_register_* only), so reuse it
93 // rather than duplicating the definitions.
94 Assets_Register::get_instance()->register_frontend_assets();
95
96 // Each script handle already depends on its vendor library, so enqueuing the
97 // initialiser pulls the library in, in the right order.
98 wp_enqueue_style( 'suredonation-tom-select' );
99 wp_enqueue_script( 'suredonation-dropdown' );
100 wp_enqueue_style( 'suredonation-intl-tel-input' );
101 wp_enqueue_script( 'suredonation-phone' );
102
103 // The payment bundle mounts Stripe Elements and the PayPal buttons. Both are
104 // client-only on mount: Stripe's elements()/mount() builds an iframe, and
105 // PayPal's createOrder does not run until the button is clicked, so nothing
106 // here reaches the server or creates a PaymentIntent.
107 wp_enqueue_script( 'suredonation-form-frontend' );
108
109 $this->enqueue_preview_gateway_assets();
110 }
111
112 /**
113 * Enqueue gateway assets for each donation form embedded in the current post.
114 *
115 * The PayPal SDK is enqueued by gateway code hooked to
116 * `suredonation_enqueue_form_frontend_scripts`, which the render callback fires
117 * with the form's id and content — that hook is how a gateway decides whether it
118 * is even used by the form. A REST render throws the enqueue away, so fire it
119 * here instead, for the forms this post actually embeds.
120 *
121 * Resolving the forms (rather than loading every gateway unconditionally) keeps
122 * the gateway's own `form_has_paypal()` style gating intact, so a post with no
123 * PayPal-enabled form does not pull the SDK into wp-admin.
124 *
125 * Also localises the payment settings the bundle reads, per form.
126 *
127 * @return void
128 * @since 1.4.0
129 */
130 private function enqueue_preview_gateway_assets() {
131 $post = get_post();
132
133 if ( ! $post instanceof \WP_Post || ! has_block( 'suredonation/donation-form', $post ) ) {
134 return;
135 }
136
137 foreach ( parse_blocks( $post->post_content ) as $block ) {
138 if ( 'suredonation/donation-form' !== ( $block['blockName'] ?? '' ) ) {
139 continue;
140 }
141
142 $form_id = absint( $block['attrs']['formId'] ?? 0 );
143 $form = $form_id ? get_post( $form_id ) : null;
144
145 if ( ! $form instanceof \WP_Post || Donation_Form::POST_TYPE !== $form->post_type ) {
146 continue;
147 }
148
149 /** This action is documented in inc/blocks/donation-form/block.php */
150 do_action( 'suredonation_enqueue_form_frontend_scripts', $form_id, $form->post_content );
151
152 wp_localize_script(
153 'suredonation-form-frontend',
154 'suredonationPayment',
155 Helper::get_form_payment_settings( $form_id )
156 );
157 }
158 }
159
160 /**
161 * Register the donation form embed block editor script.
162 *
163 * Runs before register_blocks() so the handle exists when block.json is read.
164 * Not gated by post type — the embed block should work on all post types.
165 *
166 * @return void
167 * @since 1.0.0
168 */
169 public function register_embed_block_script() {
170 $asset_file = SUREDONATION_DIR . 'assets/build/blocks/donation-form/editor.asset.php';
171 $asset = file_exists( $asset_file )
172 ? require $asset_file
173 : [
174 'dependencies' => [],
175 'version' => SUREDONATION_VER,
176 ];
177
178 wp_register_script(
179 'suredonation-donation-form-editor',
180 SUREDONATION_URL . 'assets/build/blocks/donation-form/editor.js',
181 $asset['dependencies'],
182 $asset['version'],
183 true
184 );
185
186 // Data for the block editor placeholder (logo). The campaign blocks
187 // bundle defines the same global elsewhere; localizing it here keeps the
188 // logo available wherever the donation form block is inserted.
189 wp_localize_script(
190 'suredonation-donation-form-editor',
191 'suredonationCampaignBlocks',
192 $this->get_campaign_blocks_data()
193 );
194
195 wp_register_style(
196 'suredonation-donation-form-editor',
197 SUREDONATION_URL . 'assets/build/blocks/donation-form/editor.css',
198 [],
199 $asset['version']
200 );
201 }
202
203 /**
204 * Data localized for the block editor placeholders (logo).
205 *
206 * Shared by the donation form embed block and the campaign display blocks,
207 * both of which expose it on the `suredonationCampaignBlocks` JS global.
208 *
209 * `currentPostType` lets a block scope its editor registration to a single
210 * post type (the Campaign Donate Button registers only on the campaign
211 * editor). It is read from the current screen, so it is only populated for
212 * the caller that runs on `enqueue_block_editor_assets` (the campaign editor
213 * assets); the embed-block caller runs on `init`, where there is no screen,
214 * so it receives an empty string. That is harmless — the embed block only
215 * consumes `logoUrl`.
216 *
217 * @return array<string, string>
218 * @since 1.0.0
219 */
220 public function get_campaign_blocks_data() {
221 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
222
223 return [
224 'logoUrl' => esc_url_raw( SUREDONATION_URL . 'images/suredonation-logo.svg' ),
225 'currentPostType' => $screen ? (string) $screen->post_type : '',
226 ];
227 }
228
229 /**
230 * Register custom block category for SureDonation blocks.
231 *
232 * The field-block category is limited to the donation form editor; the
233 * campaign display-block category is registered everywhere else.
234 *
235 * @param array<int, array<string, mixed>> $categories Existing block categories.
236 * @param \WP_Block_Editor_Context $context Block editor context.
237 * @return array<int, array<string, mixed>> Modified block categories.
238 * @since 0.0.1
239 */
240 public function register_block_category( $categories, $context ) {
241 // Field-block category on the donation form editor.
242 if ( isset( $context->post ) && 'suredonation_form' === $context->post->post_type ) {
243 return array_merge(
244 [
245 [
246 'slug' => 'suredonation',
247 'title' => __( 'General Fields', 'suredonation' ),
248 'icon' => null,
249 ],
250 ],
251 $categories
252 );
253 }
254
255 // Campaign display-block category on every other editor — including the
256 // Site Editor and widget contexts where $context->post is unset — so the
257 // campaign blocks always group under SureDonation in the inserter. Only
258 // the donation form editor (handled above) is excluded.
259 return array_merge(
260 [
261 [
262 'slug' => 'suredonation-campaign',
263 'title' => __( 'SureDonation', 'suredonation' ),
264 'icon' => null,
265 ],
266 ],
267 $categories
268 );
269 }
270
271 /**
272 * Enqueue the campaign display blocks editor bundle.
273 *
274 * Loads on every block editor so the campaign blocks can be added to any
275 * page/post/CPT — except the donation form editor, which has its own field
276 * blocks. On a campaign post the blocks auto-bind to that campaign; elsewhere
277 * the block inspector exposes a campaign selector.
278 *
279 * @return void
280 * @since 1.0.0
281 */
282 public function enqueue_campaign_editor_assets() {
283 $screen = get_current_screen();
284
285 // Load everywhere except the donation form editor.
286 if ( ! $screen || 'suredonation_form' === $screen->post_type ) {
287 return;
288 }
289
290 $asset_file = SUREDONATION_DIR . 'assets/build/campaign-blocks.asset.php';
291 $asset = file_exists( $asset_file )
292 ? require $asset_file
293 : [
294 'dependencies' => [ 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n', 'wp-block-editor', 'wp-data', 'wp-server-side-render' ],
295 'version' => SUREDONATION_VER,
296 ];
297
298 wp_enqueue_script(
299 'suredonation-campaign-blocks',
300 SUREDONATION_URL . 'assets/build/campaign-blocks.js',
301 $asset['dependencies'],
302 $asset['version'],
303 true
304 );
305
306 wp_set_script_translations( 'suredonation-campaign-blocks', 'suredonation' );
307
308 // Data for the campaign block editor placeholder (logo).
309 wp_localize_script(
310 'suredonation-campaign-blocks',
311 'suredonationCampaignBlocks',
312 $this->get_campaign_blocks_data()
313 );
314
315 // Style the server-side-rendered block previews in the editor.
316 $style_file = SUREDONATION_DIR . 'assets/build/blocks/campaign/style-style.css';
317 $style_version = file_exists( $style_file )
318 ? (string) filemtime( $style_file )
319 : SUREDONATION_VER;
320
321 wp_enqueue_style(
322 'suredonation-campaign-blocks',
323 SUREDONATION_URL . 'assets/build/blocks/campaign/style-style.css',
324 [],
325 $style_version
326 );
327 }
328
329 /**
330 * Append an inline stylesheet to the block-editor iframe settings.
331 *
332 * Styles enqueued via enqueue_block_editor_assets load in the editor's outer
333 * frame only; the block canvas is iframed, so server-side-rendered previews
334 * would otherwise render unstyled. Adding CSS here makes WordPress inject it
335 * inside the iframe, matching the frontend.
336 *
337 * @param array<string, mixed> $settings Block editor settings (by reference).
338 * @param string $css Stylesheet contents to inline.
339 * @return void
340 * @since 1.4.0
341 */
342 private function append_iframe_style( &$settings, $css ) {
343 if ( '' === $css ) {
344 return;
345 }
346
347 if ( ! isset( $settings['styles'] ) || ! is_array( $settings['styles'] ) ) {
348 $settings['styles'] = [];
349 }
350
351 $settings['styles'][] = [ 'css' => $css ];
352 }
353
354 /**
355 * Read a stylesheet for iframe inlining, cached per request by file mtime so
356 * the filter (which can run more than once per load) reads each file from disk
357 * at most once until it changes.
358 *
359 * @param string $style_file Absolute path to the stylesheet.
360 * @param array<string, string> $replacements Optional search => replace pairs
361 * applied to the CSS, e.g. to rewrite
362 * relative asset URLs to absolute
363 * plugin URLs so they resolve inside
364 * the iframe.
365 * @return string The stylesheet contents, or '' when unavailable.
366 * @since 1.4.0
367 */
368 private function read_iframe_css( $style_file, $replacements = [] ) {
369 // Keyed by path so the aggregate + vendor stylesheets do not evict each
370 // other's cache entry.
371 static $cache = [];
372
373 if ( ! file_exists( $style_file ) ) {
374 return '';
375 }
376
377 $mtime = filemtime( $style_file );
378 if ( ! isset( $cache[ $style_file ] ) || $cache[ $style_file ]['mtime'] !== $mtime ) {
379 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading the plugin's own/vendored stylesheet to inline into the editor iframe.
380 $css = file_get_contents( $style_file );
381 $css = false === $css ? '' : $css;
382
383 if ( '' !== $css && ! empty( $replacements ) ) {
384 $css = str_replace( array_keys( $replacements ), array_values( $replacements ), $css );
385 }
386
387 $cache[ $style_file ] = [
388 'mtime' => $mtime,
389 'css' => $css,
390 ];
391 }
392
393 return $cache[ $style_file ]['css'];
394 }
395
396 /**
397 * Inject the campaign block styles into the editor canvas iframe.
398 *
399 * @param array<string, mixed> $settings Block editor settings.
400 * @param \WP_Block_Editor_Context $context Block editor context.
401 * @return array<string, mixed> Modified settings.
402 * @since 1.0.0
403 */
404 public function add_campaign_iframe_styles( $settings, $context ) {
405 // Inject wherever the campaign blocks can be used (everywhere except the
406 // donation form editor), so their editor previews match the frontend.
407 if ( ! isset( $context->post ) || 'suredonation_form' === $context->post->post_type ) {
408 return $settings;
409 }
410
411 $this->append_iframe_style(
412 $settings,
413 $this->read_iframe_css( SUREDONATION_DIR . 'assets/build/blocks/campaign/style-style.css' )
414 );
415
416 return $settings;
417 }
418
419 /**
420 * Inject the donation form styles into the editor canvas iframe.
421 *
422 * The donation form embed block previews the real form via ServerSideRender,
423 * and the canvas is iframed, so styles enqueued on the outer frame never reach
424 * it. Three stylesheets are inlined:
425 *
426 * - the aggregate donation-form CSS, which also carries every field block's
427 * styles and the editor-preview reconciliation (see _editor-preview.scss);
428 * - the tom-select vendor CSS, which paints both the dropdown field's
429 * server-rendered `.ts-wrapper` placeholder and the real control tom-select
430 * mounts over it; and
431 * - the intl-tel-input vendor CSS, for the `.iti` wrapper that library builds
432 * around the phone input. Its flag sprites are referenced relative to the
433 * stylesheet, so those paths are rewritten to absolute plugin URLs — inlining
434 * drops the base they resolve against.
435 *
436 * Both libraries genuinely run in the canvas: they are enqueued on
437 * enqueue_block_assets (see enqueue_preview_field_scripts) and re-initialised by
438 * the block's edit component once ServerSideRender has injected the markup. The
439 * payment gateways are not, so their placeholders stay static.
440 *
441 * @param array<string, mixed> $settings Block editor settings.
442 * @param \WP_Block_Editor_Context $context Block editor context.
443 * @return array<string, mixed> Modified settings.
444 * @since 1.4.0
445 */
446 public function add_donation_form_iframe_styles( $settings, $context ) {
447 // Inject wherever the embed block can be used, including the Site Editor and
448 // widget contexts where $context->post is unset. Only the donation form
449 // builder is excluded; it styles its own field blocks separately (see
450 // add_phone_iframe_styles + form-editor).
451 if ( isset( $context->post ) && 'suredonation_form' === $context->post->post_type ) {
452 return $settings;
453 }
454
455 $this->append_iframe_style(
456 $settings,
457 $this->read_iframe_css( SUREDONATION_DIR . 'assets/build/blocks/donation-form/style-style.css' )
458 );
459 $this->append_iframe_style(
460 $settings,
461 $this->read_iframe_css( SUREDONATION_DIR . 'assets/css/vendor/tom-select.css' )
462 );
463 $this->append_iframe_style(
464 $settings,
465 $this->read_iframe_css(
466 SUREDONATION_DIR . 'assets/css/vendor/intl/intlTelInput.min.css',
467 [ '../intl/img/' => SUREDONATION_URL . 'assets/css/vendor/intl/img/' ]
468 )
469 );
470
471 return $settings;
472 }
473
474 /**
475 * Inject the intl-tel-input stylesheet into the editor canvas iframe.
476 *
477 * The phone block renders the real intl-tel-input control in the form builder
478 * editor so its preview (flag + dial code) matches the front end. The canvas
479 * is iframed, so the library CSS is added to the editor settings rather than
480 * enqueued on the outer frame. Gated to the donation form editor, where the
481 * phone block lives. (The relative flag sprite paths are rewritten to absolute
482 * plugin URLs so they resolve inside the iframe.)
483 *
484 * @param array<string, mixed> $settings Block editor settings.
485 * @param \WP_Block_Editor_Context $context Block editor context.
486 * @return array<string, mixed> Modified settings.
487 * @since 1.1.1
488 */
489 public function add_phone_iframe_styles( $settings, $context ) {
490 // Only the donation form editor uses the field blocks (incl. phone).
491 if ( ! isset( $context->post ) || 'suredonation_form' !== $context->post->post_type ) {
492 return $settings;
493 }
494
495 $this->append_iframe_style(
496 $settings,
497 $this->read_iframe_css(
498 SUREDONATION_DIR . 'assets/css/vendor/intl/intlTelInput.min.css',
499 [ '../intl/img/' => SUREDONATION_URL . 'assets/css/vendor/intl/img/' ]
500 )
501 );
502
503 return $settings;
504 }
505
506 /**
507 * Enqueue block editor assets.
508 *
509 * Only loads on the donation form editor.
510 *
511 * @return void
512 * @since 0.0.1
513 */
514 public function enqueue_editor_assets() {
515 $screen = get_current_screen();
516
517 // Only load on donation form editor.
518 if ( ! $screen || 'suredonation_form' !== $screen->post_type ) {
519 return;
520 }
521
522 // Use the asset.php content hash as the version so rebuilds bust the
523 // browser cache. Falls back to SUREDONATION_VER if the asset file
524 // is missing.
525 $blocks_asset_file = SUREDONATION_DIR . 'assets/build/blocks.asset.php';
526 $blocks_asset = file_exists( $blocks_asset_file )
527 ? require $blocks_asset_file
528 : [
529 'dependencies' => [ 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n', 'wp-block-editor', 'wp-data' ],
530 'version' => SUREDONATION_VER,
531 ];
532
533 // Enqueue the blocks script.
534 wp_enqueue_script(
535 'suredonation-blocks',
536 SUREDONATION_URL . 'assets/build/blocks.js',
537 $blocks_asset['dependencies'],
538 $blocks_asset['version'],
539 true
540 );
541
542 // Load JS translations for blocks.
543 wp_set_script_translations( 'suredonation-blocks', 'suredonation' );
544
545 // Localize script with admin data for blocks.
546 $global_currency = Payment_Helper::get_currency();
547
548 wp_localize_script(
549 'suredonation-blocks',
550 'suredonation_admin',
551 [
552 'payments' => [
553 'stripe_connected' => Stripe_Helper::is_stripe_connected(),
554 'paypal_connected' => PayPal_Helper::is_paypal_connected(),
555 'stripe_connect_url' => Stripe_Helper::get_stripe_connect_url(),
556 // Base payments-settings URL; the editor's "Configure Payment
557 // Account" CTA appends the block's selected gateway subpage.
558 'settings_url' => Payment_Helper::get_settings_url(),
559 'offline_enabled' => Offline_Helper::is_offline_enabled(),
560 'gateways' => apply_filters(
561 'suredonation_editor_payment_gateways',
562 [
563 [
564 'value' => 'stripe',
565 'label' => __( 'Stripe', 'suredonation' ),
566 'supports_recurring' => true,
567 ],
568 [
569 'value' => 'offline',
570 'label' => __( 'Offline Donations', 'suredonation' ),
571 'supports_recurring' => false,
572 ],
573 ]
574 ),
575 ],
576 'fee_recovery' => Payment_Helper::get_fee_recovery_settings(),
577 'currency' => $global_currency,
578 'currencySymbol' => Payment_Helper::get_currency_symbol( $global_currency ),
579 // Resolved default validation messages so the editor can show
580 // them as placeholders on each field's Error Message control.
581 'validationMessages' => \SureDonation\Inc\Field_Validation::get_resolved_validation_messages(),
582 ]
583 );
584 }
585
586 /**
587 * Register all blocks.
588 *
589 * @return void
590 * @since 0.0.1
591 */
592 public function register_blocks() {
593 $blocks = [
594 [
595 'dir' => SUREDONATION_DIR . 'inc/blocks/**/*.php',
596 'namespace' => 'SureDonation\\Inc\\Blocks',
597 ],
598 ];
599
600 /**
601 * Filter to add and register additional blocks.
602 *
603 * @param array<int, array<string, string>> $additional_blocks Additional blocks to register.
604 */
605 $additional_blocks = apply_filters( 'suredonation_register_additional_blocks', [] );
606
607 if ( ! empty( $additional_blocks ) && is_array( $additional_blocks ) && count( $additional_blocks ) > 0 ) {
608 $blocks = [ ...$blocks, ...$additional_blocks ];
609 }
610
611 foreach ( $blocks as $block ) {
612 if ( ! is_array( $block ) || ! isset( $block['dir'] ) || ! isset( $block['namespace'] ) ) {
613 continue;
614 }
615 $block_files = glob( $block['dir'] );
616 if ( is_array( $block_files ) ) {
617 $this->register_block( $block_files, $block['namespace'], 'Block' );
618 }
619 }
620 }
621
622 /**
623 * Register blocks from directory.
624 *
625 * @param array<int, string> $blocks_dir Array of block file paths.
626 * @param string $block_namespace Block namespace.
627 * @param string $base Base class name.
628 * @return void
629 * @since 0.0.1
630 */
631 public function register_block( $blocks_dir, $block_namespace, $base ) {
632 if ( empty( $blocks_dir ) ) {
633 return;
634 }
635
636 foreach ( $blocks_dir as $filename ) {
637 // Skip base.php and register.php.
638 $basename = basename( $filename );
639 if ( 'base.php' === $basename || 'register.php' === $basename ) {
640 continue;
641 }
642
643 require_once $filename;
644
645 // Replace hyphens with underscores in directory name.
646 $classname = str_replace( '-', '_', basename( dirname( $filename ) ) );
647
648 // Convert to title case.
649 $classname = ucwords( $classname, '_' );
650
651 $full_class_name = $block_namespace . '\\' . $classname . '\\' . $base;
652
653 // Check if the class exists.
654 if ( class_exists( $full_class_name ) ) {
655 $block = new $full_class_name();
656
657 // Call register on the block object.
658 if ( method_exists( $block, 'register' ) ) {
659 $block->register();
660 }
661 }
662 }
663 }
664 }
665