* @copyright Copyright (c) 2022, Phi Phan */ namespace BoldBlocks; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; if ( ! class_exists( Variations::class ) ) : /** * Manage custom block variations */ class Variations extends CoreComponent { /** * Post type * * @var string */ protected $post_type = 'boldblocks_variation'; /** * Post type helper instance * * @var PostType */ protected $post_type_helper; /** * Store all variations * * @var array */ protected $all_variations = []; /** * Max items that can query * * @var integer */ protected $max_items = 200; /** * List of custom style for variations * * @var array */ private $variation_style_array = []; /** * 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 ); // Custom variation columns. add_filter( 'manage_edit-' . $this->post_type . '_columns', [ $this, 'add_variation_custom_columns' ] ); add_action( 'manage_' . $this->post_type . '_posts_custom_column', [ $this, 'manage_variation_columns' ], 10, 2 ); add_filter( 'manage_edit-' . $this->post_type . '_sortable_columns', [ $this, 'variation_sortable_columns' ] ); add_action( 'pre_get_posts', [ $this, 'pre_get_posts_order_variation_custom_columns' ], 1 ); // Load custom block variation styles. add_action( 'init', [ $this, 'load_custom_block_variation_styles' ] ); // Only do customization on 'edit.php' page for variation. add_action( 'load-edit.php', [ $this, 'admin_listing_variation_page' ] ); // Enqueue scripts for variations. add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] ); // Update template on the fly for variation post type. add_action( 'admin_init', [ $this, 'update_template_for_variation' ] ); // Add rest api endpoint to create variations. add_action( 'rest_api_init', [ $this, 'build_create_variation_endpoint' ] ); // Add meta fields to meta revisioning. add_filter( 'boldblocks_post_revision_meta_keys', [ $this, 'add_fields_to_revision_meta_keys' ] ); // 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' ] ); // Enqueue styles for the iframe editor. add_filter( 'block_editor_settings_all', [ $this, 'enqueue_style_for_the_editor' ] ); // Enqueue custom scripts & styles for custom variations. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_custom_variation_scripts' ] ); // 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 variation type. $this->post_type_helper->register_post_type( $this->post_type, [ 'rest_base' => 'boldblocks-variations', 'show_in_menu' => 'edit.php?post_type=boldblocks_block', 'show_in_admin_bar' => false, 'capabilities' => array( 'create_posts' => 'do_not_allow', 'edit_posts' => 'manage_options', 'edit_others_posts' => 'manage_options', 'delete_posts' => 'manage_options', ), 'map_meta_cap' => true, ], [ 'name' => _x( 'Block Variations', 'post type general name', 'content-blocks-builder' ), 'singular_name' => _x( 'Block Variation', 'post type singular name', 'content-blocks-builder' ), 'all_items' => __( 'All Variations', 'content-blocks-builder' ), ] ); // Meta fields. $this->register_meta( 'boldblocks_variation_block_name' ); $this->register_meta( 'boldblocks_variation_name' ); $this->register_meta( 'boldblocks_variation_description' ); $this->register_meta( 'boldblocks_variation_icon' ); $this->register_meta( 'boldblocks_variation_is_default', [ 'type' => 'boolean', 'default' => false, ] ); $this->register_meta( 'boldblocks_variation_is_transformable', [ 'type' => 'boolean', 'default' => false, ] ); $this->register_meta( 'boldblocks_variation_hide_from_inserter', [ 'type' => 'boolean', 'default' => false, ] ); $this->register_meta( 'boldblocks_variation_data', [ 'default' => '{}', ] ); $this->register_meta( 'boldblocks_variation_dependent_blocks', [ 'show_in_rest' => array( 'schema' => array( 'items' => array( 'type' => 'string', ), ), ), 'type' => 'array', 'default' => [], ] ); $this->register_meta( 'boldblocks_variation_custom_style' ); // Register a block style for a custom variation. $this->register_meta( 'boldblocks_variation_enable_style', [ 'type' => 'boolean', 'default' => false, ] ); } /** * 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' ); } } /** * Load all custom variation styles * * @return void */ public function load_custom_block_variation_styles() { // Load all custom variations. $all_custom_variations = $this->get_all_variations(); foreach ( $all_custom_variations as $block_variation ) { $variation_handle = false; $custom_style = $block_variation['custom_style'] ?? false; if ( $custom_style ) { $variation_handle = $block_variation['variationClass']; // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_style( $variation_handle, '' ); wp_add_inline_style( $variation_handle, $custom_style ); wp_enqueue_block_style( $block_variation['blockName'], [ 'handle' => $variation_handle ] ); // Save custom style. $this->save_variation_style( $variation_handle, $custom_style, $block_variation ); } if ( $block_variation['enable_block_style'] ?? false ) { $style_args = [ 'name' => str_replace( '/', '-', $block_variation['variationName'] ?? '' ), 'label' => $block_variation['title'] ?? '', ]; if ( $variation_handle ) { $style_args['style_handle'] = $variation_handle; } register_block_style( $block_variation['blockName'] ?? '', $style_args ); } } } /** * Enqueue editor assets * * @return void */ public function enqueue_block_editor_assets() { // Variations access file. $variations_asset = $this->the_plugin_instance->include_file( 'build/variations.asset.php' ); // Scripts. wp_enqueue_script( 'boldblocks-variations', $this->the_plugin_instance->get_file_uri( '/build/variations.js' ), $variations_asset['dependencies'] ?? [], $this->the_plugin_instance->get_script_version( $variations_asset ), [ 'in_footer' => true ] ); // Add translation. wp_set_script_translations( 'boldblocks-variations', 'content-blocks-builder' ); } /** * Update template for variation * * @return void */ public function update_template_for_variation() { global $pagenow; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_GET['post'] ); $current_post = get_post( $post_id ); if ( ! $current_post instanceof \WP_Post || $this->post_type !== $current_post->post_type ) { return; } $block_name = get_post_meta( $post_id, 'boldblocks_variation_block_name', true ); if ( $block_name ) { $post_type_object = get_post_type_object( $this->post_type ); $post_type_object->template = [ [ $block_name ] ]; } } } /** * Get all variations * * @return array */ public function get_all_variations() { if ( ! $this->all_variations ) { $this->all_variations = $this->get_posts( $this->the_plugin_instance->is_debug_mode() ); } return $this->all_variations; } /** * 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_variation_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 variations * * @return array */ public function query_posts() { $raw_posts = get_posts( [ 'post_type' => $this->post_type, 'posts_per_page' => $this->get_max_items(), 'orderby' => [ 'menu_order' => 'DESC', 'date' => 'DESC', ], ] ); $custom_style_handler = $this->the_plugin_instance->get_component( CustomStyle::class ); $variation_posts = array_map( function ( $item ) use ( $custom_style_handler ) { $variation_name = get_post_meta( $item->ID, 'boldblocks_variation_name', true ); $variation_class = 'is-style-' . str_replace( '/', '-', $variation_name ); $custom_style = get_post_meta( $item->ID, 'boldblocks_variation_custom_style', true ); if ( $custom_style ) { $custom_style = $custom_style_handler->refine_custom_value( $custom_style, [ 'selector' => '.' . $variation_class ], 'CSS' ); } return [ 'id' => $item->ID, 'blockName' => get_post_meta( $item->ID, 'boldblocks_variation_block_name', true ), 'variationName' => $variation_name, 'title' => $item->post_title, 'postContent' => $item->post_content, 'description' => get_post_meta( $item->ID, 'boldblocks_variation_description', true ), 'blockIcon' => get_post_meta( $item->ID, 'boldblocks_variation_icon', true ), 'isDefault' => (bool) get_post_meta( $item->ID, 'boldblocks_variation_is_default', true ), 'isTransformable' => (bool) get_post_meta( $item->ID, 'boldblocks_variation_is_transformable', true ), 'hideFromInserter' => (bool) get_post_meta( $item->ID, 'boldblocks_variation_hide_from_inserter', true ), 'variationData' => get_post_meta( $item->ID, 'boldblocks_variation_data', true ), 'dependentBlocks' => get_post_meta( $item->ID, 'boldblocks_variation_dependent_blocks', true ), 'variationClass' => $variation_class, 'custom_style' => $custom_style, 'enable_block_style' => (bool) get_post_meta( $item->ID, 'boldblocks_variation_enable_style', true ), ]; }, $raw_posts ); return $variation_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_variation_posts' ); } /** * Build a custom endpoint to create variation * * @return void */ public function build_create_variation_endpoint() { register_rest_route( 'boldblocks/v1', '/createVariation/', array( 'methods' => 'POST', 'callback' => [ $this, 'create_variation' ], 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ) ); } /** * Create variation * * @param WP_REST_Request $request The request object. * @return void */ public function create_variation( $request ) { $args = $request->get_params(); if ( empty( $args['title'] ) || empty( $args['content'] ) || empty( $args['meta'] ) ) { wp_send_json_error( __( 'Invalid request!', 'content-blocks-builder' ), 400 ); } // Verify nonce. if ( ! isset( $args['cbb_variation_nonce'] ) || ! wp_verify_nonce( $args['cbb_variation_nonce'], 'cbb_variation_nonce' ) ) { wp_send_json_error( __( 'Nonce verification failed!', 'content-blocks-builder' ), 403 ); } // Using Posts controller to create a custom variation. $controller = new \WP_REST_Posts_Controller( $this->post_type ); $response = $controller->create_item( $request ); // Create a hook for new variation created. do_action( 'boldblocks_variation_created', $response->data ); if ( is_wp_error( $response ) ) { wp_send_json_error( $response ); } if ( 201 === $response->get_status() ) { wp_send_json( [ 'data' => __( 'The new variation has been created!', 'content-blocks-builder' ), 'success' => true, 'post' => $response->data, ] ); } // Default fallback if something goes wrong. wp_send_json_error( __( 'An error occurred while creating the variation.', 'content-blocks-builder' ), 500 ); } /** * Add custom columns to custom post type * * @param array $columns The array of columns. * @return array */ public function add_variation_custom_columns( $columns ) { $custom_columns = array( 'block_name' => esc_html__( 'Block name', 'content-blocks-builder' ), 'variation_name' => esc_html__( 'Variation name', 'content-blocks-builder' ), 'is_default' => esc_html__( 'Is default?', '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_variation_columns( $column, $post_id ) { switch ( $column ) { case 'block_name': echo esc_html( get_post_meta( $post_id, 'boldblocks_variation_block_name', true ) ); break; case 'variation_name': echo esc_html( get_post_meta( $post_id, 'boldblocks_variation_name', true ) ); break; case 'is_default': echo get_post_meta( $post_id, 'boldblocks_variation_is_default', true ) ? esc_html__( 'Yes', 'content-blocks-builder' ) : esc_html__( 'No', 'content-blocks-builder' ); break; // Just break out of the switch statement for everything else. default: break; } } /** * Make custom columns sortable. * * @param array $columns The array of columns. * @return array */ public function variation_sortable_columns( $columns ) { $columns['block_name'] = 'block_name'; $columns['variation_name'] = 'variation_name'; return $columns; } /** * Alter the main query for sorting. * * @param WP_Query $query The main query. * @return void */ public function pre_get_posts_order_variation_custom_columns( $query ) { // Do nothing on front end side. if ( ! is_admin() ) { return; } // Only for main query with orderby parameter. if ( $query->is_main_query() && $this->post_type === $query->get( 'post_type' ) && $query->get( 'orderby' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['boldblocks_variation_filter_block'] ) ) { // Ignore if we're filtering. return; } $orderby = $query->get( 'orderby' ); switch ( $orderby ) { case 'block_name': // set our query's meta_key, which is used for custom fields. $query->set( 'meta_key', 'boldblocks_variation_block_name' ); $query->set( 'orderby', 'meta_value' ); break; case 'variation_name': // set our query's meta_key, which is used for custom fields. $query->set( 'meta_key', 'boldblocks_variation_name' ); $query->set( 'orderby', 'meta_value' ); break; default: break; } } } /** * Add filters for custom variation columns * * @return void */ public function admin_listing_variation_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['post_type'] ) && $this->post_type === $_GET['post_type'] ) { add_filter( 'request', [ $this, 'add_custom_columns_to_query_vars' ] ); add_filter( 'restrict_manage_posts', [ $this, 'add_filter_select_control' ] ); } } /** * Add custom vars to query vars * * @param array $query_vars The array of query vars. * @return array */ public function add_custom_columns_to_query_vars( $query_vars ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['boldblocks_variation_filter_block'] ) ) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key $query_vars['meta_key'] = 'boldblocks_variation_block_name'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value, WordPress.Security.NonceVerification.Recommended $query_vars['meta_value'] = sanitize_text_field( wp_unslash( $_GET['boldblocks_variation_filter_block'] ) ); } return $query_vars; } /** * Add a filter select control to filter variation by block name * * @return void */ public function add_filter_select_control() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $block_names = $wpdb->get_col( 'SELECT DISTINCT meta_value FROM ' . $wpdb->postmeta . ' WHERE meta_key = "boldblocks_variation_block_name" ORDER BY meta_value' ); $boldblocks_variation_filter_block = false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['boldblocks_variation_filter_block'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $boldblocks_variation_filter_block = sanitize_text_field( wp_unslash( $_GET['boldblocks_variation_filter_block'] ) ); } ?> variation_style_array[ $variation_name ] ) ) { if ( 'core/html' === ( $block_variation['blockName'] ?? '' ) ) { $style = str_replace( $block_variation['variationClass'], 'cbb-html-preview', $style ); } $this->variation_style_array[ $variation_name ] = $style; } } /** * Enqueue scripts/styles for the editor * * @param array $editor_settings * @return void */ public function enqueue_style_for_the_editor( $editor_settings ) { $style = ''; if ( $this->variation_style_array ) { foreach ( $this->variation_style_array as $variation_style ) { if ( $variation_style ) { $style .= $variation_style; } } } if ( $style ) { $editor_settings['styles'][] = [ 'css' => $style ]; } return $editor_settings; } /** * Enqueue scripts for custom variations when editing them * * @param string $hook_suffix * @return void */ public function enqueue_custom_variation_scripts( $hook_suffix ) { global $post; $screen = get_current_screen(); if ( 'post.php' === $hook_suffix && $this->post_type === $screen->post_type ) { // Styles. $custom_style_handler = $this->the_plugin_instance->get_component( CustomStyle::class ); $custom_style = get_post_meta( $post->ID, 'boldblocks_variation_custom_style', true ); if ( $custom_style ) { $block_name = get_post_meta( $post->ID, 'boldblocks_variation_block_name', true ); if ( 0 === strpos( $block_name, 'core/' ) ) { $block_name = str_replace( 'core/', '', $block_name ); } $block_class = '.wp-block-post-content > .wp-block-' . str_replace( '/', '-', $block_name ); $custom_style = $custom_style_handler->refine_custom_value( $custom_style, [ 'selector' => $block_class ], 'CSS' ); $custom_style .= '.is-selected,.has-child-selected,.has-child-selected * {animation:none!important;}'; $variation_handle = 'edit-variation-style'; // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_style( $variation_handle, '' ); wp_add_inline_style( $variation_handle, $custom_style ); wp_enqueue_style( $variation_handle ); } } } /** * Get the block name for the current variation * * @return string */ public function get_block_name() { global $pagenow; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_GET['post'] ); $current_post = get_post( $post_id ); if ( ! $current_post instanceof \WP_Post || $this->post_type !== $current_post->post_type ) { return; } return get_post_meta( $post_id, 'boldblocks_variation_block_name', true ); } } /** * Get max items */ public function get_max_items() { return apply_filters( 'cbb_get_max_items', $this->max_items, $this->post_type ); } } endif;