diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index 98d8f10d5c..0bbc6a345c 100644 --- a/wp-includes/blocks.php +++ b/wp-includes/blocks.php @@ -113,6 +113,44 @@ function get_dynamic_block_names() { return $dynamic_block_names; } +/** + * Remove all dynamic blocks from the given content. + * + * @since 5.0.0 + * + * @param string $content Content of the current post. + * @return string + */ +function strip_dynamic_blocks( $content ) { + return _recurse_strip_dynamic_blocks( parse_blocks( $content ) ); +} + +/** + * Helper function for strip_dynamic_blocks(), to recurse through the block tree. + * + * @since 5.0.0 + * @access private + * + * @param array $blocks Array of blocks from parse_blocks(). + * @return string HTML from the non-dynamic blocks. + */ +function _recurse_strip_dynamic_blocks( $blocks ) { + $clean_content = ''; + $dynamic_blocks = get_dynamic_block_names(); + + foreach ( $blocks as $block ) { + if ( ! in_array( $block['blockName'], $dynamic_blocks ) ) { + if ( $block['innerBlocks'] ) { + $clean_content .= _recurse_strip_dynamic_blocks( $block['innerBlocks'] ); + } else { + $clean_content .= $block['innerHTML']; + } + } + } + + return $clean_content; +} + /** * Parses blocks out of a content string. * @@ -142,10 +180,85 @@ function parse_blocks( $content ) { * * @since 5.0.0 * - * @param string $parser_class Name of block parser class + * @param string $parser_class Name of block parser class. */ $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); $parser = new $parser_class(); return $parser->parse( $content ); } + +/** + * Parses dynamic blocks out of `post_content` and re-renders them. + * + * @since 5.0.0 + * @global WP_Post $post The post to edit. + * + * @param string $content Post content. + * @return string Updated post content. + */ +function do_blocks( $content ) { + $blocks = parse_blocks( $content ); + return _recurse_do_blocks( $blocks, $blocks ); +} + +/** + * Helper function for do_blocks(), to recurse through the block tree. + * + * @since 5.0.0 + * @access private + * + * @param array $blocks Array of blocks from parse_blocks(). + * @param array $all_blocks The top level array of blocks. + * @return string The block HTML. + */ +function _recurse_do_blocks( $blocks, $all_blocks ) { + global $post; + + /* + * Back up global post, to restore after render callback. + * Allows callbacks to run new WP_Query instances without breaking the global post. + */ + $global_post = $post; + + $rendered_content = ''; + $dynamic_blocks = get_dynamic_block_names(); + + foreach ( $blocks as $block ) { + $block = (array) $block; + if ( in_array( $block['blockName'], $dynamic_blocks ) ) { + // Find registered block type. We can assume it exists since we use the + // `get_dynamic_block_names` function as a source for pattern matching. + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + + // Replace dynamic block with server-rendered output. + $block_content = $block_type->render( (array) $block['attrs'], $block['innerHTML'] ); + } else if ( $block['innerBlocks'] ) { + $block_content = _recurse_do_blocks( $block['innerBlocks'], $blocks ); + } else { + $block_content = $block['innerHTML']; + } + + /** + * Filters the content of a single block. + * + * During the_content, each block is parsed and added to the output individually. This filter allows + * that content to be altered immediately before it's appended. + * + * @since 5.0.0 + * + * @param string $block_content The block content about to be appended. + * @param array $block The full block, including name and attributes. + * @param array $all_blocks The array of all blocks being processed. + */ + $rendered_content .= apply_filters( 'do_block', $block_content, $block, $all_blocks ); + + // Restore global $post. + $post = $global_post; + } + + // Strip remaining block comment demarcations. + $rendered_content = preg_replace( '//m', '', $rendered_content ); + + return $rendered_content; +} \ No newline at end of file diff --git a/wp-includes/blocks/archives.php b/wp-includes/blocks/archives.php new file mode 100644 index 0000000000..adcc61c233 --- /dev/null +++ b/wp-includes/blocks/archives.php @@ -0,0 +1,143 @@ + 'monthly', + 'format' => 'option', + 'show_post_count' => $show_post_count, + ) + ); + + $dropdown_args['echo'] = 0; + + $archives = wp_get_archives( $dropdown_args ); + + switch ( $dropdown_args['type'] ) { + case 'yearly': + $label = __( 'Select Year', 'gutenberg' ); + break; + case 'monthly': + $label = __( 'Select Month', 'gutenberg' ); + break; + case 'daily': + $label = __( 'Select Day', 'gutenberg' ); + break; + case 'weekly': + $label = __( 'Select Week', 'gutenberg' ); + break; + default: + $label = __( 'Select Post', 'gutenberg' ); + break; + } + + $label = esc_attr( $label ); + + $block_content = ' + '; + + $block_content = sprintf( + '
%2$s
', + esc_attr( $class ), + $block_content + ); + } else { + + /** This filter is documented in wp-includes/widgets/class-wp-widget-archives.php */ + $archives_args = apply_filters( + 'widget_archives_args', + array( + 'type' => 'monthly', + 'show_post_count' => $show_post_count, + ) + ); + + $archives_args['echo'] = 0; + + $archives = wp_get_archives( $archives_args ); + + $classnames = esc_attr( $class ); + + if ( empty( $archives ) ) { + + $block_content = sprintf( + '
%2$s
', + $classnames, + __( 'No archives to show.', 'gutenberg' ) + ); + } else { + + $block_content = sprintf( + '', + $classnames, + $archives + ); + } + } + + return $block_content; +} + +/** + * Register archives block. + */ +function register_block_core_archives() { + register_block_type( + 'core/archives', + array( + 'attributes' => array( + 'align' => array( + 'type' => 'string', + ), + 'className' => array( + 'type' => 'string', + ), + 'displayAsDropdown' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'showPostCounts' => array( + 'type' => 'boolean', + 'default' => false, + ), + ), + 'render_callback' => 'render_block_core_archives', + ) + ); +} + +add_action( 'init', 'register_block_core_archives' ); diff --git a/wp-includes/blocks/block.php b/wp-includes/blocks/block.php new file mode 100644 index 0000000000..bc33929ac5 --- /dev/null +++ b/wp-includes/blocks/block.php @@ -0,0 +1,39 @@ +post_type ) { + return ''; + } + + return do_blocks( $reusable_block->post_content ); +} + +register_block_type( + 'core/block', + array( + 'attributes' => array( + 'ref' => array( + 'type' => 'number', + ), + ), + + 'render_callback' => 'render_block_core_block', + ) +); diff --git a/wp-includes/blocks/categories.php b/wp-includes/blocks/categories.php new file mode 100644 index 0000000000..bd1175733b --- /dev/null +++ b/wp-includes/blocks/categories.php @@ -0,0 +1,103 @@ + false, + 'hierarchical' => ! empty( $attributes['showHierarchy'] ), + 'orderby' => 'name', + 'show_count' => ! empty( $attributes['showPostCounts'] ), + 'title_li' => '', + ); + + if ( ! empty( $attributes['displayAsDropdown'] ) ) { + $id = 'wp-block-categories-' . $block_id; + $args['id'] = $id; + $args['show_option_none'] = __( 'Select Category', 'gutenberg' ); + $wrapper_markup = '
%2$s
'; + $items_markup = wp_dropdown_categories( $args ); + $type = 'dropdown'; + + if ( ! is_admin() ) { + $wrapper_markup .= build_dropdown_script_block_core_categories( $id ); + } + } else { + $wrapper_markup = ''; + $items_markup = wp_list_categories( $args ); + $type = 'list'; + } + + $class = "wp-block-categories wp-block-categories-{$type} align{$align}"; + + if ( isset( $attributes['className'] ) ) { + $class .= ' ' . $attributes['className']; + } + + $block_content = sprintf( + $wrapper_markup, + esc_attr( $class ), + $items_markup + ); + + return $block_content; +} + +/** + * Generates the inline script for a categories dropdown field. + * + * @param string $dropdown_id ID of the dropdown field. + * + * @return string Returns the dropdown onChange redirection script. + */ +function build_dropdown_script_block_core_categories( $dropdown_id ) { + ob_start(); + ?> + + 'render_block_core_categories', + ) + ); +} + +add_action( 'init', 'register_block_core_categories' ); diff --git a/wp-includes/blocks/latest-comments.php b/wp-includes/blocks/latest-comments.php new file mode 100644 index 0000000000..dd310028ce --- /dev/null +++ b/wp-includes/blocks/latest-comments.php @@ -0,0 +1,182 @@ + $attributes['commentsToShow'], + 'status' => 'approve', + 'post_status' => 'publish', + ) + ) + ); + + $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 .= '
  • '; + if ( $attributes['displayAvatar'] ) { + $avatar = get_avatar( + $comment, + 48, + '', + '', + array( + 'class' => 'wp-block-latest-comments__comment-avatar', + ) + ); + if ( $avatar ) { + $list_items_markup .= $avatar; + } + } + + $list_items_markup .= '
    '; + $list_items_markup .= ''; + if ( $attributes['displayExcerpt'] ) { + $list_items_markup .= '
    ' . wpautop( get_comment_excerpt( $comment ) ) . '
    '; + } + $list_items_markup .= '
  • '; + } + } + + $class = 'wp-block-latest-comments'; + if ( $attributes['align'] ) { + $class .= " align{$attributes['align']}"; + } + if ( $attributes['displayAvatar'] ) { + $class .= ' has-avatars'; + } + if ( $attributes['displayDate'] ) { + $class .= ' has-dates'; + } + if ( $attributes['displayExcerpt'] ) { + $class .= ' has-excerpts'; + } + if ( empty( $comments ) ) { + $class .= ' no-comments'; + } + $classnames = esc_attr( $class ); + + $block_content = ! empty( $comments ) ? sprintf( + '
      %2$s
    ', + $classnames, + $list_items_markup + ) : sprintf( + '
    %2$s
    ', + $classnames, + __( 'No comments to show.', 'gutenberg' ) + ); + + return $block_content; +} + +register_block_type( + 'core/latest-comments', + array( + 'attributes' => array( + 'className' => array( + 'type' => 'string', + ), + 'commentsToShow' => array( + 'type' => 'number', + 'default' => 5, + 'minimum' => 1, + 'maximum' => 100, + ), + 'displayAvatar' => array( + 'type' => 'boolean', + 'default' => true, + ), + 'displayDate' => array( + 'type' => 'boolean', + 'default' => true, + ), + 'displayExcerpt' => array( + 'type' => 'boolean', + 'default' => true, + ), + 'align' => array( + 'type' => 'string', + 'enum' => array( 'center', 'left', 'right', 'wide', 'full', '' ), + ), + ), + 'render_callback' => 'gutenberg_render_block_core_latest_comments', + ) +); diff --git a/wp-includes/blocks/latest-posts.php b/wp-includes/blocks/latest-posts.php new file mode 100644 index 0000000000..070abafeb4 --- /dev/null +++ b/wp-includes/blocks/latest-posts.php @@ -0,0 +1,125 @@ + $attributes['postsToShow'], + 'post_status' => 'publish', + 'order' => $attributes['order'], + 'orderby' => $attributes['orderBy'], + 'category' => $attributes['categories'], + ) + ); + + $list_items_markup = ''; + + foreach ( $recent_posts as $post ) { + $post_id = $post['ID']; + + $title = get_the_title( $post_id ); + if ( ! $title ) { + $title = __( '(Untitled)', 'gutenberg' ); + } + $list_items_markup .= sprintf( + '
  • %2$s', + esc_url( get_permalink( $post_id ) ), + esc_html( $title ) + ); + + if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { + $list_items_markup .= sprintf( + '', + esc_attr( get_the_date( 'c', $post_id ) ), + esc_html( get_the_date( '', $post_id ) ) + ); + } + + $list_items_markup .= "
  • \n"; + } + + $class = 'wp-block-latest-posts'; + if ( isset( $attributes['align'] ) ) { + $class .= ' align' . $attributes['align']; + } + + if ( isset( $attributes['postLayout'] ) && 'grid' === $attributes['postLayout'] ) { + $class .= ' is-grid'; + } + + if ( isset( $attributes['columns'] ) && 'grid' === $attributes['postLayout'] ) { + $class .= ' columns-' . $attributes['columns']; + } + + if ( isset( $attributes['className'] ) ) { + $class .= ' ' . $attributes['className']; + } + + $block_content = sprintf( + '', + esc_attr( $class ), + $list_items_markup + ); + + return $block_content; +} + +/** + * Registers the `core/latest-posts` block on server. + */ +function register_block_core_latest_posts() { + register_block_type( + 'core/latest-posts', + array( + 'attributes' => array( + 'categories' => array( + 'type' => 'string', + ), + 'className' => array( + 'type' => 'string', + ), + 'postsToShow' => array( + 'type' => 'number', + 'default' => 5, + ), + 'displayPostDate' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'postLayout' => array( + 'type' => 'string', + 'default' => 'list', + ), + 'columns' => array( + 'type' => 'number', + 'default' => 3, + ), + 'align' => array( + 'type' => 'string', + ), + 'order' => array( + 'type' => 'string', + 'default' => 'desc', + ), + 'orderBy' => array( + 'type' => 'string', + 'default' => 'date', + ), + ), + 'render_callback' => 'render_block_core_latest_posts', + ) + ); +} + +add_action( 'init', 'register_block_core_latest_posts' ); diff --git a/wp-includes/blocks/shortcode.php b/wp-includes/blocks/shortcode.php new file mode 100644 index 0000000000..d18dd4f582 --- /dev/null +++ b/wp-includes/blocks/shortcode.php @@ -0,0 +1,32 @@ + 'render_block_core_shortcode', + ) + ); +} + +add_action( 'init', 'register_block_core_shortcode' ); diff --git a/wp-includes/class-wp-block-parser.php b/wp-includes/class-wp-block-parser.php index 78b6921787..96f1c3a0e4 100644 --- a/wp-includes/class-wp-block-parser.php +++ b/wp-includes/class-wp-block-parser.php @@ -306,7 +306,14 @@ class WP_Block_Parser { * block and add it as a new innerBlock to the parent */ $stack_top = array_pop( $this->stack ); - $stack_top->block->innerHTML .= substr( $this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset ); + + $html = substr( $this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset ); + if ( $stack_top->block->innerBlocks ) { + $stack_top->block->innerBlocks[] = (array) $this->freeform( $html ); + } else { + $stack_top->block->innerHTML = $html; + } + $stack_top->prev_offset = $start_offset + $token_length; $this->add_inner_block( @@ -440,8 +447,8 @@ class WP_Block_Parser { */ function add_inner_block( WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) { $parent = $this->stack[ count( $this->stack ) - 1 ]; - $parent->block->innerBlocks[] = $block; - $parent->block->innerHTML .= substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset ); + $parent->block->innerBlocks[] = (array) $this->freeform( substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset ) ); + $parent->block->innerBlocks[] = (array) $block; $parent->prev_offset = $last_offset ? $last_offset : $token_start + $token_length; } @@ -456,10 +463,16 @@ class WP_Block_Parser { $stack_top = array_pop( $this->stack ); $prev_offset = $stack_top->prev_offset; - $stack_top->block->innerHTML .= isset( $end_offset ) + $html = isset( $end_offset ) ? substr( $this->document, $prev_offset, $end_offset - $prev_offset ) : substr( $this->document, $prev_offset ); + if ( $stack_top->block->innerBlocks ) { + $stack_top->block->innerBlocks[] = (array) $this->freeform( $html ); + } else { + $stack_top->block->innerHTML = $html; + } + if ( isset( $stack_top->leading_html_start ) ) { $this->output[] = (array) self::freeform( substr( $this->document, diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index cec310b175..db41957de0 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -171,6 +171,7 @@ add_filter( 'the_title', 'wptexturize' ); add_filter( 'the_title', 'convert_chars' ); add_filter( 'the_title', 'trim' ); +add_filter( 'the_content', 'do_blocks', 9 ); add_filter( 'the_content', 'wptexturize' ); add_filter( 'the_content', 'convert_smilies', 20 ); add_filter( 'the_content', 'wpautop' ); diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php index d8646971f0..410e9fb84f 100644 --- a/wp-includes/formatting.php +++ b/wp-includes/formatting.php @@ -442,6 +442,11 @@ function wpautop( $pee, $br = true ) { if ( trim($pee) === '' ) return ''; + // We don't need to autop posts with blocks in them. + if ( has_blocks( $pee ) ) { + return $pee; + } + // Just to make things a little easier, pad the end. $pee = $pee . "\n"; @@ -3371,6 +3376,7 @@ function wp_trim_excerpt( $text = '' ) { $text = get_the_content(''); $text = strip_shortcodes( $text ); + $text = strip_dynamic_blocks( $text ); /** This filter is documented in wp-includes/post-template.php */ $text = apply_filters( 'the_content', $text ); diff --git a/wp-includes/version.php b/wp-includes/version.php index d41466d3af..48cdc61eb1 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '5.0-alpha-43751'; +$wp_version = '5.0-alpha-43752'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-settings.php b/wp-settings.php index 81cc3ccb4f..c0f0816130 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -247,6 +247,12 @@ require( ABSPATH . WPINC . '/class-wp-block-type.php' ); require( ABSPATH . WPINC . '/class-wp-block-type-registry.php' ); require( ABSPATH . WPINC . '/class-wp-block-parser.php' ); require( ABSPATH . WPINC . '/blocks.php' ); +require( ABSPATH . WPINC . '/blocks/archives.php' ); +require( ABSPATH . WPINC . '/blocks/block.php' ); +require( ABSPATH . WPINC . '/blocks/categories.php' ); +require( ABSPATH . WPINC . '/blocks/latest-comments.php' ); +require( ABSPATH . WPINC . '/blocks/latest-posts.php' ); +require( ABSPATH . WPINC . '/blocks/shortcode.php' ); $GLOBALS['wp_embed'] = new WP_Embed();