diff --git a/phpunit/directives/attributes/wp-show.php b/phpunit/directives/attributes/wp-show.php
new file mode 100644
index 00000000..a2cc3e73
--- /dev/null
+++ b/phpunit/directives/attributes/wp-show.php
@@ -0,0 +1,176 @@
+I should be shown!';
+ $tags = new WP_Directive_Processor( $markup );
+ $tags->next_tag();
+
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'open' => true ) ) );
+ $context = clone $context_before;
+ process_wp_show( $tags, $context );
+
+ $tags->next_tag( array( 'tag_closers' => 'visit' ) );
+ process_wp_show( $tags, $context );
+
+ $this->assertSame( $markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_directive_wraps_content_in_template_if_when_is_false() {
+ $markup = '
I should not be shown!
';
+
+ $tags = new WP_Directive_Processor( $markup );
+ $tags->next_tag();
+
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'open' => false ) ) );
+ $context = clone $context_before;
+ process_wp_show( $tags, $context );
+
+ $tags->next_tag( array( 'tag_closers' => 'visit' ) );
+ process_wp_show( $tags, $context );
+
+ $updated_markup = 'I should not be shown!
';
+
+ $this->assertSame( $updated_markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_directive_does_not_wrap_template_in_template() {
+ $markup = 'I should not be shown!';
+
+ $tags = new WP_Directive_Processor( $markup );
+ $tags->next_tag();
+
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'open' => false ) ) );
+ $context = clone $context_before;
+ process_wp_show( $tags, $context );
+
+ $tags->next_tag( array( 'tag_closers' => 'visit' ) );
+ process_wp_show( $tags, $context );
+
+ $this->assertSame( $markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_directive_wraps_content_preceded_by_other_html_in_template_if_when_is_false() {
+ $markup = 'Some text
I should not be shown!
';
+
+ $tags = new WP_Directive_Processor( $markup );
+ $tags->next_tag( 'div' );
+
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'open' => false ) ) );
+ $context = clone $context_before;
+ process_wp_show( $tags, $context );
+
+ $tags->next_tag( array( 'tag_closers' => 'visit' ) );
+ process_wp_show( $tags, $context );
+
+ $updated_markup = 'Some text
I should not be shown!
';
+
+ $this->assertSame( $updated_markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_directive_wraps_void_tag_in_template_if_when_is_false() {
+ $markup = '
';
+ $tags = new WP_Directive_Processor( $markup );
+ $tags->next_tag();
+
+ $context_before = new WP_Directive_Context( array( 'myblock' => array( 'open' => false ) ) );
+ $context = clone $context_before;
+ process_wp_show( $tags, $context );
+
+ $tags->next_tag( array( 'tag_closers' => 'visit' ) );
+ process_wp_show( $tags, $context );
+
+ $updated_markup = '
';
+
+ $this->assertSame( $updated_markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_nested_directives_within_wp_show_with_truthy_value() {
+ $markup = <<
+ I should be shown!
+ I should be shown!
+ I should not be shown!
+
+END;
+
+ $context_before = new WP_Directive_Context(
+ array(
+ 'myBlock' => array( 'open' => true ),
+ 'myOtherBlock' => array( 'open' => true ),
+ )
+ );
+ $context = clone $context_before;
+
+ $tags = new WP_Directive_Processor( $markup );
+
+ while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
+ process_wp_show( $tags, $context );
+ }
+
+ $updated_markup = <<
+ I should be shown!
+ I should be shown!
+ I should not be shown!
+
+END;
+ $this->assertSame( $updated_markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+
+ public function test_nested_directives_within_wp_show_with_falsy_value() {
+ $markup = <<
+ I should not be shown!
+ I should be shown!
+ I should not be shown!
+
+END;
+
+ $context_before = new WP_Directive_Context(
+ array(
+ 'myBlock' => array( 'open' => false ),
+ 'myOtherBlock' => array( 'open' => true ),
+ )
+ );
+ $context = clone $context_before;
+
+ $tags = new WP_Directive_Processor( $markup );
+
+ while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
+ process_wp_show( $tags, $context );
+ }
+
+ $updated_markup = <<
+ I should not be shown!
+
I should be shown!
+
I should not be shown!
+
+END;
+ $this->assertSame( $updated_markup, $tags->get_updated_html() );
+ $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-show directive changed context' );
+ }
+}
diff --git a/phpunit/directives/wp-directive-processor.php b/phpunit/directives/wp-directive-processor.php
index e24e247c..4f01a64f 100644
--- a/phpunit/directives/wp-directive-processor.php
+++ b/phpunit/directives/wp-directive-processor.php
@@ -125,4 +125,43 @@ public function test_set_inner_html_invalidates_bookmarks_that_point_to_replaced
$successful_seek = $tags->seek( 'replaced' );
$this->assertFalse( $successful_seek );
}
+
+ public function test_wrap_in_tag_wraps_balanced_tag_correctly() {
+ $tags = new WP_Directive_Processor( self::HTML );
+
+ $tags->next_tag( 'section' );
+ $tags->wrap_in_tag( 'TEMPLATE' );
+ $this->assertSame( 'outside
![]()
inside
', $tags->get_updated_html() );
+ $this->assertSame( 'SECTION', $tags->get_tag() );
+ $this->assertFalse( $tags->is_tag_closer() );
+ }
+
+ public function test_wrap_in_tag_wraps_void_tag_correctly() {
+ $tags = new WP_Directive_Processor( self::HTML );
+
+ $tags->next_tag( 'img' );
+ $tags->wrap_in_tag( 'TEMPLATE' );
+ $this->assertSame( 'outside
![]()
inside
', $tags->get_updated_html() );
+ $this->assertSame( 'IMG', $tags->get_tag() );
+ }
+
+ public function test_wrap_in_tag_followed_by_two_subsequent_calls_of_get_updated_html_works_correctly() {
+ $tags = new WP_Directive_Processor( self::HTML );
+
+ $tags->next_tag( 'img' );
+ $tags->wrap_in_tag( 'TEMPLATE' );
+ $tags->get_updated_html();
+ $this->assertSame( 'outside
![]()
inside
', $tags->get_updated_html() );
+ $this->assertSame( 'IMG', $tags->get_tag() );
+ }
+
+ public function test_wrap_in_tag_fails_if_passed_void_tag() {
+ $tags = new WP_Directive_Processor( self::HTML );
+
+ $tags->next_tag( 'section' );
+
+ $this->assertFalse( $tags->wrap_in_tag( 'IMG' ) );
+ $this->assertSame( self::HTML, $tags->get_updated_html() );
+ $this->assertSame( 'SECTION', $tags->get_tag() );
+ }
}
diff --git a/src/directives/attributes/wp-show.php b/src/directives/attributes/wp-show.php
new file mode 100644
index 00000000..46649f80
--- /dev/null
+++ b/src/directives/attributes/wp-show.php
@@ -0,0 +1,41 @@
+is_tag_closer() ) {
+ return;
+ }
+
+ $value = $tags->get_attribute( 'data-wp-show' );
+ if ( null === $value ) {
+ return;
+ }
+
+ $show = evaluate( $value, $context->get_context() );
+ if ( $show ) {
+ return;
+ }
+
+ if ( 'TEMPLATE' === $tags->get_tag() ) {
+ return; // Don't wrap a `` in a ``.
+ }
+
+ $wrapper_bookmark = $tags->wrap_in_tag( 'TEMPLATE' );
+ $tags->seek( $wrapper_bookmark );
+ $tags->set_attribute( 'data-wp-show', $value );
+ $tags->next_tag();
+ $tags->remove_attribute( 'data-wp-show' );
+}
diff --git a/src/directives/class-wp-directive-processor.php b/src/directives/class-wp-directive-processor.php
index b6a2e046..d611203c 100644
--- a/src/directives/class-wp-directive-processor.php
+++ b/src/directives/class-wp-directive-processor.php
@@ -131,6 +131,84 @@ public function get_balanced_tag_bookmarks() {
return array( $start_name, $end_name );
}
+ /**
+ * Wrap the current node in a given tag.
+ *
+ * When positioned on a tag opener, locate its matching closer, and wrap everything
+ * in the tag specified as an argument. When positioned on a void element, wrap that
+ * element in the argument tag.
+ *
+ * Note that the internal pointer will continue to point to the same tag as before
+ * calling the function.
+ *
+ * @param string $tag An HTML tag, specified in uppercase (e.g. "DIV").
+ * @return string|false The name of a bookmark pointing to the wrapping tag opener
+ * if successful; false otherwise.
+ *
+ * @todo Allow passing in tags with attributes, e.g. ?
+ */
+ public function wrap_in_tag( $tag ) {
+ if ( $this->is_tag_closer() ) {
+ return false;
+ }
+
+ if ( self::is_html_void_element( $tag ) ) {
+ // _doing_it_wrong(
+ // __METHOD__,
+ // __( 'Cannot wrap HTML in void tag.' ),
+ // '6.3.0'
+ // );
+ return false;
+ }
+
+ $this->get_updated_html(); // Apply potential previous updates.
+
+ if ( self::is_html_void_element( $this->get_tag() ) ) {
+ // We don't have direct access to the start and end position of the
+ // current tag. As a workaround, we set a bookmark that we then
+ // release immediately.
+ $i = 0;
+ while ( array_key_exists( 'void' . $i, $this->bookmarks ) ) {
+ ++$i;
+ }
+ $start_name = 'void' . $i;
+
+ $this->set_bookmark( $start_name );
+
+ $start = $this->bookmarks[ $start_name ]->start;
+ $end = $this->bookmarks[ $start_name ]->end + 1;
+ } else {
+ $bookmarks = $this->get_balanced_tag_bookmarks();
+ if ( ! $bookmarks ) {
+ return false;
+ }
+ list( $start_name, $end_name ) = $bookmarks;
+
+ $start = $this->bookmarks[ $start_name ]->start;
+ $end = $this->bookmarks[ $end_name ]->end + 1;
+
+ $this->release_bookmark( $end_name );
+ }
+
+ $tag = strtolower( $tag );
+ $this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $start, "<$tag>" );
+ $this->lexical_updates[] = new WP_HTML_Text_Replacement( $end, $end, "$tag>" );
+
+ $this->seek( $start_name ); // Return to original position.
+ $this->release_bookmark( $start_name );
+
+ $i = 0;
+ while ( array_key_exists( $tag . $i, $this->bookmarks ) ) {
+ ++$i;
+ }
+ $bookmark_name = $tag . $i;
+ $this->bookmarks[ $bookmark_name ] = new WP_HTML_Span(
+ $start,
+ $start + strlen( $tag ) + 2
+ );
+ return $bookmark_name;
+ }
+
/**
* Whether a given HTML element is void (e.g.
).
*
diff --git a/wp-directives.php b/wp-directives.php
index 9ca9fdac..16599a14 100644
--- a/wp-directives.php
+++ b/wp-directives.php
@@ -47,6 +47,7 @@ function () {
require_once __DIR__ . '/src/directives/attributes/wp-context.php';
require_once __DIR__ . '/src/directives/attributes/wp-class.php';
require_once __DIR__ . '/src/directives/attributes/wp-html.php';
+require_once __DIR__ . '/src/directives/attributes/wp-show.php';
require_once __DIR__ . '/src/directives/attributes/wp-style.php';
require_once __DIR__ . '/src/directives/attributes/wp-text.php';
@@ -261,6 +262,7 @@ function process_directives_in_block( $block_content ) {
'data-wp-bind' => 'process_wp_bind',
'data-wp-class' => 'process_wp_class',
'data-wp-html' => 'process_wp_html',
+ 'data-wp-show' => 'process_wp_show',
'data-wp-style' => 'process_wp_style',
'data-wp-text' => 'process_wp_text',
);