$text Text to insert. */ public function insert_before( string $text ): void { $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); } /** * Inserts text after the current token. * * @param string $text Text to insert. */ public function insert_after( string $text ): void { $span = $this->get_span(); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text ); } /** * Removes the current token. */ public function remove(): void { $span = $this->get_span(); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' ); } /** * Replaces the current token. * * @param string $text Text to replace with. */ public function replace( string $text ): void { $span = $this->get_span(); $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text ); } }; // Locate the insertion points in the HEAD. while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) { if ( 'STYLE' === $processor->get_tag() && 'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) ) { /** This is added in {@see wp_enqueue_global_styles()} */ $processor->set_bookmark( 'wp_global_styles_placeholder' ); } elseif ( 'STYLE' === $processor->get_tag() && 'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) ) { /** This is added in {@see wp_enqueue_registered_block_scripts_and_styles()} */ $processor->set_bookmark( 'wp_block_styles_placeholder' ); } elseif ( 'STYLE' === $processor->get_tag() && 'wp-block-library-inline-css' === $processor->get_attribute( 'id' ) ) { /** This is added here in {@see wp_hoist_late_printed_styles()} */ $processor->set_bookmark( 'wp_block_library' ); } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { $processor->set_bookmark( 'head_end' ); break; } } /** * Replace the placeholder for global styles enqueued during {@see wp_enqueue_global_styles()}. This is done * even if $printed_global_styles is empty. */ if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) { $processor->seek( 'wp_global_styles_placeholder' ); $processor->replace( $printed_global_styles ); $printed_global_styles = ''; } /* * Insert block styles right after wp-block-library (if it is present). The placeholder CSS comment will * always be added to the wp-block-library inline style since it gets printed at `wp_head` before the blocks * are rendered. This means that there may not actually be any block styles to hoist from the footer to * insert after this inline style. The placeholder CSS comment needs to be added so that the inline style * gets printed, but if the resulting inline style is empty after the placeholder is removed, then the * inline style is removed. */ if ( $processor->has_bookmark( 'wp_block_library' ) ) { $processor->seek( 'wp_block_library' ); $css_text = $processor->get_modifiable_text(); /* * Split the block library inline style by the placeholder to identify the original inlined CSS, which * likely would be common.css, followed by any inline styles which had been added by the theme or * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on * demand will get inserted after the inlined common.css and before the extra inline styles added by the * user. */ $css_text_around_placeholder = explode( $placeholder, $css_text, 2 ); $extra_inline_styles = ''; if ( count( $css_text_around_placeholder ) === 2 ) { $css_text = $css_text_around_placeholder[0]; if ( '' !== trim( $css_text ) ) { $inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' ); if ( $inlined_src ) { $css_text .= sprintf( "\n/*# sourceURL=%s */\n", esc_url_raw( $inlined_src ) ); } } $extra_inline_styles = $css_text_around_placeholder[1]; } /* * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to * be printed. Now that the inline style has been located and the placeholder comment has been removed, if * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely. */ if ( '' === trim( $css_text ) ) { $processor->remove(); } else { $processor->set_modifiable_text( $css_text ); } $inserted_after = $printed_core_block_styles; $printed_core_block_styles = ''; /* * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ). * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when * the combined block library stylesheet was used. The pattern here is checking to see if it is not just * a sourceURL comment after the placeholder above is removed. */ if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) { $style_processor = new WP_HTML_Tag_Processor( '' ); $style_processor->next_tag(); $style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' ); $style_processor->set_modifiable_text( $extra_inline_styles ); $inserted_after .= "{$style_processor->get_updated_html()}\n"; } if ( '' !== $inserted_after ) { $processor->insert_after( "\n" . $inserted_after ); } } // Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles. if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) { $processor->seek( 'wp_block_styles_placeholder' ); if ( '' !== $printed_other_block_styles ) { $processor->replace( "\n" . $printed_other_block_styles ); } else { $processor->remove(); } $printed_other_block_styles = ''; } // Print all remaining styles. $remaining_styles = $printed_core_block_styles . $printed_other_block_styles . $printed_global_styles . $printed_late_styles; if ( $remaining_styles && $processor->has_bookmark( 'head_end' ) ) { $processor->seek( 'head_end' ); $processor->insert_before( $remaining_styles . "\n" ); } return $processor->get_updated_html(); } ); } /** * Return the corresponding JavaScript `dataset` name for an attribute * if it represents a custom data attribute, or `null` if not. * * Custom data attributes appear in an element's `dataset` property in a * browser, but there's a specific way the names are translated from HTML * into JavaScript. This function indicates how the name would appear in * JavaScript if a browser would recognize it as a custom data attribute. * * Example: * * // Dash-letter pairs turn into capital letters. * 'postId' === wp_js_dataset_name( 'data-post-id' ); * 'Before' === wp_js_dataset_name( 'data--before' ); * '-One--Two---' === wp_js_dataset_name( 'data---one---two---' ); * * // Not every attribute name will be interpreted as a custom data attribute. * null === wp_js_dataset_name( 'post-id' ); * null === wp_js_dataset_name( 'data' ); * * // Some very surprising names will; for example, a property whose name is the empty string. * '' === wp_js_dataset_name( 'data-' ); * 0 === strlen( wp_js_dataset_name( 'data-' ) ); * * @since 6.9.0 * * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs * @see \wp_html_custom_data_attribute_name() * * @param string $html_attribute_name Raw attribute name as found in the source HTML. * @return string|null Transformed `dataset` name, if interpretable as a custom data attribute, else `null`. */ function wp_js_dataset_name( string $html_attribute_name ): ?string { if ( 0 !== substr_compare( $html_attribute_name, 'data-', 0, 5, true ) ) { return null; } $end = strlen( $html_attribute_name ); /* * If it contains characters which would end the attribute name parsing then * something else is wrong and this contains more than just an attribute name. */ if ( ( $end - 5 ) !== strcspn( $html_attribute_name, "=/> \t\f\r\n", 5 ) ) { return null; } /** * > For each name in list, for each U+002D HYPHEN-MINUS character (-) * > in the name that is followed by an ASCII lower alpha, remove the * > U+002D HYPHEN-MINUS character (-) and replace the character that * > followed it by the same character converted to ASCII uppercase. * * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs */ $custom_name = ''; $at = 5; $was_at = $at; while ( $at < $end ) { $next_dash_at = strpos( $html_attribute_name, '-', $at ); if ( false === $next_dash_at || $next_dash_at === $end - 1 ) { break; } // Transform `-a` to `A`, for example. $c = $html_attribute_name[ $next_dash_at + 1 ]; if ( ( $c >= 'A' && $c <= 'Z' ) || ( $c >= 'a' && $c <= 'z' ) ) { $prefix = substr( $html_attribute_name, $was_at, $next_dash_at - $was_at ); $custom_name .= strtolower( $prefix ); $custom_name .= strtoupper( $c ); $at = $next_dash_at + 2; $was_at = $at; continue; } $at = $next_dash_at + 1; } // If nothing has been added it means there are no dash-letter pairs; return the name as-is. return '' === $custom_name ? strtolower( substr( $html_attribute_name, 5 ) ) : ( $custom_name . strtolower( substr( $html_attribute_name, $was_at ) ) ); } /** * Returns a corresponding HTML attribute name for the given name, * if that name were found in a JS element’s `dataset` property. * * Example: * * 'data-post-id' === wp_html_custom_data_attribute_name( 'postId' ); * 'data--before' === wp_html_custom_data_attribute_name( 'Before' ); * 'data---one---two---' === wp_html_custom_data_attribute_name( '-One--Two---' ); * * // Not every attribute name will be interpreted as a custom data attribute. * null === wp_html_custom_data_attribute_name( '/not-an-attribute/' ); * null === wp_html_custom_data_attribute_name( 'no spaces' ); * * // Some very surprising names will; for example, a property whose name is the empty string. * 'data-' === wp_html_custom_data_attribute_name( '' ); * * @since 6.9.0 * * @see https://html.spec.whatwg.org/#concept-domstringmap-pairs * @see \wp_js_dataset_name() * * @param string $js_dataset_name Name of JS `dataset` property to transform. * @return string|null Corresponding name of an HTML custom data attribute for the given dataset name, * if possible to represent in HTML, otherwise `null`. */ function wp_html_custom_data_attribute_name( string $js_dataset_name ): ?string { $end = strlen( $js_dataset_name ); if ( 0 === $end ) { return 'data-'; } /* * If it contains characters which would end the attribute name parsing then * something it’s not possible to represent this in HTML. */ if ( strcspn( $js_dataset_name, "=/> \t\f\r\n" ) !== $end ) { return null; } $html_name = 'data-'; $at = 0; $was_at = $at; while ( $at < $end ) { $next_upper_after = strcspn( $js_dataset_name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', $at ); $next_upper_at = $at + $next_upper_after; if ( $next_upper_at >= $end ) { break; } $prefix = substr( $js_dataset_name, $was_at, $next_upper_at - $was_at ); $html_name .= strtolower( $prefix ); $html_name .= '-' . strtolower( $js_dataset_name[ $next_upper_at ] ); $at = $next_upper_at + 1; $was_at = $at; } if ( $was_at < $end ) { $html_name .= strtolower( substr( $js_dataset_name, $was_at ) ); } return $html_name; }