Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.37% covered (danger)
1.37%
5 / 366
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Code_Block
1.37% covered (danger)
1.37%
5 / 365
0.00% covered (danger)
0.00%
0 / 11
4374.04
0.00% covered (danger)
0.00%
0 / 1
 should_load_block
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 assets_available
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
156
 setup
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 register_editor_assets
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
6
 enqueue_view_assets
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 get_module_asset_data
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 override_block_style
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 register_block_type_args
3.79% covered (danger)
3.79%
5 / 132
0.00% covered (danger)
0.00%
0 / 1
118.76
 enqueue_editor_assets
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 render_block
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 1
552
 after_setup_theme
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Code Block
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack;
11
12require_once __DIR__ . '/class-code-block-html-replacer.php';
13
14use WP_Theme_JSON;
15
16/**
17 * Code Block class.
18 *
19 * Contains necessary functionality for the Code Block.
20 */
21abstract class Code_Block {
22    const MODULE_PREFIX = '@a8cCodeBlock/';
23
24    /**
25     * Language names for display.
26     *
27     * @var array<string, string>
28     */
29    public static $language_name_rewrites = array(
30        'Brainfuck' => 'Brainf***',
31    );
32
33    /**
34     * Filterable check for whether the block should be available.
35     *
36     * @return bool
37     */
38    private static function should_load_block(): bool {
39        $filtered_value = apply_filters( 'jetpack_mu_wpcom_should_load_code_block', true );
40        return \is_bool( $filtered_value ) ? $filtered_value : false;
41    }
42
43    /**
44     * Check if the build assets required for the code block are available.
45     *
46     * @return bool
47     */
48    private static function assets_available(): bool {
49        static $result = null;
50        if ( null === $result ) {
51            $block_definition_asset_readable = is_readable( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-block-definition/wpcom-blocks-code-block-definition.asset.php' );
52            $module_asset_readable           = is_readable( Jetpack_Mu_Wpcom::BASE_DIR . 'build-module/assets.php' );
53            $editor_style_asset_readable     = is_readable( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-editor-style/wpcom-blocks-code-editor-style.asset.php' );
54            $style_asset_readable            = is_readable( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-style/wpcom-blocks-code-style.asset.php' );
55
56            $result = $block_definition_asset_readable && $module_asset_readable && $editor_style_asset_readable && $style_asset_readable;
57            if ( ! $result && \defined( 'IS_WPCOM' ) && IS_WPCOM ) {
58                require_once WP_CONTENT_DIR . '/lib/log2logstash/log2logstash.php';
59                $data = array(
60                    'blog_id' => get_current_blog_id(),
61                );
62
63                $message = 'Missing build asset files.';
64                if ( ! $block_definition_asset_readable ) {
65                    $message .= ' Block definition asset file is missing `' . Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-block-definition/wpcom-blocks-code-block-definition.asset.php`.';
66                }
67                if ( ! $module_asset_readable ) {
68                    $message .= ' Module asset file is missing `' . Jetpack_Mu_Wpcom::BASE_DIR . 'build-module/assets.php`.';
69                }
70                if ( ! $editor_style_asset_readable ) {
71                    $message .= ' Editor style asset file is missing `' . Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-editor-style/wpcom-blocks-code-editor-style.asset.php`.';
72                }
73                if ( ! $style_asset_readable ) {
74                    $message .= ' Style asset file is missing `' . Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-style/wpcom-blocks-code-style.asset.php`.';
75                }
76
77                log2logstash(
78                    array(
79                        'feature' => 'jetpack-enhanced-code-block',
80                        'message' => $message,
81                        'extra'   => wp_json_encode( $data, JSON_UNESCAPED_SLASHES ),
82                    )
83                );
84            }
85        }
86        return $result;
87    }
88
89    /**
90     * Set up the block.
91     */
92    public static function setup() {
93        if (
94            ! self::should_load_block() ||
95            ! self::assets_available()
96        ) {
97            return;
98        }
99
100        add_action( 'after_setup_theme', array( __CLASS__, 'after_setup_theme' ), 100 );
101        add_filter( 'register_block_type_args', array( __CLASS__, 'register_block_type_args' ), 150, 2 );
102    }
103
104    /**
105     * Registration of editor scripts, styles, and modules.
106     *
107     * Called lazily when editor assets are needed, not on every request.
108     */
109    private static function register_editor_assets() {
110        static $done = false;
111        if ( $done ) {
112            return;
113        }
114        $done = true;
115
116        $block_definition_asset_file  = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-block-definition/wpcom-blocks-code-block-definition.asset.php';
117        $jetpack_wpcom_modules_assets = self::get_module_asset_data();
118
119        // The block definition must contain the script dependencies that the edit function script module requires.
120        // Append static dependency list here. Some duplicates may appear, that should be harmless.
121        $block_definition_dependencies = array_merge(
122            $block_definition_asset_file['dependencies'],
123            array(
124                'react',
125                'wp-block-editor',
126                'wp-blocks',
127                'wp-components',
128                'wp-data',
129                'wp-editor',
130                'wp-i18n',
131                'wp-keycodes',
132            )
133        );
134
135        wp_register_script(
136            self::MODULE_PREFIX . 'block-definition',
137            plugins_url( 'build/wpcom-blocks-code-block-definition/wpcom-blocks-code-block-definition.js', Jetpack_Mu_Wpcom::BASE_FILE ),
138            $block_definition_dependencies,
139            $block_definition_asset_file['version'],
140            array( 'in_footer' => true )
141        );
142
143        wp_register_script_module(
144            self::MODULE_PREFIX . 'block-edit-function',
145            plugins_url( 'build-module/wpcom-blocks-code-edit-function/wpcom-blocks-code-edit-function.js', Jetpack_Mu_Wpcom::BASE_FILE ),
146            $jetpack_wpcom_modules_assets['wpcom-blocks-code-edit-function/wpcom-blocks-code-edit-function.js']['dependencies'],
147            $jetpack_wpcom_modules_assets['wpcom-blocks-code-edit-function/wpcom-blocks-code-edit-function.js']['version']
148        );
149
150        $editor_style_asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-editor-style/wpcom-blocks-code-editor-style.asset.php';
151        wp_register_style(
152            self::MODULE_PREFIX . 'editor',
153            plugins_url( 'build/wpcom-blocks-code-editor-style/wpcom-blocks-code-editor-style.css', Jetpack_Mu_Wpcom::BASE_FILE ),
154            array(),
155            $editor_style_asset_file['version']
156        );
157
158        $block_worker_url     = plugins_url( 'build-module/wpcom-blocks-code-worker/wpcom-blocks-code-worker.js', Jetpack_Mu_Wpcom::BASE_FILE );
159        $block_worker_version = $jetpack_wpcom_modules_assets['wpcom-blocks-code-worker/wpcom-blocks-code-worker.js']['version'];
160        add_filter(
161            'script_module_data_' . self::MODULE_PREFIX . 'block-edit-function',
162            function ( array $data ) use ( $block_worker_url, $block_worker_version ): array {
163                $data['workerUrl']     = $block_worker_url;
164                $data['workerVersion'] = $block_worker_version;
165                return $data;
166            }
167        );
168    }
169
170    /**
171     * Enqueue view script module.
172     */
173    private static function enqueue_view_assets() {
174        static $done = false;
175        if ( $done ) {
176            return;
177        }
178        $done = true;
179
180        $jetpack_wpcom_modules_assets = self::get_module_asset_data();
181        wp_enqueue_script_module(
182            self::MODULE_PREFIX . 'block-front',
183            plugins_url( 'build-module/wpcom-blocks-code-block-front/wpcom-blocks-code-block-front.js', Jetpack_Mu_Wpcom::BASE_FILE ),
184            $jetpack_wpcom_modules_assets['wpcom-blocks-code-block-front/wpcom-blocks-code-block-front.js']['dependencies'],
185            $jetpack_wpcom_modules_assets['wpcom-blocks-code-block-front/wpcom-blocks-code-block-front.js']['version']
186        );
187    }
188
189    /**
190     * Get the module asset data.
191     *
192     * @return array
193     */
194    private static function get_module_asset_data() {
195        static $jetpack_wpcom_modules_assets = null;
196        if ( null === $jetpack_wpcom_modules_assets ) {
197            $jetpack_wpcom_modules_assets = include Jetpack_Mu_Wpcom::BASE_DIR . 'build-module/assets.php';
198        }
199        return $jetpack_wpcom_modules_assets;
200    }
201
202    /**
203     * Set up the block view styles.
204     *
205     * Core's `wp-block-code` handle must be used in order to work with the global styles system.
206     * It relies on checking whether this style is enqueued to add the associated global styles to the page.
207     *
208     * Instead of using a different style handle, replace the registered style for `wp-block-code`.
209     *
210     * @see https://core.trac.wordpress.org/browser/tags/6.8.3/src/wp-includes/global-styles-and-settings.php#L322
211     *
212     * @global \WP_Styles $wp_styles
213     */
214    public static function override_block_style() {
215        global $wp_styles;
216
217        $src = plugins_url( 'build/wpcom-blocks-code-style/wpcom-blocks-code-style.css', Jetpack_Mu_Wpcom::BASE_FILE );
218        // Skip work if style is registered as desired.
219        if ( isset( $wp_styles->registered['wp-block-code'] ) && $wp_styles->registered['wp-block-code']->src === $src ) {
220            return;
221        }
222
223        $was_enqueued = wp_style_is( 'wp-block-code', 'enqueued' );
224        wp_deregister_style( 'wp-block-code' );
225
226        $style_asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-blocks-code-style/wpcom-blocks-code-style.asset.php';
227        $version          = $style_asset_file['version'];
228
229        wp_register_style(
230            'wp-block-code',
231            $src,
232            array(),
233            $version
234        );
235        if ( $was_enqueued ) {
236            wp_enqueue_style( 'wp-block-code' );
237        }
238    }
239
240    /**
241     * Filter for block registration to modify the core/code block.
242     *
243     * @param array|false $args The block type arguments, or false to cancel block registration.
244     * @param string      $block_type The block type name.
245     *
246     * @return array|false The modified block type arguments, or false if $args was false.
247     */
248    public static function register_block_type_args( array|false $args, string $block_type ): array|false {
249        if (
250            ! \is_array( $args )
251            || 'core/code' !== $block_type
252
253            // In some cases the block may not include the content attribute.
254            // Only perform enhancement on the _full_, expected block.
255            || ! isset( $args['attributes']['content'] )
256
257            // Skip if the block is already processed.
258            || $args['render_callback'] === array( __CLASS__, 'render_block' )
259        ) {
260            return $args;
261        }
262
263        // Register assets and hooks only when overriding the block.
264        self::register_editor_assets();
265        self::override_block_style();
266
267        static $hooks_registered = false;
268        if ( ! $hooks_registered ) {
269            $hooks_registered = true;
270            add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_editor_assets' ) );
271            add_action(
272                'wp_enqueue_scripts',
273                function () {
274                    if ( wp_should_load_block_editor_scripts_and_styles() ) {
275                        self::enqueue_editor_assets();
276                    }
277
278                    /*
279                     * Core should handle this, but Script Module assets are not currently handled.
280                     */
281                    if (
282                        ! wp_should_load_block_assets_on_demand()
283                        && has_block( 'core/code' )
284                    ) {
285                        self::enqueue_view_assets();
286                    }
287                }
288            );
289        }
290
291        $args['render_callback']       = array( __CLASS__, 'render_block' );
292        $args['editor_script_handles'] = array_merge( array( self::MODULE_PREFIX . 'block-definition' ), $args['editor_script_handles'] ?? array() );
293
294        $args['editor_style_handles'] = array( self::MODULE_PREFIX . 'editor' );
295        $args['style_handles']        = array( 'wp-block-code' );
296        unset( $args['view_style_handles'] );
297
298        /*
299         * Add selectors for typography targetting problematic elements.
300         *
301         * - The descendent PRE element needs font-family styling like this to ensure it receives
302         *   user agent default styling like monospace, as well as PRE element styling from themes,
303         *   and can also be styled by global styles and theme.json.
304         */
305        $args['selectors'] = array(
306            'root'       => '.wp-block-code',
307            'typography' => array(
308
309                /*
310                 * These are experimental at the moment. The camelCase form appears to be used, but
311                 * it's possible the kebab-case currently used in documentation may be used when
312                 * they're stabilized.
313                 */
314                'fontFamily'  => '.wp-block-code, .wp-block-code pre',
315                'font-family' => '.wp-block-code, .wp-block-code pre',
316            ),
317        );
318
319        /**
320         * Typography support:
321         *
322         * Line height and letter spacing may be problematic for rendering in the editor,
323         * line numbers, etc. Disable them.
324         *
325         * Text decoration is problematic with additional UI elements like buttons and
326         * line numbers. Disable.
327         */
328        if ( isset( $args['supports']['typography'] ) && \is_array( $args['supports']['typography'] ) ) {
329            $args['supports']['typography']['lineHeight']                   = false;
330            $args['supports']['typography']['__experimentalLetterSpacing']  = false;
331            $args['supports']['typography']['letterSpacing']                = false;
332            $args['supports']['typography']['__experimentalTextDecoration'] = false;
333            $args['supports']['typography']['textDecoration']               = false;
334        } else {
335            $args['supports']['typography'] = array(
336                'fontSize'                      => true,
337                'lineHeight'                    => false,
338
339                // Currently experimental, but include likely stable forms as well.
340                '__experimentalFontFamily'      => true,
341                '__experimentalFontWeight'      => true,
342                '__experimentalFontStyle'       => true,
343                '__experimentalTextTransform'   => true,
344                'fontFamily'                    => true,
345                'fontWeight'                    => true,
346                'fontStyle'                     => true,
347                'textTransform'                 => true,
348
349                '__experimentalDefaultControls' => array(
350                    'fontSize' => true,
351                ),
352                'defaultControls'               => array(
353                    'fontSize' => true,
354                ),
355            );
356        }
357
358        $args['attributes'] = array(
359            // Content attribute is preserved for compatibility with the core/code block and transforms.
360            'content'                 => $args['attributes']['content'],
361            'tokenizedLines'          => array(
362                'type'    => 'array',
363                'default' =>
364                array(),
365            ),
366            'language'                => array(
367                'type'    => 'string',
368                'default' => '',
369            ),
370            'languageConfidence'      => array(
371                'type'    => 'string',
372                'default' => 'unknown',
373            ),
374            'triggerCodeUpdate'       => array(
375                'type'    => 'boolean',
376                'default' => false,
377            ),
378            'showCopyButton'          => array(
379                'type'    => 'boolean',
380                'default' => false,
381            ),
382            'showLanguageName'        => array(
383                'type'    => 'boolean',
384                'default' => false,
385            ),
386            'showLineNumbers'         => array(
387                'type'    => 'boolean',
388                'default' => false,
389            ),
390            'lineNumbersStartAt'      => array(
391                'type'    => 'number',
392                'default' => 1,
393            ),
394            'filename'                => array(
395                'type'    => 'string',
396                'default' => '',
397            ),
398            'colorComment'            => array(
399                'type' => 'string',
400            ),
401            'colorKeyword'            => array(
402                'type' => 'string',
403            ),
404            'colorBoolean'            => array(
405                'type' => 'string',
406            ),
407            'colorLiteral'            => array(
408                'type' => 'string',
409            ),
410            'colorString'             => array(
411                'type' => 'string',
412            ),
413            'colorSpecialString'      => array(
414                'type' => 'string',
415            ),
416            'colorMacroName'          => array(
417                'type' => 'string',
418            ),
419            'colorVariableDefinition' => array(
420                'type' => 'string',
421            ),
422            'colorTypeName'           => array(
423                'type' => 'string',
424            ),
425            'colorClassName'          => array(
426                'type' => 'string',
427            ),
428            'colorInvalid'            => array(
429                'type' => 'string',
430            ),
431        );
432        $args['textdomain'] = 'jetpack-mu-wpcom';
433
434        return $args;
435    }
436
437    /**
438     * Enqueue plugin assets necessary for the block editor.
439     */
440    public static function enqueue_editor_assets() {
441        static $done = false;
442        if ( $done ) {
443            return;
444        }
445        $done = true;
446
447        /*
448         * The code block registration script depends on some script modules.
449         * This "dummy" module ensures those dependencies are available.
450         */
451        wp_enqueue_script_module(
452            self::MODULE_PREFIX . 'dummy',
453            plugins_url( 'empty.js', __FILE__ ),
454            array(
455                array(
456                    'import' => 'dynamic',
457                    'id'     => self::MODULE_PREFIX . 'block-edit-function',
458                ),
459            ),
460            '0.0.0' // This script never needs to be cache busted. It will never change.
461        );
462    }
463
464    /**
465     * Render the block.
466     *
467     * @param array  $attributes The block attributes.
468     * @param string $content The block content.
469     */
470    public static function render_block( array $attributes, string $content ): string {
471        if ( empty( $attributes['tokenizedLines'] ) || ! \is_array( $attributes['tokenizedLines'] ) ) {
472            return $content;
473        }
474
475        $processed_content = Code_Block_HTML_Replacer::get_updated_html_with_replaced_content( $content, $attributes['tokenizedLines'], $attributes['language'] );
476        if ( null === $processed_content ) {
477            return $content;
478        }
479        list( $code_string, $replaced_content ) = $processed_content;
480
481        $extra_attrs      = array();
482        $style_properties = array();
483
484        if ( $attributes['showCopyButton'] ?? false ) {
485            self::enqueue_view_assets();
486        }
487
488        $show_line_numbers = $attributes['showLineNumbers'] ?? false;
489        if ( $show_line_numbers ) {
490            $extra_attrs['class']  = 'show-line-numbers';
491            $line_numbers_start_at = isset( $attributes['lineNumbersStartAt'] )
492                ? max( 0, min( 10000, (int) $attributes['lineNumbersStartAt'] ) )
493                : 1;
494
495            $max_line_number_width = floor(
496                log10( $line_numbers_start_at + \count( $attributes['tokenizedLines'] ) - 1 )
497            ) + 1;
498
499            if ( $line_numbers_start_at !== 1 ) {
500                $style_properties[] = '--line-numbers-start-at: ' . $line_numbers_start_at;
501            }
502            $style_properties[] = '--line-number-gutter-width: ' . $max_line_number_width . 'ch';
503        }
504
505        $color_attributes = array(
506            'colorComment',
507            'colorKeyword',
508            'colorBoolean',
509            'colorLiteral',
510            'colorString',
511            'colorSpecialString',
512            'colorMacroName',
513            'colorVariableDefinition',
514            'colorTypeName',
515            'colorClassName',
516            'colorInvalid',
517        );
518        foreach ( $color_attributes as $color_attr ) {
519            if ( ! empty( $attributes[ $color_attr ] ) ) {
520                $style_properties[] = "--{$color_attr}{$attributes[ $color_attr ]}";
521            }
522        }
523
524        if ( isset( $attributes['backgroundColor'] ) ) {
525            $style_properties[] = "--colorBackground: var( --wp--preset--color--{$attributes['backgroundColor']} )";
526        } elseif ( isset( $attributes['style']['color']['background'] ) ) {
527            $style_properties[] = "--colorBackground: {$attributes['style']['color']['background']}";
528        }
529
530        if ( isset( $attributes['textColor'] ) ) {
531            $style_properties[] = "--colorText: var( --wp--preset--color--{$attributes['textColor']} )";
532        } elseif ( isset( $attributes['style']['color']['text'] ) ) {
533            $style_properties[] = "--colorText: {$attributes['style']['color']['text']}";
534        }
535
536        if ( ! empty( $style_properties ) ) {
537            $extra_attrs['style'] = implode( '; ', $style_properties ) . ';';
538        }
539
540        $attrs = get_block_wrapper_attributes( $extra_attrs );
541
542        $filename_html = ( ! empty( $attributes['filename'] ) )
543            ? \sprintf( '<span class="a8c/code__filename">%s</span>', esc_html( $attributes['filename'] ) )
544            : '';
545
546        $copy_html = ( $attributes['showCopyButton'] ?? false )
547            ? \sprintf(
548                '<button class="%s element-button a8c/code__btn-copy" type="button" data-copy-text="%s" hidden>%s</button>',
549                WP_Theme_JSON::get_element_class_name( 'button' ),
550                esc_attr( $code_string ),
551                esc_html__( 'Copy', 'jetpack-mu-wpcom' )
552            )
553            : '';
554
555        $language_html = '';
556        if ( $attributes['showLanguageName'] ?? false ) {
557            $language_text = empty( $attributes['language'] )
558                ? __( 'Plain text', 'jetpack-mu-wpcom' )
559                : $attributes['language'];
560            $language_text = self::$language_name_rewrites[ $language_text ] ?? $language_text;
561            $language_html = \sprintf(
562                '<span>%s</span>',
563                esc_html( $language_text )
564            );
565        }
566
567        $header_right_html = ( $copy_html || $language_html )
568            ? "<div class=\"a8c/code__header-right\">{$copy_html}{$language_html}</div>"
569            : '';
570        $header_html       = ( $filename_html || $header_right_html )
571            ? "\n\t<div class=\"a8c/code__header\">{$filename_html}{$header_right_html}</div>"
572            : '';
573
574        $output = <<<HTML
575<div {$attrs}>{$header_html}
576    <div class="cm-editor">
577        <div class="cm-scroller">
578            {$replaced_content}
579        </div>
580    </div>
581</div>
582HTML;
583
584        return $output;
585    }
586
587    /**
588     * Hook to allow the dummy script module to inject its dependencies into the importmap.
589     *
590     * Create an opportunity between printing the importmap and printing modules
591     * in order to prevent printing the dummy module.
592     *
593     * This is not essential, but does save some HTML on the page and a network request.
594     * The dummy module is only used to signal that some additional modules
595     * should be included in the importmap.
596     */
597    public static function after_setup_theme() {
598        foreach ( array( 'wp_head', 'wp_footer', 'admin_print_footer_scripts' ) as $hook ) {
599            /*
600             * Script module actions are expected in this order:
601             *
602             * - WP_Script_Modules::print_import_map
603             * - WP_Script_Modules::print_script_module_preloads
604             * - WP_Script_Modules::print_enqueued_script_modules
605             *
606             * Attempt to remove actions starting from the end to that if a removal fails,
607             * the action can be restored to the expected position by adding it again.
608             */
609            if ( ! remove_action( $hook, array( wp_script_modules(), 'print_script_module_preloads' ) ) ) {
610                continue;
611            }
612            if ( ! remove_action( $hook, array( wp_script_modules(), 'print_enqueued_script_modules' ) ) ) {
613                add_action( $hook, array( wp_script_modules(), 'print_script_module_preloads' ) );
614                continue;
615            }
616
617            add_action(
618                $hook,
619                function () {
620                    wp_script_modules()->dequeue( self::MODULE_PREFIX . 'dummy' );
621                },
622                15
623            );
624            add_action( $hook, array( wp_script_modules(), 'print_enqueued_script_modules' ), 20 );
625            add_action( $hook, array( wp_script_modules(), 'print_script_module_preloads' ), 20 );
626            add_action(
627                $hook,
628                function () {
629                    wp_script_modules()->enqueue( self::MODULE_PREFIX . 'dummy' );
630                },
631                25
632            );
633        }
634    }
635}