Skip to content

Commit 85e3127

Browse files
committed
Do not use PHP iterators in BlockMarkupProcessor – they work differently in PHP 8.0+
1 parent cd0f49c commit 85e3127

File tree

1 file changed

+80
-36
lines changed

1 file changed

+80
-36
lines changed

components/DataLiberation/BlockMarkup/BlockMarkupProcessor.php

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace WordPress\DataLiberation\BlockMarkup;
44

5-
use RecursiveArrayIterator;
6-
use RecursiveIteratorIterator;
75
use WP_Block_Parser_Error;
86
use WP_HTML_Tag_Processor;
97

@@ -75,11 +73,22 @@ class BlockMarkupProcessor extends WP_HTML_Tag_Processor {
7573
private $last_block_error;
7674

7775
/**
78-
* Iterator for traversing nested block attributes
76+
* A flattened list of paths (arrays of keys) to every attribute found in
77+
* $block_attributes. This is used by next_block_attribute() to traverse
78+
* attributes without relying on PHP iterator classes.
7979
*
80-
* @var RecursiveIteratorIterator
80+
* @var array<int, array<int|string>>|null
8181
*/
82-
private $block_attributes_iterator;
82+
private $block_attribute_paths = null;
83+
84+
/**
85+
* The index of the current attribute inside $block_attribute_paths.
86+
* Starts at -1 so that the first call to next_block_attribute() positions
87+
* the cursor at index 0.
88+
*
89+
* @var int
90+
*/
91+
private $block_attribute_index = -1;
8392

8493
/**
8594
* Gets the type of the current token, adding a special '#block-comment' type
@@ -287,7 +296,8 @@ public function next_token(): bool {
287296

288297
$this->block_name = null;
289298
$this->block_attributes = null;
290-
$this->block_attributes_iterator = null;
299+
$this->block_attribute_paths = null;
300+
$this->block_attribute_index = -1;
291301
$this->block_closer = false;
292302
$this->self_closing_flag = false;
293303
$this->block_attributes_updated = false;
@@ -469,19 +479,19 @@ private function block_attribute_updates_to_modifiable_text_updates() {
469479
if ( ! $this->block_attributes_updated ) {
470480
return false;
471481
}
482+
472483
$encoded_attributes = json_encode(
473-
$this->block_attributes_iterator
474-
? $this->block_attributes_iterator->getSubIterator( 0 )->getArrayCopy()
475-
: $this->block_attributes,
484+
$this->block_attributes,
476485
JSON_HEX_TAG | // Convert < and > to \u003C and \u003E
477486
JSON_HEX_AMP // Convert & to \u0026
478487
);
479-
var_dump($encoded_attributes);
488+
480489
if ( $encoded_attributes === '[]' ) {
481490
$encoded_attributes = '';
482491
} else {
483492
$encoded_attributes .= ' ';
484493
}
494+
485495
$this->set_modifiable_text(
486496
' ' .
487497
$this->block_name .
@@ -502,28 +512,19 @@ public function next_block_attribute() {
502512
return false;
503513
}
504514

505-
if ( null === $this->block_attributes_iterator ) {
515+
if ( null === $this->block_attribute_paths ) {
506516
$block_attributes = $this->get_block_attributes();
507517
if ( ! is_array( $block_attributes ) ) {
508518
return false;
509519
}
510-
// Re-entrant iteration over the block attributes.
511-
$this->block_attributes_iterator = new RecursiveIteratorIterator(
512-
new RecursiveArrayIterator( $block_attributes ),
513-
RecursiveIteratorIterator::SELF_FIRST
514-
);
515-
}
516-
517-
while ( true ) {
518-
$this->block_attributes_iterator->next();
519-
if ( ! $this->block_attributes_iterator->valid() ) {
520-
break;
521-
}
522520

523-
return true;
521+
$this->block_attribute_paths = $this->build_block_attribute_paths( $block_attributes );
522+
$this->block_attribute_index = -1;
524523
}
525524

526-
return false;
525+
$this->block_attribute_index++;
526+
527+
return isset( $this->block_attribute_paths[ $this->block_attribute_index ] );
527528
}
528529

529530
/**
@@ -532,11 +533,13 @@ public function next_block_attribute() {
532533
* @return string|false The attribute key or false if no attribute was matched
533534
*/
534535
public function get_block_attribute_key() {
535-
if ( null === $this->block_attributes_iterator || false === $this->block_attributes_iterator->valid() ) {
536+
if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) {
536537
return false;
537538
}
538539

539-
return $this->block_attributes_iterator->key();
540+
$path = $this->block_attribute_paths[ $this->block_attribute_index ];
541+
542+
return $path[ count( $path ) - 1 ];
540543
}
541544

542545
/**
@@ -545,11 +548,21 @@ public function get_block_attribute_key() {
545548
* @return mixed|false The attribute value or false if no attribute was matched
546549
*/
547550
public function get_block_attribute_value() {
548-
if ( null === $this->block_attributes_iterator || false === $this->block_attributes_iterator->valid() ) {
551+
if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) {
549552
return false;
550553
}
551554

552-
return $this->block_attributes_iterator->current();
555+
$path = $this->block_attribute_paths[ $this->block_attribute_index ];
556+
$value = $this->block_attributes;
557+
558+
foreach ( $path as $segment ) {
559+
if ( ! is_array( $value ) || ! array_key_exists( $segment, $value ) ) {
560+
return false;
561+
}
562+
$value = $value[ $segment ];
563+
}
564+
565+
return $value;
553566
}
554567

555568
/**
@@ -560,18 +573,49 @@ public function get_block_attribute_value() {
560573
* @return bool Whether the value was successfully set
561574
*/
562575
public function set_block_attribute_value( $new_value ) {
563-
if ( null === $this->block_attributes_iterator || false === $this->block_attributes_iterator->valid() ) {
576+
if ( null === $this->block_attribute_paths || ! isset( $this->block_attribute_paths[ $this->block_attribute_index ] ) ) {
564577
return false;
565578
}
566579

567-
$this->block_attributes_iterator
568-
->getSubIterator( $this->block_attributes_iterator->getDepth() )
569-
->offsetSet(
570-
$this->get_block_attribute_key(),
571-
$new_value
572-
);
580+
$path = $this->block_attribute_paths[ $this->block_attribute_index ];
581+
582+
$ref =& $this->block_attributes;
583+
$depth = count( $path );
584+
for ( $i = 0; $i < $depth - 1; $i ++ ) {
585+
$segment = $path[ $i ];
586+
if ( ! is_array( $ref ) || ! array_key_exists( $segment, $ref ) ) {
587+
return false; // Path is invalid.
588+
}
589+
$ref =& $ref[ $segment ];
590+
}
591+
592+
$last_key = $path[ $depth - 1 ];
593+
$ref[ $last_key ] = $new_value;
573594
$this->block_attributes_updated = true;
574595

575596
return true;
576597
}
598+
599+
/**
600+
* Builds a list of attribute paths, using a depth-first, SELF_FIRST order
601+
* that matches the previous iterator behaviour.
602+
*
603+
* @param array<int|string, mixed> $attributes
604+
* @param array<int|string> $base_path
605+
* @return array<int, array<int|string>>
606+
*/
607+
private function build_block_attribute_paths( $attributes, $base_path = array() ) {
608+
$paths = array();
609+
610+
foreach ( $attributes as $key => $value ) {
611+
$current_path = array_merge( $base_path, array( $key ) );
612+
$paths[] = $current_path; // SELF_FIRST: include parent before children.
613+
614+
if ( is_array( $value ) ) {
615+
$paths = array_merge( $paths, $this->build_block_attribute_paths( $value, $current_path ) );
616+
}
617+
}
618+
619+
return $paths;
620+
}
577621
}

0 commit comments

Comments
 (0)