setup_hooks(); } /** * Set up hooks for various purposes. * * This method adds hooks for different purposes as needed. * * @since 1.0.0 * * @return void */ protected function setup_hooks(): void { add_action( 'init', array( $this, 'register_block_classes' ) ); add_action( 'init', array( $this, 'register_block_patterns' ) ); // Priority 11 needed for block.json translations of title and description. add_action( 'init', array( $this, 'register_blocks' ), 11 ); // Run on priority 9 to allow extenders to use the hooks with the default of 10. add_filter( 'hooked_block_types', array( $this, 'hook_blocks_into_patterns' ), 9, 4 ); add_filter( 'hooked_block_core/paragraph', array( $this, 'modify_hooked_blocks_in_patterns' ), 9, 5 ); } /** * Register custom blocks. * * This method scans a directory for custom block definitions and registers them. * * @since 1.0.0 * * @return void */ public function register_blocks(): void { $blocks_directory = sprintf( '%1$s/build/blocks/', GATHERPRESS_CORE_PATH ); $blocks = array_diff( scandir( $blocks_directory ), array( '..', '.' ) ); foreach ( $blocks as $block ) { $block_metadata_path = sprintf( '%1$s/build/blocks/%2$s', GATHERPRESS_CORE_PATH, $block ); if ( is_dir( $block_metadata_path ) ) { register_block_type( $block_metadata_path ); } } } /** * Instantiate block classes. * * @return void */ public function register_block_classes(): void { Blocks\Add_To_Calendar::get_instance(); Blocks\Dropdown::get_instance(); Blocks\Dropdown_Item::get_instance(); Blocks\Event_Date::get_instance(); Blocks\Event_Query::get_instance(); Blocks\General_Block::get_instance(); Blocks\Modal::get_instance(); Blocks\Modal_Manager::get_instance(); Blocks\Rsvp::get_instance(); Blocks\Rsvp_Form::get_instance(); Blocks\Rsvp_Response::get_instance(); Blocks\Rsvp_Template::get_instance(); } /** * Get a list of subfolder names from the /build/variations/core/ directory. * * @since 1.0.0 * * @return string[] List of block-variations foldernames. */ public function get_block_variations(): array { $variations_directory = sprintf( '%1$s/build/variations/core/', GATHERPRESS_CORE_PATH ); if ( ! file_exists( $variations_directory ) ) { return array(); } if ( empty( $this->block_variation_names ) ) { $this->block_variation_names = array_values( array_diff( scandir( $variations_directory ), array( '..', '.' ) ) ); } return array_filter( $this->block_variation_names ); } /** * Generate the default CSS class for a block. * * @since 1.0.0 * * @param string $block_name The block name in the format 'namespace/blockname'. * @return string The default CSS class for the block. */ public function get_default_block_class( string $block_name ): string { return sprintf( 'wp-block-%s', sanitize_key( str_replace( '/', '-', $block_name ) ) ); } /** * Get class name from folder name. * * @todo maybe better in the Utility class? * * @param string $foldername String with name of a folder. * * @return string Class name that reflects the given foldername. */ protected static function get_classname_from_foldername( string $foldername ): string { $foldername = basename( $foldername ); return ucwords( str_replace( '-', '_', $foldername ), '_' ); } /** * Register block patterns. * * This method registers multiple different block-patterns for GatherPress. * * @since 1.0.0 * @see https://developer.wordpress.org/reference/functions/register_block_pattern/ * * @return void */ public function register_block_patterns(): void { $block_patterns = array( array( 'gatherpress/event-template', array( 'title' => __( 'Invisible Event Template Block Pattern', 'gatherpress' ), // Even this paragraph seems useless, it's not. // It is the entry point for all our hooked blocks // and as such absolutely important! 'content' => '', // Other blocks are hooked-in here. 'inserter' => false, 'source' => 'plugin', ), ), array( 'gatherpress/venue-template', array( 'title' => __( 'Invisible Venue Template Block Pattern', 'gatherpress' ), // Even this paragraph seems useless, it's not. // It is the entry point for all our hooked blocks // and as such absolutely important! // Other blocks are hooked-in here. 'content' => '' . '
', 'inserter' => false, 'source' => 'plugin', ), ), array( 'gatherpress/venue-details', array( 'title' => __( 'Invisible Venue Details Block Pattern', 'gatherpress' ), // Even this post-title seems useless, it's not. // It is the entry point for all our hooked blocks // and as such absolutely important! 'content' => '', // Other blocks are hooked-in here. 'inserter' => false, 'source' => 'plugin', ), ), ); foreach ( $block_patterns as $block_pattern ) { /** * Made to be used with the 'template' parameter * when registering the 'gatherpress_event' post type * and will not be visible to the editor at any point. */ register_block_pattern( $block_pattern[0], $block_pattern[1] ); } } /** * Filters the list of hooked block types for a given anchor block type and relative position. * * @see https://developer.wordpress.org/reference/hooks/hooked_block_types/ * * @param string[] $hooked_block_types The list of hooked block types. * @param string $relative_position The relative position of the hooked blocks. * Can be one of 'before', 'after', * 'first_child', or 'last_child'. * @param string $anchor_block_type The anchor block type. * @param WP_Block_Template|array $context The block template, template part, or pattern * that the anchor block belongs to. * @return string[] The list of hooked block types. */ public function hook_blocks_into_patterns( array $hooked_block_types, string $relative_position, ?string $anchor_block_type, $context ): array { // Check that the place to hook into is a pattern. if ( ! is_array( $context ) || ! isset( $context['name'] ) ) { return $hooked_block_types; } // Hook blocks into the "gatherpress/event-template" pattern. if ( 'gatherpress/event-template' === $context['name'] && 'gatherpress/event-date' === $anchor_block_type && 'after' === $relative_position ) { $hooked_block_types[] = 'gatherpress/add-to-calendar'; // @todo As soon as the new venue block is in place, // load 'core/patterns' here // and fill it with {"slug":"gatherpress/venue-details"} in modify_hooked_blocks_in_patterns() later // instead of loading the (old) venue block. $hooked_block_types[] = 'gatherpress/venue'; $hooked_block_types[] = 'gatherpress/rsvp'; $hooked_block_types[] = 'core/paragraph'; $hooked_block_types[] = 'gatherpress/rsvp-response'; } // Hook blocks into the "gatherpress/venue-template" pattern. if ( 'gatherpress/venue-template' === $context['name'] && 'core/paragraph' === $anchor_block_type && 'after' === $relative_position ) { $hooked_block_types[] = 'gatherpress/venue'; } return $hooked_block_types; } /** * Filters the parsed block array for a hooked 'core/paragraph' block. * * @see https://developer.wordpress.org/reference/hooks/hooked_block_hooked_block_type/ * * @param array|null $parsed_hooked_block The parsed block array for the given * hooked block type, or null to suppress the block. * @param string $hooked_block_type The hooked block type name. * @param string $relative_position The relative position of the hooked block. * @param array $parsed_anchor_block The anchor block, in parsed block array format. * @param WP_Block_Template|WP_Post|array $context The block template, template part, * `wp_navigation` post type, or pattern * that the anchor block belongs to. * @return array|null The parsed block array for the given hooked block type, * or null to suppress the block. */ public function modify_hooked_blocks_in_patterns( ?array $parsed_hooked_block, string $hooked_block_type, string $relative_position, array $parsed_anchor_block, $context ): ?array { // Has the hooked block been suppressed by a previous filter? if ( is_null( $parsed_hooked_block ) ) { return $parsed_hooked_block; } // Check that the place to hook into is a pattern. if ( ! is_array( $context ) || ! isset( $context['name'] ) ) { return $parsed_hooked_block; } // Conditionally hook the block into the "gatherpress/venue-facts" pattern. if ( 'gatherpress/event-template' !== $context['name'] || 'gatherpress/event-date' !== $parsed_anchor_block['blockName'] || 'after' !== $relative_position ) { return $parsed_hooked_block; } // The opener text for new Events... a paragraph block. if ( 'core/paragraph' === $hooked_block_type ) { $parsed_hooked_block['attrs']['placeholder'] = __( // phpcs:ignore Generic.Files.LineLength.TooLong 'Add a description of the event and let people know what to expect, including the agenda, what they need to bring, and how to find the group.', 'gatherpress' ); } return $parsed_hooked_block; } /** * Recursively retrieves all block names from a given array of blocks. * * This method traverses a nested block structure and collects the block names, * including those of any inner blocks, into a flat array. * * @since 1.0.0 * * @param array $blocks An array of block data, typically including `blockName` and `innerBlocks`. * * @return array An array of block names found within the provided block structure. */ public function get_block_names( array $blocks ): array { $block_names = array(); if ( isset( $blocks['blockName'] ) ) { $block_names[] = $blocks['blockName']; } if ( ! empty( $blocks['innerBlocks'] ) ) { foreach ( $blocks['innerBlocks'] as $inner_block ) { $block_names = array_merge( $block_names, $this->get_block_names( $inner_block ) ); } } return $block_names; } /** * Get the post ID from block attributes or fallback to the current post ID. * * @since 1.0.0 * * @param array $block The block data. * * @return int The resolved post ID. */ public function get_post_id( array $block ): int { $post_id = isset( $block['attrs']['postId'] ) ? intval( $block['attrs']['postId'] ) : 0; return $post_id > 0 ? $post_id : get_the_ID(); } }