Skip to content

Commit

Permalink
BlockTemplateController (woocommerce#4981)
Browse files Browse the repository at this point in the history
* BlockTemplateController

* Check if theme already has template

* ThemeUtils file to check for FSE enabled themes

* Use Gutenberg global gutenberg_supports_block_templates

* Remove ThemeUtils reference

* Update with code review comments

* Delete ThemeUtils and move supports_block_templates check

* Duplicate functions from Gutenberg into Utils file

* Remove template file

* Check template directory and stylesheet directory for template
  • Loading branch information
tjcafferkey authored and jonny-bull committed Dec 14, 2021
1 parent 87a1149 commit 78d8a1f
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/BlockTemplatesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
* BlockTypesController class.
*
* @internal
*/
class BlockTemplatesController {

/**
* Holds the path for the directory where the block templates will be kept.
*
* @var string
*/
private $templates_directory;

/**
* Directory name of the block template directory.
*
* @var string
*/
const TEMPLATES_DIR_NAME = 'block-templates';

/**
* Constructor.
*/
public function __construct() {
$this->templates_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATES_DIR_NAME;
$this->init();
}

/**
* Initialization method.
*/
protected function init() {
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
}

/**
* Add the block template objects to be used.
*
* @param array $query_result Array of template objects.
* @return array
*/
public function add_block_templates( $query_result ) {
if ( ! gutenberg_supports_block_templates() ) {
return $query_result;
}

$template_files = $this->get_block_templates();

foreach ( $template_files as $template_file ) {
$query_result[] = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, 'wp_template' );
}

return $query_result;
}

/**
* Get and build the block template objects from the block template files.
*
* @return array
*/
public function get_block_templates() {
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $this->templates_directory );
$templates = array();

foreach ( $template_files as $template_file ) {
$template_slug = substr(
$template_file,
strpos( $template_file, self::TEMPLATES_DIR_NAME . DIRECTORY_SEPARATOR ) + 1 + strlen( self::TEMPLATES_DIR_NAME ),
-5
);

// If the theme already has a template then there is no need to load ours in.
if ( $this->theme_has_template( $template_slug ) ) {
continue;
}

$new_template_item = array(
'title' => ucwords( str_replace( '-', ' ', $template_slug ) ),
'slug' => $template_slug,
'path' => $template_file,
'theme' => get_template_directory(),
'type' => 'wp_template',
);
$templates[] = $new_template_item;
}

return $templates;
}

/**
* Check if the theme has a template. So we know if to load our own in or not.
*
* @param string $template_name name of the template file without .html extension e.g. 'single-product'.
* @return boolean
*/
public function theme_has_template( $template_name ) {
return is_readable( get_template_directory() . '/block-templates/' . $template_name . '.html' ) ||
is_readable( get_stylesheet_directory() . '/block-templates/' . $template_name . '.html' );
}
}
12 changes: 12 additions & 0 deletions src/Domain/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\BlockTypesController;
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
use Automattic\WooCommerce\Blocks\InboxNotifications;
use Automattic\WooCommerce\Blocks\Installer;
use Automattic\WooCommerce\Blocks\Registry\Container;
Expand Down Expand Up @@ -101,6 +102,9 @@ function() {
$this->container->get( RestApi::class );
$this->container->get( GoogleAnalytics::class );
$this->container->get( BlockTypesController::class );
if ( $this->package->feature()->is_experimental_build() ) {
$this->container->get( BlockTemplatesController::class );
}
if ( $this->package->feature()->is_feature_plugin_build() ) {
$this->container->get( PaymentsApi::class );
}
Expand Down Expand Up @@ -227,6 +231,14 @@ function ( Container $container ) {
return new BlockTypesController( $asset_api, $asset_data_registry );
}
);
if ( $this->package->feature()->is_experimental_build() ) {
$this->container->register(
BlockTemplatesController::class,
function ( Container $container ) {
return new BlockTemplatesController();
}
);
}
$this->container->register(
DraftOrders::class,
function( Container $container ) {
Expand Down
131 changes: 131 additions & 0 deletions src/Utils/BlockTemplateUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
* BlockTemplateUtils class used for serving block templates from Woo Blocks.
* IMPORTANT: These methods have been duplicated from Gutenberg/lib/full-site-editing/block-templates.php as those functions are not for public usage.
*/
class BlockTemplateUtils {
/**
* Returns an array containing the references of
* the passed blocks and their inner blocks.
*
* @param array $blocks array of blocks.
*
* @return array block references to the passed blocks and their inner blocks.
*/
public static function gutenberg_flatten_blocks( &$blocks ) {
$all_blocks = array();
$queue = array();
foreach ( $blocks as &$block ) {
$queue[] = &$block;
}
$queue_count = count( $queue );

while ( $queue_count > 0 ) {
$block = &$queue[0];
array_shift( $queue );
$all_blocks[] = &$block;

if ( ! empty( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as &$inner_block ) {
$queue[] = &$inner_block;
}
}

$queue_count = count( $queue );
}

return $all_blocks;
}

/**
* Parses wp_template content and injects the current theme's
* stylesheet as a theme attribute into each wp_template_part
*
* @param string $template_content serialized wp_template content.
*
* @return string Updated wp_template content.
*/
public static function gutenberg_inject_theme_attribute_in_content( $template_content ) {
$has_updated_content = false;
$new_content = '';
$template_blocks = parse_blocks( $template_content );

$blocks = self::gutenberg_flatten_blocks( $template_blocks );
foreach ( $blocks as &$block ) {
if (
'core/template-part' === $block['blockName'] &&
! isset( $block['attrs']['theme'] )
) {
$block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
$has_updated_content = true;
}
}

if ( $has_updated_content ) {
foreach ( $template_blocks as &$block ) {
$new_content .= serialize_block( $block );
}

return $new_content;
}

return $template_content;
}

/**
* Build a unified template object based on a theme file.
*
* @param array $template_file Theme file.
* @param array $template_type wp_template or wp_template_part.
*
* @return WP_Block_Template Template.
*/
public static function gutenberg_build_template_result_from_file( $template_file, $template_type ) {
$default_template_types = gutenberg_get_default_template_types();
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$template_content = file_get_contents( $template_file['path'] );
$theme = wp_get_theme()->get_stylesheet();

$template = new \WP_Block_Template();
$template->id = $theme . '//' . $template_file['slug'];
$template->theme = $theme;
$template->content = self::gutenberg_inject_theme_attribute_in_content( $template_content );
$template->slug = $template_file['slug'];
$template->source = 'theme';
$template->type = $template_type;
$template->title = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug'];
$template->status = 'publish';
$template->has_theme_file = true;

if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
$template->description = $default_template_types[ $template_file['slug'] ]['description'];
$template->title = $default_template_types[ $template_file['slug'] ]['title'];
}

if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) {
$template->area = $template_file['area'];
}

return $template;
}

/**
* Finds all nested template part file paths in a theme's directory.
*
* @param string $base_directory The theme's file path.
* @return array $path_list A list of paths to all template part files.
*/
public static function gutenberg_get_template_paths( $base_directory ) {
$path_list = array();
if ( file_exists( $base_directory ) ) {
$nested_files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $base_directory ) );
$nested_html_files = new \RegexIterator( $nested_files, '/^.+\.html$/i', \RecursiveRegexIterator::GET_MATCH );
foreach ( $nested_html_files as $path => $file ) {
$path_list[] = $path;
}
}
return $path_list;
}
}

0 comments on commit 78d8a1f

Please sign in to comment.