This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,36 @@
<?php
/**
* Handles the accordion block.
*
* @package GenerateBlocks Pro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
require_once 'class-accordion.php';
require_once 'class-accordion-item.php';
require_once 'class-accordion-toggle.php';
require_once 'class-accordion-toggle-icon.php';
require_once 'class-accordion-content.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_accordion_block_editor_settings', 20 );
/**
* Add block editor settings for the navigation block.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_accordion_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-accordion',
'.editor-styles-wrapper .wp-block-generateblocks-pro-accordion-item',
'.editor-styles-wrapper .wp-block-generateblocks-pro-accordion-toggle',
'.editor-styles-wrapper .wp-block-generateblocks-pro-accordion-toggle-icon',
'.editor-styles-wrapper .wp-block-generateblocks-pro-accordion-content',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Accordion block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion block class.
*/
class GenerateBlocks_Block_Accordion_Content extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/accordion-content';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Accordion block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion block class.
*/
class GenerateBlocks_Block_Accordion_Item extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/accordion-item';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Accordion block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion block class.
*/
class GenerateBlocks_Block_Accordion_Toggle_Icon extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/accordion-toggle-icon';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,84 @@
<?php
/**
* Handles the Accordion block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion block class.
*/
class GenerateBlocks_Block_Accordion_Toggle extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/accordion-toggle';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
// Bail here if the HTML Tag Processor isn't available.
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $block_content;
}
$open_by_default = $block->context['generateblocks/accordion/openByDefault'] ?? false;
$processor = new WP_HTML_Tag_Processor( $block_content );
$tag_name = $attributes['tagName'] ?? 'div';
$main_tag = $processor->next_tag( $tag_name );
$updated_html = false;
if ( $processor && $main_tag ) {
if ( $open_by_default ) {
$processor->add_class( 'gb-block-is-current' );
$updated_html = true;
}
if ( 'div' === $tag_name ) {
if ( ! $processor->get_attribute( 'tabindex' ) ) {
$processor->set_attribute( 'tabindex', '0' );
}
if ( ! $processor->get_attribute( 'role' ) ) {
$processor->set_attribute( 'role', 'button' );
}
$updated_html = true;
}
if ( $updated_html ) {
$block_content = $processor->get_updated_html();
}
}
return $block_content;
}
}
@@ -0,0 +1,91 @@
<?php
/**
* Handles the Accordion block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion block class.
*/
class GenerateBlocks_Block_Accordion extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/accordion';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( ! wp_style_is( 'generateblocks-accordion', 'enqueued' ) ) {
self::enqueue_style();
}
if ( ! wp_script_is( 'generateblocks-accordion', 'enqueued' ) ) {
self::enqueue_assets();
}
return $block_content;
}
/**
* Enqueue block styles.
*/
private static function enqueue_style() {
wp_enqueue_style(
'generateblocks-accordion',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/accordion-style.css',
[],
GENERATEBLOCKS_PRO_VERSION
);
}
/**
* Enqueue block scripts.
*/
private static function enqueue_scripts() {
wp_enqueue_script(
'generateblocks-accordion',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/accordion.js',
[],
GENERATEBLOCKS_PRO_VERSION,
true
);
}
/**
* Enqueue block assets.
*/
public static function enqueue_assets() {
self::enqueue_scripts();
self::enqueue_style();
}
}
@@ -0,0 +1,233 @@
<?php
/**
* Register our blocks.
*
* @package GenerateBlocks Pro
*/
add_action( 'init', 'generateblocks_pro_register_blocks' );
/**
* Register the GenerateBlocks Pro blocks.
*
* @since 1.0.0
*/
function generateblocks_pro_register_blocks() {
if ( ! class_exists( 'GenerateBlocks_Block' ) ) {
return;
}
require_once 'accordion/accordion.php';
require_once 'tabs/tabs.php';
require_once 'classic-menu/classic-menu.php';
require_once 'classic-menu-item/classic-menu-item.php';
require_once 'classic-sub-menu/classic-sub-menu.php';
require_once 'navigation/navigation.php';
require_once 'menu-container/menu-container.php';
require_once 'menu-toggle/menu-toggle.php';
require_once 'site-header/site-header.php';
require_once 'carousel/carousel.php';
require_once 'carousel-pagination/carousel-pagination.php';
require_once 'carousel-items/carousel-items.php';
require_once 'carousel-item/carousel-item.php';
require_once 'carousel-control/carousel-control.php';
if ( generateblocks_pro_forms_enabled() ) {
require_once 'form/form.php';
require_once 'form-render/form-render.php';
require_once 'form-field/form-field.php';
require_once 'form-field-label/form-field-label.php';
require_once 'form-field-control/form-field-control.php';
}
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/accordion',
[
'render_callback' => 'GenerateBlocks_Block_Accordion::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/accordion-item',
[
'render_callback' => 'GenerateBlocks_Block_Accordion_Item::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/accordion-toggle',
[
'render_callback' => 'GenerateBlocks_Block_Accordion_Toggle::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/accordion-toggle-icon',
[
'render_callback' => 'GenerateBlocks_Block_Accordion_Toggle_Icon::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/accordion-content',
[
'render_callback' => 'GenerateBlocks_Block_Accordion_Content::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/tabs',
[
'render_callback' => 'GenerateBlocks_Block_Tabs::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/tabs-menu',
[
'render_callback' => 'GenerateBlocks_Block_Tabs_Menu::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/tab-menu-item',
[
'render_callback' => 'GenerateBlocks_Block_Tab_Menu_Item::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/tab-items',
[
'render_callback' => 'GenerateBlocks_Block_Tab_Items::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/tab-item',
[
'render_callback' => 'GenerateBlocks_Block_Tab_Item::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/classic-menu',
[
'render_callback' => 'GenerateBlocks_Block_Classic_Menu::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/classic-menu-item',
[
'render_callback' => 'GenerateBlocks_Block_Classic_Menu_Item::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/classic-sub-menu',
[
'render_callback' => 'GenerateBlocks_Block_Classic_Sub_Menu::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/navigation',
[
'render_callback' => 'GenerateBlocks_Block_Navigation::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/menu-toggle',
[
'render_callback' => 'GenerateBlocks_Block_Menu_Toggle::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/menu-container',
[
'render_callback' => 'GenerateBlocks_Block_Menu_Container::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/site-header',
[
'render_callback' => 'GenerateBlocks_Block_Site_Header::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/carousel',
[
'render_callback' => 'GenerateBlocks_Block_Carousel::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/carousel-pagination',
[
'render_callback' => 'GenerateBlocks_Block_Carousel_Pagination::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/carousel-items',
[
'render_callback' => 'GenerateBlocks_Block_Carousel_Items::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/carousel-item',
[
'render_callback' => 'GenerateBlocks_Block_Carousel_Item::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/carousel-control',
[
'render_callback' => 'GenerateBlocks_Block_Carousel_Control::render_block',
]
);
if ( generateblocks_pro_forms_enabled() ) {
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/form',
[
'render_callback' => 'GenerateBlocks_Block_Form::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/form-field',
[
'render_callback' => 'GenerateBlocks_Block_Form_Field::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/form-field-label',
[
'render_callback' => 'GenerateBlocks_Block_Form_Field_Label::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/form-field-control',
[
'render_callback' => 'GenerateBlocks_Block_Form_Field_Control::render_block',
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_PRO_DIR . '/dist/blocks/form-render',
[
'render_callback' => 'GenerateBlocks_Block_Form_Render::render_block',
]
);
}
}
@@ -0,0 +1,13 @@
<?php
/**
* Registers the Carousel Control block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// Load the class file.
require_once GENERATEBLOCKS_PRO_DIR . 'includes/blocks/carousel-control/class-carousel-control.php';
@@ -0,0 +1,86 @@
<?php
/**
* Handles the Carousel Control block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Carousel Control block class.
*/
class GenerateBlocks_Block_Carousel_Control extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/carousel-control';
/**
* Render the Carousel Control block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
// Add default ARIA labels if not present using WP HTML Tag Processor
// Only available in WordPress 6.2+, skip for older versions.
if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
$control_type = $attributes['controlType'] ?? 'next';
$has_aria_label = isset( $attributes['htmlAttributes']['aria-label'] ) && ! empty( $attributes['htmlAttributes']['aria-label'] );
$processor = new \WP_HTML_Tag_Processor( $block_content );
// Find the first tag (the button/control element).
if ( $processor->next_tag() ) {
// Add default ARIA label if not present.
if ( ! $has_aria_label && ! $processor->get_attribute( 'aria-label' ) ) {
// Map control types to default ARIA labels.
$default_labels = [
'next' => __( 'Next slide', 'generateblocks-pro' ),
'previous' => __( 'Previous slide', 'generateblocks-pro' ),
'play' => __( 'Play carousel', 'generateblocks-pro' ),
'pause' => __( 'Pause carousel', 'generateblocks-pro' ),
'play-pause' => __( 'Toggle carousel playback', 'generateblocks-pro' ),
'first' => __( 'Go to first slide', 'generateblocks-pro' ),
'last' => __( 'Go to last slide', 'generateblocks-pro' ),
];
$aria_label = $default_labels[ $control_type ] ?? __( 'Carousel control', 'generateblocks-pro' );
$processor->set_attribute( 'aria-label', $aria_label );
}
// For play-pause controls, ensure aria-pressed attribute exists.
if ( 'play-pause' === $control_type && ! $processor->get_attribute( 'aria-pressed' ) ) {
$processor->set_attribute( 'aria-pressed', 'false' );
}
$block_content = $processor->get_updated_html();
}
}
return $block_content;
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Handles the carousel item block.
*
* @package GenerateBlocks Pro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
require_once 'class-carousel-item.php';
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Carousel Item block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Carousel Item block class.
*/
class GenerateBlocks_Block_Carousel_Item extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/carousel-item';
/**
* Render the Carousel Item block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the carousel block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-carousel-items.php';
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Carousel block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Carousel block class.
*/
class GenerateBlocks_Block_Carousel_Items extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/carousel-items';
/**
* Render the Carousel block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the carousel pagination block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-carousel-pagination.php';
@@ -0,0 +1,97 @@
<?php
/**
* Handles the Carousel Pagination block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Carousel Pagination block class.
*/
class GenerateBlocks_Block_Carousel_Pagination extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/carousel-pagination';
/**
* Render the Carousel Pagination block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$to_replace = '<span class="gb-carousel-pagination-content"></span>';
$pagination_type = $block->context['generateblocks/carousel/htmlAttributes']['data-pagination-type'] ?? 'bullets';
$replacement = '';
if ( 'fraction' === $pagination_type ) {
$replacement = '<span class="gb-carousel-current">1</span> / <span class="gb-carousel-total">1</span>';
}
if ( 'bullets' === $pagination_type ) {
$replacement = '<span class="gb-carousel-dot" style="display: inline-block !important;"></span>';
}
if ( $replacement ) {
$block_content = str_replace( $to_replace, $replacement, $block_content );
}
// Add default ARIA attributes if not present using WP HTML Tag Processor
// Only available in WordPress 6.2+, skip for older versions.
if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
$has_aria_label = isset( $attributes['htmlAttributes']['aria-label'] ) && ! empty( $attributes['htmlAttributes']['aria-label'] );
$processor = new \WP_HTML_Tag_Processor( $block_content );
// Find the first tag (the pagination container).
if ( $processor->next_tag() ) {
// Add default ARIA label if not present.
if ( ! $has_aria_label && ! $processor->get_attribute( 'aria-label' ) ) {
// Map pagination types to default ARIA labels.
$default_labels = [
'bullets' => __( 'Carousel pagination', 'generateblocks-pro' ),
'fraction' => __( 'Carousel slide counter', 'generateblocks-pro' ),
'progressbar' => __( 'Carousel progress', 'generateblocks-pro' ),
];
$aria_label = $default_labels[ $pagination_type ] ?? __( 'Carousel pagination', 'generateblocks-pro' );
$processor->set_attribute( 'aria-label', $aria_label );
}
// Add role attribute for bullets pagination if not present.
if ( 'bullets' === $pagination_type && ! $processor->get_attribute( 'role' ) ) {
$processor->set_attribute( 'role', 'navigation' );
}
$block_content = $processor->get_updated_html();
}
}
return $block_content;
}
}
@@ -0,0 +1,127 @@
<?php
/**
* Handle the carousel block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-carousel.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_carousel_block_editor_settings', 20 );
/**
* Add block editor settings for the carousel block.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_carousel_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-carousel',
'.editor-styles-wrapper .wp-block-generateblocks-pro-carousel-pagination',
'.editor-styles-wrapper .wp-block-generateblocks-pro-carousel-items',
'.editor-styles-wrapper .wp-block-generateblocks-pro-carousel-item',
'.editor-styles-wrapper .wp-block-generateblocks-pro-carousel-control',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}
add_filter( 'generateblocks_block_css', 'generateblocks_pro_carousel_block_css', 10, 2 );
/**
* Add block CSS for the carousel block.
*
* @param string $css The CSS to add.
* @param array $block The block data.
*
* @return string
*/
function generateblocks_pro_carousel_block_css( $css, $block ) {
if ( ! isset( $block['attributes']['uniqueId'] ) ) {
return $css;
}
$block_name = $block['block_name'] ?? '';
if ( 'generateblocks-pro/carousel' !== $block_name ) {
return $css;
}
$selector = '.gb-carousel-' . $block['attributes']['uniqueId'];
$init_at_raw = $block['attributes']['htmlAttributes']['data-init-at'] ?? '';
$init_at = '';
if ( is_string( $init_at_raw ) ) {
$init_at_raw = trim( $init_at_raw );
if ( '' !== $init_at_raw && false === strpbrk( $init_at_raw, '{};()/*' ) ) {
$init_at = $init_at_raw;
if ( is_numeric( $init_at ) ) {
$init_at .= 'px';
}
}
}
if ( $init_at ) {
$css_rules = [
$selector . ':not([data-gb-carousel-initialized="true"]):not([data-gb-carousel-pending="true"]) .gb-carousel-items' => [
'position' => 'relative',
'width' => '100%',
'height' => '100%',
'z-index' => '1',
'display' => 'flex',
'overflow-x' => 'hidden',
'gap' => 'var(--gb-carousel-slide-gap, 0)',
],
$selector . ':not([data-gb-carousel-initialized="true"]) .gb-carousel-items > .gb-carousel-item' => [
'flex' => '0 0 calc((100% - var(--gb-carousel-slide-gap, 0px) * (var(--gb-carousel-slides-per-view, 1) - 1)) / var(--gb-carousel-slides-per-view, 1))',
'width' => '100%',
'height' => '100%',
'position' => 'relative',
'transition-property' => 'transform',
'display' => 'block',
'user-select' => 'none',
],
];
$media_query_css = generateblocks_pro_build_css_from_array( $css_rules );
$css .= "@media (max-width: {$init_at}) {{$media_query_css}}";
$not_at_rule_css_rules = [
$selector . ':not([data-gb-carousel-initialized="true"]) .gb-carousel-control' => [
'display' => 'none',
],
$selector . ':not([data-gb-carousel-initialized="true"]) .gb-carousel-pagination' => [
'display' => 'none',
],
];
$not_at_rule_css = generateblocks_pro_build_css_from_array( $not_at_rule_css_rules );
$css .= "@media (width > {$init_at}) {{$not_at_rule_css}}";
}
$grid_rows = $block['attributes']['htmlAttributes']['data-grid-rows'] ?? '';
if ( $grid_rows ) {
$slides_per_view = $block['attributes']['htmlAttributes']['data-slides-per-view'] ?? '1';
$total_items = $grid_rows * $slides_per_view;
$selected_items = $total_items + 1;
$css_rules = [
$selector . ':not([data-gb-carousel-initialized="true"]):not([data-gb-carousel-pending="true"]) > .gb-carousel-items > .gb-carousel-item:nth-child(n+' . $selected_items . ')' => [
'display' => 'none',
],
];
$built_css = generateblocks_pro_build_css_from_array( $css_rules );
if ( $init_at ) {
$css .= "@media (max-width: {$init_at}) {{$built_css}}";
} else {
$css .= $built_css;
}
}
return $css;
}
@@ -0,0 +1,87 @@
<?php
/**
* Handles the Carousel block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Carousel block class.
*/
class GenerateBlocks_Block_Carousel extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/carousel';
/**
* Render the Carousel block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Enqueue frontend scripts and styles.
self::enqueue_scripts();
self::enqueue_styles();
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
/**
* Enqueue frontend scripts for the carousel.
*/
private static function enqueue_scripts() {
wp_enqueue_script(
'generateblocks-carousel',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/carousel.js',
[],
GENERATEBLOCKS_PRO_VERSION,
true
);
}
/**
* Enqueue frontend styles for the carousel.
*/
private static function enqueue_styles() {
wp_enqueue_style(
'generateblocks-carousel',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/carousel-style.css',
[],
GENERATEBLOCKS_PRO_VERSION
);
}
/**
* Enqueue block assets.
*/
public static function enqueue_assets() {
self::enqueue_scripts();
self::enqueue_styles();
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Classic_Menu_Item extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/classic-menu-item';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the menu block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-classic-menu-item.php';
@@ -0,0 +1,344 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Classic_Menu extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/classic-menu';
/**
* Cache for mega menu display checks.
*
* @var array
*/
private static $mega_menu_cache = [];
/**
* Check if a mega menu overlay should display based on conditions.
*
* @param int $overlay_id The overlay post ID.
* @return bool Whether the overlay should display.
*/
private static function should_display_mega_menu( $overlay_id ) {
// Return false if overlays are disabled.
if ( ! generateblocks_pro_overlays_enabled() ) {
return false;
}
if ( ! $overlay_id ) {
return false;
}
// Check cache first.
if ( isset( self::$mega_menu_cache[ $overlay_id ] ) ) {
return self::$mega_menu_cache[ $overlay_id ];
}
// Check if post exists and is published.
$overlay_post = get_post( $overlay_id );
if ( ! $overlay_post || 'publish' !== $overlay_post->post_status ) {
self::$mega_menu_cache[ $overlay_id ] = false;
return false;
}
$overlay_post_content = do_blocks( $overlay_post->post_content ) ?? '';
if ( empty( $overlay_post_content ) ) {
self::$mega_menu_cache[ $overlay_id ] = false;
return false;
}
// Check display conditions.
$display_condition = get_post_meta( $overlay_id, '_gb_overlay_display_condition', true );
$display_conditions = [];
if ( $display_condition ) {
// Check if the condition post exists and is published.
$condition_post = get_post( $display_condition );
if ( $condition_post && 'publish' === $condition_post->post_status ) {
$display_conditions = get_post_meta( $display_condition, '_gb_conditions', true ) ?? [];
}
}
$show = true;
if ( ! empty( $display_conditions ) ) {
$show = GenerateBlocks_Pro_Conditions::show( $display_conditions );
$invert_condition = GenerateBlocks_Pro_Overlays::get_overlay_meta( $overlay_id, '_gb_overlay_display_condition_invert' );
// If invert is enabled, flip the result.
if ( $invert_condition ) {
$show = ! $show;
}
}
// Cache the result.
self::$mega_menu_cache[ $overlay_id ] = $show;
return $show;
}
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Clear mega menu cache for fresh checks.
self::$mega_menu_cache = [];
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$selected_menu = $attributes['menu'] ?? '';
$unique_id = $attributes['uniqueId'] ?? '';
if ( ! $selected_menu || ! $unique_id ) {
return;
}
if ( isset( $block->context['generateblocks-pro/subMenuType'] ) ) {
$sub_menu_type = $block->context['generateblocks-pro/subMenuType'];
} elseif ( isset( $_GET['subMenuType'] ) ) { // phpcs:ignore -- No processing of data.
$sub_menu_type = esc_attr( $_GET['subMenuType'] ); // phpcs:ignore -- No processing of data.
} else {
$sub_menu_type = 'hover';
}
$classes = [
'gb-menu',
'gb-menu--base',
'gb-menu-' . $unique_id,
'gb-menu--' . $sub_menu_type,
];
if ( ! empty( $attributes['globalClasses'] ) ) {
$classes = array_merge( $classes, $attributes['globalClasses'] );
}
if ( ! empty( $attributes['className'] ) ) {
$classes[] = $attributes['className'];
}
$class = implode( ' ', $classes );
$add_menu_item_classes = function( $classes, $menu_item ) use ( $sub_menu_type, $unique_id ) {
$mega_menu = get_post_meta( $menu_item->ID, '_gb_mega_menu', true );
if ( $mega_menu && self::should_display_mega_menu( $mega_menu ) ) {
$classes[] = 'menu-item-has-gb-mega-menu';
$classes[] = 'menu-item-has-children';
}
$classes[] = 'gb-menu-item';
$new_unique_id = substr_replace( $unique_id, 'mi', 0, 2 );
$classes[] = 'gb-menu-item-' . $new_unique_id;
// Escape classes.
$classes = array_map( 'esc_attr', $classes );
return $classes;
};
$add_dropdown_icon = function( $title, $menu_item ) use ( $sub_menu_type ) {
$mega_menu = get_post_meta( $menu_item->ID, '_gb_mega_menu', true );
$has_children = in_array( 'menu-item-has-children', $menu_item->classes, true );
$show_mega_menu = $mega_menu && self::should_display_mega_menu( $mega_menu );
if ( $show_mega_menu || $has_children ) {
$modal_id = $show_mega_menu ? 'gb-overlay-' . $mega_menu : '';
$arrow_icon = '<svg class="gb-submenu-toggle-icon" viewBox="0 0 330 512" aria-hidden="true" width="1em" height="1em" fill="currentColor"><path d="M305.913 197.085c0 2.266-1.133 4.815-2.833 6.514L171.087 335.593c-1.7 1.7-4.249 2.832-6.515 2.832s-4.815-1.133-6.515-2.832L26.064 203.599c-1.7-1.7-2.832-4.248-2.832-6.514s1.132-4.816 2.832-6.515l14.162-14.163c1.7-1.699 3.966-2.832 6.515-2.832 2.266 0 4.815 1.133 6.515 2.832l111.316 111.317 111.316-111.317c1.7-1.699 4.249-2.832 6.515-2.832s4.815 1.133 6.515 2.832l14.162 14.163c1.7 1.7 2.833 4.249 2.833 6.515z"></path></svg>';
$submenu_button = sprintf(
'<span class="gb-submenu-toggle" aria-label="%3$s" role="button" aria-expanded="false" aria-haspopup="menu" tabindex="0"%2$s>%1$s</span>',
$arrow_icon,
$modal_id ? ' data-gb-overlay="' . esc_attr( $modal_id ) . '" data-gb-overlay-trigger-type="click" aria-controls="' . esc_attr( $modal_id ) . '"' : '',
sprintf(
/* translators: %s: Menu item title. */
esc_attr__( '%s Sub-Menu', 'generateblocks-pro' ),
esc_attr( wp_strip_all_tags( $title ) )
)
);
if ( 'click' === $sub_menu_type ) {
return $title . $arrow_icon;
}
return $title . $submenu_button;
}
return $title;
};
$add_link_atts = function ( $atts, $menu_item ) use ( $sub_menu_type ) {
$class = $atts['class'] ?? '';
$class .= ' gb-menu-link';
$class = trim( $class );
$atts['class'] = esc_attr( $class );
$disable_links = isset( $_GET['disableLinks'] ); // phpcs:ignore -- No processing of data.
if ( $disable_links ) {
$atts['onClick'] = 'event.preventDefault();';
}
if ( 'hover' === $sub_menu_type || 'click' === $sub_menu_type ) {
$mega_menu = get_post_meta( $menu_item->ID, '_gb_mega_menu', true );
$show_mega_menu = $mega_menu && self::should_display_mega_menu( $mega_menu );
if ( $show_mega_menu ) {
$modal_id = 'gb-overlay-' . $mega_menu;
// Add directive and modal element target.
$atts['data-gb-overlay'] = $modal_id;
if ( 'hover' === $sub_menu_type ) {
$atts['data-gb-overlay-trigger-type'] = 'hover';
}
$atts['aria-controls'] = $modal_id;
}
if ( $show_mega_menu || in_array( 'menu-item-has-children', $menu_item->classes, true ) ) {
if ( 'click' === $sub_menu_type ) {
$atts['role'] = 'button';
$atts['aria-expanded'] = 'false';
$atts['aria-label'] = sprintf(
/* translators: %s: Menu item title. */
esc_attr__( '%s Sub-Menu', 'generateblocks-pro' ),
esc_attr( wp_strip_all_tags( $menu_item->title ?? '' ) )
);
}
$atts['aria-haspopup'] = 'menu';
}
}
return $atts;
};
$add_sub_menu_attributes = function( $atts, $args ) use ( $unique_id ) {
$new_unique_id = substr_replace( $unique_id, 'sm', 0, 2 );
$atts['class'] = 'sub-menu gb-sub-menu gb-sub-menu-' . $new_unique_id;
return $atts;
};
$add_mega_menu = function( $item_output, $item ) {
$item_id = $item->ID;
$mega_menu = get_post_meta( $item_id, '_gb_mega_menu', true );
if ( ! $mega_menu || ! self::should_display_mega_menu( $mega_menu ) ) {
return $item_output;
}
$action_id = 'gb-mega-menu-' . $mega_menu;
ob_start();
do_action( $action_id, $item );
$mega_menu_content = ob_get_clean();
return $item_output . $mega_menu_content;
};
add_filter( 'nav_menu_css_class', $add_menu_item_classes, 10, 2 );
add_filter( 'nav_menu_submenu_attributes', $add_sub_menu_attributes, 10, 2 );
add_filter( 'nav_menu_item_title', $add_dropdown_icon, 10, 2 );
add_filter( 'nav_menu_link_attributes', $add_link_atts, 10, 2 );
add_filter( 'walker_nav_menu_start_el', $add_mega_menu, 10, 2 );
ob_start();
wp_nav_menu(
[
'menu' => $selected_menu,
'container' => '',
'container_class' => '',
'menu_class' => $class,
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
'fallback_cb' => false,
]
);
remove_filter( 'nav_menu_css_class', $add_menu_item_classes, 10, 2 );
remove_filter( 'nav_menu_submenu_attributes', $add_sub_menu_attributes, 10, 2 );
remove_filter( 'nav_menu_item_title', $add_dropdown_icon, 10, 2 );
remove_filter( 'nav_menu_link_attributes', $add_link_atts, 10, 2 );
remove_filter( 'walker_nav_menu_start_el', $add_mega_menu, 10, 2 );
do_action( 'generateblocks_pro_after_menu_block', $attributes, is_admin() );
$block_content .= ob_get_clean();
if ( ! wp_style_is( 'generateblocks-classic-menu', 'enqueued' ) ) {
self::enqueue_style();
}
if ( ! wp_script_is( 'generateblocks-classic-menu', 'enqueued' ) ) {
self::enqueue_scripts();
}
return $block_content;
}
/**
* Enqueue block styles.
*/
private static function enqueue_style() {
wp_enqueue_style(
'generateblocks-classic-menu',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/classic-menu-style.css',
[],
GENERATEBLOCKS_PRO_VERSION
);
}
/**
* Enqueue block scripts.
*/
private static function enqueue_scripts() {
wp_enqueue_script(
'generateblocks-classic-menu',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/classic-menu.js',
[],
GENERATEBLOCKS_PRO_VERSION,
true
);
}
/**
* Enqueue block assets.
*/
public static function enqueue_assets() {
self::enqueue_scripts();
self::enqueue_style();
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Handle the menu block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-classic-menu.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_classic_menu_block_settings' );
/**
* Add menu block settings.
* We need to add CSS this way as it prepends `.editor-styles-wrapper` to the selectors.
* Including the stylesheet via block.json does not, which causes specificity issues.
*
* @param array $settings Block editor settings.
* @return array
*/
function generateblocks_pro_classic_menu_block_settings( $settings ) {
$navigation_css = file_get_contents( GENERATEBLOCKS_PRO_DIR . 'dist/classic-menu-style.css' ); // phpcs:ignore -- correct function to use.
if ( ! $navigation_css ) {
return $settings;
}
$settings['styles'][] = [
'css' => $navigation_css,
'source' => 'generateblocks-pro/class-menu-style',
];
return $settings;
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Classic_Sub_Menu extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/classic-sub-menu';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the menu block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-classic-sub-menu.php';
@@ -0,0 +1,627 @@
<?php
/**
* Handles the Form Field Control block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form field control block class.
*/
class GenerateBlocks_Block_Form_Field_Control extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array
*/
protected static $block_ids = [];
/**
* Block name.
*
* @var string
*/
public static $block_name = 'generateblocks-pro/form-field-control';
/**
* Maximum textarea rows accepted from block attributes.
*
* @var int
*/
const MAX_TEXTAREA_ROWS = 100;
/**
* Default allow-list for custom form control HTML attributes.
*
* Prefix entries ending in `-*` match all attributes with that prefix.
*
* @var array
*/
private static $allowed_html_attributes = [
'aria-*',
'data-*',
'autocapitalize',
'autocomplete',
'enterkeyhint',
'inputmode',
'max',
'maxlength',
'min',
'minlength',
'pattern',
'spellcheck',
'step',
'title',
];
/**
* Attributes generated by the renderer or controlled by runtime behavior.
*
* @var array
*/
private static $reserved_html_attributes = [
'aria-invalid',
'aria-required',
'checked',
'class',
'disabled',
'id',
'multiple',
'name',
'placeholder',
'readonly',
'required',
'rows',
'selected',
'style',
'type',
'value',
];
/**
* Field types where custom HTML attributes apply to one rendered control.
*
* @var array
*/
private static $custom_html_attribute_field_types = [
'text',
'email',
'url',
'tel',
'number',
'textarea',
'select',
'checkbox',
];
/**
* Field-type support for known default attributes.
*
* @var array
*/
private static $html_attribute_field_type_support = [
'autocapitalize' => [ 'text', 'email', 'url', 'tel', 'textarea' ],
'enterkeyhint' => [ 'text', 'email', 'url', 'tel', 'number', 'textarea' ],
'inputmode' => [ 'text', 'email', 'url', 'tel', 'number', 'textarea' ],
'max' => [ 'number' ],
'maxlength' => [ 'text', 'email', 'url', 'tel', 'textarea' ],
'min' => [ 'number' ],
'minlength' => [ 'text', 'email', 'url', 'tel', 'textarea' ],
'pattern' => [ 'text', 'email', 'url', 'tel', 'number' ],
'spellcheck' => [ 'text', 'email', 'url', 'tel', 'textarea' ],
'step' => [ 'number' ],
];
/**
* Get the allowed custom form control HTML attributes.
*
* @return array
*/
public static function get_allowed_html_attribute_names() {
/**
* Filter custom HTML attributes allowed on generated form controls.
*
* Prefix entries ending in `-*` match all attributes with that prefix.
* The renderer still blocks generated/runtime attributes such as
* `name`, `id`, `type`, `value`, `required`, `aria-required`,
* `aria-invalid`, `style`, event handlers, `form*`, and `data-gb-*`.
*
* @since 2.6.0
* @param array $allowed_attributes Allowed attribute names and prefix markers.
*/
$allowed_attributes = apply_filters(
'generateblocks_form_control_html_attributes',
self::$allowed_html_attributes
);
if ( ! is_array( $allowed_attributes ) ) {
return self::$allowed_html_attributes;
}
$normalized = [];
foreach ( $allowed_attributes as $attribute_name ) {
$attribute_name = self::normalize_html_attribute_name( $attribute_name, true );
if ( '' !== $attribute_name ) {
$normalized[] = $attribute_name;
}
}
return array_values( array_unique( $normalized ) );
}
/**
* Sanitize custom HTML attributes before rendering generated controls.
*
* @param mixed $html_attributes Raw htmlAttributes block attribute.
* @param string $field_type Normalized field type.
* @return array
*/
public static function sanitize_html_attributes( $html_attributes, $field_type = '' ) {
$field_type = is_string( $field_type ) ? $field_type : '';
if (
! is_array( $html_attributes ) ||
! in_array( $field_type, self::$custom_html_attribute_field_types, true )
) {
return [];
}
$sanitized = [];
$allowed_attributes = self::get_allowed_html_attribute_names();
foreach ( $html_attributes as $attribute_name => $attribute_value ) {
$attribute_name = self::normalize_html_attribute_name( $attribute_name );
if ( ! self::is_allowed_html_attribute( $attribute_name, $field_type, $allowed_attributes ) ) {
continue;
}
if ( is_array( $attribute_value ) || is_object( $attribute_value ) || null === $attribute_value ) {
continue;
}
$attribute_value = (string) $attribute_value;
if ( '' === $attribute_value && 0 !== strpos( $attribute_name, 'data-' ) ) {
continue;
}
$sanitized[ $attribute_name ] = $attribute_value;
}
return $sanitized;
}
/**
* Normalize an HTML attribute name for allow-list checks.
*
* @param mixed $attribute_name Raw attribute name.
* @param bool $allow_prefix Whether `aria-*` style prefix markers are valid.
* @return string
*/
private static function normalize_html_attribute_name( $attribute_name, $allow_prefix = false ) {
if ( ! is_scalar( $attribute_name ) ) {
return '';
}
$attribute_name = strtolower( trim( (string) $attribute_name ) );
$is_prefix = $allow_prefix && '-*' === substr( $attribute_name, -2 );
$attribute_name = preg_replace( '/[^a-z0-9._:-]/', '', $attribute_name );
if ( $is_prefix ) {
if ( ! is_string( $attribute_name ) || ! preg_match( '/^[a-z][a-z0-9._:-]*-$/', $attribute_name ) ) {
return '';
}
return $attribute_name . '*';
}
if ( ! is_string( $attribute_name ) || ! preg_match( '/^[a-z][a-z0-9._:-]*$/', $attribute_name ) ) {
return '';
}
return $attribute_name;
}
/**
* Whether an attribute name can be rendered for a field type.
*
* @param string $attribute_name Normalized attribute name.
* @param string $field_type Normalized field type.
* @param array $allowed_attributes Allowed attribute names and prefix markers.
* @return bool
*/
private static function is_allowed_html_attribute( $attribute_name, $field_type, $allowed_attributes ) {
if ( '' === $attribute_name ) {
return false;
}
if (
in_array( $attribute_name, self::$reserved_html_attributes, true ) ||
0 === strpos( $attribute_name, 'on' ) ||
0 === strpos( $attribute_name, 'form' ) ||
0 === strpos( $attribute_name, 'data-gb-' )
) {
return false;
}
$is_allowed = false;
foreach ( $allowed_attributes as $allowed_attribute ) {
if ( '-*' === substr( $allowed_attribute, -2 ) ) {
$prefix = substr( $allowed_attribute, 0, -1 );
if ( 0 === strpos( $attribute_name, $prefix ) ) {
$is_allowed = true;
break;
}
continue;
}
if ( $attribute_name === $allowed_attribute ) {
$is_allowed = true;
break;
}
}
if ( ! $is_allowed ) {
return false;
}
if (
isset( self::$html_attribute_field_type_support[ $attribute_name ] ) &&
! in_array( $field_type, self::$html_attribute_field_type_support[ $attribute_name ], true )
) {
return false;
}
return true;
}
/**
* Get sanitized custom HTML attributes for a control block.
*
* @param array $attributes Block attributes.
* @param string $field_type Normalized field type.
* @return array
*/
private static function get_custom_html_attributes( $attributes, $field_type ) {
return self::sanitize_html_attributes( $attributes['htmlAttributes'] ?? [], $field_type );
}
/**
* Render the Form Field Control block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param WP_Block $block The block instance.
* @return string
*/
public static function render_block( $attributes, $block_content, $block ) {
unset( $block_content );
$field_type = GenerateBlocks_Block_Form_Field::normalize_field_type(
GenerateBlocks_Block_Form_Field::get_context_value( $block, 'fieldType', '' )
);
$field_name = sanitize_key(
GenerateBlocks_Block_Form_Field::get_context_value( $block, 'fieldName', '' )
);
$unique_id = (string) GenerateBlocks_Block_Form_Field::get_context_value( $block, 'uniqueId', '' );
$required = 'hidden' !== $field_type && ! empty( GenerateBlocks_Block_Form_Field::get_context_value( $block, 'isRequired', false ) );
$field_id = GenerateBlocks_Block_Form_Field::get_field_id( $unique_id, $field_name );
$html = self::build_control( $attributes, $field_type, $field_name, $field_id, $required, $block );
return generateblocks_maybe_add_block_css(
$html,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
}
/**
* Build the control markup.
*
* @param array $attributes Block attributes.
* @param string $field_type Field type.
* @param string $field_name Field name.
* @param string $field_id Field ID.
* @param bool $required Whether required.
* @param mixed $block Block instance.
* @return string
*/
private static function build_control( $attributes, $field_type, $field_name, $field_id, $required, $block ) {
$default_value = GenerateBlocks_Block_Form_Field::resolve_default_value( $attributes['defaultValue'] ?? '', $block );
switch ( $field_type ) {
case 'textarea':
return self::build_textarea( $attributes, $field_name, $field_id, $required, $default_value );
case 'select':
return self::build_select( $attributes, $field_name, $field_id, $required, $default_value );
case 'checkbox':
return self::build_checkbox( $attributes, $field_name, $field_id, $required );
case 'radio':
return self::build_choice_group( $attributes, $field_name, $field_id, true, $required, $default_value );
case 'checkbox-group':
return self::build_choice_group( $attributes, $field_name, $field_id, false, false, '' );
default:
return self::build_input( $attributes, $field_name, $field_id, $field_type, $required, $default_value );
}
}
/**
* Build base control classes.
*
* @param array $attributes Block attributes.
* @param string $element Element class.
* @return string
*/
private static function get_control_classes( $attributes, $element ) {
return GenerateBlocks_Block_Form_Field::get_block_classes(
'gb-form-field-control',
$attributes,
[ $element ]
);
}
/**
* Build an input element.
*
* @param array $attributes Block attributes.
* @param string $name Field name.
* @param string $id Field ID.
* @param string $type Input type.
* @param bool $required Whether required.
* @param string $default_value Default value.
* @return string
*/
private static function build_input( $attributes, $name, $id, $type, $required, $default_value ) {
$attrs = array_merge(
self::get_custom_html_attributes( $attributes, $type ),
[
'class' => self::get_control_classes( $attributes, 'gb-form-field__input' ),
'type' => $type,
'name' => $name,
'id' => $id,
]
);
if ( ! empty( $attributes['placeholder'] ) && 'hidden' !== $type ) {
$attrs['placeholder'] = (string) $attributes['placeholder'];
}
if ( '' !== $default_value ) {
$attrs['value'] = $default_value;
}
if ( $required ) {
$attrs['required'] = true;
$attrs['aria-required'] = 'true';
}
return self::single_tag( 'input', $attrs );
}
/**
* Build a textarea element.
*
* @param array $attributes Block attributes.
* @param string $name Field name.
* @param string $id Field ID.
* @param bool $required Whether required.
* @param string $default_value Default value.
* @return string
*/
private static function build_textarea( $attributes, $name, $id, $required, $default_value ) {
$attrs = array_merge(
self::get_custom_html_attributes( $attributes, 'textarea' ),
[
'class' => self::get_control_classes( $attributes, 'gb-form-field__input' ),
'name' => $name,
'id' => $id,
]
);
$rows = isset( $attributes['rows'] ) ? min( self::MAX_TEXTAREA_ROWS, absint( $attributes['rows'] ) ) : 0;
if ( $rows ) {
$attrs['rows'] = $rows;
}
if ( ! empty( $attributes['placeholder'] ) ) {
$attrs['placeholder'] = (string) $attributes['placeholder'];
}
if ( $required ) {
$attrs['required'] = true;
$attrs['aria-required'] = 'true';
}
return self::wrap_tag( 'textarea', $attrs, esc_textarea( $default_value ) );
}
/**
* Build a select element.
*
* @param array $attributes Block attributes.
* @param string $name Field name.
* @param string $id Field ID.
* @param bool $required Whether required.
* @param string $default_value Default selected value.
* @return string
*/
private static function build_select( $attributes, $name, $id, $required, $default_value ) {
$attrs = array_merge(
self::get_custom_html_attributes( $attributes, 'select' ),
[
'class' => self::get_control_classes( $attributes, 'gb-form-field__input' ),
'name' => $name,
'id' => $id,
]
);
if ( $required ) {
$attrs['required'] = true;
$attrs['aria-required'] = 'true';
}
$options_html = '';
$placeholder = $attributes['placeholder'] ?? '';
if ( '' !== $placeholder ) {
$options_html .= sprintf(
'<option value="" disabled%s>%s</option>',
'' === $default_value ? ' selected' : '',
esc_html( $placeholder )
);
}
foreach ( (array) ( $attributes['options'] ?? [] ) as $option ) {
$option = GenerateBlocks_Block_Form_Field::normalize_option( $option );
if ( ! $option ) {
continue;
}
$options_html .= sprintf(
'<option value="%s"%s>%s</option>',
esc_attr( $option['value'] ),
'' !== $default_value && $option['value'] === $default_value ? ' selected' : '',
esc_html( $option['label'] )
);
}
return self::wrap_tag( 'select', $attrs, $options_html );
}
/**
* Build a checkbox element.
*
* @param array $attributes Block attributes.
* @param string $name Field name.
* @param string $id Field ID.
* @param bool $required Whether required.
* @return string
*/
private static function build_checkbox( $attributes, $name, $id, $required ) {
$checked_value = ! empty( $attributes['checkedValue'] )
? sanitize_text_field( (string) $attributes['checkedValue'] )
: 'yes';
$attrs = array_merge(
self::get_custom_html_attributes( $attributes, 'checkbox' ),
[
'class' => self::get_control_classes( $attributes, 'gb-form-field__input' ),
'type' => 'checkbox',
'name' => $name,
'id' => $id,
'value' => $checked_value,
]
);
if ( $required ) {
$attrs['required'] = true;
$attrs['aria-required'] = 'true';
}
return self::single_tag( 'input', $attrs );
}
/**
* Build radio/checkbox-group options.
*
* @param array $attributes Block attributes.
* @param string $name Field name.
* @param string $id Field ID prefix.
* @param bool $radio Whether radio buttons.
* @param bool $required Whether radio group is required.
* @param string $default_value Default selected value.
* @return string
*/
private static function build_choice_group( $attributes, $name, $id, $radio, $required, $default_value ) {
$attrs = [
'class' => self::get_control_classes( $attributes, 'gb-form-field__group' ),
];
$html = '';
foreach ( (array) ( $attributes['options'] ?? [] ) as $index => $option ) {
$option = GenerateBlocks_Block_Form_Field::normalize_option( $option );
if ( ! $option ) {
continue;
}
$option_id = $id . '-' . $index;
$html .= sprintf(
'<label class="gb-form-field__group-option" for="%s">',
esc_attr( $option_id )
);
$input_attrs = [
'type' => $radio ? 'radio' : 'checkbox',
'name' => $radio ? $name : $name . '[]',
'id' => $option_id,
'value' => $option['value'],
];
if ( $radio && '' !== $default_value && $option['value'] === $default_value ) {
$input_attrs['checked'] = true;
}
if ( $radio && $required ) {
$input_attrs['required'] = true;
}
$html .= '<input ' . GenerateBlocks_Block_Form_Field::format_attributes( $input_attrs ) . ' />';
$html .= '<span>' . esc_html( $option['label'] ) . '</span>';
$html .= '</label>';
}
return self::wrap_tag( 'div', $attrs, $html );
}
/**
* Build a single tag.
*
* @param string $tag Tag name.
* @param array $attrs Generated attributes.
* @return string
*/
private static function single_tag( $tag, $attrs ) {
$attr_string = GenerateBlocks_Block_Form_Field::format_attributes( $attrs );
return sprintf( '<%s %s />', tag_escape( $tag ), $attr_string );
}
/**
* Build an element with content.
*
* @param string $tag Tag name.
* @param array $attrs Generated attributes.
* @param string $content Inner HTML.
* @return string
*/
private static function wrap_tag( $tag, $attrs, $content ) {
$attr_string = GenerateBlocks_Block_Form_Field::format_attributes( $attrs );
return sprintf(
'<%1$s %2$s>%3$s</%1$s>',
tag_escape( $tag ),
$attr_string,
$content
);
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Register the Form Field Control block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/class-form-field-control.php';
@@ -0,0 +1,106 @@
<?php
/**
* Handles the Form Field Label block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form field label block class.
*/
class GenerateBlocks_Block_Form_Field_Label extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array
*/
protected static $block_ids = [];
/**
* Block name.
*
* @var string
*/
public static $block_name = 'generateblocks-pro/form-field-label';
/**
* Render the Form Field Label block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param WP_Block $block The block instance.
* @return string
*/
public static function render_block( $attributes, $block_content, $block ) {
$field_type = GenerateBlocks_Block_Form_Field::normalize_field_type(
GenerateBlocks_Block_Form_Field::get_context_value( $block, 'fieldType', '' )
);
if ( 'hidden' === $field_type ) {
return '';
}
$field_name = (string) GenerateBlocks_Block_Form_Field::get_context_value( $block, 'fieldName', '' );
$unique_id = (string) GenerateBlocks_Block_Form_Field::get_context_value( $block, 'uniqueId', '' );
$required = ! empty( GenerateBlocks_Block_Form_Field::get_context_value( $block, 'isRequired', false ) );
$is_group = GenerateBlocks_Block_Form_Field::is_group_type( $field_type );
$has_content = '' !== trim( wp_strip_all_tags( (string) ( $attributes['content'] ?? '' ) ) );
if ( ! $has_content ) {
return '';
}
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
$tag_name = $is_group ? 'legend' : 'label';
if ( $processor->next_tag( $tag_name ) ) {
if ( ! $is_group ) {
$processor->set_attribute( 'for', GenerateBlocks_Block_Form_Field::get_field_id( $unique_id, $field_name ) );
}
$block_content = $processor->get_updated_html();
}
}
return $required ? self::append_required_marker( $block_content, $is_group ? 'legend' : 'label' ) : $block_content;
}
/**
* Append the runtime required marker inside the saved label/legend markup.
*
* @param string $html Label block HTML.
* @param string $tag_name Label tag name.
* @return string
*/
private static function append_required_marker( $html, $tag_name ) {
if ( false !== strpos( $html, 'gb-form-field__required' ) ) {
return $html;
}
$tag_name = tag_escape( $tag_name );
$updated = preg_replace(
sprintf( '#</%s>\s*$#i', preg_quote( $tag_name, '#' ) ),
'<span class="gb-form-field__required" aria-hidden="true"> *</span></' . $tag_name . '>',
$html,
1
);
return is_string( $updated ) ? $updated : $html;
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Register the Form Field Label block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/class-form-field-label.php';
@@ -0,0 +1,395 @@
<?php
/**
* Handles the Form Field wrapper block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form field wrapper block class.
*/
class GenerateBlocks_Block_Form_Field extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array
*/
protected static $block_ids = [];
/**
* Block name.
*
* @var string
*/
public static $block_name = 'generateblocks-pro/form-field';
/**
* All allowed field types.
*
* @var array
*/
private static $allowed_types = [ 'text', 'email', 'url', 'tel', 'number', 'hidden', 'textarea', 'select', 'checkbox', 'radio', 'checkbox-group' ];
/**
* Field types that support same-value match validation.
*
* @var array
*/
private static $matchable_types = [ 'text', 'email', 'url', 'tel', 'number', 'textarea' ];
/**
* Allowed display condition operators.
*
* @var array
*/
private static $allowed_condition_operators = [ 'is', 'isnot', 'isempty', 'isnotempty' ];
/**
* Per-render instance number, set by GenerateBlocks_Pro_Form_Render before
* rendering each form.
*
* @var int
*/
private static $render_instance = 0;
/**
* Set the current render instance number.
*
* @param int $instance Instance number (1+) or 0 to reset.
*/
public static function set_render_instance( $instance ) {
self::$render_instance = (int) $instance;
}
/**
* Get the current render instance number.
*
* @return int
*/
public static function get_render_instance() {
return (int) self::$render_instance;
}
/**
* Suffix appended to every field ID for the current render.
*
* @return string
*/
public static function id_suffix() {
return self::$render_instance > 0 ? '-' . self::$render_instance : '';
}
/**
* Normalize a field type.
*
* @param mixed $field_type Raw field type.
* @return string
*/
public static function normalize_field_type( $field_type ) {
$field_type = is_string( $field_type ) ? $field_type : '';
if ( '' === $field_type ) {
return 'text';
}
if ( ! in_array( $field_type, self::$allowed_types, true ) ) {
return 'text';
}
return $field_type;
}
/**
* Whether a field type uses the wrapper as a fieldset.
*
* @param string $field_type Field type.
* @return bool
*/
public static function is_group_type( $field_type ) {
return in_array( self::normalize_field_type( $field_type ), [ 'radio', 'checkbox-group' ], true );
}
/**
* Get a field ID shared by label/control children.
*
* @param string $unique_id Wrapper unique ID.
* @param string $field_name Field name fallback.
* @return string
*/
public static function get_field_id( $unique_id, $field_name = '' ) {
$key = $unique_id ? $unique_id : sanitize_key( $field_name );
if ( '' === $key ) {
$key = 'field';
}
return 'gb-field-' . sanitize_html_class( $key ) . self::id_suffix();
}
/**
* Get a value from a WP_Block context object.
*
* @param mixed $block Block object.
* @param string $key Context key suffix.
* @param mixed $fallback Fallback.
* @return mixed
*/
public static function get_context_value( $block, $key, $fallback = '' ) {
$context_key = 'generateblocks-pro/form-field/' . $key;
if ( is_object( $block ) && isset( $block->context ) && is_array( $block->context ) && array_key_exists( $context_key, $block->context ) ) {
return $block->context[ $context_key ];
}
return $fallback;
}
/**
* Build classes for a styles-builder-aware block.
*
* @param string $base_class Base class.
* @param array $attributes Block attributes.
* @param array $extra Extra classes.
* @return string
*/
public static function get_block_classes( $base_class, $attributes, $extra = [] ) {
$classes = [ $base_class ];
if ( ! empty( $attributes['globalClasses'] ) && is_array( $attributes['globalClasses'] ) ) {
foreach ( $attributes['globalClasses'] as $class_name ) {
$class_name = sanitize_html_class( $class_name );
if ( $class_name ) {
$classes[] = $class_name;
}
}
}
if ( ! empty( $attributes['styles'] ) && is_array( $attributes['styles'] ) && ! empty( $attributes['uniqueId'] ) ) {
$classes[] = $base_class . '-' . sanitize_html_class( $attributes['uniqueId'] );
}
foreach ( $extra as $class_name ) {
$class_name = sanitize_html_class( $class_name );
if ( $class_name ) {
$classes[] = $class_name;
}
}
return implode( ' ', array_unique( array_filter( $classes ) ) );
}
/**
* Format an associative attribute array.
*
* @param array $attributes Attribute name/value pairs.
* @return string
*/
public static function format_attributes( $attributes ) {
$out = [];
foreach ( $attributes as $name => $value ) {
if ( true === $value ) {
$out[] = esc_attr( $name );
continue;
}
if ( false === $value || null === $value || is_array( $value ) || is_object( $value ) ) {
continue;
}
$out[] = sprintf( '%s="%s"', esc_attr( $name ), esc_attr( (string) $value ) );
}
return implode( ' ', $out );
}
/**
* Normalize a choice option for rendering.
*
* @param mixed $option Raw option attribute.
* @return array|null Normalized label/value pair or null for empty rows.
*/
public static function normalize_option( $option ) {
if ( ! is_array( $option ) ) {
return null;
}
$label = isset( $option['label'] ) ? (string) $option['label'] : '';
$value = isset( $option['value'] ) ? (string) $option['value'] : '';
if ( '' === $value && '' !== $label ) {
$value = $label;
}
if ( '' === $label && '' !== $value ) {
$label = $value;
}
if ( '' === $label && '' === $value ) {
return null;
}
return [
'label' => $label,
'value' => $value,
];
}
/**
* Sanitize display conditions before rendering them as frontend data attrs.
*
* Incomplete rows can be left behind while editing, especially if a trigger
* field is removed before the empty row is deleted. Those must not create
* `data-gb-conditions`, because an empty field name evaluates as empty on the
* frontend and causes a hidden-then-revealed layout shift.
*
* @param mixed $conditions Raw conditions attribute.
* @return array
*/
private static function sanitize_conditions( $conditions ) {
if ( ! is_array( $conditions ) ) {
return [];
}
$sanitized = [];
foreach ( $conditions as $condition ) {
if ( ! is_array( $condition ) ) {
continue;
}
$field = sanitize_key( $condition['field'] ?? '' );
$operator = sanitize_key( $condition['operator'] ?? '' );
if ( '' === $field || ! in_array( $operator, self::$allowed_condition_operators, true ) ) {
continue;
}
$sanitized[] = [
'field' => $field,
'operator' => $operator,
'value' => (string) ( $condition['value'] ?? '' ),
];
}
return $sanitized;
}
/**
* Resolve dynamic tags in a default value.
*
* @param string $default_value Raw default value.
* @param mixed $block Block instance.
* @return string
*/
public static function resolve_default_value( $default_value, $block ) {
$default_value = (string) $default_value;
if ( '' !== $default_value && false !== strpos( $default_value, '{{' ) && class_exists( 'GenerateBlocks_Register_Dynamic_Tag' ) ) {
$default_value = GenerateBlocks_Register_Dynamic_Tag::replace_tags(
$default_value,
[],
$block
);
}
return (string) $default_value;
}
/**
* Render the Form Field wrapper block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param WP_Block $block The block instance.
* @return string The modified block content.
*/
public static function render_block( $attributes, $block_content, $block ) {
unset( $block );
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$field_type = self::normalize_field_type( $attributes['fieldType'] ?? '' );
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $block_content;
}
$processor = new WP_HTML_Tag_Processor( $block_content );
$tag_name = self::is_group_type( $field_type ) ? 'fieldset' : 'div';
if ( ! $processor->next_tag( $tag_name ) ) {
return $block_content;
}
$modifier_map = [
'checkbox' => 'gb-form-field--checkbox',
'radio' => 'gb-form-field--radio',
'checkbox-group' => 'gb-form-field--checkbox-group',
];
if ( isset( $modifier_map[ $field_type ] ) ) {
$processor->add_class( $modifier_map[ $field_type ] );
}
// The wrapper is a native <fieldset>, whose implicit "group" role does not
// support aria-required (Lighthouse/axe: aria-allowed-attr). The required
// state is carried by the native `required` attribute on the radio inputs;
// checkbox groups have no native per-input required, so they validate via
// data-gb-required + JS. Either way the <fieldset> needs no aria-required.
if ( 'checkbox-group' === $field_type && ! empty( $attributes['isRequired'] ) ) {
$processor->set_attribute( 'data-gb-required', 'true' );
}
$field_name = sanitize_key( $attributes['fieldName'] ?? '' );
$matches_field = sanitize_key( $attributes['matchesField'] ?? '' );
if (
$field_name &&
$matches_field &&
$field_name !== $matches_field &&
in_array( $field_type, self::$matchable_types, true )
) {
$processor->set_attribute( 'data-gb-field-name', $field_name );
$processor->set_attribute( 'data-gb-matches-field', $matches_field );
$processor->set_attribute(
'data-gb-match-message',
__( 'The matching fields must have the same value.', 'generateblocks-pro' )
);
$processor->set_attribute(
'data-gb-match-message-with-label',
/* translators: %1$s: source field label, %2$s: target field label. */
__( 'Please make sure "%1$s" matches "%2$s".', 'generateblocks-pro' )
);
$processor->set_attribute(
'data-gb-match-message-with-target',
/* translators: %s: target field label. */
__( 'This field must match "%s".', 'generateblocks-pro' )
);
}
$conditions = self::sanitize_conditions( $attributes['conditions'] ?? [] );
if ( ! empty( $conditions ) ) {
$processor->set_attribute( 'data-gb-conditions', wp_json_encode( $conditions ) );
$processor->set_attribute( 'hidden', true );
}
return $processor->get_updated_html();
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Form field block loader.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/class-form-field.php';
@@ -0,0 +1,77 @@
<?php
/**
* Form Render block.
*
* Page-side reference. Carries `formId` (a `gblocks_form` post ID) plus
* styling attributes; everything else (fields, actions, email, Turnstile,
* success/error messages) lives on the form post itself.
*
* Render delegates to the shared render helper so editor preview and
* public output stay byte-for-byte equivalent.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form Render block class.
*/
class GenerateBlocks_Block_Form_Render extends GenerateBlocks_Block {
/**
* Block name.
*
* @var string
*/
public static $block_name = 'generateblocks-pro/form-render';
/**
* Render the Form Render block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param WP_Block $block The block instance.
* @return string The modified block content.
*/
public static function render_block( $attributes, $block_content, $block ) {
unset( $block_content );
$form_id = absint( $attributes['formId'] ?? 0 );
if ( ! $form_id ) {
return '';
}
$host_post_id = $block->context['postId'] ?? get_the_ID();
$host_post_id = $host_post_id ? absint( $host_post_id ) : 0;
$rendered = GenerateBlocks_Pro_Form_Render::render( $form_id, 'public', [ 'host_post_id' => $host_post_id ] );
if ( is_wp_error( $rendered ) ) {
// Public render failure — emit nothing rather than leaking error
// detail. Admin users see a placeholder hint to help debugging.
if ( current_user_can( GenerateBlocks_Pro_Form_Post_Type::get_capability( 'manage' ) ) ) {
return sprintf(
'<div class="gb-form gb-form--missing">%s</div>',
esc_html__( 'Form not available. Make sure the referenced form is published.', 'generateblocks-pro' )
);
}
return '';
}
GenerateBlocks_Pro_Form_Render::enqueue_public_assets();
return $rendered;
}
/**
* Enqueue block assets.
*/
public static function enqueue_assets() {
GenerateBlocks_Pro_Form_Render::enqueue_public_assets();
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Form Render block loader.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/class-form-render.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_form_block_editor_settings', 20 );
/**
* Add block editor settings for the form blocks.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_form_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-form-render',
'.editor-styles-wrapper .wp-block-generateblocks-pro-form',
'.editor-styles-wrapper .wp-block-generateblocks-pro-form-field',
'.editor-styles-wrapper .wp-block-generateblocks-pro-form-field-label',
'.editor-styles-wrapper .wp-block-generateblocks-pro-form-field-control',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}
@@ -0,0 +1,62 @@
<?php
/**
* Form block.
*
* Authoring container that lives inside `gblocks_form` post content. Renders
* its inner form-field blocks with our standard wrapper class. Runtime
* action/method/security attributes are added by class-form-render.php while
* a saved form post is being rendered.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form block class.
*/
class GenerateBlocks_Block_Form extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array
*/
protected static $block_ids = [];
/**
* Block name.
*
* @var string
*/
public static $block_name = 'generateblocks-pro/form';
/**
* Render the Form block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param WP_Block $block The block instance.
* @return string The modified block content.
*/
public static function render_block( $attributes, $block_content, $block ) {
unset( $block );
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( class_exists( 'GenerateBlocks_Pro_Form_Render' ) ) {
return GenerateBlocks_Pro_Form_Render::prepare_form_block_output( $block_content );
}
return $block_content;
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Form block loader.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/class-form.php';
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Menu_Container extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/menu-container';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the menu container block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-menu-container.php';
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Menu_Toggle extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/menu-toggle';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,9 @@
<?php
/**
* Handle the menu toggle block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-menu-toggle.php';
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Navigation extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/navigation';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,62 @@
<?php
/**
* Handle the navigation block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-navigation.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_navigation_block_editor_settings', 20 );
/**
* Add block editor settings for the navigation block.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_navigation_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-classic-menu',
'.editor-styles-wrapper .wp-block-generateblocks-pro-menu-container',
'.editor-styles-wrapper .wp-block-generateblocks-pro-menu-toggle',
'.editor-styles-wrapper .wp-block-generateblocks-pro-navigation',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}
add_filter( 'generateblocks_block_css', 'generateblocks_pro_navigation_block_css', 10, 2 );
/**
* Add block CSS for the navigation block.
*
* @param string $css The CSS to add.
* @param array $block The block data.
*
* @return string
*/
function generateblocks_pro_navigation_block_css( $css, $block ) {
if ( ! isset( $block['attributes']['uniqueId'] ) ) {
return $css;
}
$block_name = $block['block_name'] ?? '';
if ( 'generateblocks-pro/navigation' !== $block_name ) {
return $css;
}
$selector = '.gb-navigation-' . $block['attributes']['uniqueId'];
$mobile_breakpoint = $block['attributes']['htmlAttributes']['data-gb-mobile-breakpoint'] ?? '';
if ( $mobile_breakpoint ) {
$css .= "@media (width > {$mobile_breakpoint}) {{$selector} .gb-menu-toggle {display: none;}}";
$css .= "@media (max-width: {$mobile_breakpoint}) {{$selector} .gb-menu-container:not(.gb-menu-container--toggled) {display: none;}}";
} else {
$css .= "{$selector} .gb-menu-toggle {display: none;}";
}
return $css;
}
@@ -0,0 +1,70 @@
<?php
/**
* Handles the Content block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Content block class.
*/
class GenerateBlocks_Block_Site_Header extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/site-header';
/**
* Store when to enqueue assets.
*
* @var array $assets_to_enqueue The assets to enqueue.
*/
public static $assets_to_enqueue = [];
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$html_attributes = $attributes['htmlAttributes'] ?? [];
$is_sticky = array_key_exists( 'data-gb-is-sticky', $html_attributes );
if ( $is_sticky ) {
wp_enqueue_script(
'generateblocks-pro-sticky',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/sticky-element.js',
[],
GENERATEBLOCKS_PRO_VERSION,
true
);
}
return $block_content;
}
}
@@ -0,0 +1,25 @@
<?php
/**
* Handle the navigation block.
*
* @package GenerateBlocks Pro
*/
// Include our files.
require_once 'class-site-header.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_site_header_block_editor_settings', 20 );
/**
* Add block editor settings for the navigation block.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_site_header_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-site-header',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}
@@ -0,0 +1,61 @@
<?php
/**
* Handles the Tab Item block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tabs menu block class.
*/
class GenerateBlocks_Block_Tab_Item extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/tab-item';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( $attributes['tabItemOpen'] ) {
$processor = class_exists( 'WP_HTML_Tag_Processor' )
? new WP_HTML_Tag_Processor( $block_content )
: false;
if ( $processor && $processor->next_tag( $attributes['tagName'] ?? 'div' ) ) {
$processor->add_class( 'gb-tabs__item-open' );
$block_content = $processor->get_updated_html();
}
}
return $block_content;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Tab Items block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tabs menu block class.
*/
class GenerateBlocks_Block_Tab_Items extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/tab-items';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,82 @@
<?php
/**
* Handles the Tab Menu Item block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tabs menu block class.
*/
class GenerateBlocks_Block_Tab_Menu_Item extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/tab-menu-item';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $block_content;
}
$processor = new WP_HTML_Tag_Processor( $block_content );
$tag_name = $attributes['tagName'] ?? 'div';
$main_tag = $processor->next_tag( $tag_name );
$updated_html = false;
if ( $processor && $main_tag ) {
if ( $attributes['tabItemOpen'] ) {
$processor->add_class( 'gb-block-is-current' );
$updated_html = true;
}
if ( 'div' === $tag_name ) {
if ( ! $processor->get_attribute( 'tabindex' ) ) {
$processor->set_attribute( 'tabindex', '0' );
}
if ( ! $processor->get_attribute( 'role' ) ) {
$processor->set_attribute( 'role', 'button' );
}
$updated_html = true;
}
if ( $updated_html ) {
$block_content = $processor->get_updated_html();
}
}
return $block_content;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Tabs Menu block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tabs menu block class.
*/
class GenerateBlocks_Block_Tabs_Menu extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/tabs-menu';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $block_content;
}
}
@@ -0,0 +1,91 @@
<?php
/**
* Handles the Tabs block.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Tabs block class.
*/
class GenerateBlocks_Block_Tabs extends GenerateBlocks_Block {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
protected static $block_ids = [];
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks-pro/tabs';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param array $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
// Add styles to this block if needed.
$block_content = generateblocks_maybe_add_block_css(
$block_content,
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
if ( ! wp_style_is( 'generateblocks-tabs', 'enqueued' ) ) {
self::enqueue_style();
}
if ( ! wp_script_is( 'generateblocks-tabs', 'enqueued' ) ) {
self::enqueue_assets();
}
return $block_content;
}
/**
* Enqueue block styles.
*/
private static function enqueue_style() {
wp_enqueue_style(
'generateblocks-tabs',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/blocks/tabs/tabs.css',
[],
GENERATEBLOCKS_PRO_VERSION
);
}
/**
* Enqueue block scripts.
*/
private static function enqueue_scripts() {
wp_enqueue_script(
'generateblocks-tabs',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/tabs.js',
[],
GENERATEBLOCKS_PRO_VERSION,
true
);
}
/**
* Enqueue block assets.
*/
public static function enqueue_assets() {
self::enqueue_scripts();
self::enqueue_style();
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Handles the accordion block.
*
* @package GenerateBlocks Pro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
require_once 'class-tabs.php';
require_once 'class-tabs-menu.php';
require_once 'class-tab-menu-item.php';
require_once 'class-tab-items.php';
require_once 'class-tab-item.php';
add_filter( 'block_editor_settings_all', 'generateblocks_pro_tabs_block_editor_settings', 20 );
/**
* Add block editor settings for the tabs block.
*
* @param array $settings The block editor settings.
*/
function generateblocks_pro_tabs_block_editor_settings( $settings ) {
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-pro-tabs',
'.editor-styles-wrapper .wp-block-generateblocks-pro-tabs-menu',
'.editor-styles-wrapper .wp-block-generateblocks-pro-tab-menu-item',
'.editor-styles-wrapper .wp-block-generateblocks-pro-tab-items',
'.editor-styles-wrapper .wp-block-generateblocks-pro-tab-item',
];
$css = implode( ',', $blocks_to_reset ) . ' {max-width:unset;margin:0}';
$settings['styles'][] = [ 'css' => $css ];
return $settings;
}