Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query block enhanced pagination: Detect inner plugin blocks during render #55714

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ecd0a3d
Flag inner plugin blocks inside query loop
DAreRodz Oct 27, 2023
95c610b
Improve PHP logic a little
DAreRodz Oct 27, 2023
d6c4e4b
Only disallow plugin blocks and post content
DAreRodz Oct 27, 2023
694cd76
Get rid of global variables
DAreRodz Oct 30, 2023
f219c47
Fix returned content from render callback
DAreRodz Oct 30, 2023
5d148fc
Handle composed query stacks
DAreRodz Oct 30, 2023
3bfcaac
Disable navigation on the browser
DAreRodz Oct 30, 2023
aeff985
Replace `count` with `empty`
DAreRodz Oct 30, 2023
a40cd92
Add PHPdocs and improve var naming
DAreRodz Oct 30, 2023
85e3917
Lint PHP
DAreRodz Oct 30, 2023
cecee32
Clarify docs a little
DAreRodz Oct 30, 2023
52ffb16
Move the disable check before preventDefault
DAreRodz Oct 30, 2023
b9c4732
Restore previous navigate logic
DAreRodz Oct 30, 2023
88cea76
Set filter priorities back to 10
DAreRodz Oct 30, 2023
93b021e
Basic inspector warnings
luisherranz Oct 30, 2023
007ede9
Make render query filter static
DAreRodz Oct 30, 2023
5f09ab2
Add stable modal logic
luisherranz Oct 30, 2023
e0b78e6
Merge remote-tracking branch 'origin/try/query-block-detect-inner-plu…
luisherranz Oct 30, 2023
27f3b29
Switch back to ToggleControl
luisherranz Oct 30, 2023
3db4310
Auto remove filter when query stack is empty
DAreRodz Oct 31, 2023
07594d9
Add first unit tests
DAreRodz Oct 31, 2023
855663b
Switch to inverse control
luisherranz Oct 31, 2023
4f7d82d
Add test case for nested queries
DAreRodz Oct 31, 2023
b9b19e0
Prevent passing `null` to the Tag Processr
DAreRodz Oct 31, 2023
e04f34f
Merge remote-tracking branch 'origin/try/query-block-detect-inner-plu…
luisherranz Oct 31, 2023
c7534a8
Get rid of explicit auto mode and notices
luisherranz Oct 31, 2023
062e795
Test directives in the Pagination Previous block
DAreRodz Oct 31, 2023
17057fd
Minor typos and improvements
luisherranz Oct 31, 2023
1b36f2e
Merge remote-tracking branch 'origin/try/query-block-detect-inner-plu…
luisherranz Oct 31, 2023
d407bed
Improve modal texts
luisherranz Oct 31, 2023
221b70b
Fix WPCS
luisherranz Nov 1, 2023
f05ffe9
Reorder teardowns
luisherranz Nov 1, 2023
14760cb
Reset the dirty flag after it's used
luisherranz Nov 1, 2023
59c0270
Prevent usage of post content block
luisherranz Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
wp_reset_postdata(); // Restore original Post Data.
}

if ( $enhanced_pagination ) {
if ( $enhanced_pagination && isset( $content ) ) {
$p = new WP_HTML_Tag_Processor( $content );
if ( $p->next_tag(
array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
);
}

if ( $enhanced_pagination ) {
if ( $enhanced_pagination && isset( $content ) ) {
$p = new WP_HTML_Tag_Processor( $content );
if ( $p->next_tag(
array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ import { useState, useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useUnsupportedBlockList } from '../utils';

const disableEnhancedPaginationDescription = __(
'You have added unsupported blocks. For the enhanced pagination to work, remove them, then re-enable "Enhanced pagination" in the Query Block settings.'
);
import { useUnsupportedBlocks } from '../utils';

const modalDescriptionId =
'wp-block-query-enhanced-pagination-modal__description';
Expand All @@ -27,37 +23,55 @@ export default function EnhancedPaginationModal( {
setAttributes,
} ) {
const [ isOpen, setOpen ] = useState( false );

const unsupported = useUnsupportedBlockList( clientId );
const { hasBlocksFromPlugins, hasPostContentBlock, hasUnsupportedBlocks } =
useUnsupportedBlocks( clientId );

useEffect( () => {
setOpen( !! unsupported.length && enhancedPagination );
}, [ unsupported.length, enhancedPagination, setOpen ] );
if ( enhancedPagination && hasUnsupportedBlocks ) {
setAttributes( { enhancedPagination: false } );
setOpen( true );
}
}, [ enhancedPagination, hasUnsupportedBlocks, setAttributes ] );

const closeModal = () => {
setOpen( false );
};

let notice = __(
'If you still want to prevent full page reloads, remove that block, then disable "Force page reload" again in the Query Block settings.'
);
if ( hasBlocksFromPlugins ) {
notice =
__(
'Currently, avoiding full page reloads is not possible when blocks from plugins are present inside the Query block.'
) +
' ' +
notice;
} else if ( hasPostContentBlock ) {
notice =
__(
'Currently, avoiding full page reloads is not possible when a Content block is present inside the Query block.'
) +
' ' +
notice;
}
luisherranz marked this conversation as resolved.
Show resolved Hide resolved

return (
isOpen && (
<Modal
title={ __( 'Enhanced pagination will be disabled' ) }
title={ __( 'Query block: Force page reload enabled' ) }
className="wp-block-query__enhanced-pagination-modal"
aria={ {
describedby: modalDescriptionId,
} }
role="alertdialog"
focusOnMount="firstElement"
isDismissible={ false }
shouldCloseOnEsc={ false }
shouldCloseOnClickOutside={ false }
onRequestClose={ closeModal }
>
<VStack alignment="right" spacing={ 5 }>
<span id={ modalDescriptionId }>
{ disableEnhancedPaginationDescription }
</span>
<Button
variant="primary"
onClick={ () => {
setAttributes( { enhancedPagination: false } );
} }
>
<span id={ modalDescriptionId }>{ notice }</span>
<Button variant="primary" onClick={ closeModal }>
{ __( 'OK' ) }
</Button>
</VStack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,45 @@
/**
* WordPress dependencies
*/
import { ToggleControl, Notice } from '@wordpress/components';
import { ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { BlockTitle } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { useUnsupportedBlockList } from '../../utils';
import { useUnsupportedBlocks } from '../../utils';

export default function EnhancedPaginationControl( {
enhancedPagination,
setAttributes,
clientId,
} ) {
const unsupported = useUnsupportedBlockList( clientId );
const { hasUnsupportedBlocks } = useUnsupportedBlocks( clientId );

let help = __( 'Browsing between pages requires a full page reload.' );
if ( enhancedPagination ) {
help = __(
"Browsing between pages won't require a full page reload, unless non-compatible blocks are detected."
);
} else if ( hasUnsupportedBlocks ) {
help = __(
"Force page reload can't be disabled because there are non-compatible blocks inside the Query block."
peterwilsoncc marked this conversation as resolved.
Show resolved Hide resolved
);
}

return (
<>
<ToggleControl
label={ __( 'Enhanced pagination' ) }
luisherranz marked this conversation as resolved.
Show resolved Hide resolved
help={ __(
'Browsing between pages won’t require a full page reload.'
) }
checked={ !! enhancedPagination }
disabled={ unsupported.length }
label={ __( 'Force page reload' ) }
help={ help }
checked={ ! enhancedPagination }
disabled={ hasUnsupportedBlocks }
onChange={ ( value ) => {
setAttributes( {
enhancedPagination: !! value,
enhancedPagination: ! value,
} );
} }
/>
{ !! unsupported.length && (
<Notice
status="warning"
isDismissible={ false }
className="wp-block-query__enhanced-pagination-notice"
>
{ __(
"Enhanced pagination doesn't support the following blocks:"
) }
<ul>
{ unsupported.map( ( id ) => (
<li key={ id }>
<BlockTitle clientId={ id } />
</li>
) ) }
</ul>
{ __(
'If you want to enable it, you have to remove all unsupported blocks first.'
) }
</Notice>
) }
</>
);
}
83 changes: 83 additions & 0 deletions packages/block-library/src/query/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,86 @@ function register_block_core_query() {
);
}
add_action( 'init', 'register_block_core_query' );

/**
* Traverse the tree of blocks looking for any plugin block (i.e., a block from
* an installed plugin) inside a Query block with the enhanced pagination
* enabled. If at least one is found, the enhanced pagination is effectively
* disabled to prevent any potential incompatibilities.
*
* @since 6.4.0
*
* @param array $parsed_block The block being rendered.
* @return string Returns the parsed block, unmodified.
*/
function block_core_query_disable_enhanced_pagination( $parsed_block ) {
static $enhanced_query_stack = array();
static $dirty_enhanced_queries = array();
static $render_query_callback = null;

$block_name = $parsed_block['blockName'];

if (
'core/query' === $block_name &&
isset( $parsed_block['attrs']['enhancedPagination'] ) &&
luisherranz marked this conversation as resolved.
Show resolved Hide resolved
true === $parsed_block['attrs']['enhancedPagination'] &&
isset( $parsed_block['attrs']['queryId'] )
) {
$enhanced_query_stack[] = $parsed_block['attrs']['queryId'];

if ( ! isset( $render_query_callback ) ) {
/**
* Filter that disables the enhanced pagination feature during block
* rendering when a plugin block has been found inside. It does so
* by adding an attribute called `data-wp-navigation-disabled` which
* is later handled by the front-end logic.
*
* @param string $content The block content.
* @param array $block The full block, including name and attributes.
* @return string Returns the modified output of the query block.
*/
$render_query_callback = static function ( $content, $block ) use ( &$enhanced_query_stack, &$dirty_enhanced_queries, &$render_query_callback ) {
$has_enhanced_pagination =
isset( $block['attrs']['enhancedPagination'] ) &&
true === $block['attrs']['enhancedPagination'] &&
isset( $block['attrs']['queryId'] );

if ( ! $has_enhanced_pagination ) {
return $content;
}

if ( isset( $dirty_enhanced_queries[ $block['attrs']['queryId'] ] ) ) {
$p = new WP_HTML_Tag_Processor( $content );
if ( $p->next_tag() ) {
$p->set_attribute( 'data-wp-navigation-disabled', 'true' );
}
$content = $p->get_updated_html();
$dirty_enhanced_queries[ $block['attrs']['queryId'] ] = null;
}

array_pop( $enhanced_query_stack );

if ( empty( $enhanced_query_stack ) ) {
remove_filter( 'render_block_core/query', $render_query_callback );
$render_query_callback = null;
}

return $content;
};

add_filter( 'render_block_core/query', $render_query_callback, 10, 2 );
}
} elseif (
! empty( $enhanced_query_stack ) &&
isset( $block_name ) &&
( ! str_starts_with( $block_name, 'core/' ) || 'core/post-content' === $block_name )
) {
foreach ( $enhanced_query_stack as $query_id ) {
$dirty_enhanced_queries[ $query_id ] = true;
}
}

return $parsed_block;
}

add_filter( 'render_block_data', 'block_core_query_disable_enhanced_pagination', 10, 1 );
38 changes: 26 additions & 12 deletions packages/block-library/src/query/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,29 +346,43 @@ export const usePatterns = ( clientId, name ) => {
};

/**
* Hook that returns a list of unsupported blocks inside the Query Loop with the
* given `clientId`.
* The object returned by useUnsupportedBlocks with info about the type of
* unsupported blocks present inside the Query block.
*
* @typedef {Object} UnsupportedBlocksInfo
* @property {boolean} hasBlocksFromPlugins True if blocks from plugins are present.
* @property {boolean} hasPostContentBlock True if a 'core/post-content' block is present.
* @property {boolean} hasUnsupportedBlocks True if there are any unsupported blocks.
*/
luisherranz marked this conversation as resolved.
Show resolved Hide resolved

/**
* Hook that returns an object with information about the unsupported blocks
* present inside a Query Loop with the given `clientId`. The returned object
* contains props that are true when a certain type of unsupported block is
* present.
*
* @param {string} clientId The block's client ID.
* @return {string[]} List of block titles.
* @return {UnsupportedBlocksInfo} The object containing the information.
*/
export const useUnsupportedBlockList = ( clientId ) => {
export const useUnsupportedBlocks = ( clientId ) => {
return useSelect(
( select ) => {
const { getClientIdsOfDescendants, getBlockName } =
select( blockEditorStore );

return getClientIdsOfDescendants( clientId ).filter(
const blocks = {};
getClientIdsOfDescendants( clientId ).forEach(
( descendantClientId ) => {
const blockName = getBlockName( descendantClientId );
return (
! blockName.startsWith( 'core/' ) ||
blockName === 'core/post-content' ||
blockName === 'core/template-part' ||
blockName === 'core/block'
);
if ( ! blockName.startsWith( 'core/' ) ) {
blocks.hasBlocksFromPlugins = true;
} else if ( blockName === 'core/post-content' ) {
blocks.hasPostContentBlock = true;
}
}
);
blocks.hasUnsupportedBlocks =
blocks.hasBlocksFromPlugins || blocks.hasPostContentBlock;
return blocks;
},
[ clientId ]
);
Expand Down
13 changes: 11 additions & 2 deletions packages/block-library/src/query/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ store( {
core: {
query: {
navigate: async ( { event, ref, context } ) => {
if ( isValidLink( ref ) && isValidEvent( event ) ) {
const isDisabled = ref.closest( '[data-wp-navigation-id]' )
?.dataset.wpNavigationDisabled;

if (
isValidLink( ref ) &&
isValidEvent( event ) &&
! isDisabled
) {
event.preventDefault();

const id = ref.closest( '[data-wp-navigation-id]' )
Expand Down Expand Up @@ -70,7 +77,9 @@ store( {
}
},
prefetch: async ( { ref } ) => {
if ( isValidLink( ref ) ) {
const isDisabled = ref.closest( '[data-wp-navigation-id]' )
?.dataset.wpNavigationDisabled;
if ( isValidLink( ref ) && ! isDisabled ) {
await prefetch( ref.href );
}
},
Expand Down
Loading
Loading