Skip to content

Commit

Permalink
Fix recursively_filter_blocks sometimes generating blocks that crash
Browse files Browse the repository at this point in the history
  • Loading branch information
mreishus committed Aug 19, 2021
1 parent f80d0b1 commit 5498fd1
Showing 1 changed file with 41 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ public static function filter_widget( $instance ) {
// Block-Based widgets: We have gutenberg blocks that could have the 'conditions' attribute
// Note: These blocks can be nested, and we would have to apply these conditions at any level.
$blocks = parse_blocks( $instance['content'] );
$blocks = self::recursively_filter_blocks( $blocks );
$blocks = self::recursively_filter_blocks( $blocks )[0];
if ( empty( $blocks ) ) {
return false;
}
Expand All @@ -739,17 +739,12 @@ public static function filter_widget( $instance ) {
* Recursively check the visibility conditions in blocks' attr.conditions.rules.
* Any that fail the check will be removed.
*
* Note that this creates blocks that will fail to render in some cases, and should
* be done with a different approach.
* Example: [{"blockName":"core\/columns","attrs":{"conditions":{"action":"show","rules":[],"match_all":"0"}},"innerBlocks":[{"blockName":"core\/column","attrs":{"conditions":{"action":"show","rules":[],"match_all":"0"}},"innerBlocks":[{"blockName":"core\/paragraph","attrs":{"conditions":{"action":"show","rules":[{"major":"page","minor":"front"}],"match_all":"0"}},"innerBlocks":[],"innerHTML":"\n<p>Home Page Only<\/p>\n","innerContent":["\n<p>Home Page Only<\/p>\n"]}],"innerHTML":"\n<div class=\"wp-block-column\"><\/div>\n","innerContent":["\n<div class=\"wp-block-column\">",null,"<\/div>\n"]},{"blockName":"core\/column","attrs":{"conditions":{"action":"show","rules":[],"match_all":"0"}},"innerBlocks":[],"innerHTML":"\n<div class=\"wp-block-column\"><\/div>\n","innerContent":["\n<div class=\"wp-block-column\">",null,"<\/div>\n"]}],"innerHTML":"\n<div class=\"wp-block-columns\">\n\n<\/div>\n","innerContent":["\n<div class=\"wp-block-columns\">",null,"\n\n",null,"<\/div>\n"]}]
* That is two columns, but one of the columns has 0 innerBlocks. However
* it tries to render one anyway and crashes, possibly because of a 'null'
* in the innerContent array (which might mean 'render a child').
*
* @param array $blocks (By reference; will be modified).
* @return array $blocks
* @return array [$blocks, $indexes_removed]
*/
public static function recursively_filter_blocks( &$blocks ) {
$indexes_removed = array();

foreach ( $blocks as $i => $block ) {
$keep = true;
if ( ! empty( $block['attrs']['conditions']['rules'] ) ) {
Expand All @@ -758,11 +753,46 @@ public static function recursively_filter_blocks( &$blocks ) {

if ( ! $keep ) {
unset( $blocks[ $i ] );
array_push( $indexes_removed, $i );
} elseif ( is_array( $block['innerBlocks'] ) ) {
$blocks[ $i ]['innerBlocks'] = self::recursively_filter_blocks( $blocks[ $i ]['innerBlocks'] );
$result = self::recursively_filter_blocks( $blocks[ $i ]['innerBlocks'] );
$blocks[ $i ]['innerBlocks'] = $result[0];

// For each block we removed in innerBlocks, we must remove a corresponding 'null' in innerContent
// if $sub_indexes_removed = [0, 2], we removed the 0th and 2nd innerBlocks, and need to remove
// the 0th and 2nd nulls in innerContent.
$sub_indexes_removed = $result[1];
$null_locations = array_keys( $blocks[ $i ]['innerContent'], null, true );
$null_count = count( $null_locations );

// phpcs:disable Squiz.PHP.CommentedOutCode.Found

/*
Example:
null_locations = [ 1, 3, 5 ] "Nulls exist at j=1, j=3, j=5".
null_count = 3. "There are 3 nulls".
sub_indexes_removed = [ 0, 2 ]. "We want to remove the 0th and 2nd null".
We need to remove the 0th null (at j = 1) and the 2nd null (at j = 5).
[ 'abc', null, 'def', null, 'ghi', null ].
..........^ j = 1, k = 0 ^.
.......................^ j = 3, k = 1 ^.
......................................^ j = 5, k = 2.
*/

// phpcs:enable Squiz.PHP.CommentedOutCode.Found

$k = $null_count - 1;
// Work backwards so as we delete items, then the other indexes don't change.
foreach ( array_reverse( $null_locations ) as $j ) {
// We are looking at a null, which is the $j index of the array and the $kth null in total (k is also 0 indexed).
if ( in_array( $k, $sub_indexes_removed, true ) ) {
array_splice( $blocks[ $i ]['innerContent'], $j, 1 );
}
$k--;
}
}
}
return $blocks;
return array( $blocks, $indexes_removed );
}

/**
Expand Down

0 comments on commit 5498fd1

Please sign in to comment.