diff --git a/plugins/woocommerce-blocks/patterns/filters.php b/plugins/woocommerce-blocks/patterns/filters.php new file mode 100644 index 00000000000..66d6c44162e --- /dev/null +++ b/plugins/woocommerce-blocks/patterns/filters.php @@ -0,0 +1,24 @@ + + + +
+ + + +
+ + + +
+ + + +
+ diff --git a/plugins/woocommerce-blocks/phpcs.xml b/plugins/woocommerce-blocks/phpcs.xml index 70199828a4b..eac8154c883 100644 --- a/plugins/woocommerce-blocks/phpcs.xml +++ b/plugins/woocommerce-blocks/phpcs.xml @@ -43,12 +43,17 @@ src/ tests/php + patterns src/ tests/php + + patterns + + tests/ diff --git a/plugins/woocommerce-blocks/src/BlockPatterns.php b/plugins/woocommerce-blocks/src/BlockPatterns.php new file mode 100644 index 00000000000..290ad440837 --- /dev/null +++ b/plugins/woocommerce-blocks/src/BlockPatterns.php @@ -0,0 +1,201 @@ +

+ * + * Other settable fields include: + * + * - Description + * - Viewport Width + * - Categories (comma-separated values) + * - Keywords (comma-separated values) + * - Block Types (comma-separated values) + * - Inserter (yes/no) + * + * @internal + */ +class BlockPatterns { + const SLUG_REGEX = '/^[A-z0-9\/_-]+$/'; + const COMMA_SEPARATED_REGEX = '/[\s,]+/'; + + /** + * Path to the patterns directory. + * + * @var string $patterns_path + */ + private $patterns_path; + + /** + * Constructor for class + * + * @param Package $package An instance of Package. + */ + public function __construct( Package $package ) { + $this->patterns_path = $package->get_path( 'patterns' ); + + add_action( 'init', array( $this, 'register_block_patterns' ) ); + } + + /** + * Registers the block patterns and categories under `./patterns/`. + */ + public function register_block_patterns() { + if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) { + return; + } + + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'inserter' => 'Inserter', + ); + + if ( ! file_exists( $this->patterns_path ) ) { + return; + } + + $files = glob( $this->patterns_path . '/*.php' ); + if ( ! $files ) { + return; + } + + foreach ( $files as $file ) { + $pattern_data = get_file_data( $file, $default_headers ); + + if ( empty( $pattern_data['slug'] ) ) { + _doing_it_wrong( + 'register_block_patterns', + esc_html( + sprintf( + /* translators: %s: file name. */ + __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'woo-gutenberg-products-block' ), + $file + ) + ), + '6.0.0' + ); + continue; + } + + if ( ! preg_match( self::SLUG_REGEX, $pattern_data['slug'] ) ) { + _doing_it_wrong( + 'register_block_patterns', + esc_html( + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'woo-gutenberg-products-block' ), + $file, + $pattern_data['slug'] + ) + ), + '6.0.0' + ); + continue; + } + + if ( \WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { + continue; + } + + // Title is a required property. + if ( ! $pattern_data['title'] ) { + _doing_it_wrong( + 'register_block_patterns', + esc_html( + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'woo-gutenberg-products-block' ), + $file + ) + ), + '6.0.0' + ); + continue; + } + + // For properties of type array, parse data as comma-separated. + foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = array_filter( + preg_split( + self::COMMA_SEPARATED_REGEX, + (string) $pattern_data[ $property ] + ) + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type int. + foreach ( array( 'viewportWidth' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = (int) $pattern_data[ $property ]; + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type bool. + foreach ( array( 'inserter' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = in_array( + strtolower( $pattern_data[ $property ] ), + array( 'yes', 'true' ), + true + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', 'woo-gutenberg-products-block' ); + if ( ! empty( $pattern_data['description'] ) ) { + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', 'woo-gutenberg-products-block' ); + } + + // The actual pattern content is the output of the file. + ob_start(); + include $file; + $pattern_data['content'] = ob_get_clean(); + if ( ! $pattern_data['content'] ) { + continue; + } + + foreach ( $pattern_data['categories'] as $key => $category ) { + $category_slug = _wp_to_kebab_case( $category ); + + $pattern_data['categories'][ $key ] = $category_slug; + + register_block_pattern_category( + $category_slug, + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + array( 'label' => __( $category, 'woo-gutenberg-products-block' ) ) + ); + } + + register_block_pattern( $pattern_data['slug'], $pattern_data ); + } + } +} diff --git a/plugins/woocommerce-blocks/src/Domain/Bootstrap.php b/plugins/woocommerce-blocks/src/Domain/Bootstrap.php index 186c18c52cc..892ab36f58d 100644 --- a/plugins/woocommerce-blocks/src/Domain/Bootstrap.php +++ b/plugins/woocommerce-blocks/src/Domain/Bootstrap.php @@ -3,8 +3,8 @@ namespace Automattic\WooCommerce\Blocks\Domain; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; -use Automattic\WooCommerce\Blocks\Migration; use Automattic\WooCommerce\Blocks\AssetsController; +use Automattic\WooCommerce\Blocks\BlockPatterns; use Automattic\WooCommerce\Blocks\BlockTemplatesController; use Automattic\WooCommerce\Blocks\BlockTypesController; use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount; @@ -13,8 +13,7 @@ use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating; use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics; use Automattic\WooCommerce\Blocks\InboxNotifications; use Automattic\WooCommerce\Blocks\Installer; -use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate; -use Automattic\WooCommerce\Blocks\Templates\ClassicTemplatesCompatibility; +use Automattic\WooCommerce\Blocks\Migration; use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery; @@ -22,10 +21,12 @@ use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Automattic\WooCommerce\Blocks\Registry\Container; +use Automattic\WooCommerce\Blocks\Templates\ClassicTemplatesCompatibility; use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate; -use Automattic\WooCommerce\StoreApi\StoreApi; +use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate; use Automattic\WooCommerce\StoreApi\RoutesController; use Automattic\WooCommerce\StoreApi\SchemaController; +use Automattic\WooCommerce\StoreApi\StoreApi; /** * Takes care of bootstrapping the plugin. @@ -119,6 +120,7 @@ class Bootstrap { $this->container->get( ProductSearchResultsTemplate::class ); $this->container->get( ProductAttributeTemplate::class ); $this->container->get( ClassicTemplatesCompatibility::class ); + $this->container->get( BlockPatterns::class ); if ( $this->package->feature()->is_feature_plugin_build() ) { $this->container->get( PaymentsApi::class ); } @@ -332,6 +334,12 @@ class Bootstrap { return $container->get( StoreApi::class )::container()->get( RoutesController::class ); } ); + $this->container->register( + BlockPatterns::class, + function () { + return new BlockPatterns( $this->package ); + } + ); } /**