Skip to content

Instantly share code, notes, and snippets.

@codersantosh
Last active November 4, 2024 09:48
Show Gist options
  • Select an option

  • Save codersantosh/d1740e1fb832df684601aab2e0451786 to your computer and use it in GitHub Desktop.

Select an option

Save codersantosh/d1740e1fb832df684601aab2e0451786 to your computer and use it in GitHub Desktop.
Automatic registration of patterns within the "patterns" folder for active plugins, similar to how it works for themes.
<?php // phpcs:ignore Class file names should be based on the class name with "class-" prepended.
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WP_Plugin_Pattern Class
*
* Handles the management of block patterns for the plugin.
*/
final class WP_Plugin_Pattern {
/**
* Cache hash used for caching block patterns.
*
* @var string
*/
private $cache_hash;
/**
* Plugin directory path.
*
* @var string
*/
private $plugin_dir;
/**
* Plugin version.
*
* @var string
*/
private $plugin_version;
/**
* Constructor method to set the plugin directory, version, and cache hash.
*
* @param string $plugin_dir Plugin directory path.
* @param string $plugin_version Plugin version.
*/
public function __construct( $plugin_dir, $plugin_version ) {
$this->plugin_dir = rtrim( $plugin_dir, '/' ); // Ensure no trailing slash.
$this->cache_hash = md5( $this->plugin_dir );
$this->plugin_version = $plugin_version;
}
/**
* Gets block pattern data for the plugin.
*
* @return array Block pattern data.
*/
public function get_block_patterns() {
$can_use_cached = ! wp_is_development_mode( 'plugin' );
$pattern_data = $this->get_pattern_cache();
if ( is_array( $pattern_data ) ) {
if ( $can_use_cached ) {
return $pattern_data;
}
// Clear pattern cache if in development mode.
$this->delete_pattern_cache();
}
$dirpath = $this->plugin_dir . '/patterns/';
$pattern_data = array();
if ( ! file_exists( $dirpath ) ) {
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
$files = glob( $dirpath . '*.php' );
if ( ! $files ) {
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
$default_headers = array(
'title' => 'Title',
'slug' => 'Slug',
'description' => 'Description',
'viewportWidth' => 'Viewport Width',
'inserter' => 'Inserter',
'categories' => 'Categories',
'keywords' => 'Keywords',
'blockTypes' => 'Block Types',
'postTypes' => 'Post Types',
'templateTypes' => 'Template Types',
);
$properties_to_parse = array(
'categories',
'keywords',
'blockTypes',
'postTypes',
'templateTypes',
);
foreach ( $files as $file ) {
$pattern = get_file_data( $file, $default_headers );
if ( empty( $pattern['slug'] ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name. */
__( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
$file
),
'6.0.0'
);
continue;
}
if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name; 2: slug value found. */
__( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
$file,
$pattern['slug']
),
'6.0.0'
);
}
// Title is a required property.
if ( ! $pattern['title'] ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name. */
__( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
$file
),
'6.0.0'
);
continue;
}
// Parse comma-separated properties as arrays.
foreach ( $properties_to_parse as $property ) {
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
} else {
unset( $pattern[ $property ] );
}
}
// Parse integer properties.
$property = 'viewportWidth';
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = (int) $pattern[ $property ];
} else {
unset( $pattern[ $property ] );
}
// Parse boolean properties.
$property = 'inserter';
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = in_array(
strtolower( $pattern[ $property ] ),
array( 'yes', 'true' ),
true
);
} else {
unset( $pattern[ $property ] );
}
$key = str_replace( $dirpath, '', $file );
$pattern_data[ $key ] = $pattern;
}
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
/**
* Gets block pattern cache for the plugin.
*
* @return array|false Cached pattern data or false if not found.
*/
private function get_pattern_cache() {
$pattern_data = get_site_transient( $this->get_cache_key() );
if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->plugin_version ) {
return $pattern_data['patterns'];
}
return false;
}
/**
* Sets block pattern cache for the plugin.
*
* @param array $patterns Block pattern data to cache.
*/
private function set_pattern_cache( array $patterns ) {
$pattern_data = array(
'version' => $this->plugin_version,
'patterns' => $patterns,
);
$cache_expiration = (int) apply_filters( 'wp_plugin_files_cache_ttl', DAY_IN_SECONDS );
set_site_transient( $this->get_cache_key(), $pattern_data, $cache_expiration );
}
/**
* Clears block pattern cache for the plugin.
*/
public function delete_pattern_cache() {
delete_site_transient( $this->get_cache_key() );
}
/**
* Generates the cache key based on the plugin's cache hash.
*
* @return string Cache key for block patterns.
*/
private function get_cache_key() {
return 'wp_plugin_files_patterns-' . $this->cache_hash;
}
}
/**
* Register any patterns that active plugins may provide under their
* `./patterns/` directory.
*
* @since 1.0.0
* @access private
*/
function _register_plugin_block_patterns() {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
/*
* Retrieve the list of active plugins.
*/
$plugins = get_option( 'active_plugins' );
$registry = WP_Block_Patterns_Registry::get_instance();
foreach ( $plugins as $plugin_file ) {
// Construct the full path to the plugin directory.
$plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file );
$dirpath = $plugin_dir . '/patterns/';
// Check if the patterns directory exists.
if ( ! is_dir( $dirpath ) ) {
continue; // Skip this plugin if the patterns directory does not exist.
}
// Get the plugin metadata, such as version and text domain.
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
$plugin_version = $plugin_data['Version'];
$text_domain = $plugin_data['TextDomain'];
// Initialize the WP_Plugin_Pattern class with the full plugin path and version.
$plugin_pattern_manager = new WP_Plugin_Pattern( $plugin_dir, $plugin_version );
// Get the block patterns from the plugin.
$patterns = $plugin_pattern_manager->get_block_patterns();
foreach ( $patterns as $file => $pattern_data ) {
// Check if the pattern is already registered.
if ( $registry->is_registered( $pattern_data['slug'] ) ) {
continue;
}
$file_path = $dirpath . $file;
// Ensure the pattern file exists.
if ( ! file_exists( $file_path ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: %s: file name. */
__( 'Could not register file "%s" as a block pattern as the file does not exist.' ),
$file
),
'1.0.0'
);
$plugin_pattern_manager->delete_pattern_cache();
continue;
}
$pattern_data['filePath'] = $file_path;
// Translate the pattern metadata.
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
if ( ! empty( $pattern_data['description'] ) ) {
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
}
// Register the block pattern.
register_block_pattern( $pattern_data['slug'], $pattern_data );
}
}
}
// Hook the function to the 'init' action.
add_action( 'init', '_register_plugin_block_patterns' );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment