Template API: Add after_add_block and after_remove_block actions (#40139)
This commit is contained in:
commit
6c47589953
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add after_add_block and after_remove block hooks to the block template API.
|
|
@ -62,22 +62,25 @@ interface BlockInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parent container that the block belongs to.
|
* Get the parent container that the block belongs to.
|
||||||
*
|
|
||||||
* @throws \RuntimeException If the block does not have a parent.
|
|
||||||
*/
|
*/
|
||||||
public function &get_parent(): ?ContainerInterface;
|
public function &get_parent(): ContainerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root template that the block belongs to.
|
* Get the root template that the block belongs to.
|
||||||
*
|
|
||||||
* @throws \RuntimeException If the block does not have a root template.
|
|
||||||
*/
|
*/
|
||||||
public function &get_root_template(): BlockTemplateInterface;
|
public function &get_root_template(): BlockTemplateInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the block from its parent and root template.
|
* Remove the block from its parent.
|
||||||
*/
|
*/
|
||||||
public function detach();
|
public function remove();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the block is detached from its parent or root template.
|
||||||
|
*
|
||||||
|
* @return bool True if the block is detached from its parent or root template.
|
||||||
|
*/
|
||||||
|
public function is_detached(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the block configuration as a formatted template.
|
* Get the block configuration as a formatted template.
|
||||||
|
|
|
@ -26,13 +26,6 @@ interface BlockTemplateInterface extends ContainerInterface {
|
||||||
*/
|
*/
|
||||||
public function get_area(): string;
|
public function get_area(): string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a block by ID.
|
|
||||||
*
|
|
||||||
* @param string $block_id The block ID.
|
|
||||||
*/
|
|
||||||
public function get_block( string $block_id ): ?BlockInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a block ID based on a base.
|
* Generate a block ID based on a base.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,6 +16,13 @@ interface ContainerInterface {
|
||||||
*/
|
*/
|
||||||
public function get_formatted_template(): array;
|
public function get_formatted_template(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a block by ID.
|
||||||
|
*
|
||||||
|
* @param string $block_id The block ID.
|
||||||
|
*/
|
||||||
|
public function get_block( string $block_id ): ?BlockInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a block from the container.
|
* Removes a block from the container.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,6 +9,13 @@ use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlockTemplate;
|
||||||
* Block template class.
|
* Block template class.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractProductFormTemplate extends AbstractBlockTemplate implements ProductFormTemplateInterface {
|
abstract class AbstractProductFormTemplate extends AbstractBlockTemplate implements ProductFormTemplateInterface {
|
||||||
|
/**
|
||||||
|
* Get the template area.
|
||||||
|
*/
|
||||||
|
public function get_area(): string {
|
||||||
|
return 'product-form';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a group block by ID.
|
* Get a group block by ID.
|
||||||
*
|
*
|
||||||
|
|
|
@ -161,38 +161,36 @@ class AbstractBlock implements BlockInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the template that this block belongs to.
|
* Get the template that this block belongs to.
|
||||||
*
|
|
||||||
* @throws \RuntimeException If the block does not have a root template.
|
|
||||||
*/
|
*/
|
||||||
public function &get_root_template(): BlockTemplateInterface {
|
public function &get_root_template(): BlockTemplateInterface {
|
||||||
if ( is_null( $this->root_template ) ) {
|
|
||||||
throw new \RuntimeException( 'The block does not have a root template.' );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->root_template;
|
return $this->root_template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parent block container.
|
* Get the parent block container.
|
||||||
*
|
|
||||||
* @throws \RuntimeException If the block does not have a parent.
|
|
||||||
*/
|
*/
|
||||||
public function &get_parent(): ContainerInterface {
|
public function &get_parent(): ContainerInterface {
|
||||||
if ( is_null( $this->parent ) ) {
|
|
||||||
throw new \RuntimeException( 'The block does not have a parent.' );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->parent;
|
return $this->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the block from its parent block container and the template it belongs to.
|
* Remove the block from its parent.
|
||||||
*/
|
*/
|
||||||
public function detach() {
|
public function remove() {
|
||||||
$this->parent = null;
|
$this->parent->remove_block( $this->id );
|
||||||
$this->root_template = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the block is detached from its parent block container or the template it belongs to.
|
||||||
|
*
|
||||||
|
* @return bool True if the block is detached from its parent block container or the template it belongs to.
|
||||||
|
*/
|
||||||
|
public function is_detached(): bool {
|
||||||
|
$is_in_parent = $this->parent->get_block( $this->id ) === $this;
|
||||||
|
$is_in_root_template = $this->get_root_template()->get_block( $this->id ) === $this;
|
||||||
|
|
||||||
|
return ! ( $is_in_parent && $is_in_root_template );
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get the block configuration as a formatted template.
|
* Get the block configuration as a formatted template.
|
||||||
*
|
*
|
||||||
|
|
|
@ -27,19 +27,36 @@ trait BlockContainerTrait {
|
||||||
* @throws \ValueError If the block configuration is invalid.
|
* @throws \ValueError If the block configuration is invalid.
|
||||||
* @throws \ValueError If a block with the specified ID already exists in the template.
|
* @throws \ValueError If a block with the specified ID already exists in the template.
|
||||||
* @throws \UnexpectedValueException If the block container is not the parent of the block.
|
* @throws \UnexpectedValueException If the block container is not the parent of the block.
|
||||||
|
* @throws \UnexpectedValueException If the block container's root template is not the same as the block's root template.
|
||||||
*/
|
*/
|
||||||
protected function &add_inner_block( BlockInterface $block ): BlockInterface {
|
protected function &add_inner_block( BlockInterface $block ): BlockInterface {
|
||||||
if ( ! $block instanceof BlockInterface ) {
|
|
||||||
throw new \UnexpectedValueException( 'The block must return an instance of BlockInterface.' );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $block->get_parent() !== $this ) {
|
if ( $block->get_parent() !== $this ) {
|
||||||
throw new \UnexpectedValueException( 'The block container is not the parent of the block.' );
|
throw new \UnexpectedValueException( 'The block container is not the parent of the block.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$root_template = $block->get_root_template();
|
if ( $block->get_root_template() !== $this->get_root_template() ) {
|
||||||
$root_template->cache_block( $block );
|
throw new \UnexpectedValueException( 'The block container\'s root template is not the same as the block\'s root template.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_detached = method_exists( $this, 'is_detached' ) && $this->is_detached();
|
||||||
|
if ( $is_detached ) {
|
||||||
|
BlockTemplateLogger::get_instance()->warning(
|
||||||
|
'Block added to detached container. Block will not be included in the template, since the container will not be included in the template.',
|
||||||
|
[
|
||||||
|
'block' => $block,
|
||||||
|
'container' => $this,
|
||||||
|
'template' => $this->get_root_template(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->get_root_template()->cache_block( $block );
|
||||||
|
}
|
||||||
|
|
||||||
$this->inner_blocks[] = &$block;
|
$this->inner_blocks[] = &$block;
|
||||||
|
|
||||||
|
$this->do_after_add_block_action( $block );
|
||||||
|
$this->do_after_add_specific_block_action( $block );
|
||||||
|
|
||||||
return $block;
|
return $block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +81,31 @@ trait BlockContainerTrait {
|
||||||
return $this->is_block_descendant( $parent );
|
return $this->is_block_descendant( $parent );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a block by ID.
|
||||||
|
*
|
||||||
|
* @param string $block_id The block ID.
|
||||||
|
*/
|
||||||
|
public function get_block( string $block_id ): ?BlockInterface {
|
||||||
|
foreach ( $this->inner_blocks as $block ) {
|
||||||
|
if ( $block->get_id() === $block_id ) {
|
||||||
|
return $block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $this->inner_blocks as $block ) {
|
||||||
|
if ( $block instanceof ContainerInterface ) {
|
||||||
|
$block = $block->get_block( $block_id );
|
||||||
|
|
||||||
|
if ( $block ) {
|
||||||
|
return $block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a block from the block container.
|
* Remove a block from the block container.
|
||||||
*
|
*
|
||||||
|
@ -89,16 +131,8 @@ trait BlockContainerTrait {
|
||||||
$block->remove_blocks();
|
$block->remove_blocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove block from root template's cache.
|
|
||||||
$root_template = $this->get_root_template();
|
|
||||||
$root_template->uncache_block( $block->get_id() );
|
|
||||||
|
|
||||||
$parent = $block->get_parent();
|
$parent = $block->get_parent();
|
||||||
$parent->remove_inner_block( $block );
|
$parent->remove_inner_block( $block );
|
||||||
|
|
||||||
// Detach block from parent and root template.
|
|
||||||
$block->detach();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,16 +154,29 @@ trait BlockContainerTrait {
|
||||||
* @param BlockInterface $block The block.
|
* @param BlockInterface $block The block.
|
||||||
*/
|
*/
|
||||||
public function remove_inner_block( BlockInterface $block ) {
|
public function remove_inner_block( BlockInterface $block ) {
|
||||||
|
// Remove block from root template's cache.
|
||||||
|
$root_template = $this->get_root_template();
|
||||||
|
$root_template->uncache_block( $block->get_id() );
|
||||||
|
|
||||||
$this->inner_blocks = array_filter(
|
$this->inner_blocks = array_filter(
|
||||||
$this->inner_blocks,
|
$this->inner_blocks,
|
||||||
function ( BlockInterface $inner_block ) use ( $block ) {
|
function ( BlockInterface $inner_block ) use ( $block ) {
|
||||||
return $inner_block !== $block;
|
return $inner_block !== $block;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
BlockTemplateLogger::get_instance()->info(
|
||||||
|
'Block removed from template.',
|
||||||
|
[
|
||||||
|
'block' => $block,
|
||||||
|
'template' => $root_template,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->do_after_remove_block_action( $block );
|
||||||
|
$this->do_after_remove_specific_block_action( $block );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the inner blocks sorted by order.
|
* Get the inner blocks sorted by order.
|
||||||
*/
|
*/
|
||||||
|
@ -168,4 +215,141 @@ trait BlockContainerTrait {
|
||||||
|
|
||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the `woocommerce_block_template_after_add_block` action.
|
||||||
|
* Handle exceptions thrown by the action.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*/
|
||||||
|
private function do_after_add_block_action( BlockInterface $block ) {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Action called after a block is added to a block container.
|
||||||
|
*
|
||||||
|
* This action can be used to perform actions after a block is added to the block container,
|
||||||
|
* such as adding a dependent block.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*
|
||||||
|
* @since 8.2.0
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_block_template_after_add_block', $block );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$this->handle_exception_doing_action(
|
||||||
|
'Error after adding block to template.',
|
||||||
|
'woocommerce_block_template_after_add_block',
|
||||||
|
$block,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the `woocommerce_block_template_area_{template_area}_after_add_block_{block_id}` action.
|
||||||
|
* Handle exceptions thrown by the action.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*/
|
||||||
|
private function do_after_add_specific_block_action( BlockInterface $block ) {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Action called after a specific block is added to a template with a specific area.
|
||||||
|
*
|
||||||
|
* This action can be used to perform actions after a specific block is added to a template with a specific area,
|
||||||
|
* such as adding a dependent block.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*
|
||||||
|
* @since 8.2.0
|
||||||
|
*/
|
||||||
|
do_action( "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_add_block_{$block->get_id()}", $block );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$this->handle_exception_doing_action(
|
||||||
|
'Error after adding block to template.',
|
||||||
|
"woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_add_block_{$block->get_id()}",
|
||||||
|
$block,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the `woocommerce_block_template_after_remove_block` action.
|
||||||
|
* Handle exceptions thrown by the action.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*/
|
||||||
|
private function do_after_remove_block_action( BlockInterface $block ) {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Action called after a block is removed from a block container.
|
||||||
|
*
|
||||||
|
* This action can be used to perform actions after a block is removed from the block container,
|
||||||
|
* such as removing a dependent block.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*
|
||||||
|
* @since 8.2.0
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_block_template_after_remove_block', $block );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$this->handle_exception_doing_action(
|
||||||
|
'Error after removing block from template.',
|
||||||
|
'woocommerce_block_template_after_remove_block',
|
||||||
|
$block,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the `woocommerce_block_template_area_{template_area}_after_remove_block_{block_id}` action.
|
||||||
|
* Handle exceptions thrown by the action.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*/
|
||||||
|
private function do_after_remove_specific_block_action( BlockInterface $block ) {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Action called after a specific block is removed from a template with a specific area.
|
||||||
|
*
|
||||||
|
* This action can be used to perform actions after a specific block is removed from a template with a specific area,
|
||||||
|
* such as removing a dependent block.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
*
|
||||||
|
* @since 8.2.0
|
||||||
|
*/
|
||||||
|
do_action( "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_remove_block_{$block->get_id()}", $block );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$this->handle_exception_doing_action(
|
||||||
|
'Error after removing block from template.',
|
||||||
|
"woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_remove_block_{$block->get_id()}",
|
||||||
|
$block,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an exception thrown by an action.
|
||||||
|
*
|
||||||
|
* @param string $message The message.
|
||||||
|
* @param string $action_tag The action tag.
|
||||||
|
* @param BlockInterface $block The block.
|
||||||
|
* @param \Exception $e The exception.
|
||||||
|
*/
|
||||||
|
private function handle_exception_doing_action( string $message, string $action_tag, BlockInterface $block, \Exception $e ) {
|
||||||
|
BlockTemplateLogger::get_instance()->error(
|
||||||
|
$message,
|
||||||
|
[
|
||||||
|
'exception' => $e,
|
||||||
|
'action' => $action_tag,
|
||||||
|
'container' => $this,
|
||||||
|
'block' => $block,
|
||||||
|
'template' => $this->get_root_template(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||||
|
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for block template modifications.
|
||||||
|
*/
|
||||||
|
class BlockTemplateLogger {
|
||||||
|
/**
|
||||||
|
* Singleton instance.
|
||||||
|
*
|
||||||
|
* @var BlockTemplateLogger
|
||||||
|
*/
|
||||||
|
protected static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance.
|
||||||
|
*
|
||||||
|
* @var \WC_Logger
|
||||||
|
*/
|
||||||
|
protected $logger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance.
|
||||||
|
*/
|
||||||
|
public static function get_instance(): BlockTemplateLogger {
|
||||||
|
if ( ! self::$instance ) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
protected function __construct() {
|
||||||
|
$this->logger = wc_get_logger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an informational message.
|
||||||
|
*
|
||||||
|
* @param string $message Message to log.
|
||||||
|
* @param array $info Additional info to log.
|
||||||
|
*/
|
||||||
|
public function info( string $message, array $info = [] ) {
|
||||||
|
$this->logger->info(
|
||||||
|
$this->format_message( $message, $info ),
|
||||||
|
[ 'source' => 'block_template' ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a warning message.
|
||||||
|
*
|
||||||
|
* @param string $message Message to log.
|
||||||
|
* @param array $info Additional info to log.
|
||||||
|
*/
|
||||||
|
public function warning( string $message, array $info = [] ) {
|
||||||
|
$this->logger->warning(
|
||||||
|
$this->format_message( $message, $info ),
|
||||||
|
[ 'source' => 'block_template' ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error message.
|
||||||
|
*
|
||||||
|
* @param string $message Message to log.
|
||||||
|
* @param array $info Additional info to log.
|
||||||
|
*/
|
||||||
|
public function error( string $message, array $info = [] ) {
|
||||||
|
$this->logger->error(
|
||||||
|
$this->format_message( $message, $info ),
|
||||||
|
[ 'source' => 'block_template' ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a message for logging.
|
||||||
|
*
|
||||||
|
* @param string $message Message to log.
|
||||||
|
* @param array $info Additional info to log.
|
||||||
|
*/
|
||||||
|
private function format_message( string $message, array $info = [] ): string {
|
||||||
|
$formatted_message = sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
$message,
|
||||||
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
|
||||||
|
print_r( $this->format_info( $info ), true ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $formatted_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format info for logging.
|
||||||
|
*
|
||||||
|
* @param array $info Info to log.
|
||||||
|
*/
|
||||||
|
private function format_info( array $info ): array {
|
||||||
|
$formatted_info = $info;
|
||||||
|
|
||||||
|
if ( isset( $info['exception'] ) && $info['exception'] instanceof \Exception ) {
|
||||||
|
$formatted_info['exception'] = $this->format_exception( $info['exception'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $info['container'] ) ) {
|
||||||
|
if ( $info['container'] instanceof BlockContainerInterface ) {
|
||||||
|
$formatted_info['container'] = $this->format_block( $info['container'] );
|
||||||
|
} elseif ( $info['container'] instanceof BlockTemplateInterface ) {
|
||||||
|
$formatted_info['container'] = $this->format_template( $info['container'] );
|
||||||
|
} elseif ( $info['container'] instanceof BlockInterface ) {
|
||||||
|
$formatted_info['container'] = $this->format_block( $info['container'] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $info['block'] ) && $info['block'] instanceof BlockInterface ) {
|
||||||
|
$formatted_info['block'] = $this->format_block( $info['block'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $info['template'] ) && $info['template'] instanceof BlockTemplateInterface ) {
|
||||||
|
$formatted_info['template'] = $this->format_template( $info['template'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $formatted_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an exception for logging.
|
||||||
|
*
|
||||||
|
* @param \Exception $exception Exception to format.
|
||||||
|
*/
|
||||||
|
private function format_exception( \Exception $exception ): array {
|
||||||
|
return [
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'source' => "{$exception->getFile()}: {$exception->getLine()}",
|
||||||
|
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
|
||||||
|
'trace' => print_r( $this->format_exception_trace( $exception->getTrace() ), true ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an exception trace for logging.
|
||||||
|
*
|
||||||
|
* @param array $trace Exception trace to format.
|
||||||
|
*/
|
||||||
|
private function format_exception_trace( array $trace ): array {
|
||||||
|
$formatted_trace = [];
|
||||||
|
|
||||||
|
foreach ( $trace as $source ) {
|
||||||
|
$formatted_trace[] = "{$source['file']}: {$source['line']}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $formatted_trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a block template for logging.
|
||||||
|
*
|
||||||
|
* @param BlockTemplateInterface $template Template to format.
|
||||||
|
*/
|
||||||
|
private function format_template( BlockTemplateInterface $template ): string {
|
||||||
|
return "{$template->get_id()} (area: {$template->get_area()})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a block for logging.
|
||||||
|
*
|
||||||
|
* @param BlockInterface $block Block to format.
|
||||||
|
*/
|
||||||
|
private function format_block( BlockInterface $block ): string {
|
||||||
|
return "{$block->get_id()} (name: {$block->get_name()})";
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
|
namespace Automattic\WooCommerce\Tests\Internal\Admin\BlockTemplates;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
|
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
|
||||||
|
|
||||||
use WC_Unit_Test_Case;
|
use WC_Unit_Test_Case;
|
||||||
|
@ -35,6 +36,140 @@ class BlockTemplateTest extends WC_Unit_Test_Case {
|
||||||
$this->assertSame( $block, $template->get_block( 'test-block-id' ) );
|
$this->assertSame( $block, $template->get_block( 'test-block-id' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the after_add_block hooks fires.
|
||||||
|
*/
|
||||||
|
public function test_after_add_block_hooks() {
|
||||||
|
$template = new BlockTemplate();
|
||||||
|
|
||||||
|
$hook_called = false;
|
||||||
|
|
||||||
|
$after_add_block_hook = function( BlockInterface $block ) use ( &$hook_called ) {
|
||||||
|
$hook_called = true;
|
||||||
|
|
||||||
|
if ( 'test-block-id' === $block->get_id() ) {
|
||||||
|
$hook_called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$root_template = $block->get_root_template();
|
||||||
|
|
||||||
|
if ( $root_template->get_block( 'test-block-id-2' ) ) {
|
||||||
|
// The block was already added, so just return.
|
||||||
|
// This short-circuiting done because this hook will be called two times:
|
||||||
|
// 1. When the `test-block-id` block is added to the root template.
|
||||||
|
// 2. When the `test-block-id-2` block is added to the template in this hook.
|
||||||
|
// Without this short-circuiting, the second time `add_block` is called in this
|
||||||
|
// hook would throw an exception, which is handled by the API (an error gets logged).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$root_template->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id-2',
|
||||||
|
'blockName' => 'test-block-name-2',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
add_action( 'woocommerce_block_template_after_add_block', $after_add_block_hook );
|
||||||
|
|
||||||
|
$specific_hook_called = false;
|
||||||
|
|
||||||
|
$specific_after_add_block_hook = function( BlockInterface $block ) use ( &$specific_hook_called ) {
|
||||||
|
if ( 'test-block-id' === $block->get_id() ) {
|
||||||
|
$specific_hook_called = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_action( 'woocommerce_block_template_area_uncategorized_after_add_block_test-block-id', $specific_after_add_block_hook );
|
||||||
|
|
||||||
|
$template->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id',
|
||||||
|
'blockName' => 'test-block-name',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$hook_called,
|
||||||
|
'Failed asserting that that the hook was called.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$specific_hook_called,
|
||||||
|
'Failed asserting that that the specific hook was called.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertNotNull(
|
||||||
|
$template->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the block was added to the template from the hook.'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
remove_action( 'woocommerce_block_template_after_add_block', $after_add_block_hook );
|
||||||
|
remove_action( 'woocommerce_block_template_area_uncategorized_after_add_block_test-block-id', $specific_after_add_block_hook );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the after_remove_block hooks fires.
|
||||||
|
*/
|
||||||
|
public function test_after_remove_block_hooks() {
|
||||||
|
$template = new BlockTemplate();
|
||||||
|
|
||||||
|
$hook_called = false;
|
||||||
|
|
||||||
|
$after_remove_block_hook = function( BlockInterface $block ) use ( &$hook_called ) {
|
||||||
|
$hook_called = true;
|
||||||
|
|
||||||
|
if ( 'test-block-id' === $block->get_id() ) {
|
||||||
|
$hook_called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$root_template = $block->get_root_template();
|
||||||
|
};
|
||||||
|
|
||||||
|
$specific_hook_called = false;
|
||||||
|
|
||||||
|
$specific_after_remove_block_hook = function( BlockInterface $block ) use ( &$specific_hook_called ) {
|
||||||
|
if ( 'test-block-id' === $block->get_id() ) {
|
||||||
|
$specific_hook_called = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
add_action( 'woocommerce_block_template_after_remove_block', $after_remove_block_hook );
|
||||||
|
add_action( 'woocommerce_block_template_area_uncategorized_after_remove_block_test-block-id', $specific_after_remove_block_hook );
|
||||||
|
|
||||||
|
$block = $template->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id',
|
||||||
|
'blockName' => 'test-block-name',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$block->remove();
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$hook_called,
|
||||||
|
'Failed asserting that that the hook was called.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$specific_hook_called,
|
||||||
|
'Failed asserting that that the specific hook was called.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$block->is_detached(),
|
||||||
|
'Failed asserting that the block was added to the template from the hook.'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
remove_action( 'woocommerce_block_template_after_remove_block', $after_remove_block_hook );
|
||||||
|
remove_action( 'woocommerce_block_template_area_uncategorized_after_remove_block_test-block-id', $specific_after_remove_block_hook );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test adding a block throws an exception if a block with the same ID already exists.
|
* Test adding a block throws an exception if a block with the same ID already exists.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -106,7 +106,7 @@ class BlockTest extends WC_Unit_Test_Case {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that removing a block from a block sets the parent and root template to null
|
* Test that removing a block from a block detaches it
|
||||||
* and that the block is removed from the root template.
|
* and that the block is removed from the root template.
|
||||||
*/
|
*/
|
||||||
public function test_remove_block() {
|
public function test_remove_block() {
|
||||||
|
@ -133,13 +133,19 @@ class BlockTest extends WC_Unit_Test_Case {
|
||||||
'Failed asserting that the child block was removed from the root template.'
|
'Failed asserting that the child block was removed from the root template.'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->expectException( \RuntimeException::class );
|
$this->assertNull(
|
||||||
|
$block->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the child block was removed from the parent.'
|
||||||
|
);
|
||||||
|
|
||||||
$child_block->get_parent();
|
$this->assertTrue(
|
||||||
|
$child_block->is_detached(),
|
||||||
|
'Failed asserting that the child block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that removing a block from a block sets the parent and root template to null
|
* Test that removing a block from a block detaches it
|
||||||
* and that the block is removed from the root template, as well as any descendants.
|
* and that the block is removed from the root template, as well as any descendants.
|
||||||
*/
|
*/
|
||||||
public function test_remove_nested_block() {
|
public function test_remove_nested_block() {
|
||||||
|
@ -166,13 +172,19 @@ class BlockTest extends WC_Unit_Test_Case {
|
||||||
'Failed asserting that the nested descendent block was removed from the root template.'
|
'Failed asserting that the nested descendent block was removed from the root template.'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->expectException( \RuntimeException::class );
|
$this->assertNull(
|
||||||
|
$block->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the nested descendent block was removed from the parent.'
|
||||||
|
);
|
||||||
|
|
||||||
$child_block->get_parent();
|
$this->assertTrue(
|
||||||
|
$child_block->is_detached(),
|
||||||
|
'Failed asserting that the nested descendent block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that removing a block from a block sets the parent and root template to null
|
* Test that removing a block from a block detaches it
|
||||||
* and that the block is removed from the root template, as well as any descendants.
|
* and that the block is removed from the root template, as well as any descendants.
|
||||||
*/
|
*/
|
||||||
public function test_remove_block_and_descendants() {
|
public function test_remove_block_and_descendants() {
|
||||||
|
@ -204,9 +216,41 @@ class BlockTest extends WC_Unit_Test_Case {
|
||||||
'Failed asserting that the nested descendent block was removed from the root template.'
|
'Failed asserting that the nested descendent block was removed from the root template.'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->expectException( \RuntimeException::class );
|
$this->assertNull(
|
||||||
|
$block->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the child block was removed from the parent.'
|
||||||
|
);
|
||||||
|
|
||||||
$child_block->get_parent();
|
$this->assertTrue(
|
||||||
|
$block->is_detached(),
|
||||||
|
'Failed asserting that the block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$child_block->is_detached(),
|
||||||
|
'Failed asserting that the child block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that removing a block by calling remove on it detaches it.
|
||||||
|
*/
|
||||||
|
public function test_remove_block_self() {
|
||||||
|
$template = new BlockTemplate();
|
||||||
|
|
||||||
|
$block = $template->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id',
|
||||||
|
'blockName' => 'test-block-name',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$block->remove();
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$block->is_detached(),
|
||||||
|
'Failed asserting that the block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -266,6 +310,49 @@ class BlockTest extends WC_Unit_Test_Case {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that a block added to a detached block is detached.
|
||||||
|
*/
|
||||||
|
public function test_block_added_to_detached_block_is_detached() {
|
||||||
|
$template = new BlockTemplate();
|
||||||
|
|
||||||
|
$block = $template->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id',
|
||||||
|
'blockName' => 'test-block-name',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$template->remove_block( 'test-block-id' );
|
||||||
|
|
||||||
|
$child_block = $block->add_block(
|
||||||
|
[
|
||||||
|
'id' => 'test-block-id-2',
|
||||||
|
'blockName' => 'test-block-name-2',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertNull(
|
||||||
|
$template->get_block( 'test-block-id' ),
|
||||||
|
'Failed asserting that the block was removed from the root template.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertNull(
|
||||||
|
$template->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the nested block is not in the root template.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertNotNull(
|
||||||
|
$block->get_block( 'test-block-id-2' ),
|
||||||
|
'Failed asserting that the nested block is in the parent.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
$child_block->is_detached(),
|
||||||
|
'Failed asserting that the nested descendent block is detached from its parent and root template.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that getting the block as a formatted template is structured correctly.
|
* Test that getting the block as a formatted template is structured correctly.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -42,7 +42,7 @@ class CustomBlockTemplate extends AbstractBlockTemplate {
|
||||||
*
|
*
|
||||||
* @param array $block_config The block data.
|
* @param array $block_config The block data.
|
||||||
*/
|
*/
|
||||||
public function add_custom_block( array $block_config ): BlockInterface {
|
public function add_custom_block( array $block_config ): CustomBlockInterface {
|
||||||
$block = new CustomBlock( $block_config, $this->get_root_template(), $this );
|
$block = new CustomBlock( $block_config, $this->get_root_template(), $this );
|
||||||
return $this->add_inner_block( $block );
|
return $this->add_inner_block( $block );
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
|
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\Block;
|
||||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
|
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockTemplate;
|
||||||
|
use Customers;
|
||||||
use WC_Unit_Test_Case;
|
use WC_Unit_Test_Case;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,13 +18,8 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
||||||
* Test that the add_block method does not exist by default on blocks.
|
* Test that the add_block method does not exist by default on blocks.
|
||||||
*/
|
*/
|
||||||
public function test_add_block_does_not_exist() {
|
public function test_add_block_does_not_exist() {
|
||||||
$template = new BlockTemplate();
|
$template = new CustomBlockTemplate();
|
||||||
$block = new CustomBlock(
|
$block = $template->add_custom_block( [ 'blockName' => 'test-block-name' ] );
|
||||||
[
|
|
||||||
'blockName' => 'test-block-name',
|
|
||||||
],
|
|
||||||
$template
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertFalse( method_exists( $block, 'add_block' ) );
|
$this->assertFalse( method_exists( $block, 'add_block' ) );
|
||||||
}
|
}
|
||||||
|
@ -33,13 +28,8 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
||||||
* Test that a custom block inserter method inserts as expected.
|
* Test that a custom block inserter method inserts as expected.
|
||||||
*/
|
*/
|
||||||
public function test_add_custom_inner_block() {
|
public function test_add_custom_inner_block() {
|
||||||
$template = new BlockTemplate();
|
$template = new CustomBlockTemplate();
|
||||||
$block = new CustomBlock(
|
$block = $template->add_custom_block( [ 'blockName' => 'test-block-name' ] );
|
||||||
[
|
|
||||||
'blockName' => 'test-block-name',
|
|
||||||
],
|
|
||||||
$template
|
|
||||||
);
|
|
||||||
|
|
||||||
$block->add_custom_inner_block( 'a' );
|
$block->add_custom_inner_block( 'a' );
|
||||||
$block->add_custom_inner_block( 'b' );
|
$block->add_custom_inner_block( 'b' );
|
||||||
|
@ -72,14 +62,8 @@ class CustomBlockTest extends WC_Unit_Test_Case {
|
||||||
* Test that a custom block is removed as expected.
|
* Test that a custom block is removed as expected.
|
||||||
*/
|
*/
|
||||||
public function test_remove_custom_inner_block() {
|
public function test_remove_custom_inner_block() {
|
||||||
$template = new BlockTemplate();
|
$template = new CustomBlockTemplate();
|
||||||
$block = new CustomBlock(
|
$block = $template->add_custom_block( [ 'blockName' => 'test-block-name' ] );
|
||||||
[
|
|
||||||
'blockName' => 'test-block-name',
|
|
||||||
],
|
|
||||||
$template
|
|
||||||
);
|
|
||||||
|
|
||||||
$block->add_custom_inner_block( 'a' );
|
$block->add_custom_inner_block( 'a' );
|
||||||
$block->add_custom_inner_block( 'b' );
|
$block->add_custom_inner_block( 'b' );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue