]+wp-block-cover__inner-container[\s|"][^>]*>/U';
+ if ( 1 === preg_match( $inner_container_start, $content, $matches, PREG_OFFSET_CAPTURE ) ) {
+ $offset = $matches[0][1];
+ $content = substr( $content, 0, $offset ) . $image . substr( $content, $offset );
+ }
+
+ return $content;
+}
+
+/**
+ * Registers the `core/cover` block renderer on server.
+ *
+ * @since 6.0.0
+ */
+function register_block_core_cover() {
+ register_block_type_from_metadata(
+ __DIR__ . '/cover',
+ array(
+ 'render_callback' => 'render_block_core_cover',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_cover' );
diff --git a/src/wp-includes/blocks/file/index.php b/src/wp-includes/blocks/file/index.php
new file mode 100644
index 0000000000000..42a3e022ee4d9
--- /dev/null
+++ b/src/wp-includes/blocks/file/index.php
@@ -0,0 +1,67 @@
+next_tag() ) {
+ $processor->set_attribute( 'data-wp-interactive', 'core/file' );
+ }
+
+ // If there are no OBJECT elements, something might have already modified the block.
+ if ( ! $processor->next_tag( 'OBJECT' ) ) {
+ return $content;
+ }
+
+ $processor->set_attribute( 'data-wp-bind--hidden', '!state.hasPdfPreview' );
+ $processor->set_attribute( 'hidden', true );
+
+ $filename = $processor->get_attribute( 'aria-label' );
+ $has_filename = is_string( $filename ) && ! empty( $filename ) && 'PDF embed' !== $filename;
+ $label = $has_filename ? sprintf(
+ /* translators: %s: filename. */
+ __( 'Embed of %s.' ),
+ $filename
+ ) : __( 'PDF embed' );
+
+ // Update object's aria-label attribute if present in block HTML.
+ // Match an aria-label attribute from an object tag.
+ $processor->set_attribute( 'aria-label', $label );
+
+ return $processor->get_updated_html();
+}
+
+/**
+ * Registers the `core/file` block on server.
+ *
+ * @since 5.8.0
+ */
+function register_block_core_file() {
+ register_block_type_from_metadata(
+ __DIR__ . '/file',
+ array(
+ 'render_callback' => 'render_block_core_file',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_file' );
diff --git a/src/wp-includes/blocks/footnotes/index.php b/src/wp-includes/blocks/footnotes/index.php
new file mode 100644
index 0000000000000..cb95cd387f194
--- /dev/null
+++ b/src/wp-includes/blocks/footnotes/index.php
@@ -0,0 +1,143 @@
+context['postId'] ) ) {
+ return '';
+ }
+
+ if ( post_password_required( $block->context['postId'] ) ) {
+ return '';
+ }
+
+ $footnotes = get_post_meta( $block->context['postId'], 'footnotes', true );
+
+ if ( ! $footnotes ) {
+ return '';
+ }
+
+ $footnotes = json_decode( $footnotes, true );
+
+ if ( ! is_array( $footnotes ) || count( $footnotes ) === 0 ) {
+ return '';
+ }
+
+ $wrapper_attributes = get_block_wrapper_attributes();
+ $footnote_index = 1;
+
+ $block_content = '';
+
+ foreach ( $footnotes as $footnote ) {
+ // Translators: %d: Integer representing the number of return links on the page.
+ $aria_label = sprintf( __( 'Jump to footnote reference %1$d' ), $footnote_index );
+ $block_content .= sprintf(
+ '
%2$s ↩︎',
+ esc_attr( $footnote['id'] ),
+ wp_kses_post( $footnote['content'] ),
+ esc_attr( $aria_label )
+ );
+ ++$footnote_index;
+ }
+
+ return sprintf(
+ '
%2$s
',
+ $wrapper_attributes,
+ $block_content
+ );
+}
+
+/**
+ * Registers the `core/footnotes` block on the server.
+ *
+ * @since 6.3.0
+ */
+function register_block_core_footnotes() {
+ register_block_type_from_metadata(
+ __DIR__ . '/footnotes',
+ array(
+ 'render_callback' => 'render_block_core_footnotes',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_footnotes' );
+
+
+/**
+ * Registers the footnotes meta field required for footnotes to work.
+ *
+ * @since 6.5.0
+ */
+function register_block_core_footnotes_post_meta() {
+ $post_types = get_post_types( array( 'show_in_rest' => true ) );
+ foreach ( $post_types as $post_type ) {
+ // Only register the meta field if the post type supports the editor, custom fields, and revisions.
+ if (
+ post_type_supports( $post_type, 'editor' ) &&
+ post_type_supports( $post_type, 'custom-fields' ) &&
+ post_type_supports( $post_type, 'revisions' )
+ ) {
+ register_post_meta(
+ $post_type,
+ 'footnotes',
+ array(
+ 'show_in_rest' => true,
+ 'single' => true,
+ 'type' => 'string',
+ 'revisions_enabled' => true,
+ )
+ );
+ }
+ }
+}
+
+/*
+ * Most post types are registered at priority 10, so use priority 20 here in
+ * order to catch them.
+*/
+add_action( 'init', 'register_block_core_footnotes_post_meta', 20 );
+
+/**
+ * Adds the footnotes field to the revisions display.
+ *
+ * @since 6.3.0
+ *
+ * @param array $fields The revision fields.
+ * @return array The revision fields.
+ */
+function wp_add_footnotes_to_revision( $fields ) {
+ $fields['footnotes'] = __( 'Footnotes' );
+ return $fields;
+}
+add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' );
+
+/**
+ * Gets the footnotes field from the revision for the revisions screen.
+ *
+ * @since 6.3.0
+ *
+ * @param string $revision_field The field value, but $revision->$field
+ * (footnotes) does not exist.
+ * @param string $field The field name, in this case "footnotes".
+ * @param object $revision The revision object to compare against.
+ * @return string The field value.
+ */
+function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) {
+ return get_metadata( 'post', $revision->ID, $field, true );
+}
+add_filter( '_wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 );
diff --git a/src/wp-includes/blocks/gallery/index.php b/src/wp-includes/blocks/gallery/index.php
new file mode 100644
index 0000000000000..5281fec126e42
--- /dev/null
+++ b/src/wp-includes/blocks/gallery/index.php
@@ -0,0 +1,183 @@
+ $inner_block ) {
+ if ( 'core/image' === $inner_block['blockName'] ) {
+ if ( ! isset( $parsed_block['innerBlocks'][ $key ]['attrs']['data-id'] ) && isset( $inner_block['attrs']['id'] ) ) {
+ $parsed_block['innerBlocks'][ $key ]['attrs']['data-id'] = esc_attr( $inner_block['attrs']['id'] );
+ }
+ }
+ }
+ }
+
+ return $parsed_block;
+}
+
+add_filter( 'render_block_data', 'block_core_gallery_data_id_backcompatibility' );
+
+/**
+ * Renders the `core/gallery` block on the server.
+ *
+ * @since 6.0.0
+ *
+ * @param array $attributes Attributes of the block being rendered.
+ * @param string $content Content of the block being rendered.
+ * @return string The content of the block being rendered.
+ */
+function block_core_gallery_render( $attributes, $content ) {
+ // Adds a style tag for the --wp--style--unstable-gallery-gap var.
+ // The Gallery block needs to recalculate Image block width based on
+ // the current gap setting in order to maintain the number of flex columns
+ // so a css var is added to allow this.
+
+ $gap = $attributes['style']['spacing']['blockGap'] ?? null;
+ // Skip if gap value contains unsupported characters.
+ // Regex for CSS value borrowed from `safecss_filter_attr`, and used here
+ // because we only want to match against the value, not the CSS attribute.
+ if ( is_array( $gap ) ) {
+ foreach ( $gap as $key => $value ) {
+ // Make sure $value is a string to avoid PHP 8.1 deprecation error in preg_match() when the value is null.
+ $value = is_string( $value ) ? $value : '';
+ $value = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
+
+ // Get spacing CSS variable from preset value if provided.
+ if ( is_string( $value ) && str_contains( $value, 'var:preset|spacing|' ) ) {
+ $index_to_splice = strrpos( $value, '|' ) + 1;
+ $slug = _wp_to_kebab_case( substr( $value, $index_to_splice ) );
+ $value = "var(--wp--preset--spacing--$slug)";
+ }
+
+ $gap[ $key ] = $value;
+ }
+ } else {
+ // Make sure $gap is a string to avoid PHP 8.1 deprecation error in preg_match() when the value is null.
+ $gap = is_string( $gap ) ? $gap : '';
+ $gap = $gap && preg_match( '%[\\\(&=}]|/\*%', $gap ) ? null : $gap;
+
+ // Get spacing CSS variable from preset value if provided.
+ if ( is_string( $gap ) && str_contains( $gap, 'var:preset|spacing|' ) ) {
+ $index_to_splice = strrpos( $gap, '|' ) + 1;
+ $slug = _wp_to_kebab_case( substr( $gap, $index_to_splice ) );
+ $gap = "var(--wp--preset--spacing--$slug)";
+ }
+ }
+
+ $unique_gallery_classname = wp_unique_id( 'wp-block-gallery-' );
+ $processed_content = new WP_HTML_Tag_Processor( $content );
+ $processed_content->next_tag();
+ $processed_content->add_class( $unique_gallery_classname );
+
+ // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
+ // gap on the gallery.
+ $fallback_gap = 'var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )';
+ $gap_value = $gap ? $gap : $fallback_gap;
+ $gap_column = $gap_value;
+
+ if ( is_array( $gap_value ) ) {
+ $gap_row = $gap_value['top'] ?? $fallback_gap;
+ $gap_column = $gap_value['left'] ?? $fallback_gap;
+ $gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column;
+ }
+
+ // The unstable gallery gap calculation requires a real value (such as `0px`) and not `0`.
+ if ( '0' === $gap_column ) {
+ $gap_column = '0px';
+ }
+
+ // Set the CSS variable to the column value, and the `gap` property to the combined gap value.
+ $gallery_styles = array(
+ array(
+ 'selector' => ".wp-block-gallery.{$unique_gallery_classname}",
+ 'declarations' => array(
+ '--wp--style--unstable-gallery-gap' => $gap_column,
+ 'gap' => $gap_value,
+ ),
+ ),
+ );
+
+ wp_style_engine_get_stylesheet_from_css_rules(
+ $gallery_styles,
+ array(
+ 'context' => 'block-supports',
+ )
+ );
+
+ // The WP_HTML_Tag_Processor class calls get_updated_html() internally
+ // when the instance is treated as a string, but here we explicitly
+ // convert it to a string.
+ $updated_content = $processed_content->get_updated_html();
+
+ /*
+ * Randomize the order of image blocks. Ideally we should shuffle
+ * the `$parsed_block['innerBlocks']` via the `render_block_data` hook.
+ * However, this hook doesn't apply inner block updates when blocks are
+ * nested.
+ * @todo In the future, if this hook supports updating innerBlocks in
+ * nested blocks, it should be refactored.
+ *
+ * @see: https://github.com/WordPress/gutenberg/pull/58733
+ */
+ if ( empty( $attributes['randomOrder'] ) ) {
+ return $updated_content;
+ }
+
+ // This pattern matches figure elements with the `wp-block-image` class to
+ // avoid the gallery's wrapping `figure` element and extract images only.
+ $pattern = '/
]*\bwp-block-image\b[^>]*>.*?<\/figure>/s';
+
+ // Find all Image blocks.
+ preg_match_all( $pattern, $updated_content, $matches );
+ if ( ! $matches ) {
+ return $updated_content;
+ }
+ $image_blocks = $matches[0];
+
+ // Randomize the order of Image blocks.
+ shuffle( $image_blocks );
+ $i = 0;
+ $content = preg_replace_callback(
+ $pattern,
+ static function () use ( $image_blocks, &$i ) {
+ $new_image_block = $image_blocks[ $i ];
+ ++$i;
+ return $new_image_block;
+ },
+ $updated_content
+ );
+
+ return $content;
+}
+/**
+ * Registers the `core/gallery` block on server.
+ *
+ * @since 5.9.0
+ */
+function register_block_core_gallery() {
+ register_block_type_from_metadata(
+ __DIR__ . '/gallery',
+ array(
+ 'render_callback' => 'block_core_gallery_render',
+ )
+ );
+}
+
+add_action( 'init', 'register_block_core_gallery' );
diff --git a/src/wp-includes/blocks/heading/index.php b/src/wp-includes/blocks/heading/index.php
new file mode 100644
index 0000000000000..471e31f19f2ee
--- /dev/null
+++ b/src/wp-includes/blocks/heading/index.php
@@ -0,0 +1,56 @@
+Hello World
+ *
+ * Would be transformed to:
+ * Hello World
+ *
+ * @since 6.2.0
+ *
+ * @param array $attributes Attributes of the block being rendered.
+ * @param string $content Content of the block being rendered.
+ *
+ * @return string The content of the block being rendered.
+ */
+function block_core_heading_render( $attributes, $content ) {
+ if ( ! $content ) {
+ return $content;
+ }
+
+ $p = new WP_HTML_Tag_Processor( $content );
+
+ $header_tags = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' );
+ while ( $p->next_tag() ) {
+ if ( in_array( $p->get_tag(), $header_tags, true ) ) {
+ $p->add_class( 'wp-block-heading' );
+ break;
+ }
+ }
+
+ return $p->get_updated_html();
+}
+
+/**
+ * Registers the `core/heading` block on server.
+ *
+ * @since 6.2.0
+ */
+function register_block_core_heading() {
+ register_block_type_from_metadata(
+ __DIR__ . '/heading',
+ array(
+ 'render_callback' => 'block_core_heading_render',
+ )
+ );
+}
+
+add_action( 'init', 'register_block_core_heading' );
diff --git a/src/wp-includes/blocks/home-link/index.php b/src/wp-includes/blocks/home-link/index.php
new file mode 100644
index 0000000000000..d61aa0bc235e2
--- /dev/null
+++ b/src/wp-includes/blocks/home-link/index.php
@@ -0,0 +1,176 @@
+ array(),
+ 'inline_styles' => '',
+ );
+
+ // Text color.
+ $has_named_text_color = array_key_exists( 'textColor', $context );
+ $has_custom_text_color = isset( $context['style']['color']['text'] );
+
+ // If has text color.
+ if ( $has_custom_text_color || $has_named_text_color ) {
+ // Add has-text-color class.
+ $colors['css_classes'][] = 'has-text-color';
+ }
+
+ if ( $has_named_text_color ) {
+ // Add the color class.
+ $colors['css_classes'][] = sprintf( 'has-%s-color', $context['textColor'] );
+ } elseif ( $has_custom_text_color ) {
+ // Add the custom color inline style.
+ $colors['inline_styles'] .= sprintf( 'color: %s;', $context['style']['color']['text'] );
+ }
+
+ // Background color.
+ $has_named_background_color = array_key_exists( 'backgroundColor', $context );
+ $has_custom_background_color = isset( $context['style']['color']['background'] );
+
+ // If has background color.
+ if ( $has_custom_background_color || $has_named_background_color ) {
+ // Add has-background class.
+ $colors['css_classes'][] = 'has-background';
+ }
+
+ if ( $has_named_background_color ) {
+ // Add the background-color class.
+ $colors['css_classes'][] = sprintf( 'has-%s-background-color', $context['backgroundColor'] );
+ } elseif ( $has_custom_background_color ) {
+ // Add the custom background-color inline style.
+ $colors['inline_styles'] .= sprintf( 'background-color: %s;', $context['style']['color']['background'] );
+ }
+
+ return $colors;
+}
+
+/**
+ * Build an array with CSS classes and inline styles defining the font sizes
+ * which will be applied to the home link markup in the front-end.
+ *
+ * @since 6.0.0
+ *
+ * @param array $context Home link block context.
+ * @return array Font size CSS classes and inline styles.
+ */
+function block_core_home_link_build_css_font_sizes( $context ) {
+ // CSS classes.
+ $font_sizes = array(
+ 'css_classes' => array(),
+ 'inline_styles' => '',
+ );
+
+ $has_named_font_size = array_key_exists( 'fontSize', $context );
+ $has_custom_font_size = isset( $context['style']['typography']['fontSize'] );
+
+ if ( $has_named_font_size ) {
+ // Add the font size class.
+ $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] );
+ } elseif ( $has_custom_font_size ) {
+ // Add the custom font size inline style.
+ $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', $context['style']['typography']['fontSize'] );
+ }
+
+ return $font_sizes;
+}
+
+/**
+ * Builds an array with classes and style for the li wrapper
+ *
+ * @since 6.0.0
+ *
+ * @param array $context Home link block context.
+ * @return string The li wrapper attributes.
+ */
+function block_core_home_link_build_li_wrapper_attributes( $context ) {
+ $colors = block_core_home_link_build_css_colors( $context );
+ $font_sizes = block_core_home_link_build_css_font_sizes( $context );
+ $classes = array_merge(
+ $colors['css_classes'],
+ $font_sizes['css_classes']
+ );
+ $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] );
+ $classes[] = 'wp-block-navigation-item';
+
+ if ( is_front_page() ) {
+ $classes[] = 'current-menu-item';
+ } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) {
+ // Edge case where the Reading settings has a posts page set but not a static homepage.
+ $classes[] = 'current-menu-item';
+ }
+
+ $wrapper_attributes = get_block_wrapper_attributes(
+ array(
+ 'class' => implode( ' ', $classes ),
+ 'style' => $style_attribute,
+ )
+ );
+
+ return $wrapper_attributes;
+}
+
+/**
+ * Renders the `core/home-link` block.
+ *
+ * @since 6.0.0
+ *
+ * @param array $attributes The block attributes.
+ * @param string $content The saved content.
+ * @param WP_Block $block The parsed block.
+ *
+ * @return string Returns the post content with the home url added.
+ */
+function render_block_core_home_link( $attributes, $content, $block ) {
+ if ( empty( $attributes['label'] ) ) {
+ $attributes['label'] = __( 'Home' );
+ }
+ $aria_current = '';
+
+ if ( is_front_page() ) {
+ $aria_current = ' aria-current="page"';
+ } elseif ( is_home() && ( (int) get_option( 'page_for_posts' ) !== get_queried_object_id() ) ) {
+ // Edge case where the Reading settings has a posts page set but not a static homepage.
+ $aria_current = ' aria-current="page"';
+ }
+
+ return sprintf(
+ '%4$s',
+ block_core_home_link_build_li_wrapper_attributes( $block->context ),
+ esc_url( home_url() ),
+ $aria_current,
+ wp_kses_post( $attributes['label'] )
+ );
+}
+
+/**
+ * Register the home block
+ *
+ * @since 6.0.0
+ *
+ * @uses render_block_core_home_link()
+ * @throws WP_Error An WP_Error exception parsing the block definition.
+ */
+function register_block_core_home_link() {
+ register_block_type_from_metadata(
+ __DIR__ . '/home-link',
+ array(
+ 'render_callback' => 'render_block_core_home_link',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_home_link' );
diff --git a/src/wp-includes/blocks/image/index.php b/src/wp-includes/blocks/image/index.php
new file mode 100644
index 0000000000000..bc312167f5329
--- /dev/null
+++ b/src/wp-includes/blocks/image/index.php
@@ -0,0 +1,385 @@
+set_bookmark( 'here' );
+ $opener = $this->bookmarks['here'];
+
+ // Allow comments within the definition of “empty.”
+ while ( $this->next_token() && '#comment' === $this->get_token_name() ) {
+ continue;
+ }
+
+ if ( 'FIGCAPTION' !== $this->get_tag() || ! $this->is_tag_closer() ) {
+ return false;
+ }
+
+ $this->set_bookmark( 'here' );
+ $closer = $this->bookmarks['here'];
+
+ return new WP_HTML_Span( $opener->start, $closer->start + $closer->length - $opener->start );
+ }
+ };
+
+ if ( ! $processor->next_tag( 'img' ) || ! $processor->get_attribute( 'src' ) ) {
+ return '';
+ }
+
+ $has_id_binding = isset( $attributes['metadata']['bindings']['id'] ) && isset( $attributes['id'] );
+
+ // Ensure the `wp-image-id` classname on the image block supports block bindings.
+ if ( $has_id_binding ) {
+ // If there's a mismatch with the 'wp-image-' class and the actual id, the id was
+ // probably overridden by block bindings. Update it to the correct value.
+ // See https://github.com/WordPress/gutenberg/issues/62886 for why this is needed.
+ $id = $attributes['id'];
+ $image_classnames = $processor->get_attribute( 'class' );
+ $class_with_binding_value = "wp-image-$id";
+ if ( is_string( $image_classnames ) && ! str_contains( $image_classnames, $class_with_binding_value ) ) {
+ $image_classnames = preg_replace( '/wp-image-(\d+)/', $class_with_binding_value, $image_classnames );
+ $processor->set_attribute( 'class', $image_classnames );
+ }
+ }
+
+ // For backwards compatibility, the data-id html attribute is only set for
+ // image blocks nested in a gallery. Detect if the image is in a gallery by
+ // checking the data-id attribute.
+ // See the `block_core_gallery_data_id_backcompatibility` function.
+ if ( isset( $attributes['data-id'] ) ) {
+ // If there's a binding for the `id`, the `id` attribute is used for the
+ // value, since `data-id` does not support block bindings.
+ // Else the `data-id` is used for backwards compatibility, since
+ // third parties may be filtering its value.
+ $data_id = $has_id_binding ? $attributes['id'] : $attributes['data-id'];
+ $processor->set_attribute( 'data-id', $data_id );
+ }
+
+ /*
+ * If the `caption` attribute is empty and we encounter a `` element,
+ * we take note of its span so we can remove it later.
+ */
+ if ( $processor->next_tag( 'FIGCAPTION' ) && empty( $attributes['caption'] ) ) {
+ $figcaption_span = $processor->block_core_image_extract_empty_figcaption_element();
+ }
+
+ $link_destination = $attributes['linkDestination'] ?? 'none';
+ $lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block );
+
+ /*
+ * If the lightbox is enabled and the image is not linked, adds the filter and
+ * the JavaScript view file.
+ */
+ if (
+ isset( $lightbox_settings ) &&
+ 'none' === $link_destination &&
+ isset( $lightbox_settings['enabled'] ) &&
+ true === $lightbox_settings['enabled']
+ ) {
+ wp_enqueue_script_module( '@wordpress/block-library/image/view' );
+
+ /*
+ * This render needs to happen in a filter with priority 15 to ensure that
+ * it runs after the duotone filter and that duotone styles are applied to
+ * the image in the lightbox. Lightbox has to work with any plugins that
+ * might use filters as well. Removing this can be considered in the future
+ * if the way the blocks are rendered changes, or if a new kind of filter is
+ * introduced.
+ */
+ add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 );
+ } else {
+ /*
+ * Remove the filter if previously added by other Image blocks.
+ */
+ remove_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15 );
+ }
+
+ $output = $processor->get_updated_html();
+ if ( ! empty( $figcaption_span ) ) {
+ return substr( $output, 0, $figcaption_span->start ) . substr( $output, $figcaption_span->start + $figcaption_span->length );
+ }
+ return $output;
+}
+
+/**
+ * Adds the lightboxEnabled flag to the block data.
+ *
+ * This is used to determine whether the lightbox should be rendered or not.
+ *
+ * @since 6.4.0
+ *
+ * @param array $block Block data.
+ *
+ * @return array|null Filtered block data.
+ */
+function block_core_image_get_lightbox_settings( $block ) {
+ // Gets the lightbox setting from the block attributes.
+ if ( isset( $block['attrs']['lightbox'] ) ) {
+ $lightbox_settings = $block['attrs']['lightbox'];
+ }
+
+ if ( ! isset( $lightbox_settings ) ) {
+ $lightbox_settings = wp_get_global_settings( array( 'lightbox' ), array( 'block_name' => 'core/image' ) );
+
+ // If not present in global settings, check the top-level global settings.
+ //
+ // NOTE: If no block-level settings are found, the previous call to
+ // `wp_get_global_settings` will return the whole `theme.json` structure in
+ // which case we can check if the "lightbox" key is present at the top-level
+ // of the global settings and use its value.
+ if ( isset( $lightbox_settings['lightbox'] ) ) {
+ $lightbox_settings = wp_get_global_settings( array( 'lightbox' ) );
+ }
+ }
+
+ return $lightbox_settings ?? null;
+}
+
+/**
+ * Adds the directives and layout needed for the lightbox behavior.
+ *
+ * @since 6.4.0
+ *
+ * @param string $block_content Rendered block content.
+ * @param array $block Block object.
+ *
+ * @return string Filtered block content.
+ */
+function block_core_image_render_lightbox( $block_content, $block ) {
+ /*
+ * If there's no IMG tag in the block then return the given block content
+ * as-is. There's nothing that this code can knowingly modify to add the
+ * lightbox behavior.
+ */
+ $processor = new WP_HTML_Tag_Processor( $block_content );
+ if ( $processor->next_tag( 'figure' ) ) {
+ $processor->set_bookmark( 'figure' );
+ }
+ if ( ! $processor->next_tag( 'img' ) ) {
+ return $block_content;
+ }
+
+ $alt = $processor->get_attribute( 'alt' );
+ $img_uploaded_src = $processor->get_attribute( 'src' );
+ $img_class_names = $processor->get_attribute( 'class' );
+ $img_styles = $processor->get_attribute( 'style' );
+ $img_width = 'none';
+ $img_height = 'none';
+ $img_srcset = false;
+ $aria_label = __( 'Enlarge' );
+ $dialog_aria_label = __( 'Enlarged image' );
+
+ if ( isset( $block['attrs']['id'] ) ) {
+ $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] );
+ $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] );
+ $img_srcset = wp_get_attachment_image_srcset( $block['attrs']['id'] );
+ $img_width = $img_metadata['width'] ?? 'none';
+ $img_height = $img_metadata['height'] ?? 'none';
+ }
+
+ // Figure.
+ $processor->seek( 'figure' );
+ $figure_class_names = $processor->get_attribute( 'class' );
+ $figure_styles = $processor->get_attribute( 'style' );
+
+ // Create unique id and set the image metadata in the state.
+ $unique_image_id = uniqid();
+
+ wp_interactivity_state(
+ 'core/image',
+ array(
+ 'metadata' => array(
+ $unique_image_id => array(
+ 'uploadedSrc' => $img_uploaded_src,
+ 'lightboxSrcset' => $img_srcset,
+ 'figureClassNames' => $figure_class_names,
+ 'figureStyles' => $figure_styles,
+ 'imgClassNames' => $img_class_names,
+ 'imgStyles' => $img_styles,
+ 'targetWidth' => $img_width,
+ 'targetHeight' => $img_height,
+ 'scaleAttr' => $block['attrs']['scale'] ?? false,
+ 'ariaLabel' => $dialog_aria_label,
+ 'alt' => $alt,
+ ),
+ ),
+ )
+ );
+
+ $processor->add_class( 'wp-lightbox-container' );
+ $processor->set_attribute( 'data-wp-interactive', 'core/image' );
+ $processor->set_attribute(
+ 'data-wp-context',
+ wp_json_encode(
+ array(
+ 'imageId' => $unique_image_id,
+ ),
+ JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
+ )
+ );
+ $processor->set_attribute( 'data-wp-key', $unique_image_id );
+
+ // Image.
+ $processor->next_tag( 'img' );
+ $processor->set_attribute( 'data-wp-init', 'callbacks.setButtonStyles' );
+ $processor->set_attribute( 'data-wp-on--load', 'callbacks.setButtonStyles' );
+ $processor->set_attribute( 'data-wp-on-window--resize', 'callbacks.setButtonStyles' );
+
+ // Set an event to preload the image on pointerenter and pointerdown(mobile).
+ // Pointerleave is used to cancel the preload if the user hovers away from the image
+ // before the predefined delay.
+ $processor->set_attribute( 'data-wp-on--pointerenter', 'actions.preloadImageWithDelay' );
+ $processor->set_attribute( 'data-wp-on--pointerdown', 'actions.preloadImage' );
+ $processor->set_attribute( 'data-wp-on--pointerleave', 'actions.cancelPreload' );
+
+ // Sets an event callback on the `img` because the `figure` element can also
+ // contain a caption, and we don't want to trigger the lightbox when the
+ // caption is clicked.
+ $processor->set_attribute( 'data-wp-on--click', 'actions.showLightbox' );
+ $processor->set_attribute( 'data-wp-class--hide', 'state.isContentHidden' );
+ $processor->set_attribute( 'data-wp-class--show', 'state.isContentVisible' );
+
+ $body_content = $processor->get_updated_html();
+
+ // Adds a button alongside image in the body content.
+ $img = null;
+ preg_match( '/
]+>/', $body_content, $img );
+
+ $button =
+ $img[0]
+ . '';
+
+ $body_content = preg_replace( '/
]+>/', $button, $body_content );
+
+ add_action( 'wp_footer', 'block_core_image_print_lightbox_overlay' );
+
+ return $body_content;
+}
+
+/**
+ * @since 6.5.0
+ */
+function block_core_image_print_lightbox_overlay() {
+ $close_button_label = esc_attr__( 'Close' );
+
+ // If the current theme does NOT have a `theme.json`, or the colors are not
+ // defined, it needs to set the background color & close button color to some
+ // default values because it can't get them from the Global Styles.
+ $background_color = '#fff';
+ $close_button_color = '#000';
+ if ( wp_theme_has_theme_json() ) {
+ $global_styles_color = wp_get_global_styles( array( 'color' ) );
+ if ( ! empty( $global_styles_color['background'] ) ) {
+ $background_color = esc_attr( $global_styles_color['background'] );
+ }
+ if ( ! empty( $global_styles_color['text'] ) ) {
+ $close_button_color = esc_attr( $global_styles_color['text'] );
+ }
+ }
+
+ echo <<
+
+
+
+
+
+
+
+
+
+
+
+
+
+HTML;
+}
+
+/**
+ * Registers the `core/image` block on server.
+ *
+ * @since 5.9.0
+ */
+function register_block_core_image() {
+ register_block_type_from_metadata(
+ __DIR__ . '/image',
+ array(
+ 'render_callback' => 'render_block_core_image',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_image' );
diff --git a/src/wp-includes/blocks/latest-comments/index.php b/src/wp-includes/blocks/latest-comments/index.php
new file mode 100644
index 0000000000000..eec7e104533d1
--- /dev/null
+++ b/src/wp-includes/blocks/latest-comments/index.php
@@ -0,0 +1,171 @@
+ $attributes['commentsToShow'],
+ 'status' => 'approve',
+ 'post_status' => 'publish',
+ ),
+ array()
+ )
+ );
+
+ $list_items_markup = '';
+ if ( ! empty( $comments ) ) {
+ // Prime the cache for associated posts. This is copied from \WP_Widget_Recent_Comments::widget().
+ $post_ids = array_unique( wp_list_pluck( $comments, 'comment_post_ID' ) );
+ _prime_post_caches( $post_ids, strpos( get_option( 'permalink_structure' ), '%category%' ), false );
+
+ foreach ( $comments as $comment ) {
+ $list_items_markup .= '';
+ }
+ }
+
+ $classnames = array();
+ if ( $attributes['displayAvatar'] ) {
+ $classnames[] = 'has-avatars';
+ }
+ if ( $attributes['displayDate'] ) {
+ $classnames[] = 'has-dates';
+ }
+ if ( 'none' !== $display_content ) {
+ $classnames[] = 'has-excerpts';
+ }
+ if ( empty( $comments ) ) {
+ $classnames[] = 'no-comments';
+ }
+ $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classnames ) ) );
+
+ return ! empty( $comments ) ? sprintf(
+ '
+
+ get_registered( 'core/legacy-widget' );
+ echo $block->render( $_GET['legacy-widget-preview'] );
+ ?>
+
+
+
+
+
+ is transformed to