From 9438392cfa4e213ae69daf10124ee83c8d5e4e00 Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:08:22 +0100 Subject: [PATCH 1/7] chore: updates minimum PHP version Changes the minimum required PHP version to 8.0. This ensures compatibility with newer language features and security updates. --- blockparty-modal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockparty-modal.php b/blockparty-modal.php index 39edb15..74c2e40 100644 --- a/blockparty-modal.php +++ b/blockparty-modal.php @@ -4,7 +4,7 @@ * Description: Modal block for WordPress editor. * Version: 1.0.1 * Requires at least: 6.8 - * Requires PHP: 7.4 + * Requires PHP: 8.0 * Author: Be API Technical Team * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html From bf1dcdacfc73eb5b082947e13433736aa9c17273 Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:09:32 +0100 Subject: [PATCH 2/7] feat: restricts modal trigger blocks by filter Allows developers to filter which blocks can be used as modal triggers via the `blockparty_modal_trigger_allowed_blocks` filter. By default, only the core/button block can be linked to a modal. This prevents unintended behavior and gives more control over which blocks can trigger modals. Updates the block editor to only show the "Attached modal" panel for blocks that are in the allowed list. Updates documentation to reflect the new filter. --- README.md | 29 +++++++++++++++++---- blockparty-modal.php | 48 ++++++++++++++++++++++++++++++++++- src/blockparty-modal/index.js | 12 ++++++++- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7e9edc9..7f189fd 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Blockparty Modal is a WordPress plugin that lets you add accessible modal dialog - **Modal dialog**: Uses the native `` element for semantics and accessibility - **Configurable content**: Title (with heading level), rich text content, and optional close button - **Close behaviour**: Choose how the modal closes — click outside, close button only, or prevent closing by backdrop -- **Trigger linking**: Link any block (e.g. core/button) to open a specific modal via `linkedModalId` +- **Trigger linking**: Link a block to open a specific modal via `linkedModalId`; by default only the Button block is allowed as a trigger (filterable) - **Stable modal ID**: Each modal can have a unique ID for trigger association - **Layout & styling**: Supports wide and full-width alignment, dimensions, colors, and spacing - **Internationalized**: Multilingual support with translation files (French included) @@ -70,11 +70,27 @@ npm run build - **Close behaviour** — "Any" (click outside or close button), "Close button only", or "None" - **Close button** — show or hide the close button - **Prevent scroll** — lock body scroll when the modal is open -4. To open the modal from a button: - - Add a **Button** block (or another block that supports the modal trigger) - - In the block settings, set **Linked Modal ID** to the same value as the modal’s **Modal ID** +4. To open the modal from a trigger block: + - Add a **Button** block (by default, only the Button block can be a modal trigger) + - In the block sidebar, open **Attached modal** and select the modal to open - On the frontend, clicking that button will open the corresponding modal +### Blocks allowed as modal triggers + +By default, only the **core/button** block can be linked to a modal. To allow other blocks (e.g. paragraph, image, or custom blocks), use the filter `blockparty_modal_trigger_allowed_blocks` in your theme or plugin: + +```php +add_filter( 'blockparty_modal_trigger_allowed_blocks', function ( $blocks ) { + $blocks[] = 'core/paragraph'; + $blocks[] = 'my-plugin/cta'; + return $blocks; +} ); +``` + +- **Filter name:** `blockparty_modal_trigger_allowed_blocks` +- **Parameters:** `array` — List of block names (e.g. `'core/button'`). +- **Default:** `array( 'core/button' )` + ## 🛠️ Development ### Project Structure @@ -231,7 +247,10 @@ This plugin is distributed under the GPL-2.0-or-later license. See the [LICENSE] ## 📝 Changelog -See [readme.txt](readme.txt) for version history. +See [readme.txt](readme.txt) for the full version history. Recent highlights: + +- **1.0.1** — Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes. +- **1.0.0** — Initial release (Modal block, trigger linking, close behaviour, i18n). --- diff --git a/blockparty-modal.php b/blockparty-modal.php index 74c2e40..175792d 100644 --- a/blockparty-modal.php +++ b/blockparty-modal.php @@ -36,11 +36,43 @@ function init(): void { wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); wp_set_script_translations( 'blockparty-modal-editor-script', 'blockparty-modal', BLOCKPARTY_MODAL_DIR . '/languages' ); } + add_action( 'init', __NAMESPACE__ . '\\init', 10, 0 ); +/** + * Passes the list of blocks allowed as modal triggers to the block editor settings + * so the "Attached modal" panel is only shown for those blocks. + * + * @param array $settings Block editor settings. + * @param \WP_Block_Editor_Context $context Block editor context. + * @return array Modified settings. + */ +function block_editor_settings_modal_trigger_blocks( array $settings, $context ): array { + $allowed = apply_filters( + 'blockparty_modal_trigger_allowed_blocks', + get_default_modal_trigger_allowed_blocks() + ); + $settings['blockpartyModalTriggerAllowedBlocks'] = array_values( + array_filter( is_array( $allowed ) ? $allowed : [], 'is_string' ) + ); + return $settings; +} + +add_filter( 'block_editor_settings_all', __NAMESPACE__ . '\\block_editor_settings_modal_trigger_blocks', 10, 2 ); + +/** + * Default list of block names allowed to be used as modal triggers (linkedModalId). + * + * @return string[] Block names (e.g. 'core/button'). + */ +function get_default_modal_trigger_allowed_blocks(): array { + return [ 'core/button' ]; +} + /** * Wraps block output with a trigger wrapper when linkedModalId is set, * so the view script can open the modal on click. + * Only blocks in the allowed list (filterable) get this behavior; by default only core/button. * For core/button, the inner link or button is turned into the trigger (no wrapper). * * @param string $block_content The block content. @@ -56,9 +88,23 @@ function render_block_add_modal_trigger( $block_content, $block ) { return $block_content; } + $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; + $allowed_blocks = apply_filters( + 'blockparty_modal_trigger_allowed_blocks', + get_default_modal_trigger_allowed_blocks() + ); + $allowed_blocks = array_filter( + is_array( $allowed_blocks ) ? $allowed_blocks : array(), + 'is_string' + ); + + if ( '' === $block_name || ! in_array( $block_name, $allowed_blocks, true ) ) { + return $block_content; + } + $dialog_id = 'modal-' . $linked_modal_id; - if ( isset( $block['blockName'] ) && 'core/button' === $block['blockName'] ) { + if ( 'core/button' === $block_name ) { $modified = modify_button_block_for_modal_trigger( $block_content, $linked_modal_id, $dialog_id ); if ( null !== $modified ) { return $modified; diff --git a/src/blockparty-modal/index.js b/src/blockparty-modal/index.js index 2889384..ae8176a 100644 --- a/src/blockparty-modal/index.js +++ b/src/blockparty-modal/index.js @@ -52,7 +52,7 @@ blockTypes.forEach( ( blockType ) => { } } ); -// Add "Open modal on click" panel with Combobox to all blocks except the modal itself. +// Add "Attached modal" panel with Combobox only to blocks allowed as modal triggers (see filter blockparty_modal_trigger_allowed_blocks). addFilter( 'editor.BlockEdit', 'blockparty-modal/with-modal-trigger-control', @@ -63,6 +63,16 @@ addFilter( return ; } + const allowedBlocks = useSelect( ( select ) => { + const settings = select( 'core/block-editor' ).getSettings(); + const list = settings?.blockpartyModalTriggerAllowedBlocks; + return Array.isArray( list ) ? list : [ 'core/button' ]; + }, [] ); + + if ( ! allowedBlocks.includes( name ) ) { + return ; + } + const modalOptions = useSelect( ( select ) => { const blocks = select( 'core/block-editor' ).getBlocks(); return getModalOptionsFromBlocks( blocks ); From f717a91a3a1967645ddec74b37ec6fa745b37934 Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:16:30 +0100 Subject: [PATCH 3/7] fix(style.css): remove margin-block default value if first or last child Adds control over the top and bottom margins of the modal's first and last blocks. This change improves the spacing and visual appearance of the modal content. --- src/blockparty-modal/style.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/blockparty-modal/style.scss b/src/blockparty-modal/style.scss index 1d126c0..2894156 100644 --- a/src/blockparty-modal/style.scss +++ b/src/blockparty-modal/style.scss @@ -27,6 +27,11 @@ &:not(.block-editor-block-list__block) { margin: var(--wp-block-blockparty-modal-margin); + + &:first-child, + &:last-child { + margin-block: var(--wp-block-blockparty-modal-margin); + } } &::backdrop { From 9a3ec92e4a6aee2fe6e8e763c7c33be82cc304ec Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:16:46 +0100 Subject: [PATCH 4/7] feat: enhances modal selection for reusable blocks Improves the modal selection process by including modals located within reusable blocks and patterns. This is achieved by leveraging `getClientIdsWithDescendants` and `getBlock` from the block editor store, ensuring all modals, regardless of their nesting, are available for selection. --- src/blockparty-modal/index.js | 5 ++-- src/blockparty-modal/utils.js | 43 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/blockparty-modal/index.js b/src/blockparty-modal/index.js index ae8176a..f0040b3 100644 --- a/src/blockparty-modal/index.js +++ b/src/blockparty-modal/index.js @@ -23,7 +23,7 @@ import metadata from './block.json'; import { MODAL_BLOCK_NAME, LINKED_MODAL_ATTR, - getModalOptionsFromBlocks, + getModalOptionsFromEditor, addLinkedModalAttribute, } from './utils'; @@ -74,8 +74,7 @@ addFilter( } const modalOptions = useSelect( ( select ) => { - const blocks = select( 'core/block-editor' ).getBlocks(); - return getModalOptionsFromBlocks( blocks ); + return getModalOptionsFromEditor( select ); }, [] ); const options = [ diff --git a/src/blockparty-modal/utils.js b/src/blockparty-modal/utils.js index c851942..eef2875 100644 --- a/src/blockparty-modal/utils.js +++ b/src/blockparty-modal/utils.js @@ -26,34 +26,33 @@ export function generateStableModalId() { } /** - * Recursively collect blockparty/modal blocks with modalId and title. + * Collect modal options from the block editor store using getClientIdsWithDescendants + * and getBlock, so modals inside reusable blocks (core/block) and patterns are included. * - * @param {Object[]} blocks - Block list. + * @param {Function} select - The wp.data select function (e.g. from useSelect). * @return {Object[]} Options for ComboboxControl. */ -export function getModalOptionsFromBlocks( blocks ) { +export function getModalOptionsFromEditor( select ) { + const blockEditor = select( 'core/block-editor' ); + const clientIds = blockEditor.getClientIdsWithDescendants(); + if ( ! Array.isArray( clientIds ) ) { + return []; + } const options = []; - function traverse( blockList ) { - if ( ! blockList || ! blockList.length ) { - return; - } - for ( const block of blockList ) { - if ( block.name === MODAL_BLOCK_NAME ) { - const modalId = block.attributes?.modalId || block.clientId; - const title = - block.attributes?.title?.trim() || - __( 'Modal', 'blockparty-modal' ); - options.push( { - value: modalId, - label: title || `#${ modalId.slice( 0, 8 ) }`, - } ); - } - if ( block.innerBlocks?.length ) { - traverse( block.innerBlocks ); - } + for ( const clientId of clientIds ) { + const block = blockEditor.getBlock( clientId ); + if ( ! block || block.name !== MODAL_BLOCK_NAME ) { + continue; } + const modalId = block.attributes?.modalId || block.clientId; + const title = + block.attributes?.title?.trim() || + __( 'Modal', 'blockparty-modal' ); + options.push( { + value: modalId, + label: title || `#${ String( modalId ).slice( 0, 8 ) }`, + } ); } - traverse( blocks ); return options; } From b6e523b123c99390b861bff64fe9114654b3598d Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:18:40 +0100 Subject: [PATCH 5/7] fix: adds vendor autoloading Ensures compatibility by including the Composer autoloader, allowing the plugin to utilize external libraries effectively. --- blockparty-modal.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/blockparty-modal.php b/blockparty-modal.php index 175792d..f829205 100644 --- a/blockparty-modal.php +++ b/blockparty-modal.php @@ -23,6 +23,12 @@ define( 'BLOCKPARTY_MODAL_URL', plugin_dir_url( __FILE__ ) ); define( 'BLOCKPARTY_MODAL_DIR', plugin_dir_path( __FILE__ ) ); + +// Require vendor +if ( file_exists( BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php' ) ) { + require BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php'; +} + /** * Registers the block(s) metadata from the `blocks-manifest.php` and registers the block type(s) * based on the registered block metadata. Behind the scenes, it registers also all assets so they can be enqueued From aa18d959839e5cbd9dba727b6db39d24d8a48b43 Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:21:42 +0100 Subject: [PATCH 6/7] fix(php): psalm issues --- blockparty-modal.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/blockparty-modal.php b/blockparty-modal.php index f829205..054cf8c 100644 --- a/blockparty-modal.php +++ b/blockparty-modal.php @@ -26,6 +26,7 @@ // Require vendor if ( file_exists( BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php' ) ) { + /** @psalm-suppress UnresolvableInclude */ require BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php'; } @@ -49,17 +50,18 @@ function init(): void { * Passes the list of blocks allowed as modal triggers to the block editor settings * so the "Attached modal" panel is only shown for those blocks. * - * @param array $settings Block editor settings. - * @param \WP_Block_Editor_Context $context Block editor context. - * @return array Modified settings. + * @param array $settings Block editor settings. + * @param \WP_Block_Editor_Context $_context Block editor context (unused). + * @return array Modified settings. */ -function block_editor_settings_modal_trigger_blocks( array $settings, $context ): array { - $allowed = apply_filters( +function block_editor_settings_modal_trigger_blocks( array $settings, \WP_Block_Editor_Context $_context ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Required by block_editor_settings_all filter signature. + /** @psalm-suppress MixedAssignment */ + $raw = apply_filters( 'blockparty_modal_trigger_allowed_blocks', get_default_modal_trigger_allowed_blocks() ); $settings['blockpartyModalTriggerAllowedBlocks'] = array_values( - array_filter( is_array( $allowed ) ? $allowed : [], 'is_string' ) + array_filter( is_array( $raw ) ? $raw : [], 'is_string' ) ); return $settings; } @@ -81,11 +83,11 @@ function get_default_modal_trigger_allowed_blocks(): array { * Only blocks in the allowed list (filterable) get this behavior; by default only core/button. * For core/button, the inner link or button is turned into the trigger (no wrapper). * - * @param string $block_content The block content. - * @param array $block The full block, including attributes. + * @param string $block_content The block content. + * @param array $block The full block, including attributes. * @return string Filtered block content. */ -function render_block_add_modal_trigger( $block_content, $block ) { +function render_block_add_modal_trigger( $block_content, array $block ) { $linked_modal_id = isset( $block['attrs']['linkedModalId'] ) ? $block['attrs']['linkedModalId'] : ''; @@ -94,13 +96,14 @@ function render_block_add_modal_trigger( $block_content, $block ) { return $block_content; } - $block_name = isset( $block['blockName'] ) ? $block['blockName'] : ''; - $allowed_blocks = apply_filters( + $block_name = (string) ( $block['blockName'] ?? '' ); + /** @psalm-suppress MixedAssignment */ + $raw_blocks = apply_filters( 'blockparty_modal_trigger_allowed_blocks', get_default_modal_trigger_allowed_blocks() ); $allowed_blocks = array_filter( - is_array( $allowed_blocks ) ? $allowed_blocks : array(), + is_array( $raw_blocks ) ? $raw_blocks : array(), 'is_string' ); From 036d966cb22bb09132dbc8065652e13769409b83 Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 18 Feb 2026 15:24:40 +0100 Subject: [PATCH 7/7] chore: updates plugin to version 1.0.2 Bumps the plugin version to 1.0.2 in all relevant files. Includes changes to crawl modal blocks from patterns, set the minimum required PHP version to 8.0, and address style issues. --- .plugin-data | 2 +- README.md | 13 +++++++++++-- blockparty-modal.php | 4 ++-- package.json | 2 +- readme.txt | 6 ++++++ src/blockparty-modal/block.json | 2 +- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.plugin-data b/.plugin-data index 883d19a..783dffd 100644 --- a/.plugin-data +++ b/.plugin-data @@ -1,4 +1,4 @@ { - "version": "1.0.1", + "version": "1.0.2", "slug": "blockparty-modal" } diff --git a/README.md b/README.md index 7f189fd..49a012f 100644 --- a/README.md +++ b/README.md @@ -249,8 +249,17 @@ This plugin is distributed under the GPL-2.0-or-later license. See the [LICENSE] See [readme.txt](readme.txt) for the full version history. Recent highlights: -- **1.0.1** — Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes. -- **1.0.0** — Initial release (Modal block, trigger linking, close behaviour, i18n). +- **1.0.2** + - Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes. + - Crawl Modal blocks from patterns + - Set min required PHP version to 8.0 + - Style issues + +- **1.0.1** + - Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes. + +- **1.0.0** + - Initial release (Modal block, trigger linking, close behaviour, i18n). --- diff --git a/blockparty-modal.php b/blockparty-modal.php index 054cf8c..b028c3b 100644 --- a/blockparty-modal.php +++ b/blockparty-modal.php @@ -2,7 +2,7 @@ /** * Plugin Name: Blockparty Modal * Description: Modal block for WordPress editor. - * Version: 1.0.1 + * Version: 1.0.2 * Requires at least: 6.8 * Requires PHP: 8.0 * Author: Be API Technical Team @@ -19,7 +19,7 @@ exit; // Exit if accessed directly. } -define( 'BLOCKPARTY_MODAL_VERSION', '1.0.1' ); +define( 'BLOCKPARTY_MODAL_VERSION', '1.0.2' ); define( 'BLOCKPARTY_MODAL_URL', plugin_dir_url( __FILE__ ) ); define( 'BLOCKPARTY_MODAL_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/package.json b/package.json index 863b883..0da2287 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockparty-modal", - "version": "1.0.1", + "version": "1.0.2", "description": "Add a modal block to the WordPress editor.", "author": "Be API", "license": "GPL-2.0-or-later", diff --git a/readme.txt b/readme.txt index 6cf5e8e..1a7766d 100644 --- a/readme.txt +++ b/readme.txt @@ -45,6 +45,12 @@ directory take precedence. For example, `/assets/screenshot-1.png` would win ove == Changelog == += 1.0.2 = +* Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes. +* Crawl Modal blocks from patterns +* Set min required PHP version to 8.0 +* Style issues + = 1.0.1 = * Fix margin style for dialog element; set to auto by default instead of 0. * Remove dupplicated InnerBlocks.Content diff --git a/src/blockparty-modal/block.json b/src/blockparty-modal/block.json index 2b6b430..9ce4cad 100644 --- a/src/blockparty-modal/block.json +++ b/src/blockparty-modal/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/modal", - "version": "1.0.1", + "version": "1.0.2", "title": "Modal", "category": "widgets", "description": "Insert a modal dialog that opens on trigger. Configure content and behaviour in the editor; the modal is displayed on the frontend when activated.",