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

Bits: Introduce the ability to store and render bits #6390

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
125 changes: 125 additions & 0 deletions src/wp-includes/bits.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
/**
* WordPress Bits system, for replacing placeholder tokens in a document,
* for semantically independent content types, with externally-sourced data.
*
* @package WordPress
* @subpackage Bits
* @since {WP_VERSION}
*/

declare( strict_types=1 );

/**
* Hello Dolly Bit Rendering function.
*
* @param string $name Fully-qualified name of the matched Bit.
* E.g. "core/post-author".
* @param string $output_type Either "rich-text" or "plaintext" depending on where the Bit is found.
* E.g. when inside an HTML attribute or TITLE element, only plaintext is allowed,
* But when found inside a P element, rich formatting is allowed.
* @param array|null $attributes Configured parameters of the Bit, if provided.
* E.g. `<//wp-bit:hello-dolly year="2024">` produces `array( 'year' => '2024' )`.
* @param mixed $context Context passed into the Bit from the surrounding system.
* This argument is not yet specified and will always be `null`.
*
* @return mixed An HTML template for rendering into the page, either as a plain string or in array form.
*/
function core_bit_hello_dolly( string $name, string $output_type, ?array $attributes, mixed $context ): mixed {
static $vocalists = array(
'Julie Dahle Aagård',
'Mindi Abair',
'Lorez Alexandria',
'Karrin Allyson',
'Michelle Amato',
'Ernestine Anderson',
'Ivie Anderson',
);

$vocalist = $vocalists[ wp_rand( 0, count( $vocalists ) - 1 ) ];

switch ( $output_type ) {
case 'plaintext':
return $vocalist;

case 'rich-text':
return array(
'<span data-vocalist="</%name>"></%name></span>',
array( 'name' => $vocalist ),
);
}
}

class WP_Hello_Dolly_Bit extends BitProvider {
/**
* @inheritDoc
*/
public function handle_plaintext( string $bit_name, ?array $attributes ): string {
return self::get_random_vocalist();
}

/**
* @inheritDoc
*/
public function handle_richtext( string $bit_name, ?array $attributes ): WP_HTML_Template {
$name = self::get_random_vocalist();

return new WP_HTML_Template(
'<span data-vocalist="</%name>"></%name></span>',
array( 'name' => $name )
);
}

/**
* Returns the name of a random Jazz vocalist.
*
* @return string
*/
private function get_random_vocalist(): string {
static $vocalists = array(
'Julie Dahle Aagård',
'Mindi Abair',
'Lorez Alexandria',
'Karrin Allyson',
'Michelle Amato',
'Ernestine Anderson',
'Ivie Anderson',
);

return $vocalists[ wp_rand( 0, count( $vocalists ) - 1 ) ];
}
}

abstract class BitProvider {
/**
* Performs initialize of Bit Provider during WordPress bootup.
*/
public function register(): void {
// This is optional.
};

/**
* Called to source content in plaintext contexts. For example, when a
* Bit is found within an HTML attribute, or inside a `TITLE` element.
*
* @see WP_HTML_Template
*
* @param string $bit_name Full name with namespace of matched Bit, e.g. "core/post-author".
* @param array|null $attributes Configured attributes found on Bit, if found, otherwise `null`.
*
* @return string Plaintext value for provided content.
*/
abstract public function handle_plaintext( string $bit_name, ?array $attributes ): string;

/**
* Called to source content in Rich Text (HTML Markup) contexts. For example,
* when a Bit is found within the inner content of an HTML tag.
*
* @see WP_HTML_Template
*
* @param string $bit_name Full name with namespace of matched Bit, e.g. "core/post-author".
* @param array|null $attributes Configured attributes found on Bit, if found, otherwise `null`.
* @return WP_HTML_Template HTML template for provided content: a string or array.
*/
abstract public function handle_richtext( string $bit_name, ?array $attributes ): WP_HTML_Template;
}
2 changes: 2 additions & 0 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,8 @@ function render_block( $parsed_block ) {
* }
*/
function parse_blocks( $content ) {
$content = wp_replace_bits( $content );

/**
* Filter to allow plugins to replace the server-side block parser.
*
Expand Down
77 changes: 77 additions & 0 deletions src/wp-includes/html-api/class-wp-bits.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

function wp_replace_bits( $content ) {
$processor = new class ( $content ) extends WP_HTML_Tag_Processor {

Check failure on line 4 in src/wp-includes/html-api/class-wp-bits.php

View workflow job for this annotation

GitHub Actions / PHP coding standards / Run coding standards checks

There must be no space between the class keyword and the open parenthesis for an anonymous class. Found: 1 space
private $deferred_updates = array();

public function replace_token( $new_content ) {
$this->set_bookmark( 'here' );
$here = $this->bookmarks['here'];

$this->deferred_updates[] = new WP_HTML_Text_Replacement(
$here->start,
$here->length,
$new_content
);
}

public function flush_updates() {
foreach ( $this->deferred_updates as $update ) {
$this->lexical_updates[] = $update;
}
}
};

while ( $processor->next_token() ) {
switch ( $processor->get_token_type() ) {
case '#funky-comment':
$processor->replace_token( '<b>bl<em>ar</em>g</b>' );
break;

case '#tag':
foreach ( $processor->get_attribute_names_with_prefix( '' ) ?? [] as $name ) {

Check failure on line 32 in src/wp-includes/html-api/class-wp-bits.php

View workflow job for this annotation

GitHub Actions / PHP coding standards / Run coding standards checks

Short array syntax is not allowed
$value = $processor->get_attribute( $name );
if ( is_string( $value ) ) {
$new_value = preg_replace_callback(
'~<//wp:([^>]+)>~',
static function ( $bit ) {
return 'blarg';
},
$value
);

if ( $new_value !== $value ) {
$processor->set_attribute( $name, $new_value );
}
}
}
break;

case '#comment':
if ( WP_HTML_Tag_Processor::COMMENT_AS_HTML_COMMENT !== $processor->get_comment_type() ) {
break;
}

$text = $processor->get_modifiable_text();
if ( 1 === preg_match( '~^<//wp:([^>]+)>$~', $text ) ) {
$processor->replace_token( '<b>Bla<em>rg</em>!</b>' );
break;
}

$new_value = preg_replace_callback(
'~<//wp:([^>]+)>~',
static function ( $bit ) {
return 'blarg';
},
$text
);

$processor->replace_token( "<!--{$new_value}-->" );
break;
}
}
$processor->flush_updates();
$content = $processor->get_updated_html();

return $content;
}
48 changes: 48 additions & 0 deletions src/wp-includes/html-api/class-wp-html-template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare( strict_types=1 );

class WP_HTML_Template {
/**
* HTML Template indicating where to mix a static HTML template
* and placeholders for dynamic values.
*
* @var string
*/
public $template;

/**
* Stores data necessary to render a template, if any required.
*
* @var array|null
*/
public $data;

/**
* Constructor function.
*
* Example:
*
* // No placeholders are required, only a template string.
* new WP_HTML_Template( '<p>Hello, World!</p>' );
*
* // Placeholders for simple substitution.
* new WP_HTML_Template( '<p>Hello, </%name>!</p>', array( 'name' => $name ) );
*
* // Spread-operator for sets of attributes.
* new WP_HTML_Template(
* '<button ...interactivity_args>Click me!</button>',
* array(
* 'data-wp-text="context.buttonLabel"',
* 'data-wp-click="actions.clickButton",
* )
* );
*
* @param string $template Static HTML template, possibly including placeholders.
* @param array|null $data Optional. Data provided for placeholders, if any.
*/
public function __construct( string $template, array $data = null ) {
$this->template = $template;
$this->data = $data;
}
}
2 changes: 2 additions & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@
require ABSPATH . WPINC . '/html-api/class-wp-html-stack-event.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-processor-state.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-processor.php';
require ABSPATH . WPINC . '/html-api/class-wp-html-template.php';
require ABSPATH . WPINC . '/html-api/class-wp-bits.php';
require ABSPATH . WPINC . '/class-wp-http.php';
require ABSPATH . WPINC . '/class-wp-http-streams.php';
require ABSPATH . WPINC . '/class-wp-http-curl.php';
Expand Down
Loading