* @copyright Copyright (c) 2022, Phi Phan */ namespace BoldBlocks; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; if ( ! class_exists( Patterns::class ) ) : /** * Manage custom patterns. */ class Patterns extends CoreComponent { /** * Post type * * @var string */ protected $post_type = 'boldblocks_pattern'; /** * Post type helper instance * * @var PostType */ protected $post_type_helper; /** * Store all custom patterns * * @var array */ protected $all_custom_patterns = []; /** * Max items that can query * * @var integer */ protected $max_items = 200; /** * Constructor */ public function __construct( $the_plugin_instance ) { parent::__construct( $the_plugin_instance ); $this->post_type_helper = PostType::get_instance(); } /** * Run main hooks * * @return void */ public function run() { // Register custom post type. add_action( 'init', [ $this, 'register_custom_post_type' ], 5 ); // Load data for js. add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] ); // Register patterns. add_action( 'init', [ $this, 'register_patterns' ] ); // Custom pattern columns. add_filter( 'manage_edit-' . $this->post_type . '_columns', [ $this, 'add_pattern_custom_columns' ] ); add_action( 'manage_' . $this->post_type . '_posts_custom_column', [ $this, 'manage_pattern_columns' ], 10, 2 ); // Add rest api endpoint to query all pattern categories. add_action( 'rest_api_init', [ $this, 'register_get_pattern_categories_endpoint' ] ); // Register rest fields. add_action( 'rest_api_init', [ $this, 'register_rest_fields' ] ); // Add meta fields to meta revisioning. add_filter( 'boldblocks_post_revision_meta_keys', [ $this, 'add_fields_to_revision_meta_keys' ] ); // Change the placeholder text. add_filter( 'enter_title_here', [ $this, 'enter_title_here' ] ); // Handle save post. add_action( 'save_post_' . $this->post_type, [ $this, 'save_post' ], 10, 2 ); // Clear the transient cache on upgraded. add_action( 'cbb/version_upgraded', [ $this, 'clear_transient_cache' ] ); // Force to remove custom fields. add_action( 'add_meta_boxes', [ $this, 'remove_meta_boxes' ] ); } /** * Register custom post type * * @return void */ public function register_custom_post_type() { // Register pattern type. $this->post_type_helper->register_post_type( $this->post_type, [ 'rest_base' => 'boldblocks-patterns', 'show_in_menu' => 'edit.php?post_type=boldblocks_block', 'capabilities' => [ 'read_post' => 'manage_options', 'edit_post' => 'manage_options', 'delete_post' => 'manage_options', 'edit_posts' => 'manage_options', 'delete_posts' => 'manage_options', ], ], [ 'name' => _x( 'Patterns', 'post type general name', 'content-blocks-builder' ), 'singular_name' => _x( 'Pattern', 'post type singular name', 'content-blocks-builder' ), 'name_admin_bar' => _x( 'Pattern', 'add new on admin bar', 'content-blocks-builder' ), 'all_items' => __( 'All Patterns', 'content-blocks-builder' ), 'add_new' => __( 'Add New Pattern', 'content-blocks-builder' ), ] ); // Pattern keywords. $this->post_type_helper->register_taxonomy( 'boldblocks_pattern_keywords', $this->post_type, [], [ 'name' => _x( 'Keywords', 'Taxonomy General Name', 'content-blocks-builder' ), 'singular_name' => _x( 'Keyword', 'Taxonomy Singular Name', 'content-blocks-builder' ), ] ); // Meta fields. $this->register_meta( 'boldblocks_pattern_categories', [ 'type' => 'array', 'show_in_rest' => array( 'schema' => array( 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', ), 'label' => array( 'type' => 'string', ), ), ), ), ), 'default' => [], ] ); $this->register_meta( 'boldblocks_pattern_description' ); $this->register_meta( 'boldblocks_pattern_dependencies', [ 'type' => 'array', 'show_in_rest' => array( 'schema' => array( 'items' => array( 'type' => 'object', 'properties' => array( 'slug' => array( 'type' => 'string', ), 'name' => array( 'type' => 'string', ), ), ), ), ), 'default' => [], ] ); $this->register_meta( 'boldblocks_pattern_dependent_blocks', [ 'show_in_rest' => array( 'schema' => array( 'items' => array( 'type' => 'string', ), ), ), 'type' => 'array', 'default' => [], ] ); $this->register_meta( 'boldblocks_pattern_block_types', [ 'type' => 'array', 'show_in_rest' => array( 'schema' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), ), 'default' => [], ] ); $this->register_meta( 'boldblocks_pattern_disabled_inserter', [ 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => false, ] ); // Setting fields. // phpcs:disable PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic register_setting( 'cbb', 'cbb_pattern_categories', [ 'type' => 'array', 'show_in_rest' => array( 'schema' => array( 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', ), 'label' => array( 'type' => 'string', ), ), ), ), ), 'sanitize_callback' => function( $value ) { if ( ! is_array( $value ) ) { return []; } $categories = []; foreach ( $value as $category ) { if ( ! is_array( $category ) ) { continue; } $categories[] = [ 'name' => sanitize_text_field( $category['name'] ?? '' ), 'label' => sanitize_text_field( $category['label'] ?? '' ), ]; } return $categories; }, 'default' => [], ] ); register_setting( 'cbb', 'cbb_pattern_categories_all_label', [ 'type' => 'string', 'show_in_rest' => true, 'default' => __( 'All patterns by CBB', 'content-blocks-builder' ), ] ); // phpcs:enable PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic } /** * Register meta field * * @param string $meta_key * @param array $args * @return void */ private function register_meta( $meta_key, $args = [] ) { $this->post_type_helper->register_meta( $this->post_type, $meta_key, $args ); } /** * Remove custom meta boxes * * @param string $post_type * @return void */ public function remove_meta_boxes( $post_type ) { if ( $post_type === $this->post_type ) { remove_meta_box( 'postcustom', false, 'normal' ); } } /** * Enqueue editor assets * * @return void */ public function enqueue_block_editor_assets() { // Custom blocks access file. $patterns_asset = $this->the_plugin_instance->include_file( 'build/patterns.asset.php' ); // Scripts. wp_enqueue_script( 'boldblocks-patterns', $this->the_plugin_instance->get_file_uri( '/build/patterns.js' ), $patterns_asset['dependencies'] ?? [], $this->the_plugin_instance->get_script_version( $patterns_asset ), [ 'in_footer' => true ] ); // Add translation. wp_set_script_translations( 'boldblocks-patterns', 'content-blocks-builder' ); } /** * Register all custom patterns * * @return void */ public function register_patterns() { // Register custom categories. $pattern_categories = get_option( 'cbb_pattern_categories', [] ); if ( is_array( $pattern_categories ) ) { foreach ( $pattern_categories as $category ) { if ( ! empty( $category['name'] ) && ! empty( $category['label'] ) ) { if ( ! \WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( $category['name'] ) ) { register_block_pattern_category( $category['name'], [ 'label' => $category['label'] ] ); } } } } // Register a custom category for patterns made by CBB. register_block_pattern_category( 'cbb', [ 'label' => get_option( 'cbb_pattern_categories_all_label', _x( 'All patterns by CBB', 'CBB block pattern category', 'content-blocks-builder' ) ) ] ); if ( apply_filters( 'boldblocks_load_patterns', true ) ) { // Query all patterns. $pattern_posts = $this->get_all_custom_patterns(); foreach ( $pattern_posts as $pattern_name => $pattern_settings ) { register_block_pattern( sprintf( 'boldblocks/%s', $pattern_name ), $pattern_settings ); } } } /** * Get all custom patterns. * * @return array */ public function get_all_custom_patterns() { if ( ! $this->all_custom_patterns ) { $this->all_custom_patterns = $this->get_posts( $this->the_plugin_instance->is_debug_mode() ); } return $this->all_custom_patterns; } /** * Query posts and cache the results. * * @param bool $force_refresh Optional. Whether to force the cache to be refreshed. Default false. * @return array Array of WP_Post objects. */ public function get_posts( $force_refresh = false ) { if ( $force_refresh ) { return $this->query_posts(); } $cache_key = 'bb_pattern_posts'; $posts = get_transient( $cache_key ); // If nothing is found, build the object. if ( false === $posts ) { // Query posts. $posts = $this->query_posts(); if ( ! is_wp_error( $posts ) && count( $posts ) > 0 ) { set_transient( $cache_key, $posts, HOUR_IN_SECONDS ); } } return $posts; } /** * Query and parse patterns * * @return array */ public function query_posts() { // Query all patterns. $raw_posts = get_posts( [ 'post_type' => $this->post_type, 'posts_per_page' => $this->get_max_items(), 'orderby' => [ 'menu_order' => 'DESC', 'date' => 'DESC', ], ] ); $pattern_posts = []; foreach ( $raw_posts as $pattern_post ) { $blocks = parse_blocks( $pattern_post->post_content ); if ( count( $blocks ) === 0 ) { continue; } $categories = get_post_meta( $pattern_post->ID, 'boldblocks_pattern_categories', true ); if ( ! is_array( $categories ) ) { $categories = []; } else { $categories = array_map( function( $item ) { return $item['name'] ?? ''; }, $categories ); } $categories[] = 'cbb'; $pattern_settings = [ 'title' => wp_strip_all_tags( $pattern_post->post_title ), 'content' => $pattern_post->post_content, 'categories' => $categories, 'description' => get_post_meta( $pattern_post->ID, 'boldblocks_pattern_description', true ) ?? '', 'inserter' => ! ( get_post_meta( $pattern_post->ID, 'boldblocks_pattern_disabled_inserter', true ) ?? false ), ]; $keywords = get_the_terms( $pattern_post, 'boldblocks_pattern_keywords' ); if ( $keywords && ! is_wp_error( $keywords ) ) { $keywords = wp_list_pluck( $keywords, 'name' ); } else { $keywords = false; } if ( $keywords ) { $pattern_settings['keywords'] = $keywords; } // Add blockTypes. $pattern_settings['blockTypes'] = []; if ( in_array( 'header', $categories, true ) ) { $pattern_settings['blockTypes'][] = 'core/template-part/header'; } if ( in_array( 'footer', $categories, true ) ) { $pattern_settings['blockTypes'][] = 'core/template-part/footer'; } if ( in_array( 'query', $categories, true ) ) { $pattern_settings['blockTypes'][] = 'core/query'; } $pattern_posts[ sanitize_key( $pattern_post->post_name ) ] = $pattern_settings; } return $pattern_posts; } /** * Handle save post * * @param int $post_id * @param WP_Post $post * @return void */ public function save_post( $post_id, $post ) { // Ignore auto-draft status. if ( 'auto-draft' === $post->post_status ) { return; } // Ignore autosave. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } $this->clear_transient_cache(); } /** * Clear transient cache * * @return void */ public function clear_transient_cache() { delete_transient( 'bb_pattern_posts' ); } /** * Add custom columns to custom post type * * @param array $columns The array of columns. * @return array */ public function add_pattern_custom_columns( $columns ) { $custom_columns = array( 'pattern_name' => esc_html__( 'Pattern name', 'content-blocks-builder' ), ); // Add after title column. $return = array_slice( $columns, 0, 2, true ) + $custom_columns + array_slice( $columns, 2, count( $columns ) - 2, true ); return $return; } /** * Manage custom columns. * * @param string $column the column name. * @param int $post_id the post id. */ public function manage_pattern_columns( $column, $post_id ) { switch ( $column ) { case 'pattern_name': echo esc_html( 'boldblocks/' . sanitize_key( get_post_field( 'post_name', $post_id ) ) ); break; // Just break out of the switch statement for everything else. default: break; } } /** * Register an endpoint to query pattern categories. * * @return array */ public function register_get_pattern_categories_endpoint() { register_rest_route( 'boldblocks/v1', '/getPatternCategories/', array( 'methods' => 'GET', 'callback' => [ $this, 'get_pattern_categories' ], 'permission_callback' => '__return_true', ) ); } /** * Build pattern categories. * * @param WP_Rest_Request $request * @return WP_Rest_Response */ public function get_pattern_categories( $request ) { $categories = \WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); return $categories; } /** * Add custom block's meta fields to meta revisioning. * * @param array $fields * @return array */ public function add_fields_to_revision_meta_keys( $fields ) { return array_merge( $fields, [ 'boldblocks_pattern_categories' ] ); } /** * Register custom rest fields for easier getting, updating data. * * @return void */ public function register_rest_fields() { register_rest_field( $this->post_type, 'keywords', array( 'get_callback' => function( $params ) { $keywords = wp_list_pluck( wp_get_object_terms( $params['id'], 'boldblocks_pattern_keywords' ), 'name' ); return \implode( ',', $keywords ); }, 'update_callback' => function( $value, $post ) { wp_set_post_terms( $post->ID, $value, 'boldblocks_pattern_keywords' ); }, 'schema' => array( 'type' => 'string', ), ) ); } /** * Placeholder text. Default 'Add title'. * * @param string $text * @return string */ public function enter_title_here( $text ) { $post_type = get_post_type(); if ( $this->post_type === $post_type ) { $text = __( 'Add pattern title', 'content-blocks-builder' ); } return $text; } /** * Get max items */ public function get_max_items() { return apply_filters( 'cbb_get_max_items', $this->max_items, $this->post_type ); } } endif;