Skip to content
This repository has been archived by the owner on Nov 6, 2022. It is now read-only.

PHP API for registering blocks with Block Lab #434

Merged
merged 17 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from 12 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
14 changes: 14 additions & 0 deletions php/blocks/class-field.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ public function from_array( $config ) {
$this->settings = $config['settings'];
}

if ( ! isset( $config['type'] ) ) {
$control_class_name = 'Block_Lab\\Blocks\\Controls\\';
$control_class_name .= ucwords( $this->control, '_' );
if ( class_exists( $control_class_name ) ) {
/**
* An instance of the control, to retrieve the correct type.
*
* @var Control_Abstract $control_class
*/
$control_class = new $control_class_name();
$this->type = $control_class->type;
}
}

// Add any other non-default keys to the settings array.
$field_defaults = array( 'name', 'label', 'control', 'type', 'order', 'settings' );
$field_settings = array_diff( array_keys( $config ), $field_defaults );
Expand Down
88 changes: 68 additions & 20 deletions php/blocks/class-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ class Loader extends Component_Abstract {
protected $assets = [];

/**
* JSON representing last loaded blocks.
* An associative array of block config data for the blocks that will be registered.
*
* @var string
* The key of each item in the array is the block name.
*
* @var array
*/
protected $blocks = '';
protected $blocks = array();

/**
* Load the Loader.
Expand Down Expand Up @@ -100,7 +102,7 @@ protected function editor_assets() {
// Add dynamic Gutenberg blocks.
wp_add_inline_script(
'block-lab-blocks',
'const blockLabBlocks = ' . $this->blocks,
'const blockLabBlocks = ' . wp_json_encode( $this->blocks ),
'before'
);

Expand All @@ -112,8 +114,7 @@ protected function editor_assets() {
$this->plugin->get_version()
);

$blocks = json_decode( $this->blocks, true );
$block_names = wp_list_pluck( $blocks, 'name' );
$block_names = wp_list_pluck( $this->blocks, 'name' );

foreach ( $block_names as $block_name ) {
$this->enqueue_block_styles( $block_name, array( 'preview', 'block' ) );
Expand Down Expand Up @@ -155,9 +156,7 @@ protected function dynamic_block_loader() {
return;
}

$blocks = json_decode( $this->blocks, true );

foreach ( $blocks as $block_name => $block_config ) {
foreach ( $this->blocks as $block_name => $block_config ) {
$block = new Block();
$block->from_array( $block_config );
$this->register_block( $block_name, $block );
Expand Down Expand Up @@ -201,9 +200,7 @@ protected function register_block( $block_name, $block ) {
* @return array
*/
protected function register_categories( $categories ) {
$blocks = json_decode( $this->blocks, true );

foreach ( $blocks as $block_config ) {
foreach ( $this->blocks as $block_config ) {
if ( ! isset( $block_config['category'] ) ) {
continue;
}
Expand Down Expand Up @@ -495,11 +492,10 @@ protected function block_template( $name, $type = 'block' ) {
* Load all the published blocks and blocks/block.json files.
*/
protected function retrieve_blocks() {
$this->blocks = '';
$blocks = [];

// Retrieve blocks from blocks.json.
// Reverse to preserve order of preference when using array_merge.
/**
* Retrieve blocks from blocks.json.
* Reverse to preserve order of preference when using array_merge.
*/
$blocks_files = array_reverse( (array) block_lab()->locate_template( 'blocks/blocks.json', '', false ) );
foreach ( $blocks_files as $blocks_file ) {
// This is expected to be on the local filesystem, so file_get_contents() is ok to use here.
Expand All @@ -508,10 +504,13 @@ protected function retrieve_blocks() {

// Merge if no json_decode error occurred.
if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$blocks = array_merge( $blocks, $block_data );
$this->blocks = array_merge( $this->blocks, $block_data );
}
}

/**
* Retrieve blocks stored as posts in the WordPress database.
*/
$block_posts = new \WP_Query(
[
'post_type' => block_lab()->get_post_type_slug(),
Expand All @@ -527,11 +526,60 @@ protected function retrieve_blocks() {

// Merge if no json_decode error occurred.
if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$blocks = array_merge( $blocks, $block_data );
$this->blocks = array_merge( $this->blocks, $block_data );
}
}
}

$this->blocks = wp_json_encode( $blocks );
/**
* Use this action to add new blocks and fields with the block_lab_add_block and block_lab_add_field helper functions.
*/
do_action( 'block_lab_add_blocks' );

/**
* Filter the available blocks.
*
* This is used internally by the block_lab_add_block and block_lab_add_field helper functions,
* but it can also be used to hide certain blocks if desired.
*
* @param array $blocks An associative array of blocks.
*/
$this->blocks = apply_filters( 'block_lab_blocks', $this->blocks );
}

/**
* Add a new block.
*
* This method should be called during the block_lab_add_blocks action, to ensure
* that the block isn't added too late.
*
* @param array $block_config The config of the block to add.
*/
public function add_block( $block_config ) {
if ( ! isset( $block_config['name'] ) ) {
return;
}

$this->blocks[ "block-lab/{$block_config['name']}" ] = $block_config;
}

/**
* Add a new field to an existing block.
*
* This method should be called during the block_lab_add_blocks action, to ensure
* that the block isn't added too late.
*
* @param string $block_name The name of the block that the field is added to.
* @param array $field_config The config of the field to add.
*/
public function add_field( $block_name, $field_config ) {
if ( ! isset( $this->blocks[ "block-lab/{$block_name}" ] ) ) {
return;
}
if ( ! isset( $field_config['name'] ) ) {
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it alright that I made this change? Maybe something else was intended.

Copy link
Member Author

@lukecarbis lukecarbis Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, nothing else intended. Good pickup.

}

$this->blocks[ "block-lab/{$block_name}" ]['fields'][ $field_config['name'] ] = $field_config;
}
}
69 changes: 69 additions & 0 deletions php/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,72 @@ function block_field_config( $name ) {
}
return (array) $block_lab_config->fields[ $name ];
}

/**
* Add a new block.
*
* @param string $block_name The block name (slug), like 'example-block'.
* @param array $block_config {
* An associative array containing the block configuration.
*
* @type string $title The block title.
* @type string $icon The block icon. See assets/icons.json for a JSON array of all possible values. Default: 'block_lab'.
* @type string $category The slug of a registered category. Categories include: common, formatting, layout, widgets, embed. Default: 'common'.
* @type array $excluded Exclude the block in these post types. Default: [].
* @type string[] $keywords An array of up to three keywords. Default: [].
* @type array $fields {
* An associative array containing block fields. Each key in the array should be the field slug.
*
* @type array {$slug} {
* An associative array describing a field. Refer to the $field_config parameter of block_lab_add_field().
* }
* }
* }
*/
function block_lab_add_block( $block_name, $block_config = array() ) {
$block_config['name'] = str_replace( '_', '-', sanitize_title( $block_name ) );

$default_config = array(
'title' => str_replace( '-', ' ', ucwords( $block_config['name'], '-' ) ),
'icon' => 'block_lab',
'category' => 'common',
'excluded' => array(),
'keywords' => array(),
'fields' => array(),
);

$block_config = wp_parse_args( $block_config, $default_config );
block_lab()->loader->add_block( $block_config );
}

/**
* Add a field to a block.
*
* @param string $block_name The block name (slug), like 'example-block'.
* @param string $field_name The field name (slug), like 'first-name'.
* @param array $field_config {
* An associative array containing the field configuration.
*
* @type string $name The field name.
* @type string $label The field label.
* @type string $control The field control type. Default: 'text'.
* @type int $order The order that the field appears in. Default: 0.
* @type array $settings {
* An associative array of settings for the field. Each field has a different set of possible settings.
* Check the register_settings method for the field, found in php/blocks/controls/class-{field name}.php.
* }
* }
*/
function block_lab_add_field( $block_name, $field_name, $field_config = array() ) {
$field_config['name'] = str_replace( '_', '-', sanitize_title( $field_name ) );

$default_config = array(
'label' => str_replace( '-', ' ', ucwords( $field_config['name'], '-' ) ),
'control' => 'text',
'order' => 0,
'settings' => array(),
);

$field_config = wp_parse_args( $field_config, $default_config );
block_lab()->loader->add_field( $block_name, $field_config );
}
67 changes: 67 additions & 0 deletions tests/php/unit/blocks/test-class-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ class Test_Loader extends Abstract_Template {
*/
public $instance;

/**
* A mock block config without a name.
*
* @var array
*/
public $block_config_without_name = array(
'foo' => 'Example Value'
);

/**
* A mock block config with a name.
*
* @var array
*/
public $block_config_with_name = array(
'name' => 'Example Block'
);

/**
* Test init.
*
Expand Down Expand Up @@ -318,6 +336,55 @@ function( $directory ) use ( $overridden_theme_template_path ) {
$this->assertContains( $expected_overriden_template_contents, ob_get_clean() );
}

/**
* Test add_block.
*
* @covers \Block_Lab\Blocks\Loader::add_block()
*/
public function test_add_block() {
// The block config does not have a name, so it should not be added to the $blocks property.
$this->instance->add_block( $this->block_config_without_name );
$this->assertEmpty( $this->get_protected_property( 'blocks' ) );

// Now that the block config has a name, it should be added to the $blocks property.
$this->instance->add_block( $this->block_config_with_name );
$actual_blocks = $this->get_protected_property( 'blocks' );
$this->assertEquals(
$this->block_config_with_name,
$actual_blocks[ "block-lab/{$this->block_config_with_name['name']}" ]
);
}

/**
* Test add_field.
*
* @covers \Block_Lab\Blocks\Loader::add_field()
*/
public function test_add_field() {
$block_name = 'example-block';
$full_block_name = "block-lab/{$block_name}";
$field_name = 'baz-field';
$field_config_with_name = [ 'name' => $field_name ];
$field_config_without_name = [ 'baz' => 'example' ];

// The block does not exist in the $blocks property, so this should not add anything to it.
$this->instance->add_field( $block_name, $field_config_with_name );
$this->assertEmpty( $this->get_protected_property( 'blocks' ) );

// The second argument doesn't have a 'name' value, so this shouldn't add anything to the $blocks property.
$this->instance->add_field( $block_name, $field_config_without_name );
$this->assertEmpty( $this->get_protected_property( 'blocks' ) );

// Now that the block name exists in the $blocks property, this should add the field to it.
$this->set_protected_property( 'blocks', [ $full_block_name => [] ] );
$this->instance->add_field( $block_name, $field_config_with_name );
$actual_blocks = $this->get_protected_property( 'blocks' );
$this->assertEquals(
$field_config_with_name,
$actual_blocks[ $full_block_name ]['fields'][ $field_name ]
);
}

/**
* Gets the full paths of the template CSS files, in order of reverse priority.
*
Expand Down
16 changes: 15 additions & 1 deletion tests/php/unit/helpers/class-abstract-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public function invoke_protected_method( $method_name, $args = array() ) {
* Gets a protected property's value.
*
* @param string $property The name of the property to get.
* @return mixed The property value
* @return mixed The property value.
* @throws ReflectionException For a non-accessible property.
*/
public function get_protected_property( $property ) {
Expand All @@ -115,6 +115,20 @@ public function get_protected_property( $property ) {
return $property->getValue( $this->instance );
}

/**
* Sets a protected property's value.
*
* @param string $property The name of the property to get.
* @param mixed $value The new property value.
* @throws ReflectionException For a non-accessible property.
*/
public function set_protected_property( $property, $value ) {
$reflection = new \ReflectionObject( $this->instance );
$property = $reflection->getProperty( $property );
$property->setAccessible( true );
$property->setValue( $this->instance, $value );
}

/**
* Gets the directories that block templates and CSS files could be in.
*/
Expand Down