From 1bbceed02ed0bb147882dee62ccc80d0e77d965a Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 1 Aug 2024 12:02:27 +0200 Subject: [PATCH 01/52] Enable post format filter in the query loop --- lib/compat/wordpress-6.7/taxonomy.php | 19 +++++++++++++++++++ lib/load.php | 1 + .../block-library/src/post-template/edit.js | 14 +++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 lib/compat/wordpress-6.7/taxonomy.php diff --git a/lib/compat/wordpress-6.7/taxonomy.php b/lib/compat/wordpress-6.7/taxonomy.php new file mode 100644 index 00000000000000..328472c4d17947 --- /dev/null +++ b/lib/compat/wordpress-6.7/taxonomy.php @@ -0,0 +1,19 @@ + slug === taxonomySlug ); if ( taxonomy?.rest_base ) { - accumulator[ taxonomy?.rest_base ] = terms; + // Convert post format term ID's to a comma separated string, + // otherwise the Rest API will return a "rest_invalid_param" error + // and there will be a PHP fatal error from _post_format_request. + if ( + taxonomy.rest_base === 'post_format' && + Array.isArray( terms ) && + terms.length > 0 + ) { + accumulator[ taxonomy.rest_base ] = + terms.join( ',' ); + } else { + accumulator[ taxonomy.rest_base ] = terms; + } } return accumulator; }, From d12d992cbf4fc0777ef1b3a48b754c294f059c4f Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 1 Aug 2024 12:28:13 +0200 Subject: [PATCH 02/52] try to fix CS issues --- lib/compat/wordpress-6.7/taxonomy.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.7/taxonomy.php b/lib/compat/wordpress-6.7/taxonomy.php index 328472c4d17947..1fc40f5785e2ac 100644 --- a/lib/compat/wordpress-6.7/taxonomy.php +++ b/lib/compat/wordpress-6.7/taxonomy.php @@ -8,12 +8,15 @@ /** * Add support for the post format taxonomy in the REST API. * See wp-includes/taxonomy.php line 170. - * + * * Needed to add post formats to the taxonomy filter in the query loop. - * + * * @since 6.7.0 */ -add_filter('register_post_format_taxonomy_args', function( $args ) { - $args['show_in_rest'] = true; - return $args; -} ); +add_filter( + 'register_post_format_taxonomy_args', + function( $args ) { + $args['show_in_rest'] = true; + return $args; + } +); From e8035279b8215bca473eb5a99fc1babf33ba7b95 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 1 Aug 2024 12:33:16 +0200 Subject: [PATCH 03/52] how about this cs fix then? --- lib/compat/wordpress-6.7/taxonomy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/taxonomy.php b/lib/compat/wordpress-6.7/taxonomy.php index 1fc40f5785e2ac..e49d0ad69cd590 100644 --- a/lib/compat/wordpress-6.7/taxonomy.php +++ b/lib/compat/wordpress-6.7/taxonomy.php @@ -15,7 +15,7 @@ */ add_filter( 'register_post_format_taxonomy_args', - function( $args ) { + function ( $args ) { $args['show_in_rest'] = true; return $args; } From ada8db46d847e303562022416f39f31cbf5d21c0 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 2 Aug 2024 09:25:27 +0200 Subject: [PATCH 04/52] Create a new option for post formats --- .../block-library/src/post-template/edit.js | 19 +++---- packages/block-library/src/query/block.json | 3 +- .../query/edit/inspector-controls/index.js | 20 +++++++- .../post-format-controls.js | 51 +++++++++++++++++++ .../inspector-controls/taxonomy-controls.js | 7 ++- 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 packages/block-library/src/query/edit/inspector-controls/post-format-controls.js diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 77411309e9236f..fb86f272b9956c 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -90,6 +90,7 @@ export default function PostTemplateEdit( { taxQuery, parents, pages, + postFormat, // We gather extra query args to pass to the REST API call. // This way extenders of Query Loop can add their own query args, // and have accurate previews in the editor. @@ -138,17 +139,8 @@ export default function PostTemplateEdit( { ( { slug } ) => slug === taxonomySlug ); if ( taxonomy?.rest_base ) { - // Convert post format term ID's to a comma separated string, - // otherwise the Rest API will return a "rest_invalid_param" error - // and there will be a PHP fatal error from _post_format_request. - if ( - taxonomy.rest_base === 'post_format' && - Array.isArray( terms ) && - terms.length > 0 - ) { - accumulator[ taxonomy.rest_base ] = - terms.join( ',' ); - } else { + // Skip post formats, as they are handled separately. + if ( ! taxonomy.rest_base === 'post_format' ) { accumulator[ taxonomy.rest_base ] = terms; } } @@ -175,6 +167,10 @@ export default function PostTemplateEdit( { if ( parents?.length ) { query.parent = parents; } + if ( postFormat?.length ) { + query.post_format = postFormat; + } + // If sticky is not set, it will return all posts in the results. // If sticky is set to `only`, it will limit the results to sticky posts only. // If it is anything else, it will exclude sticky posts from results. For the record the value stored is `exclude`. @@ -217,6 +213,7 @@ export default function PostTemplateEdit( { templateSlug, taxQuery, parents, + postFormat, restQueryArgs, previewPostType, ] diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index b602032df36005..c00ef2efde9bed 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -25,7 +25,8 @@ "sticky": "", "inherit": true, "taxQuery": null, - "parents": [] + "parents": [], + "postFormat": [] } }, "tagName": { diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 5e83ea3aaa9730..c00115a75aed1e 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -24,6 +24,7 @@ import OrderControl from './order-control'; import AuthorControl from './author-control'; import ParentControl from './parent-control'; import { TaxonomyControls } from './taxonomy-controls'; +import PostFormatControls from './post-format-controls'; import StickyControl from './sticky-control'; import EnhancedPaginationControl from './enhanced-pagination-control'; import CreateNewPostLink from './create-new-post-link'; @@ -52,6 +53,7 @@ export default function QueryInspectorControls( props ) { inherit, taxQuery, parents, + postFormat, } = query; const allowedControls = useAllowedControls( attributes ); const [ showSticky, setShowSticky ] = useState( postType === 'post' ); @@ -127,12 +129,15 @@ export default function QueryInspectorControls( props ) { const showParentControl = isControlAllowed( allowedControls, 'parents' ) && isPostTypeHierarchical; + const showPostFormatControl = + taxonomies && taxonomies.some( ( { slug } ) => slug === 'post_format' ); const showFiltersPanel = showTaxControl || showAuthorControl || showSearchControl || - showParentControl; + showParentControl || + showPostFormatControl; const dropdownMenuProps = useToolsPanelDropdownMenuProps(); return ( @@ -263,6 +268,7 @@ export default function QueryInspectorControls( props ) { parents: [], search: '', taxQuery: null, + postFormat: [], } ); setQuerySearch( '' ); } } @@ -324,6 +330,18 @@ export default function QueryInspectorControls( props ) { /> ) } + { showPostFormatControl && ( + !! postFormat?.length } + label={ __( 'Formats' ) } + onDeselect={ () => setQuery( { postFormat: [] } ) } + > + + + ) } ) } diff --git a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js new file mode 100644 index 00000000000000..4855a7ee48dc31 --- /dev/null +++ b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js @@ -0,0 +1,51 @@ +/** + * WordPress dependencies + */ +import { SelectControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __ } from '@wordpress/i18n'; + +export default function PostFormatControls( { onChange, query } ) { + const { postType, postFormat } = query; + + const { postFormats } = useSelect( + ( select ) => { + const { getEntityRecords } = select( coreStore ); + return { + postFormats: getEntityRecords( 'taxonomy', 'post_format', { + order: 'asc', + _fields: 'id,name', + context: 'view', + per_page: -1, + post_type: postType, + } ), + }; + }, + [ postType ] + ); + + const postFormatOptions = postFormats + ? [ + { value: '', label: __( 'All' ) }, + ...postFormats.map( ( { id, name } ) => ( { + value: id, + label: decodeEntities( name ), + } ) ), + ] + : [ { value: '', label: __( 'All' ) } ]; + + return ( + { + onChange( { postFormat: value } ); + } } + /> + ); +} diff --git a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js index 447b667cdacb56..aaa3b4aba58ac5 100644 --- a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js @@ -50,11 +50,16 @@ const getTermIdByTermValue = ( terms, termValue ) => { export function TaxonomyControls( { onChange, query } ) { const { postType, taxQuery } = query; - const taxonomies = useTaxonomies( postType ); + let taxonomies = useTaxonomies( postType ); if ( ! taxonomies || taxonomies.length === 0 ) { return null; } + // Remove post formats, as they are handled separately. + taxonomies = taxonomies.filter( + ( taxonomy ) => taxonomy.slug !== 'post_format' + ); + return ( { taxonomies.map( ( taxonomy ) => { From 63883ac6d7ec10ffbe169f2bf0ede270ea4886c6 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Mon, 5 Aug 2024 12:05:19 +0200 Subject: [PATCH 05/52] Update the control to use the post format theme support --- .../post-format-controls.js | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js index 4855a7ee48dc31..e97c86bcaf1ce5 100644 --- a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js @@ -4,16 +4,43 @@ import { SelectControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { decodeEntities } from '@wordpress/html-entities'; import { __ } from '@wordpress/i18n'; +// All WP post formats, sorted alphabetically by translated name. +const POST_FORMATS = [ + { slug: 'aside', name: __( 'Aside' ) }, + { slug: 'audio', name: __( 'Audio' ) }, + { slug: 'chat', name: __( 'Chat' ) }, + { slug: 'gallery', name: __( 'Gallery' ) }, + { slug: 'image', name: __( 'Image' ) }, + { slug: 'link', name: __( 'Link' ) }, + { slug: 'quote', name: __( 'Quote' ) }, + { slug: 'standard', name: __( 'Standard' ) }, + { slug: 'status', name: __( 'Status' ) }, + { slug: 'video', name: __( 'Video' ) }, +].sort( ( a, b ) => { + const normalizedA = a.name.toUpperCase(); + const normalizedB = b.name.toUpperCase(); + + if ( normalizedA < normalizedB ) { + return -1; + } + if ( normalizedA > normalizedB ) { + return 1; + } + return 0; +} ); + export default function PostFormatControls( { onChange, query } ) { const { postType, postFormat } = query; - const { postFormats } = useSelect( + // WIP/TODO: Only show formats supported by *both* the theme and the (custom) post type. + const { supportedFormats } = useSelect( ( select ) => { + const themeSupports = select( coreStore ).getThemeSupports(); const { getEntityRecords } = select( coreStore ); return { + supportedFormats: themeSupports.formats, postFormats: getEntityRecords( 'taxonomy', 'post_format', { order: 'asc', _fields: 'id,name', @@ -26,20 +53,27 @@ export default function PostFormatControls( { onChange, query } ) { [ postType ] ); - const postFormatOptions = postFormats - ? [ - { value: '', label: __( 'All' ) }, - ...postFormats.map( ( { id, name } ) => ( { - value: id, - label: decodeEntities( name ), - } ) ), - ] - : [ { value: '', label: __( 'All' ) } ]; + // TODO: It seems that even when the theme does not support post formats, + // the 'standard' format is still returned, so the control shows. + // We probably don't want this control to show in the filter in this case. + + const postFormats = POST_FORMATS.filter( ( format ) => + supportedFormats.includes( format.slug ) + ); + + const postFormatOptions = [ + ...( postFormats || [] ).map( ( format ) => ( { + value: format.slug, + label: format.name, + } ) ), + ]; + // TODO: the multiple selection is lacking design. return ( Date: Mon, 5 Aug 2024 12:12:13 +0200 Subject: [PATCH 06/52] Post template preview: Get the ID of the post format to use in the query. --- .../block-library/src/post-template/edit.js | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index fb86f272b9956c..1059212da45ef5 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -109,7 +109,6 @@ export default function PostTemplateEdit( { const { posts, blocks } = useSelect( ( select ) => { const { getEntityRecords, getTaxonomies } = select( coreStore ); - const { getBlocks } = select( blockEditorStore ); const templateCategory = inherit && templateSlug?.startsWith( 'category-' ) && @@ -168,7 +167,51 @@ export default function PostTemplateEdit( { query.parent = parents; } if ( postFormat?.length ) { - query.post_format = postFormat; + // Get the name and id of the registered post formats from the wp_terms table. + const postFormats = getEntityRecords( + 'taxonomy', + 'post_format', + { + order: 'asc', + _fields: 'id,name', + context: 'view', + per_page: -1, + post_type: postType, + } + ); + + // Return early if there are no post formats in the database, + // and show the "No results found" message. + if ( ! postFormats ) { + return { posts: [] }; + } + + // Helper function to compare the value of postFormat to the name in the postFormats object. + const isSameTermName = ( termA, termB ) => + termA.toLowerCase() === termB.toLowerCase(); + + // Map the postFormat values to the corresponding IDs (term_taxonomy_id). + let formatIds = postFormat.map( ( name ) => { + const matchingFormat = Object.values( postFormats ).find( + ( format ) => isSameTermName( format.name, name ) + ); + return matchingFormat ? matchingFormat.id : null; + } ); + + // Convert post format term ID's to a comma separated string, + // otherwise the Rest API will return a "rest_invalid_param" error + // and there will be a PHP fatal error from _post_format_request. + formatIds = formatIds + .filter( ( id ) => id !== null ) + .join( ',' ); + + // Return early if there are no matching post formats in the database, + // and show the "No results found" message. + if ( ! formatIds ) { + return { posts: [] }; + } + + query.post_format = formatIds; } // If sticky is not set, it will return all posts in the results. @@ -190,6 +233,7 @@ export default function PostTemplateEdit( { // When we preview Query Loop blocks we should prefer the current // block's postType, which is passed through block context. const usedPostType = previewPostType || postType; + const { getBlocks } = select( blockEditorStore ); return { posts: getEntityRecords( 'postType', usedPostType, { ...query, From ac44e6f0ca9ef510b8f2acb545ba7274778e2c94 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 6 Aug 2024 08:18:29 +0200 Subject: [PATCH 07/52] Filter the query loop vars to support post formats on the front. --- lib/compat/wordpress-6.7/blocks.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 18d21621be7197..cfbb933bfe033d 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -43,3 +43,30 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file return $settings; } add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_allow_variations_php_file', 10, 2 ); + +/** + * Filter the query vars (tax_query) for the Query Loop block to support post formats. + * + * @param array $query The query vars. + * @param WP_Block $block Block instance. + * @return array The filtered query vars. + */ +function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { + if ( isset( $block->context['query']['postFormat'] ) && + ! empty( $block->context['query']['postFormat'] ) ) { + // Add the required "post-format-" prefix to each format. + $formats = array_map( function( $format ) { + return "post-format-" . $format; + }, $block->context['query']['postFormat'] ); + + $query['tax_query'] = array( + array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $formats, + ), + ); + } + return $query; +} +add_filter( 'query_loop_block_query_vars', 'gutenberg_filter_query_loop_block_query_vars_post_format', 10, 2 ); From 63bad0fd40d07db9f275c5a508a759a3a4bff709 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 6 Aug 2024 08:22:48 +0200 Subject: [PATCH 08/52] Remove the post type from PostFormatControls --- .../query/edit/inspector-controls/index.js | 4 +++ .../post-format-controls.js | 26 +++++-------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index c00115a75aed1e..5b9265e1832ab1 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -129,6 +129,10 @@ export default function QueryInspectorControls( props ) { const showParentControl = isControlAllowed( allowedControls, 'parents' ) && isPostTypeHierarchical; + + // TODO: this condition does not work for custom post types, + // useTaxonomies util does not return the post format taxonomy + // for custom post types, only posts? const showPostFormatControl = taxonomies && taxonomies.some( ( { slug } ) => slug === 'post_format' ); diff --git a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js index e97c86bcaf1ce5..cc8c287230a783 100644 --- a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js @@ -32,26 +32,14 @@ const POST_FORMATS = [ } ); export default function PostFormatControls( { onChange, query } ) { - const { postType, postFormat } = query; + const { postFormat } = query; - // WIP/TODO: Only show formats supported by *both* the theme and the (custom) post type. - const { supportedFormats } = useSelect( - ( select ) => { - const themeSupports = select( coreStore ).getThemeSupports(); - const { getEntityRecords } = select( coreStore ); - return { - supportedFormats: themeSupports.formats, - postFormats: getEntityRecords( 'taxonomy', 'post_format', { - order: 'asc', - _fields: 'id,name', - context: 'view', - per_page: -1, - post_type: postType, - } ), - }; - }, - [ postType ] - ); + const { supportedFormats } = useSelect( ( select ) => { + const themeSupports = select( coreStore ).getThemeSupports(); + return { + supportedFormats: themeSupports.formats, + }; + }, [] ); // TODO: It seems that even when the theme does not support post formats, // the 'standard' format is still returned, so the control shows. From 6f6e3e8d0112a6829abb2fdf95aef27e0b8faba8 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 6 Aug 2024 08:43:36 +0200 Subject: [PATCH 09/52] PHPCS fixes --- lib/compat/wordpress-6.7/blocks.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index cfbb933bfe033d..95d6975711a979 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -55,9 +55,12 @@ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $bloc if ( isset( $block->context['query']['postFormat'] ) && ! empty( $block->context['query']['postFormat'] ) ) { // Add the required "post-format-" prefix to each format. - $formats = array_map( function( $format ) { - return "post-format-" . $format; - }, $block->context['query']['postFormat'] ); + $formats = array_map( + function ( $format ) { + return 'post-format-' . $format; + }, + $block->context['query']['postFormat'] + ); $query['tax_query'] = array( array( From 7482122822e345c08ea5d8a2651a77f74362fbd6 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 07:32:45 +0200 Subject: [PATCH 10/52] Update the condition for when then post format control should show - Update the condition for when then post format control should show, using the core store to get the theme supports. -Remove the post format check from the taxQuery build. --- .../block-library/src/post-template/edit.js | 5 +---- .../src/query/edit/inspector-controls/index.js | 18 +++++++++++++----- .../inspector-controls/post-format-controls.js | 4 ---- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 1059212da45ef5..f9afb9429a5d72 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -138,10 +138,7 @@ export default function PostTemplateEdit( { ( { slug } ) => slug === taxonomySlug ); if ( taxonomy?.rest_base ) { - // Skip post formats, as they are handled separately. - if ( ! taxonomy.rest_base === 'post_format' ) { - accumulator[ taxonomy.rest_base ] = terms; - } + accumulator[ taxonomy.rest_base ] = terms; } return accumulator; }, diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 85f3d343ec5cd1..b9317b13c3551b 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -12,6 +12,8 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { debounce } from '@wordpress/compose'; @@ -136,11 +138,17 @@ export default function QueryInspectorControls( props ) { isControlAllowed( allowedControls, 'parents' ) && isPostTypeHierarchical; - // TODO: this condition does not work for custom post types, - // useTaxonomies util does not return the post format taxonomy - // for custom post types, only posts? - const showPostFormatControl = - taxonomies && taxonomies.some( ( { slug } ) => slug === 'post_format' ); + // Check if post formats are supported. + // If there are no supported formats, getThemeSupports still includes the default 'standard' format, + // and in this case the control should not be shown since the user has no other formats to choose from. + const showPostFormatControl = useSelect( ( select ) => { + const themeSupports = select( coreStore ).getThemeSupports(); + return ( + themeSupports.formats && + themeSupports.formats.length > 0 && + themeSupports.formats.some( ( format ) => format !== 'standard' ) + ); + }, [] ); const showFiltersPanel = showTaxControl || diff --git a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js index cc8c287230a783..99c9e3c05a361c 100644 --- a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js @@ -41,10 +41,6 @@ export default function PostFormatControls( { onChange, query } ) { }; }, [] ); - // TODO: It seems that even when the theme does not support post formats, - // the 'standard' format is still returned, so the control shows. - // We probably don't want this control to show in the filter in this case. - const postFormats = POST_FORMATS.filter( ( format ) => supportedFormats.includes( format.slug ) ); From d3fa212c290f4212fe1fc389403f8632627b98b2 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 09:33:01 +0200 Subject: [PATCH 11/52] WIP: Try to add format to the REST API posts controller through a custom Class. --- ...ss-gutenberg-rest-posts-controller-6-7.php | 1131 +++++++++++++++++ lib/compat/wordpress-6.7/rest-api.php | 22 + lib/compat/wordpress-6.7/taxonomy.php | 22 - lib/load.php | 5 +- .../block-library/src/post-template/edit.js | 49 +- 5 files changed, 1160 insertions(+), 69 deletions(-) create mode 100644 lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php create mode 100644 lib/compat/wordpress-6.7/rest-api.php delete mode 100644 lib/compat/wordpress-6.7/taxonomy.php diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php new file mode 100644 index 00000000000000..9d57219e509d7f --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -0,0 +1,1131 @@ + 400 ) + ); + } + + // Ensure an include parameter is set in case the orderby is set to 'include'. + if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { + return new WP_Error( + 'rest_orderby_include_missing_include', + __( 'You need to define an include parameter to order by include.' ), + array( 'status' => 400 ) + ); + } + + // Retrieve the list of registered collection query parameters. + $registered = $this->get_collection_params(); + $args = array(); + + /* + * This array defines mappings between public API query parameters whose + * values are accepted as-passed, and their internal WP_Query parameter + * name equivalents (some are the same). Only values which are also + * present in $registered will be set. + */ + $parameter_mappings = array( + 'author' => 'author__in', + 'author_exclude' => 'author__not_in', + 'exclude' => 'post__not_in', + 'include' => 'post__in', + 'menu_order' => 'menu_order', + 'offset' => 'offset', + 'order' => 'order', + 'orderby' => 'orderby', + 'page' => 'paged', + 'parent' => 'post_parent__in', + 'parent_exclude' => 'post_parent__not_in', + 'search' => 's', + 'search_columns' => 'search_columns', + 'slug' => 'post_name__in', + 'status' => 'post_status', + ); + + /* + * For each known parameter which is both registered and present in the request, + * set the parameter's value on the query $args. + */ + foreach ( $parameter_mappings as $api_param => $wp_param ) { + if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { + $args[ $wp_param ] = $request[ $api_param ]; + } + } + + // Check for & assign any parameters which require special handling or setting. + $args['date_query'] = array(); + + if ( isset( $registered['before'], $request['before'] ) ) { + $args['date_query'][] = array( + 'before' => $request['before'], + 'column' => 'post_date', + ); + } + + if ( isset( $registered['modified_before'], $request['modified_before'] ) ) { + $args['date_query'][] = array( + 'before' => $request['modified_before'], + 'column' => 'post_modified', + ); + } + + if ( isset( $registered['after'], $request['after'] ) ) { + $args['date_query'][] = array( + 'after' => $request['after'], + 'column' => 'post_date', + ); + } + + if ( isset( $registered['modified_after'], $request['modified_after'] ) ) { + $args['date_query'][] = array( + 'after' => $request['modified_after'], + 'column' => 'post_modified', + ); + } + + // Ensure our per_page parameter overrides any provided posts_per_page filter. + if ( isset( $registered['per_page'] ) ) { + $args['posts_per_page'] = $request['per_page']; + } + + if ( isset( $registered['sticky'], $request['sticky'] ) ) { + $sticky_posts = get_option( 'sticky_posts', array() ); + if ( ! is_array( $sticky_posts ) ) { + $sticky_posts = array(); + } + if ( $request['sticky'] ) { + /* + * As post__in will be used to only get sticky posts, + * we have to support the case where post__in was already + * specified. + */ + $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; + + /* + * If we intersected, but there are no post IDs in common, + * WP_Query won't return "no posts" for post__in = array() + * so we have to fake it a bit. + */ + if ( ! $args['post__in'] ) { + $args['post__in'] = array( 0 ); + } + } elseif ( $sticky_posts ) { + /* + * As post___not_in will be used to only get posts that + * are not sticky, we have to support the case where post__not_in + * was already specified. + */ + $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); + } + } + + $args = $this->prepare_tax_query( $args, $request ); + + if ( isset( $request['format'] ) ) { + // If format is not an array, convert it to an array so that the + // required prefix can be added to all items. + $formats = is_array( $request['format'] ) ? $request['format'] : array( $request['format'] ); + // Add the post-format- prefix. + $terms = array_map( + function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); + // Add the formats to the tax_query as post format taxonomies. + $args['tax_query'][] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + ); + } + + // Force the post_type argument, since it's not a user input variable. + $args['post_type'] = $this->post_type; + + /** + * Filters WP_Query arguments when querying posts via the REST API. + * + * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. + * + * Possible hook names include: + * + * - `rest_post_query` + * - `rest_page_query` + * - `rest_attachment_query` + * + * Enables adding extra arguments or setting defaults for a post collection request. + * + * @since 4.7.0 + * @since 5.7.0 Moved after the `tax_query` query arg is generated. + * + * @link https://developer.wordpress.org/reference/classes/wp_query/ + * + * @param array $args Array of arguments for WP_Query. + * @param WP_REST_Request $request The REST API request. + */ + $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); + $query_args = $this->prepare_items_query( $args, $request ); + + $posts_query = new WP_Query(); + $query_result = $posts_query->query( $query_args ); + + // Allow access to all password protected posts if the context is edit. + if ( 'edit' === $request['context'] ) { + add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); + } + + $posts = array(); + + update_post_author_caches( $query_result ); + update_post_parent_caches( $query_result ); + + if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { + update_post_thumbnail_cache( $posts_query ); + } + + foreach ( $query_result as $post ) { + if ( ! $this->check_read_permission( $post ) ) { + continue; + } + + $data = $this->prepare_item_for_response( $post, $request ); + $posts[] = $this->prepare_response_for_collection( $data ); + } + + // Reset filter. + if ( 'edit' === $request['context'] ) { + remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); + } + + $page = (int) $query_args['paged']; + $total_posts = $posts_query->found_posts; + + if ( $total_posts < 1 && $page > 1 ) { + // Out-of-bounds, run the query again without LIMIT for total count. + unset( $query_args['paged'] ); + + $count_query = new WP_Query(); + $count_query->query( $query_args ); + $total_posts = $count_query->found_posts; + } + + $max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); + + if ( $page > $max_pages && $total_posts > 0 ) { + return new WP_Error( + 'rest_post_invalid_page_number', + __( 'The page number requested is larger than the number of pages available.' ), + array( 'status' => 400 ) + ); + } + + $response = rest_ensure_response( $posts ); + + $response->header( 'X-WP-Total', (int) $total_posts ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + $request_params = $request->get_query_params(); + $collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) ); + $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); + + if ( $page > 1 ) { + $prev_page = $page - 1; + + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Retrieves the post's schema, conforming to JSON Schema. + * + * @since 4.7.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + // Base properties for every Post. + 'properties' => array( + 'date' => array( + 'description' => __( "The date the post was published, in the site's timezone." ), + 'type' => array( 'string', 'null' ), + 'format' => 'date-time', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'date_gmt' => array( + 'description' => __( 'The date the post was published, as GMT.' ), + 'type' => array( 'string', 'null' ), + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'guid' => array( + 'description' => __( 'The globally unique identifier for the post.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => array( + 'raw' => array( + 'description' => __( 'GUID for the post, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ), + 'rendered' => array( + 'description' => __( 'GUID for the post, transformed for display.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ), + 'id' => array( + 'description' => __( 'Unique identifier for the post.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'link' => array( + 'description' => __( 'URL to the post.' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'modified' => array( + 'description' => __( "The date the post was last modified, in the site's timezone." ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'modified_gmt' => array( + 'description' => __( 'The date the post was last modified, as GMT.' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'slug' => array( + 'description' => __( 'An alphanumeric identifier for the post unique to its type.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'arg_options' => array( + 'sanitize_callback' => array( $this, 'sanitize_slug' ), + ), + ), + 'status' => array( + 'description' => __( 'A named status for the post.' ), + 'type' => 'string', + 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), + 'context' => array( 'view', 'edit' ), + 'arg_options' => array( + 'validate_callback' => array( $this, 'check_status' ), + ), + ), + 'type' => array( + 'description' => __( 'Type of post.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'password' => array( + 'description' => __( 'A password to protect access to the content and excerpt.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + ), + ), + ); + + $post_type_obj = get_post_type_object( $this->post_type ); + if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { + $schema['properties']['permalink_template'] = array( + 'description' => __( 'Permalink template for the post.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ); + + $schema['properties']['generated_slug'] = array( + 'description' => __( 'Slug automatically generated from the post title.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ); + + $schema['properties']['class_list'] = array( + 'description' => __( 'An array of the class names for the post container element.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'string', + ), + ); + } + + if ( $post_type_obj->hierarchical ) { + $schema['properties']['parent'] = array( + 'description' => __( 'The ID for the parent of the post.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ); + } + + $post_type_attributes = array( + 'title', + 'editor', + 'author', + 'excerpt', + 'thumbnail', + 'comments', + 'revisions', + 'page-attributes', + 'post-formats', + 'custom-fields', + ); + $fixed_schemas = array( + 'post' => array( + 'title', + 'editor', + 'author', + 'excerpt', + 'thumbnail', + 'comments', + 'revisions', + 'post-formats', + 'custom-fields', + ), + 'page' => array( + 'title', + 'editor', + 'author', + 'excerpt', + 'thumbnail', + 'comments', + 'revisions', + 'page-attributes', + 'custom-fields', + ), + 'attachment' => array( + 'title', + 'author', + 'comments', + 'revisions', + 'custom-fields', + 'thumbnail', + ), + ); + + foreach ( $post_type_attributes as $attribute ) { + if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { + continue; + } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { + continue; + } + + switch ( $attribute ) { + + case 'title': + $schema['properties']['title'] = array( + 'description' => __( 'The title for the post.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'arg_options' => array( + 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). + 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). + ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Title for the post, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + ), + 'rendered' => array( + 'description' => __( 'HTML title for the post, transformed for display.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ); + break; + + case 'editor': + $schema['properties']['content'] = array( + 'description' => __( 'The content for the post.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'arg_options' => array( + 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). + 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). + ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Content for the post, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + ), + 'rendered' => array( + 'description' => __( 'HTML content for the post, transformed for display.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'block_version' => array( + 'description' => __( 'Version of the content block format used by the post.' ), + 'type' => 'integer', + 'context' => array( 'edit' ), + 'readonly' => true, + ), + 'protected' => array( + 'description' => __( 'Whether the content is protected with a password.' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ); + break; + + case 'author': + $schema['properties']['author'] = array( + 'description' => __( 'The ID for the author of the post.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ); + break; + + case 'excerpt': + $schema['properties']['excerpt'] = array( + 'description' => __( 'The excerpt for the post.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'arg_options' => array( + 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). + 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). + ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Excerpt for the post, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + ), + 'rendered' => array( + 'description' => __( 'HTML excerpt for the post, transformed for display.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'protected' => array( + 'description' => __( 'Whether the excerpt is protected with a password.' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ); + break; + + case 'thumbnail': + $schema['properties']['featured_media'] = array( + 'description' => __( 'The ID of the featured media for the post.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ); + break; + + case 'comments': + $schema['properties']['comment_status'] = array( + 'description' => __( 'Whether or not comments are open on the post.' ), + 'type' => 'string', + 'enum' => array( 'open', 'closed' ), + 'context' => array( 'view', 'edit' ), + ); + $schema['properties']['ping_status'] = array( + 'description' => __( 'Whether or not the post can be pinged.' ), + 'type' => 'string', + 'enum' => array( 'open', 'closed' ), + 'context' => array( 'view', 'edit' ), + ); + break; + + case 'page-attributes': + $schema['properties']['menu_order'] = array( + 'description' => __( 'The order of the post in relation to other posts.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ); + break; + + case 'post-formats': + // Get the native post formats and remove the array keys. + $formats = array_values( get_post_format_slugs() ); + + $schema['properties']['format'] = array( + 'description' => __( 'The format for the post.' ), + 'type' => 'string', + 'enum' => $formats, + 'context' => array( 'view', 'edit' ), + ); + break; + + case 'custom-fields': + $schema['properties']['meta'] = $this->meta->get_field_schema(); + break; + + } + } + + if ( 'post' === $this->post_type ) { + $schema['properties']['sticky'] = array( + 'description' => __( 'Whether or not the post should be treated as sticky.' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + ); + } + + $schema['properties']['template'] = array( + 'description' => __( 'The theme file to use to display the post.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'arg_options' => array( + 'validate_callback' => array( $this, 'check_template' ), + ), + ); + + $schema['properties']['format'] = array( + 'description' => __( 'The format for the post.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'anyOf' => array( + array( + 'type' => 'string', + 'enum' => array_values( get_post_format_slugs() ), + ), + array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + 'enum' => array_values( get_post_format_slugs() ), + ), + ), + ), + ); + + $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); + + foreach ( $taxonomies as $taxonomy ) { + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + + if ( array_key_exists( $base, $schema['properties'] ) ) { + $taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name'; + _doing_it_wrong( + 'register_taxonomy', + sprintf( + /* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */ + __( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ), + $taxonomy->name, + $taxonomy_field_name_with_conflict, + $base + ), + '5.4.0' + ); + } + + $schema['properties'][ $base ] = array( + /* translators: %s: Taxonomy name. */ + 'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'context' => array( 'view', 'edit' ), + ); + } + + $schema_links = $this->get_schema_links(); + + if ( $schema_links ) { + $schema['links'] = $schema_links; + } + + // Take a snapshot of which fields are in the schema pre-filtering. + $schema_fields = array_keys( $schema['properties'] ); + + /** + * Filters the post's schema. + * + * The dynamic portion of the filter, `$this->post_type`, refers to the + * post type slug for the controller. + * + * Possible hook names include: + * + * - `rest_post_item_schema` + * - `rest_page_item_schema` + * - `rest_attachment_item_schema` + * + * @since 5.4.0 + * + * @param array $schema Item schema data. + */ + $schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema ); + + // Emit a _doing_it_wrong warning if user tries to add new properties using this filter. + $new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields ); + if ( count( $new_fields ) > 0 ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: register_rest_field */ + __( 'Please use %s to add new schema properties.' ), + 'register_rest_field' + ), + '5.4.0' + ); + } + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the query params for the posts collection. + * + * @since 4.7.0 + * @since 5.4.0 The `tax_relation` query parameter was added. + * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + $query_params['context']['default'] = 'view'; + + $query_params['after'] = array( + 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_after'] = array( + 'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + if ( post_type_supports( $this->post_type, 'author' ) ) { + $query_params['author'] = array( + 'description' => __( 'Limit result set to posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['author_exclude'] = array( + 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['before'] = array( + 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_before'] = array( + 'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['exclude'] = array( + 'description' => __( 'Ensure result set excludes specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + $query_params['include'] = array( + 'description' => __( 'Limit result set to specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['menu_order'] = array( + 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), + 'type' => 'integer', + ); + } + + $query_params['offset'] = array( + 'description' => __( 'Offset the result set by a specific number of items.' ), + 'type' => 'integer', + ); + + $query_params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + ); + + $query_params['orderby'] = array( + 'description' => __( 'Sort collection by post attribute.' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'author', + 'date', + 'id', + 'include', + 'modified', + 'parent', + 'relevance', + 'slug', + 'include_slugs', + 'title', + ), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['orderby']['enum'][] = 'menu_order'; + } + + $post_type = get_post_type_object( $this->post_type ); + + if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { + $query_params['parent'] = array( + 'description' => __( 'Limit result set to items with particular parent IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['parent_exclude'] = array( + 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['search_columns'] = array( + 'default' => array(), + 'description' => __( 'Array of column names to be searched.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), + 'type' => 'string', + ), + ); + + $query_params['slug'] = array( + 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ); + + $query_params['status'] = array( + 'default' => 'publish', + 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), + 'type' => 'string', + ), + 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), + ); + + $query_params = $this->prepare_taxonomy_limit_schema( $query_params ); + + if ( 'post' === $this->post_type ) { + $query_params['sticky'] = array( + 'description' => __( 'Limit result set to items that are sticky.' ), + 'type' => 'boolean', + ); + } + + $query_params['format'] = array( + 'default' => 'standard', + 'description' => __( 'Limit result set to posts assigned one or more formats.' ), + 'type' => 'array', + 'items' => array( + 'anyOf' => array( + array( + 'type' => 'string', + 'enum' => array_values( get_post_format_slugs() ), + ), + array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + 'enum' => array_values( get_post_format_slugs() ), + ), + ), + ), + ), + ); + + /** + * Filters collection parameters for the posts controller. + * + * The dynamic part of the filter `$this->post_type` refers to the post + * type slug for the controller. + * + * This filter registers the collection parameter, but does not map the + * collection parameter to an internal WP_Query parameter. Use the + * `rest_{$this->post_type}_query` filter to set WP_Query parameters. + * + * @since 4.7.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param WP_Post_Type $post_type Post type object. + */ + return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); + } + + /** + * Prepares the 'tax_query' for a collection of posts. + * + * @since 5.7.0 + * + * @param array $args WP_Query arguments. + * @param WP_REST_Request $request Full details about the request. + * @return array Updated query arguments. + */ + private function prepare_tax_query( array $args, WP_REST_Request $request ) { + $relation = $request['tax_relation']; + + if ( $relation ) { + $args['tax_query'] = array( 'relation' => $relation ); + } + + $taxonomies = wp_list_filter( + get_object_taxonomies( $this->post_type, 'objects' ), + array( 'show_in_rest' => true ) + ); + + foreach ( $taxonomies as $taxonomy ) { + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + + $tax_include = $request[ $base ]; + $tax_exclude = $request[ $base . '_exclude' ]; + + if ( $tax_include ) { + $terms = array(); + $include_children = false; + $operator = 'IN'; + + if ( rest_is_array( $tax_include ) ) { + $terms = $tax_include; + } elseif ( rest_is_object( $tax_include ) ) { + $terms = empty( $tax_include['terms'] ) ? array() : $tax_include['terms']; + $include_children = ! empty( $tax_include['include_children'] ); + + if ( isset( $tax_include['operator'] ) && 'AND' === $tax_include['operator'] ) { + $operator = 'AND'; + } + } + + if ( $terms ) { + $args['tax_query'][] = array( + 'taxonomy' => $taxonomy->name, + 'field' => 'term_id', + 'terms' => $terms, + 'include_children' => $include_children, + 'operator' => $operator, + ); + } + } + + if ( $tax_exclude ) { + $terms = array(); + $include_children = false; + + if ( rest_is_array( $tax_exclude ) ) { + $terms = $tax_exclude; + } elseif ( rest_is_object( $tax_exclude ) ) { + $terms = empty( $tax_exclude['terms'] ) ? array() : $tax_exclude['terms']; + $include_children = ! empty( $tax_exclude['include_children'] ); + } + + if ( $terms ) { + $args['tax_query'][] = array( + 'taxonomy' => $taxonomy->name, + 'field' => 'term_id', + 'terms' => $terms, + 'include_children' => $include_children, + 'operator' => 'NOT IN', + ); + } + } + } + + return $args; + } + + /** + * Prepares the collection schema for including and excluding items by terms. + * + * @since 5.7.0 + * + * @param array $query_params Collection schema. + * @return array Updated schema. + */ + private function prepare_taxonomy_limit_schema( array $query_params ) { + $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); + + if ( ! $taxonomies ) { + return $query_params; + } + + $query_params['tax_relation'] = array( + 'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ), + 'type' => 'string', + 'enum' => array( 'AND', 'OR' ), + ); + + $limit_schema = array( + 'type' => array( 'object', 'array' ), + 'oneOf' => array( + array( + 'title' => __( 'Term ID List' ), + 'description' => __( 'Match terms with the listed IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + ), + array( + 'title' => __( 'Term ID Taxonomy Query' ), + 'description' => __( 'Perform an advanced term query.' ), + 'type' => 'object', + 'properties' => array( + 'terms' => array( + 'description' => __( 'Term IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ), + 'include_children' => array( + 'description' => __( 'Whether to include child terms in the terms limiting the result set.' ), + 'type' => 'boolean', + 'default' => false, + ), + ), + 'additionalProperties' => false, + ), + ), + ); + + $include_schema = array_merge( + array( + /* translators: %s: Taxonomy name. */ + 'description' => __( 'Limit result set to items with specific terms assigned in the %s taxonomy.' ), + ), + $limit_schema + ); + // 'operator' is supported only for 'include' queries. + $include_schema['oneOf'][1]['properties']['operator'] = array( + 'description' => __( 'Whether items must be assigned all or any of the specified terms.' ), + 'type' => 'string', + 'enum' => array( 'AND', 'OR' ), + 'default' => 'OR', + ); + + $exclude_schema = array_merge( + array( + /* translators: %s: Taxonomy name. */ + 'description' => __( 'Limit result set to items except those with specific terms assigned in the %s taxonomy.' ), + ), + $limit_schema + ); + + foreach ( $taxonomies as $taxonomy ) { + $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; + $base_exclude = $base . '_exclude'; + + $query_params[ $base ] = $include_schema; + $query_params[ $base ]['description'] = sprintf( $query_params[ $base ]['description'], $base ); + + $query_params[ $base_exclude ] = $exclude_schema; + $query_params[ $base_exclude ]['description'] = sprintf( $query_params[ $base_exclude ]['description'], $base ); + + if ( ! $taxonomy->hierarchical ) { + unset( $query_params[ $base ]['oneOf'][1]['properties']['include_children'] ); + unset( $query_params[ $base_exclude ]['oneOf'][1]['properties']['include_children'] ); + } + } + + return $query_params; + } +} diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php new file mode 100644 index 00000000000000..56cb020b67c57b --- /dev/null +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -0,0 +1,22 @@ +register_routes(); +} + +add_action( 'rest_api_init', 'gutenberg_post_format_rest_posts_controller' ); diff --git a/lib/compat/wordpress-6.7/taxonomy.php b/lib/compat/wordpress-6.7/taxonomy.php deleted file mode 100644 index e49d0ad69cd590..00000000000000 --- a/lib/compat/wordpress-6.7/taxonomy.php +++ /dev/null @@ -1,22 +0,0 @@ - slug === taxonomySlug ); if ( taxonomy?.rest_base ) { - accumulator[ taxonomy.rest_base ] = terms; + accumulator[ taxonomy?.rest_base ] = terms; } return accumulator; }, @@ -164,51 +164,8 @@ export default function PostTemplateEdit( { query.parent = parents; } if ( postFormat?.length ) { - // Get the name and id of the registered post formats from the wp_terms table. - const postFormats = getEntityRecords( - 'taxonomy', - 'post_format', - { - order: 'asc', - _fields: 'id,name', - context: 'view', - per_page: -1, - post_type: postType, - } - ); - - // Return early if there are no post formats in the database, - // and show the "No results found" message. - if ( ! postFormats ) { - return { posts: [] }; - } - - // Helper function to compare the value of postFormat to the name in the postFormats object. - const isSameTermName = ( termA, termB ) => - termA.toLowerCase() === termB.toLowerCase(); - - // Map the postFormat values to the corresponding IDs (term_taxonomy_id). - let formatIds = postFormat.map( ( name ) => { - const matchingFormat = Object.values( postFormats ).find( - ( format ) => isSameTermName( format.name, name ) - ); - return matchingFormat ? matchingFormat.id : null; - } ); - - // Convert post format term ID's to a comma separated string, - // otherwise the Rest API will return a "rest_invalid_param" error - // and there will be a PHP fatal error from _post_format_request. - formatIds = formatIds - .filter( ( id ) => id !== null ) - .join( ',' ); - - // Return early if there are no matching post formats in the database, - // and show the "No results found" message. - if ( ! formatIds ) { - return { posts: [] }; - } - - query.post_format = formatIds; + // Convert postFormat to a comma-separated string. + query.format = postFormat.join( ',' ); } // If sticky is not set, it will return all posts in the results. From 4ab896c3245987991bcddac8fde6cec8d641a0c1 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 10:24:49 +0200 Subject: [PATCH 12/52] Update since Add the changes to the doc block of each method. --- .../class-gutenberg-rest-posts-controller-6-7.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 9d57219e509d7f..15c127e421a785 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -18,6 +18,7 @@ class Gutenberg_REST_Posts_Controller_6_7 extends WP_REST_Posts_Controller { * Retrieves a collection of posts. * * @since 4.7.0 + * @since 6.7.0 Added support for the `format` query parameter. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. @@ -280,6 +281,7 @@ function ( $format ) { * Retrieves the post's schema, conforming to JSON Schema. * * @since 4.7.0 + * @since 6.7.0 Added support for the `format` query parameter. * * @return array Item schema data. */ @@ -743,6 +745,7 @@ public function get_item_schema() { * @since 4.7.0 * @since 5.4.0 The `tax_relation` query parameter was added. * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. + * @since 6.7.0 Added support for the `format` query parameter. * * @return array Collection parameters. */ From 367f6c1357c2e800cd82a2c08c35936a3b252dfe Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 10:25:33 +0200 Subject: [PATCH 13/52] Regenerate the fixture for the query loop, including postFormat. --- test/integration/fixtures/blocks/core__query.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/fixtures/blocks/core__query.json b/test/integration/fixtures/blocks/core__query.json index b050aaa2b5b1fd..456c7cee428f43 100644 --- a/test/integration/fixtures/blocks/core__query.json +++ b/test/integration/fixtures/blocks/core__query.json @@ -16,7 +16,8 @@ "sticky": "", "inherit": true, "taxQuery": null, - "parents": [] + "parents": [], + "postFormat": [] }, "tagName": "div", "enhancedPagination": false From 2f6e716e2cba363660c9df505252871c28bbb048 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 10:33:46 +0200 Subject: [PATCH 14/52] Revert removing the post formats from TaxonomyControls --- .../src/query/edit/inspector-controls/taxonomy-controls.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js index aaa3b4aba58ac5..447b667cdacb56 100644 --- a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js @@ -50,16 +50,11 @@ const getTermIdByTermValue = ( terms, termValue ) => { export function TaxonomyControls( { onChange, query } ) { const { postType, taxQuery } = query; - let taxonomies = useTaxonomies( postType ); + const taxonomies = useTaxonomies( postType ); if ( ! taxonomies || taxonomies.length === 0 ) { return null; } - // Remove post formats, as they are handled separately. - taxonomies = taxonomies.filter( - ( taxonomy ) => taxonomy.slug !== 'post_format' - ); - return ( { taxonomies.map( ( taxonomy ) => { From def06f2c94989f2dbf43bfe212dc6a2f95ced357 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 11:04:09 +0200 Subject: [PATCH 15/52] Update class-gutenberg-rest-posts-controller-6-7.php --- .../class-gutenberg-rest-posts-controller-6-7.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 15c127e421a785..31ecb5fcf3aef1 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -915,6 +915,10 @@ public function get_collection_params() { ); } + /* + * I have temporarilly removed this because I believe it is incorrect, + * if I don't comment this out, then there are no results if + * the query loop block is not set to filter by format. $query_params['format'] = array( 'default' => 'standard', 'description' => __( 'Limit result set to posts assigned one or more formats.' ), @@ -935,6 +939,7 @@ public function get_collection_params() { ), ), ); + */ /** * Filters collection parameters for the posts controller. From 8d877064b070e6b20a4e79bbb65a40c7d2f0ce5b Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 11:37:05 +0200 Subject: [PATCH 16/52] Combine the post format with the tax_query array instead of replacing it. --- lib/compat/wordpress-6.7/blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 95d6975711a979..1f275020ddf5f2 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -62,7 +62,7 @@ function ( $format ) { $block->context['query']['postFormat'] ); - $query['tax_query'] = array( + $query['tax_query'][] = array( array( 'taxonomy' => 'post_format', 'field' => 'slug', From 7a989f9b7b138b2833a6137cae1329b77c6b270a Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 13 Aug 2024 12:24:18 +0200 Subject: [PATCH 17/52] WIP: comment out breaking code to allow testing. --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 31ecb5fcf3aef1..d7bc860ca3811b 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -643,6 +643,7 @@ public function get_item_schema() { ), ); + /* $schema['properties']['format'] = array( 'description' => __( 'The format for the post.' ), 'type' => 'array', @@ -661,6 +662,7 @@ public function get_item_schema() { ), ), ); + */ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); From 7008f76c74fc0c72a4816a6e6e36d40631d2d294 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 04:08:45 +0200 Subject: [PATCH 18/52] edit.js: Don't convert the post format to a string Post template edit.js: Don't convert the post format to a string. This conversion prevented selecting multiple post formats in the query filter. --- packages/block-library/src/post-template/edit.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 22650404017d31..61305779d71dd1 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -164,8 +164,7 @@ export default function PostTemplateEdit( { query.parent = parents; } if ( postFormat?.length ) { - // Convert postFormat to a comma-separated string. - query.format = postFormat.join( ',' ); + query.format = postFormat; } // If sticky is not set, it will return all posts in the results. From 7c6ac16e37efef036e7bb6a9346e6e52b1746373 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 04:13:52 +0200 Subject: [PATCH 19/52] Update edit.js --- packages/block-library/src/post-template/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 61305779d71dd1..c2ec776a82bf06 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -109,6 +109,7 @@ export default function PostTemplateEdit( { const { posts, blocks } = useSelect( ( select ) => { const { getEntityRecords, getTaxonomies } = select( coreStore ); + const { getBlocks } = select( blockEditorStore ); const templateCategory = inherit && templateSlug?.startsWith( 'category-' ) && @@ -186,7 +187,6 @@ export default function PostTemplateEdit( { // When we preview Query Loop blocks we should prefer the current // block's postType, which is passed through block context. const usedPostType = previewPostType || postType; - const { getBlocks } = select( blockEditorStore ); return { posts: getEntityRecords( 'postType', usedPostType, { ...query, From 51540696dfba586e0f95ae46dfc7edbca689f3e6 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 06:23:51 +0200 Subject: [PATCH 20/52] Simplify the format array in get_item_schema Change the type to a string that matches the format slugs. Each post can only have one format. --- ...ass-gutenberg-rest-posts-controller-6-7.php | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index d7bc860ca3811b..9f5170c65a6b00 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -643,26 +643,12 @@ public function get_item_schema() { ), ); - /* $schema['properties']['format'] = array( 'description' => __( 'The format for the post.' ), - 'type' => 'array', + 'type' => 'string', 'context' => array( 'view', 'edit' ), - 'anyOf' => array( - array( - 'type' => 'string', - 'enum' => array_values( get_post_format_slugs() ), - ), - array( - 'type' => 'array', - 'items' => array( - 'type' => 'string', - 'enum' => array_values( get_post_format_slugs() ), - ), - ), - ), + 'enum' => array_values( get_post_format_slugs() ), ); - */ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); From c09d09326c1c1d295f71370def3e644ac90db288 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 09:45:05 +0200 Subject: [PATCH 21/52] Remove the changes to get_collection_params() I could not get this change to work. Maybe it should not even be added, so I am removing it. --- ...ss-gutenberg-rest-posts-controller-6-7.php | 222 +----------------- 1 file changed, 1 insertion(+), 221 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 9f5170c65a6b00..023cb989677d24 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -149,7 +149,7 @@ public function get_items( $request ) { $args = $this->prepare_tax_query( $args, $request ); - if ( isset( $request['format'] ) ) { + if ( isset( $request['format'] ) && ! empty( $request['format'] ) ) { // If format is not an array, convert it to an array so that the // required prefix can be added to all items. $formats = is_array( $request['format'] ) ? $request['format'] : array( $request['format'] ); @@ -727,226 +727,6 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } - /** - * Retrieves the query params for the posts collection. - * - * @since 4.7.0 - * @since 5.4.0 The `tax_relation` query parameter was added. - * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. - * @since 6.7.0 Added support for the `format` query parameter. - * - * @return array Collection parameters. - */ - public function get_collection_params() { - $query_params = parent::get_collection_params(); - - $query_params['context']['default'] = 'view'; - - $query_params['after'] = array( - 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), - 'type' => 'string', - 'format' => 'date-time', - ); - - $query_params['modified_after'] = array( - 'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ), - 'type' => 'string', - 'format' => 'date-time', - ); - - if ( post_type_supports( $this->post_type, 'author' ) ) { - $query_params['author'] = array( - 'description' => __( 'Limit result set to posts assigned to specific authors.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - $query_params['author_exclude'] = array( - 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - } - - $query_params['before'] = array( - 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), - 'type' => 'string', - 'format' => 'date-time', - ); - - $query_params['modified_before'] = array( - 'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ), - 'type' => 'string', - 'format' => 'date-time', - ); - - $query_params['exclude'] = array( - 'description' => __( 'Ensure result set excludes specific IDs.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - - $query_params['include'] = array( - 'description' => __( 'Limit result set to specific IDs.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - - if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { - $query_params['menu_order'] = array( - 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), - 'type' => 'integer', - ); - } - - $query_params['offset'] = array( - 'description' => __( 'Offset the result set by a specific number of items.' ), - 'type' => 'integer', - ); - - $query_params['order'] = array( - 'description' => __( 'Order sort attribute ascending or descending.' ), - 'type' => 'string', - 'default' => 'desc', - 'enum' => array( 'asc', 'desc' ), - ); - - $query_params['orderby'] = array( - 'description' => __( 'Sort collection by post attribute.' ), - 'type' => 'string', - 'default' => 'date', - 'enum' => array( - 'author', - 'date', - 'id', - 'include', - 'modified', - 'parent', - 'relevance', - 'slug', - 'include_slugs', - 'title', - ), - ); - - if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { - $query_params['orderby']['enum'][] = 'menu_order'; - } - - $post_type = get_post_type_object( $this->post_type ); - - if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { - $query_params['parent'] = array( - 'description' => __( 'Limit result set to items with particular parent IDs.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - $query_params['parent_exclude'] = array( - 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'default' => array(), - ); - } - - $query_params['search_columns'] = array( - 'default' => array(), - 'description' => __( 'Array of column names to be searched.' ), - 'type' => 'array', - 'items' => array( - 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), - 'type' => 'string', - ), - ); - - $query_params['slug'] = array( - 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ); - - $query_params['status'] = array( - 'default' => 'publish', - 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), - 'type' => 'array', - 'items' => array( - 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), - 'type' => 'string', - ), - 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), - ); - - $query_params = $this->prepare_taxonomy_limit_schema( $query_params ); - - if ( 'post' === $this->post_type ) { - $query_params['sticky'] = array( - 'description' => __( 'Limit result set to items that are sticky.' ), - 'type' => 'boolean', - ); - } - - /* - * I have temporarilly removed this because I believe it is incorrect, - * if I don't comment this out, then there are no results if - * the query loop block is not set to filter by format. - $query_params['format'] = array( - 'default' => 'standard', - 'description' => __( 'Limit result set to posts assigned one or more formats.' ), - 'type' => 'array', - 'items' => array( - 'anyOf' => array( - array( - 'type' => 'string', - 'enum' => array_values( get_post_format_slugs() ), - ), - array( - 'type' => 'array', - 'items' => array( - 'type' => 'string', - 'enum' => array_values( get_post_format_slugs() ), - ), - ), - ), - ), - ); - */ - - /** - * Filters collection parameters for the posts controller. - * - * The dynamic part of the filter `$this->post_type` refers to the post - * type slug for the controller. - * - * This filter registers the collection parameter, but does not map the - * collection parameter to an internal WP_Query parameter. Use the - * `rest_{$this->post_type}_query` filter to set WP_Query parameters. - * - * @since 4.7.0 - * - * @param array $query_params JSON Schema-formatted collection parameters. - * @param WP_Post_Type $post_type Post type object. - */ - return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); - } - /** * Prepares the 'tax_query' for a collection of posts. * From 713786ef28ce8836f5510b977adfa0266089863d Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 13:46:01 +0200 Subject: [PATCH 22/52] Allow filtering for the standard post format --- ...ss-gutenberg-rest-posts-controller-6-7.php | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 023cb989677d24..b8147d3ded675e 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -153,19 +153,49 @@ public function get_items( $request ) { // If format is not an array, convert it to an array so that the // required prefix can be added to all items. $formats = is_array( $request['format'] ) ? $request['format'] : array( $request['format'] ); - // Add the post-format- prefix. - $terms = array_map( - function ( $format ) { - return 'post-format-' . $format; - }, - $formats - ); - // Add the formats to the tax_query as post format taxonomies. - $args['tax_query'][] = array( - 'taxonomy' => 'post_format', - 'field' => 'slug', - 'terms' => $terms, - ); + + $tax_query = array( 'relation' => 'OR' ); + + // The default post format, 'standard', is not saved in the database. + // If 'standard' is part of the request, the query needs to exclude the formats. + if ( in_array( 'standard', $formats, true ) ) { + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ); + // Remove the standard format: + $formats = array_diff( $formats, array( 'standard' ) ); + } + + // Add any remaining formats to the tax query. + if ( ! empty( $formats ) ) { + // Add the post-format- prefix. + $terms = array_map( + function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); + + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + 'operator' => 'IN', + ); + } + + // Enable filtering by both post formats and other taxonomies by combining them with AND. + if ( isset( $args['tax_query'] ) ) { + $args['tax_query'][] = array( + 'relation' => 'AND', + $tax_query, + ); + } else { + $args['tax_query'] = $tax_query; + } } // Force the post_type argument, since it's not a user input variable. From c7d7c883d3ff24960d71ba98ab6b05254928639a Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 22 Aug 2024 14:09:23 +0200 Subject: [PATCH 23/52] Filter register_post_type_args instead of using rest_api_init --- lib/compat/wordpress-6.7/post-formats.php | 19 +++++++++++++++++++ lib/compat/wordpress-6.7/rest-api.php | 12 ------------ lib/load.php | 1 + 3 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 lib/compat/wordpress-6.7/post-formats.php diff --git a/lib/compat/wordpress-6.7/post-formats.php b/lib/compat/wordpress-6.7/post-formats.php new file mode 100644 index 00000000000000..22790640b37865 --- /dev/null +++ b/lib/compat/wordpress-6.7/post-formats.php @@ -0,0 +1,19 @@ +register_routes(); -} - -add_action( 'rest_api_init', 'gutenberg_post_format_rest_posts_controller' ); - /** * Update the preload paths registered in Core (`site-editor.php` or `edit-form-blocks.php`). * diff --git a/lib/load.php b/lib/load.php index 561e2b3f48930b..22b6fa1626e031 100644 --- a/lib/load.php +++ b/lib/load.php @@ -109,6 +109,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/script-modules.php'; require __DIR__ . '/compat/wordpress-6.7/class-wp-block-templates-registry.php'; require __DIR__ . '/compat/wordpress-6.7/compat.php'; +require __DIR__ . '/compat/wordpress-6.7/post-formats.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; From c13e9205e56ba0eaafb29a366ad25dab585eb5c4 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 27 Aug 2024 09:10:33 +0200 Subject: [PATCH 24/52] Rename "postFormat" parameter to "format" Rename "postFormat" parameter to "format". Simplify the name of the control from PostFormatControls to FormatControls. Simplify the name of the constant that checks if the FormatControls should be used, from showPostFormatControl to showFormatControl. --- lib/compat/wordpress-6.7/blocks.php | 6 ++--- .../block-library/src/post-template/edit.js | 8 +++---- packages/block-library/src/query/block.json | 2 +- ...-format-controls.js => format-controls.js} | 23 +++++++++---------- .../query/edit/inspector-controls/index.js | 20 ++++++++-------- .../fixtures/blocks/core__query.json | 2 +- 6 files changed, 30 insertions(+), 31 deletions(-) rename packages/block-library/src/query/edit/inspector-controls/{post-format-controls.js => format-controls.js} (74%) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 1f275020ddf5f2..1ab238c4e1c782 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -52,14 +52,14 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file * @return array The filtered query vars. */ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { - if ( isset( $block->context['query']['postFormat'] ) && - ! empty( $block->context['query']['postFormat'] ) ) { + if ( isset( $block->context['query']['format'] ) && + ! empty( $block->context['query']['format'] ) ) { // Add the required "post-format-" prefix to each format. $formats = array_map( function ( $format ) { return 'post-format-' . $format; }, - $block->context['query']['postFormat'] + $block->context['query']['format'] ); $query['tax_query'][] = array( diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index c2ec776a82bf06..a9e279ee2c3052 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -90,7 +90,7 @@ export default function PostTemplateEdit( { taxQuery, parents, pages, - postFormat, + format, // We gather extra query args to pass to the REST API call. // This way extenders of Query Loop can add their own query args, // and have accurate previews in the editor. @@ -164,8 +164,8 @@ export default function PostTemplateEdit( { if ( parents?.length ) { query.parent = parents; } - if ( postFormat?.length ) { - query.format = postFormat; + if ( format?.length ) { + query.format = format; } // If sticky is not set, it will return all posts in the results. @@ -210,7 +210,7 @@ export default function PostTemplateEdit( { templateSlug, taxQuery, parents, - postFormat, + format, restQueryArgs, previewPostType, ] diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index c00ef2efde9bed..9eb84959638236 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -26,7 +26,7 @@ "inherit": true, "taxQuery": null, "parents": [], - "postFormat": [] + "format": [] } }, "tagName": { diff --git a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js similarity index 74% rename from packages/block-library/src/query/edit/inspector-controls/post-format-controls.js rename to packages/block-library/src/query/edit/inspector-controls/format-controls.js index 99c9e3c05a361c..6acdcae65167e7 100644 --- a/packages/block-library/src/query/edit/inspector-controls/post-format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -31,8 +31,8 @@ const POST_FORMATS = [ return 0; } ); -export default function PostFormatControls( { onChange, query } ) { - const { postFormat } = query; +export default function FormatControls( { onChange, query } ) { + const { format } = query; const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); @@ -41,28 +41,27 @@ export default function PostFormatControls( { onChange, query } ) { }; }, [] ); - const postFormats = POST_FORMATS.filter( ( format ) => - supportedFormats.includes( format.slug ) + const formats = POST_FORMATS.filter( ( item ) => + supportedFormats.includes( item.slug ) ); - const postFormatOptions = [ - ...( postFormats || [] ).map( ( format ) => ( { - value: format.slug, - label: format.name, + const formatOptions = [ + ...( formats || [] ).map( ( item ) => ( { + value: item.slug, + label: item.name, } ) ), ]; - // TODO: the multiple selection is lacking design. return ( { - onChange( { postFormat: value } ); + onChange( { format: value } ); } } /> ); diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index b9317b13c3551b..973686e6f3d176 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -26,7 +26,7 @@ import OrderControl from './order-control'; import AuthorControl from './author-control'; import ParentControl from './parent-control'; import { TaxonomyControls } from './taxonomy-controls'; -import PostFormatControls from './post-format-controls'; +import FormatControls from './format-controls'; import StickyControl from './sticky-control'; import EnhancedPaginationControl from './enhanced-pagination-control'; import CreateNewPostLink from './create-new-post-link'; @@ -61,7 +61,7 @@ export default function QueryInspectorControls( props ) { inherit, taxQuery, parents, - postFormat, + format, } = query; const allowedControls = useAllowedControls( attributes ); const [ showSticky, setShowSticky ] = useState( postType === 'post' ); @@ -141,12 +141,12 @@ export default function QueryInspectorControls( props ) { // Check if post formats are supported. // If there are no supported formats, getThemeSupports still includes the default 'standard' format, // and in this case the control should not be shown since the user has no other formats to choose from. - const showPostFormatControl = useSelect( ( select ) => { + const showFormatControl = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); return ( themeSupports.formats && themeSupports.formats.length > 0 && - themeSupports.formats.some( ( format ) => format !== 'standard' ) + themeSupports.formats.some( ( type ) => type !== 'standard' ) ); }, [] ); @@ -155,7 +155,7 @@ export default function QueryInspectorControls( props ) { showAuthorControl || showSearchControl || showParentControl || - showPostFormatControl; + showFormatControl; const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const showPostCountControl = isControlAllowed( @@ -337,7 +337,7 @@ export default function QueryInspectorControls( props ) { parents: [], search: '', taxQuery: null, - postFormat: [], + format: [], } ); setQuerySearch( '' ); } } @@ -399,13 +399,13 @@ export default function QueryInspectorControls( props ) { /> ) } - { showPostFormatControl && ( + { showFormatControl && ( !! postFormat?.length } + hasValue={ () => !! format?.length } label={ __( 'Formats' ) } - onDeselect={ () => setQuery( { postFormat: [] } ) } + onDeselect={ () => setQuery( { format: [] } ) } > - diff --git a/test/integration/fixtures/blocks/core__query.json b/test/integration/fixtures/blocks/core__query.json index 456c7cee428f43..a5ee4523128df8 100644 --- a/test/integration/fixtures/blocks/core__query.json +++ b/test/integration/fixtures/blocks/core__query.json @@ -17,7 +17,7 @@ "inherit": true, "taxQuery": null, "parents": [], - "postFormat": [] + "format": [] }, "tagName": "div", "enhancedPagination": false From a1a561bb66eeeea5185747d421bf17363744e733 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 09:02:23 +0200 Subject: [PATCH 25/52] Add format to get_collection_params() --- ...ss-gutenberg-rest-posts-controller-6-7.php | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index b8147d3ded675e..30a98ef63646a3 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -757,6 +757,212 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + /** + * Retrieves the query params for the posts collection. + * + * @since 4.7.0 + * @since 5.4.0 The `tax_relation` query parameter was added. + * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. + * @since 6.7.0 The `format` query parameter was added. + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + $query_params['context']['default'] = 'view'; + + $query_params['after'] = array( + 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_after'] = array( + 'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + if ( post_type_supports( $this->post_type, 'author' ) ) { + $query_params['author'] = array( + 'description' => __( 'Limit result set to posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['author_exclude'] = array( + 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['before'] = array( + 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['modified_before'] = array( + 'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ), + 'type' => 'string', + 'format' => 'date-time', + ); + + $query_params['exclude'] = array( + 'description' => __( 'Ensure result set excludes specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + $query_params['include'] = array( + 'description' => __( 'Limit result set to specific IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['menu_order'] = array( + 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), + 'type' => 'integer', + ); + } + + $query_params['offset'] = array( + 'description' => __( 'Offset the result set by a specific number of items.' ), + 'type' => 'integer', + ); + + $query_params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + ); + + $query_params['orderby'] = array( + 'description' => __( 'Sort collection by post attribute.' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'author', + 'date', + 'id', + 'include', + 'modified', + 'parent', + 'relevance', + 'slug', + 'include_slugs', + 'title', + ), + ); + + if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { + $query_params['orderby']['enum'][] = 'menu_order'; + } + + $post_type = get_post_type_object( $this->post_type ); + + if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { + $query_params['parent'] = array( + 'description' => __( 'Limit result set to items with particular parent IDs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + $query_params['parent_exclude'] = array( + 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + ); + } + + $query_params['search_columns'] = array( + 'default' => array(), + 'description' => __( 'Array of column names to be searched.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), + 'type' => 'string', + ), + ); + + $query_params['slug'] = array( + 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ); + + $query_params['status'] = array( + 'default' => 'publish', + 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), + 'type' => 'string', + ), + 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), + ); + + $query_params = $this->prepare_taxonomy_limit_schema( $query_params ); + + if ( 'post' === $this->post_type ) { + $query_params['sticky'] = array( + 'description' => __( 'Limit result set to items that are sticky.' ), + 'type' => 'boolean', + ); + } + + if ( post_type_supports( $this->post_type, 'post-formats' ) ) { + $query_params['format'] = array( + 'default' => 'standard', + 'description' => __( 'Limit result set to items assigned one or more formats.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array_values( get_post_format_slugs() ), + 'type' => 'string', + ), + ); + } + + /** + * Filters collection parameters for the posts controller. + * + * The dynamic part of the filter `$this->post_type` refers to the post + * type slug for the controller. + * + * This filter registers the collection parameter, but does not map the + * collection parameter to an internal WP_Query parameter. Use the + * `rest_{$this->post_type}_query` filter to set WP_Query parameters. + * + * @since 4.7.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param WP_Post_Type $post_type Post type object. + */ + return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); + } + /** * Prepares the 'tax_query' for a collection of posts. * From b87d25c98e305ec6d64d152a753c9fca2de21740 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 09:15:03 +0200 Subject: [PATCH 26/52] Update lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php Co-authored-by: George Mamadashvili --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 30a98ef63646a3..c67291f523b748 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -173,7 +173,7 @@ public function get_items( $request ) { if ( ! empty( $formats ) ) { // Add the post-format- prefix. $terms = array_map( - function ( $format ) { + static function ( $format ) { return 'post-format-' . $format; }, $formats From 4786c24dfda0c143de1b9f9f46b0bca84c58aad0 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 09:17:04 +0200 Subject: [PATCH 27/52] Update class-gutenberg-rest-posts-controller-6-7.php --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 30a98ef63646a3..727f2e349e3208 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -149,7 +149,7 @@ public function get_items( $request ) { $args = $this->prepare_tax_query( $args, $request ); - if ( isset( $request['format'] ) && ! empty( $request['format'] ) ) { + if ( ! empty( $request['format'] ) ) { // If format is not an array, convert it to an array so that the // required prefix can be added to all items. $formats = is_array( $request['format'] ) ? $request['format'] : array( $request['format'] ); From 78470b008f617b140eee1f29d57e1c2fcfd50fa3 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 09:39:05 +0200 Subject: [PATCH 28/52] PHPCS: remove white space --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 91dc46901f5bc6..6d637a43e6be80 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -932,7 +932,7 @@ public function get_collection_params() { 'type' => 'boolean', ); } - + if ( post_type_supports( $this->post_type, 'post-formats' ) ) { $query_params['format'] = array( 'default' => 'standard', From 8f32caf8cd714c71558549ce3a2fcce066ea6df3 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 10:59:07 +0200 Subject: [PATCH 29/52] Remove get_item_schema() --- ...ss-gutenberg-rest-posts-controller-6-7.php | 450 ------------------ 1 file changed, 450 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 6d637a43e6be80..5096ecb52161ed 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -307,456 +307,6 @@ static function ( $format ) { return $response; } - /** - * Retrieves the post's schema, conforming to JSON Schema. - * - * @since 4.7.0 - * @since 6.7.0 Added support for the `format` query parameter. - * - * @return array Item schema data. - */ - public function get_item_schema() { - if ( $this->schema ) { - return $this->add_additional_fields_schema( $this->schema ); - } - - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => $this->post_type, - 'type' => 'object', - // Base properties for every Post. - 'properties' => array( - 'date' => array( - 'description' => __( "The date the post was published, in the site's timezone." ), - 'type' => array( 'string', 'null' ), - 'format' => 'date-time', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'date_gmt' => array( - 'description' => __( 'The date the post was published, as GMT.' ), - 'type' => array( 'string', 'null' ), - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'guid' => array( - 'description' => __( 'The globally unique identifier for the post.' ), - 'type' => 'object', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - 'properties' => array( - 'raw' => array( - 'description' => __( 'GUID for the post, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - 'readonly' => true, - ), - 'rendered' => array( - 'description' => __( 'GUID for the post, transformed for display.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - ), - ), - 'id' => array( - 'description' => __( 'Unique identifier for the post.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'link' => array( - 'description' => __( 'URL to the post.' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'modified' => array( - 'description' => __( "The date the post was last modified, in the site's timezone." ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'modified_gmt' => array( - 'description' => __( 'The date the post was last modified, as GMT.' ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'slug' => array( - 'description' => __( 'An alphanumeric identifier for the post unique to its type.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'arg_options' => array( - 'sanitize_callback' => array( $this, 'sanitize_slug' ), - ), - ), - 'status' => array( - 'description' => __( 'A named status for the post.' ), - 'type' => 'string', - 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), - 'context' => array( 'view', 'edit' ), - 'arg_options' => array( - 'validate_callback' => array( $this, 'check_status' ), - ), - ), - 'type' => array( - 'description' => __( 'Type of post.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'password' => array( - 'description' => __( 'A password to protect access to the content and excerpt.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - ), - ), - ); - - $post_type_obj = get_post_type_object( $this->post_type ); - if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { - $schema['properties']['permalink_template'] = array( - 'description' => __( 'Permalink template for the post.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - 'readonly' => true, - ); - - $schema['properties']['generated_slug'] = array( - 'description' => __( 'Slug automatically generated from the post title.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - 'readonly' => true, - ); - - $schema['properties']['class_list'] = array( - 'description' => __( 'An array of the class names for the post container element.' ), - 'type' => 'array', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - 'items' => array( - 'type' => 'string', - ), - ); - } - - if ( $post_type_obj->hierarchical ) { - $schema['properties']['parent'] = array( - 'description' => __( 'The ID for the parent of the post.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - ); - } - - $post_type_attributes = array( - 'title', - 'editor', - 'author', - 'excerpt', - 'thumbnail', - 'comments', - 'revisions', - 'page-attributes', - 'post-formats', - 'custom-fields', - ); - $fixed_schemas = array( - 'post' => array( - 'title', - 'editor', - 'author', - 'excerpt', - 'thumbnail', - 'comments', - 'revisions', - 'post-formats', - 'custom-fields', - ), - 'page' => array( - 'title', - 'editor', - 'author', - 'excerpt', - 'thumbnail', - 'comments', - 'revisions', - 'page-attributes', - 'custom-fields', - ), - 'attachment' => array( - 'title', - 'author', - 'comments', - 'revisions', - 'custom-fields', - 'thumbnail', - ), - ); - - foreach ( $post_type_attributes as $attribute ) { - if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { - continue; - } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { - continue; - } - - switch ( $attribute ) { - - case 'title': - $schema['properties']['title'] = array( - 'description' => __( 'The title for the post.' ), - 'type' => 'object', - 'context' => array( 'view', 'edit', 'embed' ), - 'arg_options' => array( - 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). - 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). - ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Title for the post, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - ), - 'rendered' => array( - 'description' => __( 'HTML title for the post, transformed for display.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - ), - ); - break; - - case 'editor': - $schema['properties']['content'] = array( - 'description' => __( 'The content for the post.' ), - 'type' => 'object', - 'context' => array( 'view', 'edit' ), - 'arg_options' => array( - 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). - 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). - ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Content for the post, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - ), - 'rendered' => array( - 'description' => __( 'HTML content for the post, transformed for display.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'block_version' => array( - 'description' => __( 'Version of the content block format used by the post.' ), - 'type' => 'integer', - 'context' => array( 'edit' ), - 'readonly' => true, - ), - 'protected' => array( - 'description' => __( 'Whether the content is protected with a password.' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - ), - ); - break; - - case 'author': - $schema['properties']['author'] = array( - 'description' => __( 'The ID for the author of the post.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ); - break; - - case 'excerpt': - $schema['properties']['excerpt'] = array( - 'description' => __( 'The excerpt for the post.' ), - 'type' => 'object', - 'context' => array( 'view', 'edit', 'embed' ), - 'arg_options' => array( - 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). - 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). - ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Excerpt for the post, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'edit' ), - ), - 'rendered' => array( - 'description' => __( 'HTML excerpt for the post, transformed for display.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'protected' => array( - 'description' => __( 'Whether the excerpt is protected with a password.' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - ), - ); - break; - - case 'thumbnail': - $schema['properties']['featured_media'] = array( - 'description' => __( 'The ID of the featured media for the post.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ); - break; - - case 'comments': - $schema['properties']['comment_status'] = array( - 'description' => __( 'Whether or not comments are open on the post.' ), - 'type' => 'string', - 'enum' => array( 'open', 'closed' ), - 'context' => array( 'view', 'edit' ), - ); - $schema['properties']['ping_status'] = array( - 'description' => __( 'Whether or not the post can be pinged.' ), - 'type' => 'string', - 'enum' => array( 'open', 'closed' ), - 'context' => array( 'view', 'edit' ), - ); - break; - - case 'page-attributes': - $schema['properties']['menu_order'] = array( - 'description' => __( 'The order of the post in relation to other posts.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - ); - break; - - case 'post-formats': - // Get the native post formats and remove the array keys. - $formats = array_values( get_post_format_slugs() ); - - $schema['properties']['format'] = array( - 'description' => __( 'The format for the post.' ), - 'type' => 'string', - 'enum' => $formats, - 'context' => array( 'view', 'edit' ), - ); - break; - - case 'custom-fields': - $schema['properties']['meta'] = $this->meta->get_field_schema(); - break; - - } - } - - if ( 'post' === $this->post_type ) { - $schema['properties']['sticky'] = array( - 'description' => __( 'Whether or not the post should be treated as sticky.' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit' ), - ); - } - - $schema['properties']['template'] = array( - 'description' => __( 'The theme file to use to display the post.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'arg_options' => array( - 'validate_callback' => array( $this, 'check_template' ), - ), - ); - - $schema['properties']['format'] = array( - 'description' => __( 'The format for the post.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'enum' => array_values( get_post_format_slugs() ), - ); - - $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); - - foreach ( $taxonomies as $taxonomy ) { - $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; - - if ( array_key_exists( $base, $schema['properties'] ) ) { - $taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name'; - _doing_it_wrong( - 'register_taxonomy', - sprintf( - /* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */ - __( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ), - $taxonomy->name, - $taxonomy_field_name_with_conflict, - $base - ), - '5.4.0' - ); - } - - $schema['properties'][ $base ] = array( - /* translators: %s: Taxonomy name. */ - 'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ), - 'type' => 'array', - 'items' => array( - 'type' => 'integer', - ), - 'context' => array( 'view', 'edit' ), - ); - } - - $schema_links = $this->get_schema_links(); - - if ( $schema_links ) { - $schema['links'] = $schema_links; - } - - // Take a snapshot of which fields are in the schema pre-filtering. - $schema_fields = array_keys( $schema['properties'] ); - - /** - * Filters the post's schema. - * - * The dynamic portion of the filter, `$this->post_type`, refers to the - * post type slug for the controller. - * - * Possible hook names include: - * - * - `rest_post_item_schema` - * - `rest_page_item_schema` - * - `rest_attachment_item_schema` - * - * @since 5.4.0 - * - * @param array $schema Item schema data. - */ - $schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema ); - - // Emit a _doing_it_wrong warning if user tries to add new properties using this filter. - $new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields ); - if ( count( $new_fields ) > 0 ) { - _doing_it_wrong( - __METHOD__, - sprintf( - /* translators: %s: register_rest_field */ - __( 'Please use %s to add new schema properties.' ), - 'register_rest_field' - ), - '5.4.0' - ); - } - - $this->schema = $schema; - - return $this->add_additional_fields_schema( $this->schema ); - } - /** * Retrieves the query params for the posts collection. * From deab94cd68afb078843615a524bfba6ff897b962 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 29 Aug 2024 13:24:29 +0200 Subject: [PATCH 30/52] Update the logic for the standard post format in gutenberg_filter_query_loop_block_query_vars_post_format --- lib/compat/wordpress-6.7/blocks.php | 49 ++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 1ab238c4e1c782..dc47f25850801e 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -54,21 +54,46 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { if ( isset( $block->context['query']['format'] ) && ! empty( $block->context['query']['format'] ) ) { - // Add the required "post-format-" prefix to each format. - $formats = array_map( - function ( $format ) { - return 'post-format-' . $format; - }, - $block->context['query']['format'] - ); - $query['tax_query'][] = array( - array( + $formats = $block->context['query']['format']; + $tax_query = array( 'relation' => 'OR' ); + + // The default post format, 'standard', is not saved in the database. + // If 'standard' is part of the request, the query needs to exclude the formats. + if ( in_array( 'standard', $formats, true ) ) { + $tax_query[] = array( 'taxonomy' => 'post_format', 'field' => 'slug', - 'terms' => $formats, - ), - ); + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ); + // Remove the standard format: + $formats = array_diff( $formats, array( 'standard' ) ); + } + + // Add any remaining formats to the tax query. + if ( ! empty( $formats ) ) { + // Add the post-format- prefix. + $terms = array_map( + static function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); + + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + 'operator' => 'IN', + ); + } + + // This condition is intended to prevent $tax_query from being added to $query + // if it only contains the relation. + if ( count( $tax_query ) > 1 ) { + $query['tax_query'][] = $tax_query; + } } return $query; } From c4d9e1da051116b99f31369ae9eed1000e5f339e Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 30 Aug 2024 11:50:22 +0200 Subject: [PATCH 31/52] Replace SelectControl with FormTokenField --- .../inspector-controls/format-controls.js | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js index 6acdcae65167e7..320dccbc377786 100644 --- a/packages/block-library/src/query/edit/inspector-controls/format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -1,26 +1,27 @@ /** * WordPress dependencies */ -import { SelectControl } from '@wordpress/components'; +import { FormTokenField } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; // All WP post formats, sorted alphabetically by translated name. +// Value is the post format slug. Label is the name. const POST_FORMATS = [ - { slug: 'aside', name: __( 'Aside' ) }, - { slug: 'audio', name: __( 'Audio' ) }, - { slug: 'chat', name: __( 'Chat' ) }, - { slug: 'gallery', name: __( 'Gallery' ) }, - { slug: 'image', name: __( 'Image' ) }, - { slug: 'link', name: __( 'Link' ) }, - { slug: 'quote', name: __( 'Quote' ) }, - { slug: 'standard', name: __( 'Standard' ) }, - { slug: 'status', name: __( 'Status' ) }, - { slug: 'video', name: __( 'Video' ) }, + { value: 'aside', label: __( 'Aside' ) }, + { value: 'audio', label: __( 'Audio' ) }, + { value: 'chat', label: __( 'Chat' ) }, + { value: 'gallery', label: __( 'Gallery' ) }, + { value: 'image', label: __( 'Image' ) }, + { value: 'link', label: __( 'Link' ) }, + { value: 'quote', label: __( 'Quote' ) }, + { value: 'standard', label: __( 'Standard' ) }, + { value: 'status', label: __( 'Status' ) }, + { value: 'video', label: __( 'Video' ) }, ].sort( ( a, b ) => { - const normalizedA = a.name.toUpperCase(); - const normalizedB = b.name.toUpperCase(); + const normalizedA = a.label.toUpperCase(); + const normalizedB = b.label.toUpperCase(); if ( normalizedA < normalizedB ) { return -1; @@ -32,7 +33,7 @@ const POST_FORMATS = [ } ); export default function FormatControls( { onChange, query } ) { - const { format } = query; + const format = query.format.map( ( item ) => item.toLowerCase() ); const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); @@ -42,27 +43,34 @@ export default function FormatControls( { onChange, query } ) { }, [] ); const formats = POST_FORMATS.filter( ( item ) => - supportedFormats.includes( item.slug ) + supportedFormats.includes( item.value ) ); - const formatOptions = [ - ...( formats || [] ).map( ( item ) => ( { - value: item.slug, - label: item.name, - } ) ), - ]; + const suggestions = formats + .filter( ( item ) => ! format.includes( item.value ) ) + .map( ( item ) => item.label ); return ( - { - onChange( { format: value } ); + const normalizedValue = value.map( ( item ) => + item.toLowerCase() + ); + // Only allow the post formats that are in the 'formats' constant. + const values = normalizedValue.filter( ( item ) => + formats.some( + ( supportedFormat ) => supportedFormat.value === item + ) + ); + onChange( { format: values } ); } } + __experimentalShowHowTo={ false } + __experimentalExpandOnFocus + __nextHasNoMarginBottom + __next40pxDefaultSize /> ); } From e2b82d35745b445e5285371cc375b2504c562c62 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Sat, 31 Aug 2024 11:55:45 +0200 Subject: [PATCH 32/52] Set the constant format to an empty array when format is not queried --- .../src/query/edit/inspector-controls/format-controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js index 320dccbc377786..04979490ad3e1c 100644 --- a/packages/block-library/src/query/edit/inspector-controls/format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -33,7 +33,7 @@ const POST_FORMATS = [ } ); export default function FormatControls( { onChange, query } ) { - const format = query.format.map( ( item ) => item.toLowerCase() ); + const format = query?.format?.map( ( item ) => item.toLowerCase() ) || []; const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); From 5739bf3f65cf493421ad05c80c1122e599c2d4f0 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Sun, 1 Sep 2024 03:36:11 +0200 Subject: [PATCH 33/52] Hide the control and reset the format value if the post type does not support post formats. --- .../query/edit/inspector-controls/index.js | 35 ++++++++++++++----- packages/block-library/src/query/utils.js | 17 ++++++++- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 973686e6f3d176..14a5c2957c426b 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -65,7 +65,11 @@ export default function QueryInspectorControls( props ) { } = query; const allowedControls = useAllowedControls( attributes ); const [ showSticky, setShowSticky ] = useState( postType === 'post' ); - const { postTypesTaxonomiesMap, postTypesSelectOptions } = usePostTypes(); + const { + postTypesTaxonomiesMap, + postTypesSelectOptions, + postFormatSupportMap, + } = usePostTypes(); const taxonomies = useTaxonomies( postType ); const isPostTypeHierarchical = useIsPostTypeHierarchical( postType ); useEffect( () => { @@ -94,6 +98,13 @@ export default function QueryInspectorControls( props ) { } // We need to reset `parents` because they are tied to each post type. updateQuery.parents = []; + // Post types can register post format support with `add_post_type_support`. + // But we need to reset the `format` property when switching to post types + // that do not support post formats. + const hasFormatSupport = postFormatSupportMap[ newValue ]; + if ( ! hasFormatSupport ) { + updateQuery.format = []; + } setQuery( updateQuery ); }; const [ querySearch, setQuerySearch ] = useState( query.search ); @@ -141,14 +152,20 @@ export default function QueryInspectorControls( props ) { // Check if post formats are supported. // If there are no supported formats, getThemeSupports still includes the default 'standard' format, // and in this case the control should not be shown since the user has no other formats to choose from. - const showFormatControl = useSelect( ( select ) => { - const themeSupports = select( coreStore ).getThemeSupports(); - return ( - themeSupports.formats && - themeSupports.formats.length > 0 && - themeSupports.formats.some( ( type ) => type !== 'standard' ) - ); - }, [] ); + const showFormatControl = useSelect( + ( select ) => { + const themeSupports = select( coreStore ).getThemeSupports(); + const postTypeHasFormatSupport = postFormatSupportMap[ postType ]; + return ( + isControlAllowed( allowedControls, 'format' ) && // First check if the control is allowed + postTypeHasFormatSupport && + themeSupports.formats && + themeSupports.formats.length > 0 && + themeSupports.formats.some( ( type ) => type !== 'standard' ) + ); + }, + [ allowedControls, postFormatSupportMap, postType ] + ); const showFiltersPanel = showTaxControl || diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index 2e9412b1683cb0..04e147ffcf5dc9 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -94,6 +94,7 @@ export const mapToIHasNameAndId = ( entities, path ) => { * Returns a helper object that contains: * 1. An `options` object from the available post types, to be passed to a `SelectControl`. * 2. A helper map with available taxonomies per post type. + * 3. A helper map with post format support per post type. * * @return {Object} The helper object related to post types. */ @@ -124,7 +125,21 @@ export const usePostTypes = () => { } ) ), [ postTypes ] ); - return { postTypesTaxonomiesMap, postTypesSelectOptions }; + const postFormatSupportMap = useMemo( () => { + if ( ! postTypes?.length ) { + return {}; + } + return postTypes.reduce( ( accumulator, type ) => { + accumulator[ type.slug ] = + type.supports?.[ 'post-formats' ] || false; + return accumulator; + }, {} ); + }, [ postTypes ] ); + return { + postTypesTaxonomiesMap, + postTypesSelectOptions, + postFormatSupportMap, + }; }; /** From 90fbdd923e63e4cc71a15ece8bd2adcbf3add513 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Sun, 1 Sep 2024 03:39:22 +0200 Subject: [PATCH 34/52] Rename postFormatSupportMap to postTypeFormatSupportMap --- .../src/query/edit/inspector-controls/index.js | 9 +++++---- packages/block-library/src/query/utils.js | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 14a5c2957c426b..c28e4e03765e24 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -68,7 +68,7 @@ export default function QueryInspectorControls( props ) { const { postTypesTaxonomiesMap, postTypesSelectOptions, - postFormatSupportMap, + postTypeFormatSupportMap, } = usePostTypes(); const taxonomies = useTaxonomies( postType ); const isPostTypeHierarchical = useIsPostTypeHierarchical( postType ); @@ -101,7 +101,7 @@ export default function QueryInspectorControls( props ) { // Post types can register post format support with `add_post_type_support`. // But we need to reset the `format` property when switching to post types // that do not support post formats. - const hasFormatSupport = postFormatSupportMap[ newValue ]; + const hasFormatSupport = postTypeFormatSupportMap[ newValue ]; if ( ! hasFormatSupport ) { updateQuery.format = []; } @@ -155,7 +155,8 @@ export default function QueryInspectorControls( props ) { const showFormatControl = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); - const postTypeHasFormatSupport = postFormatSupportMap[ postType ]; + const postTypeHasFormatSupport = + postTypeFormatSupportMap[ postType ]; return ( isControlAllowed( allowedControls, 'format' ) && // First check if the control is allowed postTypeHasFormatSupport && @@ -164,7 +165,7 @@ export default function QueryInspectorControls( props ) { themeSupports.formats.some( ( type ) => type !== 'standard' ) ); }, - [ allowedControls, postFormatSupportMap, postType ] + [ allowedControls, postTypeFormatSupportMap, postType ] ); const showFiltersPanel = diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index 04e147ffcf5dc9..68da2573bab0f6 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -125,7 +125,7 @@ export const usePostTypes = () => { } ) ), [ postTypes ] ); - const postFormatSupportMap = useMemo( () => { + const postTypeFormatSupportMap = useMemo( () => { if ( ! postTypes?.length ) { return {}; } @@ -138,7 +138,7 @@ export const usePostTypes = () => { return { postTypesTaxonomiesMap, postTypesSelectOptions, - postFormatSupportMap, + postTypeFormatSupportMap, }; }; From a1017efd72b5c488fe469825f0ab8c32a1d5889c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Sep 2024 11:53:18 +0400 Subject: [PATCH 35/52] Fix REST API controller override condition --- lib/compat/wordpress-6.7/post-formats.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.7/post-formats.php b/lib/compat/wordpress-6.7/post-formats.php index 22790640b37865..0e7c6a253abfea 100644 --- a/lib/compat/wordpress-6.7/post-formats.php +++ b/lib/compat/wordpress-6.7/post-formats.php @@ -9,11 +9,15 @@ * Registers the extension of the WP_REST_Posts_Controller class, * to add support for post formats. */ -function gutenberg_post_format_rest_posts_controller( $args, $post_type ) { - if ( post_type_supports( $post_type, 'post-formats' ) ) { +function gutenberg_post_format_rest_posts_controller( $args ) { + /** + * This hook runs before support values are available via `post_type_supports`. + * * Check registration arguments for REST API controller override. + */ + if ( ! empty( $args['supports'] ) && in_array( 'post-formats', $args['supports'], true ) ) { $args['rest_controller_class'] = 'Gutenberg_REST_Posts_Controller_6_7'; } return $args; } -add_filter( 'register_post_type_args', 'gutenberg_post_format_rest_posts_controller', 10, 2 ); +add_filter( 'register_post_type_args', 'gutenberg_post_format_rest_posts_controller', 10 ); From 601644fed09a7ccbef7086006cc8c9975af28043 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Sep 2024 12:16:22 +0400 Subject: [PATCH 36/52] Don't set the default post format in the schema It will be used as the default argument if none is supplied. --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 5096ecb52161ed..8aa44672ebaeb6 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -485,7 +485,6 @@ public function get_collection_params() { if ( post_type_supports( $this->post_type, 'post-formats' ) ) { $query_params['format'] = array( - 'default' => 'standard', 'description' => __( 'Limit result set to items assigned one or more formats.' ), 'type' => 'array', 'items' => array( From 8eab071a5ae68e6ea7fb43bf13834b10282db7b7 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Sep 2024 12:17:41 +0400 Subject: [PATCH 37/52] Satisfy PHP linter --- .../class-gutenberg-rest-posts-controller-6-7.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 8aa44672ebaeb6..276f60e4c74e13 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -13,6 +13,13 @@ * @see WP_REST_Controller */ class Gutenberg_REST_Posts_Controller_6_7 extends WP_REST_Posts_Controller { + /** + * Post type. + * + * @since 4.7.0 + * @var string + */ + protected $post_type; /** * Retrieves a collection of posts. From 8121870e200679abe1039b561db5b17043f623c4 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Sep 2024 12:59:35 +0400 Subject: [PATCH 38/52] Update the 'FormatsControl' to match suggestion labels to their static values --- .../inspector-controls/format-controls.js | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js index 04979490ad3e1c..42d18cf0809e03 100644 --- a/packages/block-library/src/query/edit/inspector-controls/format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -32,9 +32,19 @@ const POST_FORMATS = [ return 0; } ); -export default function FormatControls( { onChange, query } ) { - const format = query?.format?.map( ( item ) => item.toLowerCase() ) || []; +// A helper function to convert translatable post format names into their static values. +function formatNamesToValues( names, formats ) { + return names + .map( ( name ) => { + return formats.find( + ( item ) => + item.label.toLocaleLowerCase() === name.toLocaleLowerCase() + )?.value; + } ) + .filter( Boolean ); +} +export default function FormatControls( { onChange, query: { format } } ) { const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); return { @@ -46,6 +56,12 @@ export default function FormatControls( { onChange, query } ) { supportedFormats.includes( item.value ) ); + const values = format + .map( + ( name ) => formats.find( ( item ) => item.value === name )?.label + ) + .filter( Boolean ); + const suggestions = formats .filter( ( item ) => ! format.includes( item.value ) ) .map( ( item ) => item.label ); @@ -53,19 +69,12 @@ export default function FormatControls( { onChange, query } ) { return ( { - const normalizedValue = value.map( ( item ) => - item.toLowerCase() - ); - // Only allow the post formats that are in the 'formats' constant. - const values = normalizedValue.filter( ( item ) => - formats.some( - ( supportedFormat ) => supportedFormat.value === item - ) - ); - onChange( { format: values } ); + onChange={ ( newValues ) => { + onChange( { + format: formatNamesToValues( newValues, formats ), + } ); } } __experimentalShowHowTo={ false } __experimentalExpandOnFocus From 1b291ed7094271a71d7cbe5b89e6f0748af5346e Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Sep 2024 13:15:33 +0400 Subject: [PATCH 39/52] Refactor showFormatControl logic --- .../query/edit/inspector-controls/index.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index c28e4e03765e24..0bf44f4d1d069f 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -149,23 +149,28 @@ export default function QueryInspectorControls( props ) { isControlAllowed( allowedControls, 'parents' ) && isPostTypeHierarchical; - // Check if post formats are supported. - // If there are no supported formats, getThemeSupports still includes the default 'standard' format, - // and in this case the control should not be shown since the user has no other formats to choose from. + const postTypeHasFormatSupport = postTypeFormatSupportMap[ postType ]; const showFormatControl = useSelect( ( select ) => { + // Check if the post type supports post formats and if the control is allowed. + if ( + ! postTypeHasFormatSupport || + ! isControlAllowed( allowedControls, 'format' ) + ) { + return false; + } + const themeSupports = select( coreStore ).getThemeSupports(); - const postTypeHasFormatSupport = - postTypeFormatSupportMap[ postType ]; + + // If there are no supported formats, getThemeSupports still includes the default 'standard' format, + // and in this case the control should not be shown since the user has no other formats to choose from. return ( - isControlAllowed( allowedControls, 'format' ) && // First check if the control is allowed - postTypeHasFormatSupport && themeSupports.formats && themeSupports.formats.length > 0 && themeSupports.formats.some( ( type ) => type !== 'standard' ) ); }, - [ allowedControls, postTypeFormatSupportMap, postType ] + [ allowedControls, postTypeHasFormatSupport ] ); const showFiltersPanel = From e798f3bf67a45644296f325eea44c56adafec7c7 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 5 Sep 2024 06:31:27 +0200 Subject: [PATCH 40/52] gutenberg_filter_query_loop_block_query_vars_post_format: Switch to using an early return with empty(). --- lib/compat/wordpress-6.7/blocks.php | 77 +++++++++++++++-------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index dc47f25850801e..c20237cd7d75c8 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -52,49 +52,52 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file * @return array The filtered query vars. */ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { - if ( isset( $block->context['query']['format'] ) && - ! empty( $block->context['query']['format'] ) ) { + // Return early if there is no format: + if ( empty( $block->context['query']['format'] ) ) { + return $query; + } - $formats = $block->context['query']['format']; - $tax_query = array( 'relation' => 'OR' ); + $formats = $block->context['query']['format']; + $tax_query = array( 'relation' => 'OR' ); - // The default post format, 'standard', is not saved in the database. - // If 'standard' is part of the request, the query needs to exclude the formats. - if ( in_array( 'standard', $formats, true ) ) { - $tax_query[] = array( - 'taxonomy' => 'post_format', - 'field' => 'slug', - 'terms' => array(), - 'operator' => 'NOT EXISTS', - ); - // Remove the standard format: - $formats = array_diff( $formats, array( 'standard' ) ); - } + // The default post format, 'standard', is not stored in the database. + // If 'standard' is part of the request, the query needs to exclude all post items that + // has a format assigned. + if ( in_array( 'standard', $formats, true ) ) { + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => array(), + 'operator' => 'NOT EXISTS', + ); + // Remove the standard format, since it cannot be queried. + $formats = array_diff( $formats, array( 'standard' ) ); + } - // Add any remaining formats to the tax query. - if ( ! empty( $formats ) ) { - // Add the post-format- prefix. - $terms = array_map( - static function ( $format ) { - return 'post-format-' . $format; - }, - $formats - ); + // Add any remaining formats to the tax query. + if ( ! empty( $formats ) ) { + // Add the post-format- prefix. + $terms = array_map( + static function ( $format ) { + return 'post-format-' . $format; + }, + $formats + ); - $tax_query[] = array( - 'taxonomy' => 'post_format', - 'field' => 'slug', - 'terms' => $terms, - 'operator' => 'IN', - ); - } + $tax_query[] = array( + 'taxonomy' => 'post_format', + 'field' => 'slug', + 'terms' => $terms, + 'operator' => 'IN', + ); + } - // This condition is intended to prevent $tax_query from being added to $query - // if it only contains the relation. - if ( count( $tax_query ) > 1 ) { - $query['tax_query'][] = $tax_query; - } + // This condition is intended to prevent $tax_query from being added to $query + // if it only contains the relation. + if ( count( $tax_query ) > 1 ) { + $query['tax_query'][] = $tax_query; } + return $query; } add_filter( 'query_loop_block_query_vars', 'gutenberg_filter_query_loop_block_query_vars_post_format', 10, 2 ); From 664b745246bb9ea8b76ea716bcb6dc0609e61499 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 5 Sep 2024 06:41:07 +0200 Subject: [PATCH 41/52] Update lib/compat/wordpress-6.7/post-formats.php --- lib/compat/wordpress-6.7/post-formats.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/post-formats.php b/lib/compat/wordpress-6.7/post-formats.php index 0e7c6a253abfea..9ed880b5b88521 100644 --- a/lib/compat/wordpress-6.7/post-formats.php +++ b/lib/compat/wordpress-6.7/post-formats.php @@ -12,7 +12,7 @@ function gutenberg_post_format_rest_posts_controller( $args ) { /** * This hook runs before support values are available via `post_type_supports`. - * * Check registration arguments for REST API controller override. + * Check registration arguments for REST API controller override. */ if ( ! empty( $args['supports'] ) && in_array( 'post-formats', $args['supports'], true ) ) { $args['rest_controller_class'] = 'Gutenberg_REST_Posts_Controller_6_7'; From 812c372081e94c6fece0205abbac5e733a5ca1d5 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Thu, 5 Sep 2024 07:20:38 +0200 Subject: [PATCH 42/52] End the comment with a full stop. --- lib/compat/wordpress-6.7/blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index c20237cd7d75c8..7c38d322c18b1d 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -52,7 +52,7 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file * @return array The filtered query vars. */ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { - // Return early if there is no format: + // Return early if there is no format. if ( empty( $block->context['query']['format'] ) ) { return $query; } From ce0583b85e806d9953fef6761c69284ec203ad2a Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Mon, 9 Sep 2024 07:27:35 +0200 Subject: [PATCH 43/52] Update doc blocks and replace aray_diff with unset and array_search. --- lib/compat/wordpress-6.7/blocks.php | 4 ++-- .../class-gutenberg-rest-posts-controller-6-7.php | 11 ++++++----- lib/compat/wordpress-6.7/post-formats.php | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 7c38d322c18b1d..010051fcd3da36 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -62,7 +62,7 @@ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $bloc // The default post format, 'standard', is not stored in the database. // If 'standard' is part of the request, the query needs to exclude all post items that - // has a format assigned. + // have a format assigned. if ( in_array( 'standard', $formats, true ) ) { $tax_query[] = array( 'taxonomy' => 'post_format', @@ -71,7 +71,7 @@ function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $bloc 'operator' => 'NOT EXISTS', ); // Remove the standard format, since it cannot be queried. - $formats = array_diff( $formats, array( 'standard' ) ); + unset( $formats[ array_search( 'standard', $formats, true ) ] ); } // Add any remaining formats to the tax query. diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 276f60e4c74e13..a5d43f9f781a95 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -163,8 +163,9 @@ public function get_items( $request ) { $tax_query = array( 'relation' => 'OR' ); - // The default post format, 'standard', is not saved in the database. - // If 'standard' is part of the request, the query needs to exclude the formats. + // The default post format, 'standard', is not stored in the database. + // If 'standard' is part of the request, the query needs to exclude all post items that + // have a format assigned. if ( in_array( 'standard', $formats, true ) ) { $tax_query[] = array( 'taxonomy' => 'post_format', @@ -172,8 +173,8 @@ public function get_items( $request ) { 'terms' => array(), 'operator' => 'NOT EXISTS', ); - // Remove the standard format: - $formats = array_diff( $formats, array( 'standard' ) ); + // Remove the standard format, since it cannot be queried. + unset( $formats[ array_search( 'standard', $formats, true ) ] ); } // Add any remaining formats to the tax query. @@ -492,7 +493,7 @@ public function get_collection_params() { if ( post_type_supports( $this->post_type, 'post-formats' ) ) { $query_params['format'] = array( - 'description' => __( 'Limit result set to items assigned one or more formats.' ), + 'description' => __( 'Limit result set to items assigned one or more given formats.' ), 'type' => 'array', 'items' => array( 'enum' => array_values( get_post_format_slugs() ), diff --git a/lib/compat/wordpress-6.7/post-formats.php b/lib/compat/wordpress-6.7/post-formats.php index 9ed880b5b88521..d3de5b83957e29 100644 --- a/lib/compat/wordpress-6.7/post-formats.php +++ b/lib/compat/wordpress-6.7/post-formats.php @@ -1,6 +1,7 @@ Date: Mon, 9 Sep 2024 08:40:18 +0200 Subject: [PATCH 44/52] Update doc block --- lib/compat/wordpress-6.7/blocks.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 010051fcd3da36..09e7f91b7ca898 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -45,7 +45,9 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_allow_variations_php_file', 10, 2 ); /** - * Filter the query vars (tax_query) for the Query Loop block to support post formats. + * Adds post format query vars to the query loop block's WP_Query when the block's attributes call for them. + * + * @see query_loop_block_query_vars * * @param array $query The query vars. * @param WP_Block $block Block instance. From 9cbe2ad76a1cfdab3b056eb1878b8bc0a5d9663d Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Mon, 9 Sep 2024 13:38:45 +0200 Subject: [PATCH 45/52] Add backport changelog --- backport-changelog/6.7/7314.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.7/7314.md diff --git a/backport-changelog/6.7/7314.md b/backport-changelog/6.7/7314.md new file mode 100644 index 00000000000000..fab3428e960850 --- /dev/null +++ b/backport-changelog/6.7/7314.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7270 + +* https://github.com/WordPress/gutenberg/pull/64167 From 4b46ea2c813f09a8895e31257715917af7c2d1f5 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Mon, 9 Sep 2024 13:50:35 +0200 Subject: [PATCH 46/52] Oops: Fix the copy-paste typo in the changelog entry. --- backport-changelog/6.7/7314.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backport-changelog/6.7/7314.md b/backport-changelog/6.7/7314.md index fab3428e960850..7d75cdff0f9075 100644 --- a/backport-changelog/6.7/7314.md +++ b/backport-changelog/6.7/7314.md @@ -1,3 +1,3 @@ -https://github.com/WordPress/wordpress-develop/pull/7270 +https://github.com/WordPress/wordpress-develop/pull/7314 * https://github.com/WordPress/gutenberg/pull/64167 From d98fbaacd10e6ffee996bd7813ca8aad62f333c8 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 10 Sep 2024 03:09:42 +0200 Subject: [PATCH 47/52] Update lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php Co-authored-by: Timothy Jacobs --- .../wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index a5d43f9f781a95..916730138a42c3 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -495,6 +495,7 @@ public function get_collection_params() { $query_params['format'] = array( 'description' => __( 'Limit result set to items assigned one or more given formats.' ), 'type' => 'array', + 'uniqueItems' => true, 'items' => array( 'enum' => array_values( get_post_format_slugs() ), 'type' => 'string', From 9514a05b60bd0bcf59caadd647586b70c54bdd38 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 10 Sep 2024 04:22:22 +0200 Subject: [PATCH 48/52] Rename gutenberg_filter_query_loop_block_query_vars_post_format to gutenberg_add_format_query_vars_to_query_loop_block --- lib/compat/wordpress-6.7/blocks.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 09e7f91b7ca898..1621dbd3209ede 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -53,7 +53,7 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file * @param WP_Block $block Block instance. * @return array The filtered query vars. */ -function gutenberg_filter_query_loop_block_query_vars_post_format( $query, $block ) { +function gutenberg_add_format_query_vars_to_query_loop_block( $query, $block ) { // Return early if there is no format. if ( empty( $block->context['query']['format'] ) ) { return $query; @@ -102,4 +102,4 @@ static function ( $format ) { return $query; } -add_filter( 'query_loop_block_query_vars', 'gutenberg_filter_query_loop_block_query_vars_post_format', 10, 2 ); +add_filter( 'query_loop_block_query_vars', 'gutenberg_add_format_query_vars_to_query_loop_block', 10, 2 ); From a702271cc110f0928167438787744310eb843fc3 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Tue, 10 Sep 2024 06:04:48 +0200 Subject: [PATCH 49/52] Gutenberg_REST_Posts_Controller_6_7 get_items: remove the array conversion $request['format'] does not need to be converted into an array, any array conversion needed is handled by the schema. This commit removes the array conversion from get_items. --- .../class-gutenberg-rest-posts-controller-6-7.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php index 916730138a42c3..c7de4371c94f56 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php @@ -157,10 +157,7 @@ public function get_items( $request ) { $args = $this->prepare_tax_query( $args, $request ); if ( ! empty( $request['format'] ) ) { - // If format is not an array, convert it to an array so that the - // required prefix can be added to all items. - $formats = is_array( $request['format'] ) ? $request['format'] : array( $request['format'] ); - + $formats = $request['format']; $tax_query = array( 'relation' => 'OR' ); // The default post format, 'standard', is not stored in the database. From 4df463976501947f76f0302110f60fae1ecbce55 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Wed, 11 Sep 2024 06:04:46 +0200 Subject: [PATCH 50/52] Add two checks that help fail gracefully when errors occur In gutenberg_add_format_query_vars_to_query_loop_block: return early if the format attribute is not an array. In FormatControls: Prevent a JS error that occurs if the format is not an array. - The purpose is to prevent a user error from breaking the editor, for example if the format is invalid because the user has edited the block markup incorrectly. A check is implemented that preserves the value if for format is an array or string, and uses an empty array if it is neither. --- lib/compat/wordpress-6.7/blocks.php | 4 ++-- .../edit/inspector-controls/format-controls.js | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index 1621dbd3209ede..a50d33267d1d99 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -54,8 +54,8 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file * @return array The filtered query vars. */ function gutenberg_add_format_query_vars_to_query_loop_block( $query, $block ) { - // Return early if there is no format. - if ( empty( $block->context['query']['format'] ) ) { + // Return early if there is no format or if the format is not an array. + if ( empty( $block->context['query']['format'] ) || ! is_array( $block->context['query']['format'] ) ) { return $query; } diff --git a/packages/block-library/src/query/edit/inspector-controls/format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js index 42d18cf0809e03..e65b54b418baca 100644 --- a/packages/block-library/src/query/edit/inspector-controls/format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -45,6 +45,20 @@ function formatNamesToValues( names, formats ) { } export default function FormatControls( { onChange, query: { format } } ) { + // 'format' is expected to be an array. If it is not an array, for example + // if a user has manually entered an invalid value in the block markup, + // convert it to an array to prevent JavaScript errors. + let formatArray = format; + if ( Array.isArray( format ) ) { + formatArray = format; + } else if ( typeof format === 'string' ) { + // If the format is a string, preserve it as an array. + formatArray = [ format ]; + } else { + // If the format is not a string or an array, set it to an empty array. + formatArray = []; + } + const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); return { @@ -56,7 +70,7 @@ export default function FormatControls( { onChange, query: { format } } ) { supportedFormats.includes( item.value ) ); - const values = format + const values = formatArray .map( ( name ) => formats.find( ( item ) => item.value === name )?.label ) From 25ceeec27ebb2b80b29148e1b18267c4fc5cb905 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Wed, 11 Sep 2024 08:48:41 +0200 Subject: [PATCH 51/52] Update the docBlock for gutenberg_add_format_query_vars_to_query_loop_block Add apostrophes around the hook name in the docBlock of gutenberg_add_format_query_vars_to_query_loop_block. --- lib/compat/wordpress-6.7/blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php index a50d33267d1d99..6b9526f8056fd3 100644 --- a/lib/compat/wordpress-6.7/blocks.php +++ b/lib/compat/wordpress-6.7/blocks.php @@ -47,7 +47,7 @@ function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file /** * Adds post format query vars to the query loop block's WP_Query when the block's attributes call for them. * - * @see query_loop_block_query_vars + * @see 'query_loop_block_query_vars' * * @param array $query The query vars. * @param WP_Block $block Block instance. From a85a9196612b5a77bfa2dc5b2512b2c19008f3ce Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Wed, 11 Sep 2024 11:02:20 +0200 Subject: [PATCH 52/52] FormatControls: simplify array conversion --- .../edit/inspector-controls/format-controls.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/format-controls.js b/packages/block-library/src/query/edit/inspector-controls/format-controls.js index e65b54b418baca..d26fd9d81ce6f7 100644 --- a/packages/block-library/src/query/edit/inspector-controls/format-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/format-controls.js @@ -48,16 +48,7 @@ export default function FormatControls( { onChange, query: { format } } ) { // 'format' is expected to be an array. If it is not an array, for example // if a user has manually entered an invalid value in the block markup, // convert it to an array to prevent JavaScript errors. - let formatArray = format; - if ( Array.isArray( format ) ) { - formatArray = format; - } else if ( typeof format === 'string' ) { - // If the format is a string, preserve it as an array. - formatArray = [ format ]; - } else { - // If the format is not a string or an array, set it to an empty array. - formatArray = []; - } + const normalizedFormats = Array.isArray( format ) ? format : [ format ]; const { supportedFormats } = useSelect( ( select ) => { const themeSupports = select( coreStore ).getThemeSupports(); @@ -70,7 +61,7 @@ export default function FormatControls( { onChange, query: { format } } ) { supportedFormats.includes( item.value ) ); - const values = formatArray + const values = normalizedFormats .map( ( name ) => formats.find( ( item ) => item.value === name )?.label )