Skip to content

Commit

Permalink
Query block enhanced pagination: Detect inner plugin blocks during re…
Browse files Browse the repository at this point in the history
…nder (#55714)

* Flag inner plugin blocks inside query loop

* Improve PHP logic a little

* Only disallow plugin blocks and post content

* Get rid of global variables

* Fix returned content from render callback

* Handle composed query stacks

* Disable navigation on the browser

* Replace `count` with `empty`

* Add PHPdocs and improve var naming

* Lint PHP

* Clarify docs a little

* Move the disable check before preventDefault

* Restore previous navigate logic

* Set filter priorities back to 10

* Basic inspector warnings

* Make render query filter static

* Add stable modal logic

* Switch back to ToggleControl

* Auto remove filter when query stack is empty

* Add first unit tests

* Switch to inverse control

* Add test case for nested queries

* Prevent passing `null` to the Tag Processr

* Get rid of explicit auto mode and notices

* Test directives in the Pagination Previous block

* Minor typos and improvements

* Improve modal texts

* Fix WPCS

* Reorder teardowns

* Reset the dirty flag after it's used

* Prevent usage of post content block

---------

Co-authored-by: David Arenas <david.arenas@automattic.com>
  • Loading branch information
luisherranz and DAreRodz committed Nov 1, 2023
1 parent 16367dc commit b483df2
Show file tree
Hide file tree
Showing 8 changed files with 439 additions and 69 deletions.
2 changes: 1 addition & 1 deletion packages/block-library/src/query-pagination-next/index.php
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
56 changes: 35 additions & 21 deletions packages/block-library/src/query/edit/enhanced-pagination-modal.js
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;
}

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."
);
}

return (
<>
<ToggleControl
label={ __( 'Enhanced pagination' ) }
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 @@ -129,3 +129,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'] ) &&
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.
*/

/**
* 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

0 comments on commit b483df2

Please sign in to comment.