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

Commit

Permalink
Merge pull request #434 from getblocklab/feature/php-api
Browse files Browse the repository at this point in the history
PHP API for registering blocks with Block Lab
  • Loading branch information
lukecarbis authored Oct 22, 2019
2 parents 5b44a55 + 2cfd4ad commit 8306071
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 21 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"require-dev": {
"brain/monkey": "^2",
"mockery/mockery": "^1.2.4",
"squizlabs/php_codesniffer": "^3.4",
"wp-coding-standards/wpcs": "2.1.1"
}
Expand Down
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();

/**
* A data store for sharing data to helper functions.
Expand Down Expand Up @@ -138,7 +140,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 @@ -150,8 +152,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 @@ -193,9 +194,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 @@ -239,9 +238,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 @@ -531,11 +528,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 @@ -544,10 +540,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 @@ -563,11 +562,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;
}

$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 @@ -318,3 +318,72 @@ function block_field_config( $name ) {

return (array) $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 );
}
19 changes: 19 additions & 0 deletions tests/php/unit/blocks/test-class-field.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ public function test_from_array() {
$this->assertAttributeNotEmpty( 'settings', $this->instance->settings['sub_fields']['baz'] );
}

/**
* Test from_array when there is no 'type' in the $config argument.
*
* @covers \Block_Lab\Blocks\Field::from_array()
*/
public function test_from_array_without_type() {
$this->instance->from_array( [ 'control' => 'rich_text' ] );
$this->assertEquals( 'string', $this->instance->type );

$this->instance = new Blocks\Field( array() );
$this->instance->from_array( [ 'control' => 'post' ] );
$this->assertEquals( 'object', $this->instance->type );

// The control class doesn't exist, so this shouldn't change the default value of $type, 'string'.
$this->instance = new Blocks\Field( array() );
$this->instance->from_array( [ 'control' => 'non-existent' ] );
$this->assertEquals( 'string', $this->instance->type );
}

/**
* Test to_array.
*
Expand Down
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'
);

/**
* Teardown.
*
Expand Down Expand Up @@ -376,6 +394,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
2 changes: 1 addition & 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 Down
Loading

0 comments on commit 8306071

Please sign in to comment.