admin管理员组文章数量:1290552
When adding the "Latest Posts" block within Gutenberg, is it possible to filter by custom post type?
Here are the options I can see:
Would I need to create a custom block for this type of functionality?
I've found a good discussion here, but I've ended up down a bit of GitHub rabbit hole.
When adding the "Latest Posts" block within Gutenberg, is it possible to filter by custom post type?
Here are the options I can see:
Would I need to create a custom block for this type of functionality?
I've found a good discussion here, but I've ended up down a bit of GitHub rabbit hole.
Share Improve this question edited Jan 27, 2019 at 19:23 Sam asked Jan 27, 2019 at 19:11 SamSam 2,1963 gold badges30 silver badges59 bronze badges2 Answers
Reset to default 4It's Not (Reasonably) Possible (Yet) :(
This was such an interesting question to dig into. In short, we can get really, really close to accomplishing this using Block Filters and some hackish duct-tape on the PHP side. But it falls apart in the home stretch.
It's worth noting that the crux of the complication is specific to the Latest Posts block's implementation - doing something similar to other core blocks is totally possible, depending on the block and modification.
To get this working properly in the editor without weird side-effects requires a degree of hackery that goes so far beyond an acceptable compromise - I strongly discourage doing so. I've detailed the specific point of failure in the last section of this answer.
There's also a chance that a change in core might result in this solution automagically becoming fully-functional as-is at a later date, or adding the necessary extension points to complete the solution otherwise.
The best solution would be to deregister core/latest-posts
in favor of a custom block, perhaps even using the Gutenberg repo as upstream in order to pull in new developments to the core block.
(But We Can Get Suuuuper Close)
In short, it's possible to add post-type filtering to the latest posts block in a manner that's fully functional on the front-end and with working controls in the Block Editor. What we can't do is get the block in the editor to display posts of the appropriate type.
Despite falling short of a stable solution, much of what went into this attempt has a lot of merit for other customizations and modifications - particularly on the JS side. There's a lot that can be learned here to apply elsewhere, so I think it's still worth examining the attempt and where it breaks down.
Register Additional Block Attributes
We can add a new postTypes
array attribute to the Latest Posts block in order to store the selected post types using a blocks.registerBlockType
filter:
import { addFilter } from '@wordpress/hooks';
function addPostTypesAttribute( settings, name ) {
if( name !== 'core/latest-posts' )
return settings;
settings.attributes = {
...settings.attributes,
postTypes: {
type: 'array',
default: [ 'post' ]
}
};
return settings;
}
addFilter(
'blocks.registerBlockType',
'wpse326869/latest-posts-post-types/add-attribute',
addPostTypesAttribute
);
Add Controls to Interact with New Attributes
Filter the Edit Component
The editor.BlockEdit
filter allows us to interact with/modify the component returned from the block's edit()
function.
Shim Components and Functionality with a HOC
The @wordpress/compose
package provides a handy utility for "wrapping" a component in a Higher-Order Component/"HOC" (a function which takes a component as an argument and returns an augmented or wrapped version of it), effectively allowing us to shim our own components and functionality into a pre-existing component.
Place Sidebar Controls with the SlotFills System
To add control components for a block to the sidebar, we simply add them as children of an <InspectorControls>
component, and Gutenberg's nifty SlotFills
system will render them outside of the block in the appropriate location.
Select a Control
In my opinion, FormTokenField
is the perfect component to allow the user to enter a number of different post types. But a SelectControl
or ComboboxControl
might be more appropriate for your use-case.
Some amount of documentation for most controls can be found in The Block Editor Handboox, but often times you might find that you need to scour the sources on GitHub to make sense of everything.
Implementation
All the above in mind, we can add controls to interact with our new postTypes
attribute as such:
import { createHigherOrderComponent } from '@wordpress/compose';
import { Fragment } from '@wordpress/element';
import { InspectorControls } from '@wordpress/block-editor';
import { FormTokenField } from '@wordpress/components'
import { useSelect } from '@wordpress/data';
// A multiple Post-Type selection control implemented on top of FormTokenField.
const PostTypesControl = ( props ) => {
const { value = [], onChange } = props;
const types = useSelect(
( select ) => ( select( 'core' ).getPostTypes() ?? [] ).map( ( { slug, name } ) => ( { value: slug, title: name } ) ),
[]
);
const tokenIsValid = ( title ) => types.some( type => type.title === title );
const titleToValue = ( title ) => title ? types.find( type => type.title === title )?.value || '' : '';
return (
<FormTokenField
value={ value }
onChange={ onChange }
suggestions={ types.map( type => type.title ) }
saveTransform={ titleToValue }
__experimentalValidateInput={ tokenIsValid }
/>
);
};
// A HOC which adds a PostTypesControl to a block which has a `postTypes` attribute.
const withPostTypesControl = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const {
name,
attributes: { postTypes = [ 'post' ] },
setAttributes,
} = props;
if( name !== 'core/latest-posts' )
return <BlockEdit {...props} />;
const setPostTypes = ( postTypes ) => setAttributes( { postTypes } );
return (
<Fragment>
<BlockEdit {...props} />
<InspectorControls>
<PanelBody title="Post Types" initialOpen={false}>
<PanelRow>
<PostTypesControl
value={ postTypes }
onChange={ setPostTypes }
/>
</PanelRow>
</PanelBody>
</InspectorControls>
</Fragment>
);
},
'wpse326869withPostTypesControl'
);
addFilter(
'editor.BlockEdit',
'wpse326869/latest-posts-post-types/add-controls',
withPostTypesControl
);
Use the Attribute to Filter Posts Query
Were Latest Posts a static block type which rendered it's content directly to HTML stored in the post_content
, we'd probably use a blocks.getSaveElement
filter here to use the attribute and modify the markup.
But Latest Posts is a dynamic block, meaning that when WordPress renders the block on the front-end it executes a PHP function in order to produce the content per page-load, just like a shortcode.
Unfortunately, looking at the source for the PHP function responsible for rendering the block, there's no useful filter to modify the arguments which are sent to get_posts()
...
What we can do, however, is get a little hacky and use a block_type_metadata_settings
filter to hijack the Latest Post block's PHP render callback - this generally seems inadvisable; I probably wouldn't distribute code leveraging such a hack. In our custom render callback, we can add a pre_get_posts
filter to add our postTypes
attribute to the query, execute the original render callback, and then remove the filter once more.
For ease of state management between these functions, I've chosen to use a singleton class to implement the above.
class WPSE326869_LatestPostsPostTypes {
protected static $instance = null;
protected $original_render_callback;
protected $block_attributes;
protected function __construct() {
add_filter( 'block_type_metadata_settings', [ $this, 'filter_block_settings' ], 10, 2 );
}
public static function get_instance() {
if( is_null( self::$instance ) )
self::$instance = new self();
return self::$instance;
}
public function filter_block_settings( $args, $data ) {
if( $data['name'] !== 'core/latest-posts' )
return $args;
$this->original_render_callback = $args['render_callback'];
$args['render_callback'] = [ $this, 'render_block' ];
return $args;
}
public function latest_posts_query_types( $query ) {
if( empty( $this->block_attributes['postTypes'] ) )
return;
$public_types = get_post_types(
[
'show_in_rest' => true,
]
);
$types = array_intersect( $public_types, $this->block_attributes['postTypes'] );
$query->set( 'post_type', $types );
}
public function render_block( $attributes, $block_content, $block ) {
$this->block_attributes = $attributes;
add_filter( 'pre_get_posts', [ $this, 'latest_posts_query_types' ] );
$block_content = (string) call_user_func( $this->original_render_callback, $attributes, $block_content, $block );
remove_filter( 'pre_get_posts', [ $this, 'latest_posts_query_types' ] );
return $block_content;
}
}
WPSE326869_LatestPostsPostTypes::get_instance();
Summary & Point of Failure
What Works
All of the above in place, we end up with a function "Post Types" control added to the Latest Post block's sidebar settings (with autocomplete suggestions, to boot!) which stores it's values in a new attribute added to the block, and uses that attribute to appropriately adjust the block's server-side query.
At this point, the modified block will successfully display post-type filtered latest posts on the front-end!
What Doesn't
Back in the Block Editor, however, there's a little wonkiness afoot. It all boils down to the edit()
component using a REST API request hard-coded for the post
post-type in order to quickly deliver visual updates in the editor instead of the block's PHP render callback. The result is that the block does not display posts of the selected types within the editor.
You might think to use more pre_get_posts
hackery to modify the results of that query - but doing so could well lead to the Gutenberg data stores incorrectly classifying the results as posts
, which could cause all sorts of other issues.
The only functional approach I can see would be to totally abuse the editor.BlockEdit
filter and completely replace the block's edit()
component. While that should be possible on paper, I feel it would be such an egregious no-no that I will not explore or demonstrate the possibility. Silently swapping out the implementation of core functionality on-the-fly is a sure-fire way to create bugs, confusion, and chaos.
What Might, Someday
I haven't dug into it too far, but the PRs which introduced the Latest Posts Block and the Server Side Rendering solution both heavily reference each other. I haven't researched why the Latest Posts block ultimately did not end up using the SSR component for rendering in the Block Editor. If it ever did however, I believe the solution implemented above would be fully functional.
It's also possible that core will introduce more applicable block filters or even additional props to the Latest Posts block which would make this more plausible.
Why not create your own custom post display block using this easy to use plugin lazyblock https://wordpress/plugins/lazy-blocks/ ?
Simple steps are here, first create your custom loop using wp_query, you can take help of https://generatewp/wp_query/ here is my custom loop for portfolio post type I had created
// WP_Query arguments $args = array( 'post_type' => array( 'uni_portfolio' ), ); // The Query $query = new WP_Query( $args ); // The Loop if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); // do something } } else { // no posts found } // Restore original Post Data wp_reset_postdata();
Then create a new blank block in lazy block plugin, its very simple and you can look at documentation here https://lazyblocks/documentation/blocks-creation/ , you can add few fields as per your need to, for example how many post to display etc.
Then add just this function utilizing your above wp_query to create output of your block, I have used one field number of post to control post count in the block
add_filter( 'lazyblock/custom-post-loop/frontend_callback', 'uni_block_output_custompostloop', 10, 2 );
if ( ! function_exists( 'uni_block_output_custompostloop' ) ) :
function uni_block_output_custompostloop( $output, $attributes ) {
ob_start();
// your query start from here
$args = array( 'post_type' => array( 'uni_portfolio' ), 'posts_per_page' => esc_html( $attributes['uni_number_of_post'] ), );
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
if ( has_post_thumbnail()) : // Check if thumbnail exists
the_post_thumbnail();
endif;
?>
<?php the_terms($post->ID, 'portfolio_category') ?>
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<?php
}
} else {
echo "<div>no posts found</div>";
}
wp_reset_postdata();
// your query code end here
return ob_get_clean();
}
endif;
Notice lazyblock/custom-post-loop is the slug of the block you created using lazyblock plugin
本文标签: How to filter by custom post type on Gutenberg quotLatest Posts39 block
版权声明:本文标题:How to filter by custom post type on Gutenberg "Latest Posts' block 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741500012a2382013.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论