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,83 @@
<?php
/**
* Handles block functionality.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Block class.
*/
class 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 = '';
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
static::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to check.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) static::$block_ids );
}
/**
* Render the 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 ) {
return $block_content;
}
/**
* Get the block CSS.
*
* @param array $attributes The block attributes.
*/
public static function get_css( $attributes ) {
$id = $attributes['uniqueId'] ?? '';
if ( ! $id ) {
return '';
}
// Store this block ID in memory.
static::store_block_id( $id );
return apply_filters(
'generateblocks_block_css',
$attributes['css'] ?? '',
[
'attributes' => $attributes ?? [],
'block_name' => static::$block_name ?? '',
]
);
}
}
@@ -0,0 +1,307 @@
<?php
/**
* Handles the Button Container block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Button Container related functions.
*/
class GenerateBlocks_Block_Button_Container {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
private static $block_ids = [];
/**
* Keep track of CSS we want to output once per block type.
*
* @var boolean
*/
private static $singular_css_added = false;
/**
* Block defaults.
*/
public static function defaults() {
return [
'alignment' => '',
'alignmentTablet' => '',
'alignmentMobile' => '',
'marginTop' => '',
'marginRight' => '',
'marginBottom' => '',
'marginLeft' => '',
'marginTopTablet' => '',
'marginRightTablet' => '',
'marginBottomTablet' => '',
'marginLeftTablet' => '',
'marginTopMobile' => '',
'marginRightMobile' => '',
'marginBottomMobile' => '',
'marginLeftMobile' => '',
'marginUnit' => 'px',
'stack' => false,
'stackTablet' => false,
'stackMobile' => false,
'fillHorizontalSpace' => false,
'fillHorizontalSpaceTablet' => false,
'fillHorizontalSpaceMobile' => false,
];
}
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
self::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to store.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) self::$block_ids );
}
/**
* Compile our CSS data based on our block attributes.
*
* @param array $attributes Our block attributes.
*/
public static function get_css_data( $attributes ) {
$css = new GenerateBlocks_Dynamic_CSS();
$desktop_css = new GenerateBlocks_Dynamic_CSS();
$tablet_css = new GenerateBlocks_Dynamic_CSS();
$tablet_only_css = new GenerateBlocks_Dynamic_CSS();
$mobile_css = new GenerateBlocks_Dynamic_CSS();
$css_data = [];
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['buttonContainer']
);
$id = $attributes['uniqueId'];
$blockVersion = ! empty( $settings['blockVersion'] ) ? $settings['blockVersion'] : 1;
// Only add this CSS once.
if ( ! self::$singular_css_added ) {
$css->set_selector( '.gb-button-wrapper' );
$css->add_property( 'display', 'flex' );
$css->add_property( 'flex-wrap', 'wrap' );
$css->add_property( 'align-items', 'flex-start' );
$css->add_property( 'justify-content', 'flex-start' );
$css->add_property( 'clear', 'both' );
do_action(
'generateblocks_block_one_time_css_data',
'button-container',
$settings,
$css
);
self::$singular_css_added = true;
}
// Map deprecated settings.
$settings = GenerateBlocks_Map_Deprecated_Attributes::map_attributes( $settings );
$css->set_selector( '.gb-button-wrapper-' . $id );
$css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignment'] ) );
generateblocks_add_spacing_css( $css, $settings );
$stack_desktop = $desktop_css;
$stack_tablet_only = $tablet_only_css;
if ( $blockVersion < 2 ) {
$stack_desktop = $css;
$stack_tablet_only = $tablet_css;
}
if ( $settings['stack'] ) {
$stack_desktop->set_selector( '.gb-button-wrapper-' . $id );
$stack_desktop->add_property( 'flex-direction', 'column' );
$stack_desktop->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['alignment'] ) );
}
if ( $settings['fillHorizontalSpace'] ) {
$stack_desktop->set_selector( '.gb-button-wrapper-' . $id . ' > .gb-button' );
$stack_desktop->add_property( 'flex', '1' );
}
if ( $settings['stack'] && $settings['fillHorizontalSpace'] ) {
$stack_desktop->add_property( 'width', '100%' );
$stack_desktop->add_property( 'box-sizing', 'border-box' );
}
$tablet_css->set_selector( '.gb-button-wrapper-' . $id );
$tablet_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
generateblocks_add_spacing_css( $tablet_css, $settings, 'Tablet' );
if ( $settings['stackTablet'] ) {
$stack_tablet_only->set_selector( '.gb-button-wrapper-' . $id );
$stack_tablet_only->add_property( 'flex-direction', 'column' );
$stack_tablet_only->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
}
if ( $settings['fillHorizontalSpaceTablet'] ) {
$stack_tablet_only->set_selector( '.gb-button-wrapper-' . $id . ' > .gb-button' );
$stack_tablet_only->add_property( 'flex', '1' );
}
if ( $settings['stackTablet'] && $settings['fillHorizontalSpaceTablet'] ) {
$stack_tablet_only->add_property( 'width', '100%' );
$stack_tablet_only->add_property( 'box-sizing', 'border-box' );
}
$mobile_css->set_selector( '.gb-button-wrapper-' . $id );
$mobile_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
generateblocks_add_spacing_css( $mobile_css, $settings, 'Mobile' );
if ( $settings['stackMobile'] ) {
$mobile_css->add_property( 'flex-direction', 'column' );
$mobile_css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
}
if ( $settings['fillHorizontalSpaceMobile'] ) {
$mobile_css->set_selector( '.gb-button-wrapper-' . $id . ' > .gb-button' );
$mobile_css->add_property( 'flex', '1' );
}
if ( $settings['stackMobile'] && $settings['fillHorizontalSpaceMobile'] ) {
$mobile_css->add_property( 'width', '100%' );
$mobile_css->add_property( 'box-sizing', 'border-box' );
}
// Store this block ID in memory.
self::store_block_id( $id );
/**
* Do generateblocks_block_css_data hook
*
* @since 1.0
*
* @param string $name The name of our block.
* @param array $settings The settings for the current block.
* @param object $css Our desktop/main CSS data.
* @param object $desktop_css Our desktop only CSS data.
* @param object $tablet_css Our tablet CSS data.
* @param object $tablet_only_css Our tablet only CSS data.
* @param object $mobile_css Our mobile CSS data.
*/
do_action(
'generateblocks_block_css_data',
'button-container',
$settings,
$css,
$desktop_css,
$tablet_css,
$tablet_only_css,
$mobile_css
);
return [
'main' => $css->css_output(),
'desktop' => $desktop_css->css_output(),
'tablet' => $tablet_css->css_output(),
'tablet_only' => $tablet_only_css->css_output(),
'mobile' => $mobile_css->css_output(),
];
}
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
if ( ! isset( $attributes['isDynamic'] ) || ! $attributes['isDynamic'] ) {
// Add styles to this block if needed.
$content = generateblocks_maybe_add_block_css(
$content,
[
'class_name' => 'GenerateBlocks_Block_Button_Container',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $content;
}
if ( isset( $block->parsed_block['innerBlocks'] ) ) {
$button_count = apply_filters(
'generateblocks_button_count',
count( (array) $block->parsed_block['innerBlocks'] ),
$attributes,
$block
);
if ( 0 === $button_count ) {
return;
}
}
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['buttonContainer']
);
$classNames = array(
'gb-button-wrapper',
'gb-button-wrapper-' . $settings['uniqueId'],
);
if ( ! empty( $settings['className'] ) ) {
$classNames[] = $settings['className'];
}
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => 'GenerateBlocks_Block_Button_Container',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<div %s>',
generateblocks_attr(
'button-container',
array(
'id' => isset( $settings['anchor'] ) ? $settings['anchor'] : null,
'class' => implode( ' ', $classNames ),
),
$settings,
$block
)
);
$output .= $content;
$output .= '</div>';
return $output;
}
}
@@ -0,0 +1,553 @@
<?php
/**
* Handles the Button block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Button related functions.
*/
class GenerateBlocks_Block_Button {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
private static $block_ids = [];
/**
* Keep track of CSS we want to output once per block type.
*
* @var boolean
*/
private static $singular_css_added = false;
/**
* Block defaults.
*/
public static function defaults() {
return [
'backgroundColor' => '',
'backgroundColorHover' => '',
'backgroundColorCurrent' => '',
'textColor' => '',
'textColorHover' => '',
'textColorCurrent' => '',
'fontFamilyFallback' => '',
'googleFont' => false,
'googleFontVariants' => '',
'icon' => '',
'hasIcon' => false,
'iconLocation' => 'left',
'removeText' => false,
'ariaLabel' => '',
'gradient' => false,
'gradientDirection' => '',
'gradientColorOne' => '',
'gradientColorOneOpacity' => '',
'gradientColorStopOne' => '',
'gradientColorTwo' => '',
'gradientColorTwoOpacity' => '',
'gradientColorStopTwo' => '',
'hasButtonContainer' => false,
'variantRole' => '',
'buttonType' => 'link',
// Deprecated attributes.
'backgroundColorOpacity' => 1,
'backgroundColorHoverOpacity' => 1,
'borderColorHoverOpacity' => 1,
'borderColorOpacity' => 1,
'fontSize' => false,
'fontSizeTablet' => false,
'fontSizeMobile' => false,
'fontSizeUnit' => 'px',
'letterSpacing' => '',
'letterSpacingTablet' => '',
'letterSpacingMobile' => '',
'fontWeight' => '',
'textTransform' => '',
'alignment' => '',
'alignmentTablet' => '',
'alignmentMobile' => '',
'fontFamily' => '',
'iconPaddingTop' => '',
'iconPaddingRight' => '0.5',
'iconPaddingBottom' => '',
'iconPaddingLeft' => '',
'iconPaddingTopTablet' => '',
'iconPaddingRightTablet' => '',
'iconPaddingBottomTablet' => '',
'iconPaddingLeftTablet' => '',
'iconPaddingTopMobile' => '',
'iconPaddingRightMobile' => '',
'iconPaddingBottomMobile' => '',
'iconPaddingLeftMobile' => '',
'iconPaddingUnit' => 'em',
'iconSize' => 1,
'iconSizeTablet' => '',
'iconSizeMobile' => '',
'iconSizeUnit' => 'em',
'borderColor' => '',
'borderColorHover' => '',
'borderColorCurrent' => '',
];
}
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
self::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to store.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) self::$block_ids );
}
/**
* Compile our CSS data based on our block attributes.
*
* @param array $attributes Our block attributes.
*/
public static function get_css_data( $attributes ) {
$css = new GenerateBlocks_Dynamic_CSS();
$desktop_css = new GenerateBlocks_Dynamic_CSS();
$tablet_css = new GenerateBlocks_Dynamic_CSS();
$tablet_only_css = new GenerateBlocks_Dynamic_CSS();
$mobile_css = new GenerateBlocks_Dynamic_CSS();
$css_data = [];
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['button']
);
$id = $attributes['uniqueId'];
$blockVersion = ! empty( $settings['blockVersion'] ) ? $settings['blockVersion'] : 1;
// Use legacy settings if needed.
if ( $blockVersion < 2 ) {
$settings = GenerateBlocks_Legacy_Attributes::get_settings( '1.4.0', 'button', $settings, $attributes );
}
// Map deprecated settings.
$settings = GenerateBlocks_Map_Deprecated_Attributes::map_attributes( $settings );
$selector = generateblocks_get_css_selector( 'button', $attributes );
$use_visited_selector = generateblocks_use_visited_selector( 'button', $attributes );
$using_global_style = isset( $settings['useGlobalStyle'] ) && $settings['useGlobalStyle'];
// Back-compatibility for when icon held a value.
if ( $settings['icon'] ) {
$settings['hasIcon'] = true;
}
$gradientColorStopOneValue = '';
$gradientColorStopTwoValue = '';
if ( $settings['gradient'] ) {
if ( $settings['gradientColorOne'] && '' !== $settings['gradientColorStopOne'] ) {
$gradientColorStopOneValue = ' ' . $settings['gradientColorStopOne'] . '%';
}
if ( $settings['gradientColorTwo'] && '' !== $settings['gradientColorStopTwo'] ) {
$gradientColorStopTwoValue = ' ' . $settings['gradientColorStopTwo'] . '%';
}
}
// Only add this CSS once.
if ( ! self::$singular_css_added ) {
// Singular CSS is no longer supported since 1.9.0.
do_action(
'generateblocks_block_one_time_css_data',
'button',
$settings,
$css
);
self::$singular_css_added = true;
}
$visited_selector = $use_visited_selector
? ', ' . $selector . ':visited'
: '';
$css->set_selector( $selector . $visited_selector );
generateblocks_add_layout_css( $css, $settings );
generateblocks_add_sizing_css( $css, $settings );
generateblocks_add_flex_child_css( $css, $settings );
generateblocks_add_typography_css( $css, $settings );
generateblocks_add_spacing_css( $css, $settings );
generateblocks_add_border_css( $css, $settings );
$css->add_property( 'background-color', generateblocks_hex2rgba( $settings['backgroundColor'], $settings['backgroundColorOpacity'] ) );
$css->add_property( 'color', $settings['textColor'] );
$css->add_property( 'text-decoration', 'none' );
if ( $settings['gradient'] ) {
$css->add_property( 'background-image', 'linear-gradient(' . $settings['gradientDirection'] . 'deg, ' . generateblocks_hex2rgba( $settings['gradientColorOne'], $settings['gradientColorOneOpacity'] ) . $gradientColorStopOneValue . ', ' . generateblocks_hex2rgba( $settings['gradientColorTwo'], $settings['gradientColorTwoOpacity'] ) . $gradientColorStopTwoValue . ')' );
}
if ( $blockVersion < 3 && ! $using_global_style ) {
$css->add_property( 'display', 'inline-flex' );
$css->add_property( 'align-items', 'center' );
$css->add_property( 'justify-content', 'center' );
$css->add_property( 'text-align', 'center' );
}
$css->set_selector( $selector . ':hover, ' . $selector . ':active, ' . $selector . ':focus' );
generateblocks_add_border_color_css( $css, $settings, 'Hover' );
$css->add_property( 'background-color', generateblocks_hex2rgba( $settings['backgroundColorHover'], $settings['backgroundColorHoverOpacity'] ) );
$css->add_property( 'color', $settings['textColorHover'] );
$visited_selector = $use_visited_selector
? ', ' . $selector . '.gb-block-is-current:visited'
: '';
$current_selector = sprintf(
'%1$s.gb-block-is-current, %1$s.gb-block-is-current:hover, %1$s.gb-block-is-current:active, %1$s.gb-block-is-current:focus',
$selector
);
$css->set_selector( $current_selector . $visited_selector );
generateblocks_add_border_color_css( $css, $settings, 'Current' );
$css->add_property( 'background-color', $settings['backgroundColorCurrent'] );
$css->add_property( 'color', $settings['textColorCurrent'] );
if ( $settings['hasIcon'] ) {
$css->set_selector( $selector . ' .gb-icon' );
if ( $blockVersion < 4 ) {
$css->add_property( 'font-size', $settings['iconSize'], $settings['iconSizeUnit'] );
}
$css->add_property( 'line-height', '0' );
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 4 ) {
// Need to check for blockVersion here instead of mapping as iconPaddingRight has a default.
$css->add_property( 'padding', array( $settings['iconPaddingTop'], $settings['iconPaddingRight'], $settings['iconPaddingBottom'], $settings['iconPaddingLeft'] ), $settings['iconPaddingUnit'] );
} else {
$css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTop', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRight', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottom', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeft', $settings['iconStyles'] ),
)
);
}
}
if ( $blockVersion < 3 ) {
$css->add_property( 'align-items', 'center' );
$css->add_property( 'display', 'inline-flex' );
}
$css->set_selector( $selector . ' .gb-icon svg' );
if ( $blockVersion < 4 ) {
$css->add_property( 'height', '1em' );
$css->add_property( 'width', '1em' );
}
$css->add_property( 'width', generateblocks_get_array_attribute_value( 'width', $settings['iconStyles'] ) );
$css->add_property( 'height', generateblocks_get_array_attribute_value( 'height', $settings['iconStyles'] ) );
$css->add_property( 'fill', 'currentColor' );
}
$tablet_css->set_selector( $selector );
generateblocks_add_layout_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_sizing_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_flex_child_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_typography_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_spacing_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_border_css( $tablet_css, $settings, 'Tablet' );
if ( $settings['hasIcon'] ) {
$tablet_css->set_selector( $selector . ' .gb-icon' );
if ( $blockVersion < 4 ) {
$tablet_css->add_property( 'font-size', $settings['iconSizeTablet'], $settings['iconSizeUnit'] );
}
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 4 ) {
$tablet_css->add_property( 'padding', array( $settings['iconPaddingTopTablet'], $settings['iconPaddingRightTablet'], $settings['iconPaddingBottomTablet'], $settings['iconPaddingLeftTablet'] ), $settings['iconPaddingUnit'] );
} else {
$tablet_css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTopTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRightTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottomTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeftTablet', $settings['iconStyles'] ),
)
);
}
}
$tablet_css->set_selector( $selector . ' .gb-icon svg' );
$tablet_css->add_property( 'width', generateblocks_get_array_attribute_value( 'widthTablet', $settings['iconStyles'] ) );
$tablet_css->add_property( 'height', generateblocks_get_array_attribute_value( 'heightTablet', $settings['iconStyles'] ) );
}
$mobile_css->set_selector( $selector );
generateblocks_add_layout_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_sizing_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_flex_child_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_typography_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_spacing_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_border_css( $mobile_css, $settings, 'Mobile' );
if ( $settings['hasIcon'] ) {
$mobile_css->set_selector( $selector . ' .gb-icon' );
if ( $blockVersion < 4 ) {
$mobile_css->add_property( 'font-size', $settings['iconSizeMobile'], $settings['iconSizeUnit'] );
}
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 4 ) {
$mobile_css->add_property( 'padding', array( $settings['iconPaddingTopMobile'], $settings['iconPaddingRightMobile'], $settings['iconPaddingBottomMobile'], $settings['iconPaddingLeftMobile'] ), $settings['iconPaddingUnit'] );
} else {
$mobile_css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTopMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRightMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottomMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeftMobile', $settings['iconStyles'] ),
)
);
}
}
$mobile_css->set_selector( $selector . ' .gb-icon svg' );
$mobile_css->add_property( 'width', generateblocks_get_array_attribute_value( 'widthMobile', $settings['iconStyles'] ) );
$mobile_css->add_property( 'height', generateblocks_get_array_attribute_value( 'heightMobile', $settings['iconStyles'] ) );
}
// Store this block ID in memory.
self::store_block_id( $id );
/**
* Do generateblocks_block_css_data hook
*
* @since 1.0
*
* @param string $name The name of our block.
* @param array $settings The settings for the current block.
* @param object $css Our desktop/main CSS data.
* @param object $desktop_css Our desktop only CSS data.
* @param object $tablet_css Our tablet CSS data.
* @param object $tablet_only_css Our tablet only CSS data.
* @param object $mobile_css Our mobile CSS data.
*/
do_action(
'generateblocks_block_css_data',
'button',
$settings,
$css,
$desktop_css,
$tablet_css,
$tablet_only_css,
$mobile_css
);
return [
'main' => $css->css_output(),
'desktop' => $desktop_css->css_output(),
'tablet' => $tablet_css->css_output(),
'tablet_only' => $tablet_only_css->css_output(),
'mobile' => $mobile_css->css_output(),
];
}
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
if ( ! isset( $attributes['hasUrl'] ) && strpos( trim( $content ), '<a' ) === 0 ) {
$attributes['hasUrl'] = true;
}
if ( ! isset( $attributes['useDynamicData'] ) || ! $attributes['useDynamicData'] ) {
// Add styles to this block if needed.
$content = generateblocks_maybe_add_block_css(
$content,
[
'class_name' => 'GenerateBlocks_Block_Button',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $content;
}
$allow_empty_content = false;
// Add an attribute showing we're working with the Button block.
$attributes['isButton'] = true;
if ( empty( $attributes['dynamicContentType'] ) ) {
$dynamic_content = GenerateBlocks_Dynamic_Content::get_static_content( $content );
if ( ! empty( $attributes['hasIcon'] ) && ! empty( $attributes['removeText'] ) ) {
// Allow icon-only items to continue.
$allow_empty_content = true;
}
} else {
$dynamic_content = GenerateBlocks_Dynamic_Content::get_content( $attributes, $block );
}
if ( ! $dynamic_content && '0' !== $dynamic_content && ! $allow_empty_content ) {
return '';
}
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['button']
);
$classNames = array(
'gb-button',
'gb-button-' . $settings['uniqueId'],
);
if ( ! empty( $settings['className'] ) ) {
$classNames[] = $settings['className'];
}
if ( empty( $settings['hasIcon'] ) ) {
$classNames[] = 'gb-button-text';
}
$relAttributes = array();
if ( ! empty( $settings['relNoFollow'] ) ) {
$relAttributes[] = 'nofollow';
}
if ( ! empty( $settings['target'] ) ) {
$relAttributes[] = 'noopener';
$relAttributes[] = 'noreferrer';
}
if ( ! empty( $settings['relSponsored'] ) ) {
$relAttributes[] = 'sponsored';
}
$icon_html = '';
// Extract our icon from the static HTML.
if ( $settings['hasIcon'] ) {
$icon_html = GenerateBlocks_Dynamic_Content::get_icon_html( $content );
}
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => 'GenerateBlocks_Block_Button',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
foreach ( (array) $dynamic_content as $content ) {
$tagName = 'span';
$dynamic_link = GenerateBlocks_Dynamic_Content::get_dynamic_url( $attributes, $block );
if ( ! empty( $content['attributes']['href'] ) || $dynamic_link ) {
$tagName = 'a';
}
if ( 'button' === $settings['buttonType'] ) {
$tagName = 'button';
}
$button_attributes = array(
'id' => ! empty( $settings['anchor'] ) ? $settings['anchor'] : null,
'class' => implode( ' ', $classNames ),
'href' => 'a' === $tagName ? $dynamic_link : null,
'rel' => ! empty( $relAttributes ) ? implode( ' ', $relAttributes ) : null,
'target' => ! empty( $settings['target'] ) ? '_blank' : null,
'aria-label' => ! empty( $settings['ariaLabel'] ) ? $settings['ariaLabel'] : null,
);
if ( isset( $content['attributes'] ) ) {
foreach ( $content['attributes'] as $attribute => $value ) {
if ( 'class' === $attribute ) {
$button_attributes[ $attribute ] .= ' ' . $value;
} else {
$button_attributes[ $attribute ] = $value;
}
}
}
$output .= sprintf(
'<%1$s %2$s>',
$tagName,
generateblocks_attr(
'dynamic-button',
$button_attributes,
$settings,
$block
)
);
if ( $icon_html ) {
if ( 'left' === $settings['iconLocation'] ) {
$output .= $icon_html;
}
$output .= '<span class="gb-button-text">';
}
if ( isset( $content['content'] ) ) {
$output .= $content['content'];
} else {
$output .= $content;
}
if ( $icon_html ) {
$output .= '</span>';
if ( 'right' === $settings['iconLocation'] ) {
$output .= $icon_html;
}
}
$output .= sprintf(
'</%s>',
$tagName
);
}
return $output;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Element block.
*/
class GenerateBlocks_Block_Element 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/element';
/**
* 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,306 @@
<?php
/**
* Handles the Grid block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Grid related functions.
*/
class GenerateBlocks_Block_Grid {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
private static $block_ids = [];
/**
* Keep track of CSS we want to output once per block type.
*
* @var boolean
*/
private static $singular_css_added = false;
/**
* Block defaults.
*/
public static function defaults() {
return [
'horizontalGap' => '',
'verticalGap' => '',
'verticalAlignment' => '',
'horizontalGapTablet' => '',
'verticalGapTablet' => '',
'verticalAlignmentTablet' => 'inherit',
'horizontalGapMobile' => '',
'verticalGapMobile' => '',
'verticalAlignmentMobile' => 'inherit',
'horizontalAlignment' => '',
'horizontalAlignmentTablet' => '',
'horizontalAlignmentMobile' => '',
'useLegacyRowGap' => false,
];
}
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
self::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to store.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) self::$block_ids );
}
/**
* Compile our CSS data based on our block attributes.
*
* @param array $attributes Our block attributes.
*/
public static function get_css_data( $attributes ) {
$css = new GenerateBlocks_Dynamic_CSS();
$desktop_css = new GenerateBlocks_Dynamic_CSS();
$tablet_css = new GenerateBlocks_Dynamic_CSS();
$tablet_only_css = new GenerateBlocks_Dynamic_CSS();
$mobile_css = new GenerateBlocks_Dynamic_CSS();
$css_data = [];
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['gridContainer']
);
$id = $attributes['uniqueId'];
$blockVersion = ! empty( $settings['blockVersion'] ) ? $settings['blockVersion'] : 1;
// Use legacy settings if needed.
if ( $blockVersion < 2 ) {
$settings = GenerateBlocks_Legacy_Attributes::get_settings( '1.4.0', 'grid', $settings, $attributes );
}
$gap_direction = 'left';
if ( is_rtl() ) {
$gap_direction = 'right';
}
// Don't output horizontal gap defaults if we're using global styles.
if ( $blockVersion < 2 && isset( $settings['useGlobalStyle'] ) && $settings['useGlobalStyle'] && isset( $settings['globalStyleId'] ) && $settings['globalStyleId'] ) {
if ( (string) $settings['horizontalGap'] === (string) $defaults['gridContainer']['horizontalGap'] ) {
$settings['horizontalGap'] = '';
}
}
// Only add this CSS once.
if ( ! self::$singular_css_added ) {
do_action(
'generateblocks_block_one_time_css_data',
'grid',
$settings,
$css
);
self::$singular_css_added = true;
}
$css->set_selector( '.gb-grid-wrapper-' . $id );
$css->add_property( 'display', 'flex' );
$css->add_property( 'flex-wrap', 'wrap' );
$css->add_property( 'align-items', $settings['verticalAlignment'] );
$css->add_property( 'justify-content', $settings['horizontalAlignment'] );
if ( $blockVersion > 2 && ! $settings['useLegacyRowGap'] ) {
$css->add_property( 'row-gap', $settings['verticalGap'], 'px' );
}
if ( $settings['horizontalGap'] ) {
$css->add_property( 'margin-' . $gap_direction, '-' . $settings['horizontalGap'] . 'px' );
}
$css->set_selector( '.gb-grid-wrapper-' . $id . ' > .gb-grid-column' );
$css->add_property( 'box-sizing', 'border-box' );
$css->add_property( 'padding-' . $gap_direction, $settings['horizontalGap'], 'px' );
if ( $blockVersion < 3 || $settings['useLegacyRowGap'] ) {
$css->add_property( 'padding-bottom', $settings['verticalGap'], 'px' );
}
$tablet_css->set_selector( '.gb-grid-wrapper-' . $id );
if ( $blockVersion > 2 && ! $settings['useLegacyRowGap'] ) {
$tablet_css->add_property( 'row-gap', $settings['verticalGapTablet'], 'px' );
}
if ( 'inherit' !== $settings['verticalAlignmentTablet'] ) {
$tablet_css->add_property( 'align-items', $settings['verticalAlignmentTablet'] );
}
if ( 'inherit' !== $settings['horizontalAlignmentTablet'] ) {
$tablet_css->add_property( 'justify-content', $settings['horizontalAlignmentTablet'] );
}
if ( $settings['horizontalGapTablet'] ) {
$tablet_css->add_property( 'margin-' . $gap_direction, '-' . $settings['horizontalGapTablet'] . 'px' );
} elseif ( 0 === $settings['horizontalGapTablet'] ) {
$tablet_css->add_property( 'margin-' . $gap_direction, $settings['horizontalGapTablet'] );
}
$tablet_css->set_selector( '.gb-grid-wrapper-' . $id . ' > .gb-grid-column' );
$tablet_css->add_property( 'padding-' . $gap_direction, $settings['horizontalGapTablet'], 'px' );
if ( $blockVersion < 3 || $settings['useLegacyRowGap'] ) {
$tablet_css->add_property( 'padding-bottom', $settings['verticalGapTablet'], 'px' );
}
$mobile_css->set_selector( '.gb-grid-wrapper-' . $id );
if ( $blockVersion > 2 && ! $settings['useLegacyRowGap'] ) {
$mobile_css->add_property( 'row-gap', $settings['verticalGapMobile'], 'px' );
}
if ( 'inherit' !== $settings['verticalAlignmentMobile'] ) {
$mobile_css->add_property( 'align-items', $settings['verticalAlignmentMobile'] );
}
if ( 'inherit' !== $settings['horizontalAlignmentMobile'] ) {
$mobile_css->add_property( 'justify-content', $settings['horizontalAlignmentMobile'] );
}
if ( $settings['horizontalGapMobile'] ) {
$mobile_css->add_property( 'margin-' . $gap_direction, '-' . $settings['horizontalGapMobile'] . 'px' );
} elseif ( 0 === $settings['horizontalGapMobile'] ) {
$mobile_css->add_property( 'margin-' . $gap_direction, $settings['horizontalGapMobile'] );
}
$mobile_css->set_selector( '.gb-grid-wrapper-' . $id . ' > .gb-grid-column' );
$mobile_css->add_property( 'padding-' . $gap_direction, $settings['horizontalGapMobile'], 'px' );
if ( $blockVersion < 3 || $settings['useLegacyRowGap'] ) {
$mobile_css->add_property( 'padding-bottom', $settings['verticalGapMobile'], 'px' );
}
// Store this block ID in memory.
self::store_block_id( $id );
/**
* Do generateblocks_block_css_data hook
*
* @since 1.0
*
* @param string $name The name of our block.
* @param array $settings The settings for the current block.
* @param object $css Our desktop/main CSS data.
* @param object $desktop_css Our desktop only CSS data.
* @param object $tablet_css Our tablet CSS data.
* @param object $tablet_only_css Our tablet only CSS data.
* @param object $mobile_css Our mobile CSS data.
*/
do_action(
'generateblocks_block_css_data',
'grid',
$settings,
$css,
$desktop_css,
$tablet_css,
$tablet_only_css,
$mobile_css
);
return [
'main' => $css->css_output(),
'desktop' => $desktop_css->css_output(),
'tablet' => $tablet_css->css_output(),
'tablet_only' => $tablet_only_css->css_output(),
'mobile' => $mobile_css->css_output(),
];
}
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
if ( ! isset( $attributes['isDynamic'] ) || ! $attributes['isDynamic'] ) {
// Add styles to this block if needed.
$content = generateblocks_maybe_add_block_css(
$content,
[
'class_name' => 'GenerateBlocks_Block_Grid',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $content;
}
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['gridContainer']
);
$classNames = array(
'gb-grid-wrapper',
'gb-grid-wrapper-' . $settings['uniqueId'],
);
if ( ! empty( $settings['className'] ) ) {
$classNames[] = $settings['className'];
}
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => 'GenerateBlocks_Block_Grid',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<div %s>',
generateblocks_attr(
'grid-wrapper',
array(
'id' => isset( $settings['anchor'] ) ? $settings['anchor'] : null,
'class' => implode( ' ', $classNames ),
),
$settings,
$block
)
);
if ( empty( $attributes['isQueryLoop'] ) ) {
$output .= $content;
} else {
$output .= GenerateBlocks_Block_Query_Loop::render_block( $attributes, $content, $block );
}
$output .= '</div>';
return $output;
}
}
@@ -0,0 +1,828 @@
<?php
/**
* Handles the Headline block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Headline related functions.
*/
class GenerateBlocks_Block_Headline {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
private static $block_ids = [];
/**
* Keep track of CSS we want to output once per block type.
*
* @var boolean
*/
private static $singular_css_added = false;
/**
* Block defaults.
*/
public static function defaults() {
return [
'element' => 'h2',
'cssClasses' => '',
'backgroundColor' => '',
'textColor' => '',
'linkColor' => '',
'linkColorHover' => '',
'highlightTextColor' => '',
'fontFamilyFallback' => '',
'googleFont' => false,
'googleFontVariants' => '',
'icon' => '',
'hasIcon' => false,
'iconColor' => '',
'iconLocation' => 'inline',
'iconLocationTablet' => '',
'iconLocationMobile' => '',
'removeText' => false,
'ariaLabel' => '',
// Deprecated attributes.
'backgroundColorOpacity' => 1,
'borderColorOpacity' => 1,
'iconColorOpacity' => 1,
'fontSize' => '',
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
'lineHeight' => '',
'lineHeightTablet' => '',
'lineHeightMobile' => '',
'lineHeightUnit' => 'em',
'letterSpacing' => '',
'letterSpacingTablet' => '',
'letterSpacingMobile' => '',
'fontWeight' => '',
'textTransform' => '',
'alignment' => '',
'alignmentTablet' => '',
'alignmentMobile' => '',
'iconVerticalAlignment' => 'center',
'iconVerticalAlignmentTablet' => '',
'iconVerticalAlignmentMobile' => '',
'inlineWidth' => false,
'inlineWidthTablet' => false,
'inlineWidthMobile' => false,
'fontFamily' => '',
'borderColor' => '',
'iconPaddingTop' => '',
'iconPaddingRight' => '0.5',
'iconPaddingBottom' => '',
'iconPaddingLeft' => '',
'iconPaddingTopTablet' => '',
'iconPaddingRightTablet' => '',
'iconPaddingBottomTablet' => '',
'iconPaddingLeftTablet' => '',
'iconPaddingTopMobile' => '',
'iconPaddingRightMobile' => '',
'iconPaddingBottomMobile' => '',
'iconPaddingLeftMobile' => '',
'iconPaddingUnit' => 'em',
'iconSize' => 1,
'iconSizeTablet' => '',
'iconSizeMobile' => '',
'iconSizeUnit' => 'em',
];
}
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
self::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to store.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) self::$block_ids );
}
/**
* Compile our CSS data based on our block attributes.
*
* @param array $attributes Our block attributes.
*/
public static function get_css_data( $attributes ) {
$css = new GenerateBlocks_Dynamic_CSS();
$desktop_css = new GenerateBlocks_Dynamic_CSS();
$tablet_css = new GenerateBlocks_Dynamic_CSS();
$tablet_only_css = new GenerateBlocks_Dynamic_CSS();
$mobile_css = new GenerateBlocks_Dynamic_CSS();
$css_data = [];
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['headline']
);
$id = $attributes['uniqueId'];
$blockVersion = ! empty( $settings['blockVersion'] ) ? $settings['blockVersion'] : 1;
// Map deprecated settings.
$settings = GenerateBlocks_Map_Deprecated_Attributes::map_attributes( $settings );
$selector = generateblocks_get_css_selector( 'headline', $attributes );
// Back-compatibility for when icon held a value.
if ( $settings['icon'] ) {
$settings['hasIcon'] = true;
}
// Only add this CSS once.
if ( ! self::$singular_css_added ) {
// Singular CSS is no longer supported since 1.9.0.
do_action(
'generateblocks_block_one_time_css_data',
'headline',
$settings,
$css
);
self::$singular_css_added = true;
}
if ( ! isset( $attributes['hasWrapper'] ) ) {
$css->set_selector( $selector );
generateblocks_add_layout_css( $css, $settings );
generateblocks_add_sizing_css( $css, $settings );
generateblocks_add_flex_child_css( $css, $settings );
generateblocks_add_typography_css( $css, $settings );
generateblocks_add_spacing_css( $css, $settings );
generateblocks_add_border_css( $css, $settings );
$css->add_property( 'color', $settings['textColor'] );
$css->add_property( 'background-color', generateblocks_hex2rgba( $settings['backgroundColor'], $settings['backgroundColorOpacity'] ) );
if ( $blockVersion < 2 && $settings['inlineWidth'] ) {
if ( $settings['hasIcon'] ) {
$css->add_property( 'display', 'inline-flex' );
} else {
$css->add_property( 'display', 'inline-block' );
}
}
if ( $blockVersion < 2 && $settings['hasIcon'] ) {
if ( ! $settings['inlineWidth'] ) {
$css->add_property( 'display', 'flex' );
}
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'text-align', $settings['alignment'] );
} else {
$css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignment'] ) );
}
if ( 'inline' === $settings['iconLocation'] ) {
$css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignment'] ) );
}
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'flex-direction', 'column' );
}
}
$css->set_selector( $selector . ' a' );
$css->add_property( 'color', $settings['linkColor'] );
$css->set_selector( $selector . ' a:hover' );
$css->add_property( 'color', $settings['linkColorHover'] );
if ( $settings['hasIcon'] ) {
$css->set_selector( $selector . ' .gb-icon' );
$css->add_property( 'line-height', '0' );
$css->add_property( 'color', generateblocks_hex2rgba( $settings['iconColor'], $settings['iconColorOpacity'] ) );
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 3 ) {
// Need to check for blockVersion here instead of mapping as iconPaddingRight has a default.
$css->add_property( 'padding', array( $settings['iconPaddingTop'], $settings['iconPaddingRight'], $settings['iconPaddingBottom'], $settings['iconPaddingLeft'] ), $settings['iconPaddingUnit'] );
} else {
$css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTop', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRight', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottom', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeft', $settings['iconStyles'] ),
)
);
}
}
if ( $blockVersion < 2 ) {
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'display', 'inline' );
} else {
$css->add_property( 'display', 'inline-flex' );
}
}
$css->set_selector( $selector . ' .gb-icon svg' );
if ( $blockVersion < 3 ) {
$css->add_property( 'width', $settings['iconSize'], $settings['iconSizeUnit'] );
$css->add_property( 'height', $settings['iconSize'], $settings['iconSizeUnit'] );
}
$css->add_property( 'width', generateblocks_get_array_attribute_value( 'width', $settings['iconStyles'] ) );
$css->add_property( 'height', generateblocks_get_array_attribute_value( 'height', $settings['iconStyles'] ) );
$css->add_property( 'fill', 'currentColor' );
}
if ( $settings['highlightTextColor'] ) {
$css->set_selector( $selector . ' .gb-highlight' );
$css->add_property( 'color', $settings['highlightTextColor'] );
}
$tablet_css->set_selector( $selector );
generateblocks_add_layout_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_sizing_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_flex_child_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_typography_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_spacing_css( $tablet_css, $settings, 'Tablet' );
generateblocks_add_border_css( $tablet_css, $settings, 'Tablet' );
if ( $blockVersion < 2 && $settings['inlineWidthTablet'] ) {
if ( $settings['hasIcon'] ) {
$tablet_css->add_property( 'display', 'inline-flex' );
} else {
$tablet_css->add_property( 'display', 'inline-block' );
}
}
if ( $settings['hasIcon'] ) {
if ( $blockVersion < 2 ) {
$tablet_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
if ( 'inline' === $settings['iconLocationTablet'] ) {
$tablet_css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignmentTablet'] ) );
}
if ( 'above' === $settings['iconLocationTablet'] ) {
$tablet_css->add_property( 'flex-direction', 'column' );
}
}
$tablet_css->set_selector( $selector . ' .gb-icon' );
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 3 ) {
$tablet_css->add_property( 'padding', array( $settings['iconPaddingTopTablet'], $settings['iconPaddingRightTablet'], $settings['iconPaddingBottomTablet'], $settings['iconPaddingLeftTablet'] ), $settings['iconPaddingUnit'] );
} else {
$tablet_css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTopTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRightTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottomTablet', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeftTablet', $settings['iconStyles'] ),
)
);
}
}
if ( $blockVersion < 2 ) {
if ( 'above' === $settings['iconLocationTablet'] || ( 'above' === $settings['iconLocation'] && '' == $settings['iconLocationTablet'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$tablet_css->add_property( 'align-self', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
}
if ( 'above' === $settings['iconLocationTablet'] ) {
$tablet_css->add_property( 'display', 'inline' );
}
}
$tablet_css->set_selector( $selector . ' .gb-icon svg' );
if ( $blockVersion < 3 ) {
$tablet_css->add_property( 'width', $settings['iconSizeTablet'], $settings['iconSizeUnit'] );
$tablet_css->add_property( 'height', $settings['iconSizeTablet'], $settings['iconSizeUnit'] );
}
$tablet_css->add_property( 'width', generateblocks_get_array_attribute_value( 'widthTablet', $settings['iconStyles'] ) );
$tablet_css->add_property( 'height', generateblocks_get_array_attribute_value( 'heightTablet', $settings['iconStyles'] ) );
}
$mobile_css->set_selector( $selector );
generateblocks_add_layout_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_sizing_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_flex_child_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_typography_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_spacing_css( $mobile_css, $settings, 'Mobile' );
generateblocks_add_border_css( $mobile_css, $settings, 'Mobile' );
if ( $blockVersion < 2 && $settings['inlineWidthMobile'] ) {
if ( $settings['hasIcon'] ) {
$mobile_css->add_property( 'display', 'inline-flex' );
} else {
$mobile_css->add_property( 'display', 'inline-block' );
}
}
if ( $settings['hasIcon'] ) {
if ( $blockVersion < 2 ) {
$mobile_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
if ( 'inline' === $settings['iconLocationMobile'] ) {
$mobile_css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignmentMobile'] ) );
}
if ( 'above' === $settings['iconLocationMobile'] ) {
$mobile_css->add_property( 'flex-direction', 'column' );
}
}
$mobile_css->set_selector( $selector . ' .gb-icon' );
if ( ! $settings['removeText'] ) {
if ( $blockVersion < 3 ) {
$mobile_css->add_property( 'padding', array( $settings['iconPaddingTopMobile'], $settings['iconPaddingRightMobile'], $settings['iconPaddingBottomMobile'], $settings['iconPaddingLeftMobile'] ), $settings['iconPaddingUnit'] );
} else {
$mobile_css->add_property(
'padding',
array(
generateblocks_get_array_attribute_value( 'paddingTopMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingRightMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingBottomMobile', $settings['iconStyles'] ),
generateblocks_get_array_attribute_value( 'paddingLeftMobile', $settings['iconStyles'] ),
)
);
}
}
if ( $blockVersion < 2 ) {
if ( 'above' === $settings['iconLocationMobile'] || ( 'above' === $settings['iconLocation'] && '' == $settings['iconLocationMobile'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$mobile_css->add_property( 'align-self', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
}
if ( 'above' === $settings['iconLocationMobile'] ) {
$mobile_css->add_property( 'display', 'inline' );
}
}
$mobile_css->set_selector( $selector . ' .gb-icon svg' );
if ( $blockVersion < 3 ) {
$mobile_css->add_property( 'width', $settings['iconSizeMobile'], $settings['iconSizeUnit'] );
$mobile_css->add_property( 'height', $settings['iconSizeMobile'], $settings['iconSizeUnit'] );
}
$mobile_css->add_property( 'width', generateblocks_get_array_attribute_value( 'widthMobile', $settings['iconStyles'] ) );
$mobile_css->add_property( 'height', generateblocks_get_array_attribute_value( 'heightMobile', $settings['iconStyles'] ) );
}
} else {
/**
* All of the below CSS is when the Headline block had a surrounding `div` element.
* This surroundng div was deprecated in 1.2.0.
*
* @TODO Move this CSS to separate function to clean up this function.
*/
$fontFamily = $settings['fontFamily'];
if ( $fontFamily && $settings['fontFamilyFallback'] ) {
$fontFamily = $fontFamily . ', ' . $settings['fontFamilyFallback'];
}
$css->set_selector( '.gb-headline-wrapper' );
$css->add_property( 'display', 'flex' );
$css->set_selector( '.gb-headline-wrapper > .gb-headline' );
$css->add_property( 'margin', '0' );
$css->add_property( 'padding', '0' );
$css->set_selector( '.gb-headline-' . $id );
$css->add_property( 'font-family', $fontFamily );
$css->add_property( 'text-align', $settings['alignment'] );
$css->add_property( 'color', $settings['textColor'] );
if ( ! $settings['hasIcon'] ) {
$css->add_property( 'background-color', generateblocks_hex2rgba( $settings['backgroundColor'], $settings['backgroundColorOpacity'] ) );
if ( $settings['inlineWidth'] ) {
$css->add_property( 'display', 'inline-block' );
}
$css->add_property( 'border-width', array( $settings['borderSizeTop'], $settings['borderSizeRight'], $settings['borderSizeBottom'], $settings['borderSizeLeft'] ), 'px' );
$css->add_property( 'border-color', generateblocks_hex2rgba( $settings['borderColor'], $settings['borderColorOpacity'] ) );
}
$css->add_property( 'font-size', $settings['fontSize'], $settings['fontSizeUnit'] );
$css->add_property( 'font-weight', $settings['fontWeight'] );
$css->add_property( 'text-transform', $settings['textTransform'] );
$css->add_property( 'line-height', $settings['lineHeight'], $settings['lineHeightUnit'] );
$css->add_property( 'letter-spacing', $settings['letterSpacing'], 'em' );
if ( ! $settings['hasIcon'] ) {
$css->add_property( 'padding', array( $settings['paddingTop'], $settings['paddingRight'], $settings['paddingBottom'], $settings['paddingLeft'] ), $settings['paddingUnit'] );
$css->add_property( 'margin', array( $settings['marginTop'], $settings['marginRight'], $settings['marginBottom'], $settings['marginLeft'] ), $settings['marginUnit'] );
if ( function_exists( 'generate_get_default_fonts' ) && '' === $settings['marginBottom'] ) {
$defaultBlockStyles = generateblocks_get_default_styles();
if ( isset( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottom'] ) ) {
$css->add_property( 'margin-bottom', $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottom'], $defaultBlockStyles['headline'][ $settings['element'] ]['marginUnit'] );
}
}
}
$css->set_selector( '.gb-headline-' . $id . ' a, .gb-headline-' . $id . ' a:visited' );
$css->add_property( 'color', $settings['linkColor'] );
$css->set_selector( '.gb-headline-' . $id . ' a:hover' );
$css->add_property( 'color', $settings['linkColorHover'] );
if ( $settings['hasIcon'] ) {
$css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon' );
$css->add_property( 'line-height', '0' );
if ( ! $settings['removeText'] ) {
$css->add_property( 'padding', array( $settings['iconPaddingTop'], $settings['iconPaddingRight'], $settings['iconPaddingBottom'], $settings['iconPaddingLeft'] ), $settings['iconPaddingUnit'] );
}
$css->add_property( 'color', generateblocks_hex2rgba( $settings['iconColor'], $settings['iconColorOpacity'] ) );
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'display', 'inline' );
}
$css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon svg' );
$css->add_property( 'width', $settings['iconSize'], $settings['iconSizeUnit'] );
$css->add_property( 'height', $settings['iconSize'], $settings['iconSizeUnit'] );
$css->add_property( 'fill', 'currentColor' );
$css->set_selector( '.gb-headline-wrapper-' . $id );
$css->add_property( 'padding', array( $settings['paddingTop'], $settings['paddingRight'], $settings['paddingBottom'], $settings['paddingLeft'] ), $settings['paddingUnit'] );
$css->add_property( 'margin', array( $settings['marginTop'], $settings['marginRight'], $settings['marginBottom'], $settings['marginLeft'] ), $settings['marginUnit'] );
$defaultBlockStyles = generateblocks_get_default_styles();
if ( '' === $settings['marginBottom'] && ! $settings['removeText'] && isset( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottom'] ) && is_numeric( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottom'] ) ) {
$css->add_property( 'margin-bottom', $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottom'], $defaultBlockStyles['headline'][ $settings['element'] ]['marginUnit'] );
}
if ( '' === $settings['fontSize'] && ! $settings['removeText'] && isset( $defaultBlockStyles['headline'][ $settings['element'] ]['fontSize'] ) ) {
$css->add_property( 'font-size', $defaultBlockStyles['headline'][ $settings['element'] ]['fontSize'], $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeUnit'] );
} else {
$css->add_property( 'font-size', $settings['fontSize'], $settings['fontSizeUnit'] );
}
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'text-align', $settings['alignment'] );
} else {
$css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignment'] ) );
}
if ( $settings['inlineWidth'] ) {
$css->add_property( 'display', 'inline-flex' );
}
if ( 'inline' === $settings['iconLocation'] ) {
$css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignment'] ) );
}
$css->add_property( 'background-color', generateblocks_hex2rgba( $settings['backgroundColor'], $settings['backgroundColorOpacity'] ) );
$css->add_property( 'color', $settings['textColor'] );
$css->add_property( 'border-width', array( $settings['borderSizeTop'], $settings['borderSizeRight'], $settings['borderSizeBottom'], $settings['borderSizeLeft'] ), 'px' );
$css->add_property( 'border-color', generateblocks_hex2rgba( $settings['borderColor'], $settings['borderColorOpacity'] ) );
if ( 'above' === $settings['iconLocation'] ) {
$css->add_property( 'flex-direction', 'column' );
}
}
if ( $settings['highlightTextColor'] ) {
$css->set_selector( '.gb-headline-' . $id . ' .gb-highlight' );
$css->add_property( 'color', $settings['highlightTextColor'] );
}
$tablet_css->set_selector( '.gb-headline-' . $id );
$tablet_css->add_property( 'text-align', $settings['alignmentTablet'] );
$tablet_css->add_property( 'font-size', $settings['fontSizeTablet'], $settings['fontSizeUnit'] );
$tablet_css->add_property( 'line-height', $settings['lineHeightTablet'], $settings['lineHeightUnit'] );
$tablet_css->add_property( 'letter-spacing', $settings['letterSpacingTablet'], 'em' );
if ( ! $settings['hasIcon'] ) {
$tablet_css->add_property( 'margin', array( $settings['marginTopTablet'], $settings['marginRightTablet'], $settings['marginBottomTablet'], $settings['marginLeftTablet'] ), $settings['marginUnit'] );
$tablet_css->add_property( 'padding', array( $settings['paddingTopTablet'], $settings['paddingRightTablet'], $settings['paddingBottomTablet'], $settings['paddingLeftTablet'] ), $settings['paddingUnit'] );
$tablet_css->add_property( 'border-width', array( $settings['borderSizeTopTablet'], $settings['borderSizeRightTablet'], $settings['borderSizeBottomTablet'], $settings['borderSizeLeftTablet'] ), 'px' );
if ( $settings['inlineWidthTablet'] ) {
$tablet_css->add_property( 'display', 'inline-flex' );
}
}
if ( $settings['hasIcon'] ) {
$tablet_css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon' );
if ( ! $settings['removeText'] ) {
$tablet_css->add_property( 'padding', array( $settings['iconPaddingTopTablet'], $settings['iconPaddingRightTablet'], $settings['iconPaddingBottomTablet'], $settings['iconPaddingLeftTablet'] ), $settings['iconPaddingUnit'] );
}
if ( 'above' === $settings['iconLocationTablet'] || ( 'above' === $settings['iconLocation'] && '' == $settings['iconLocationTablet'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$tablet_css->add_property( 'align-self', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
}
$tablet_css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon svg' );
$tablet_css->add_property( 'width', $settings['iconSizeTablet'], $settings['iconSizeUnit'] );
$tablet_css->add_property( 'height', $settings['iconSizeTablet'], $settings['iconSizeUnit'] );
$tablet_css->set_selector( '.gb-headline-wrapper-' . $id );
$tablet_css->add_property( 'margin', array( $settings['marginTopTablet'], $settings['marginRightTablet'], $settings['marginBottomTablet'], $settings['marginLeftTablet'] ), $settings['marginUnit'] );
$tablet_css->add_property( 'padding', array( $settings['paddingTopTablet'], $settings['paddingRightTablet'], $settings['paddingBottomTablet'], $settings['paddingLeftTablet'] ), $settings['paddingUnit'] );
$tablet_css->add_property( 'border-width', array( $settings['borderSizeTopTablet'], $settings['borderSizeRightTablet'], $settings['borderSizeBottomTablet'], $settings['borderSizeLeftTablet'] ), 'px' );
$tablet_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentTablet'] ) );
$defaultBlockStyles = generateblocks_get_default_styles();
if ( '' === $settings['marginBottomTablet'] && ! $settings['removeText'] && isset( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomTablet'] ) && is_numeric( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomTablet'] ) ) {
$tablet_css->add_property( 'margin-bottom', $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomTablet'], $defaultBlockStyles['headline'][ $settings['element'] ]['marginUnit'] );
}
if ( '' === $settings['fontSizeTablet'] && ! $settings['removeText'] && isset( $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeTablet'] ) ) {
$tablet_css->add_property( 'font-size', $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeTablet'], $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeUnit'] );
} else {
$tablet_css->add_property( 'font-size', $settings['fontSizeTablet'], $settings['fontSizeUnit'] );
}
if ( $settings['inlineWidthTablet'] ) {
$tablet_css->add_property( 'display', 'inline-flex' );
}
if ( 'inline' === $settings['iconLocationTablet'] ) {
$tablet_css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignmentTablet'] ) );
}
if ( 'above' === $settings['iconLocationTablet'] ) {
$tablet_css->add_property( 'flex-direction', 'column' );
}
}
$mobile_css->set_selector( '.gb-headline-' . $id );
$mobile_css->add_property( 'text-align', $settings['alignmentMobile'] );
$mobile_css->add_property( 'font-size', $settings['fontSizeMobile'], $settings['fontSizeUnit'] );
$mobile_css->add_property( 'line-height', $settings['lineHeightMobile'], $settings['lineHeightUnit'] );
$mobile_css->add_property( 'letter-spacing', $settings['letterSpacingMobile'], 'em' );
if ( ! $settings['hasIcon'] ) {
$mobile_css->add_property( 'margin', array( $settings['marginTopMobile'], $settings['marginRightMobile'], $settings['marginBottomMobile'], $settings['marginLeftMobile'] ), $settings['marginUnit'] );
$mobile_css->add_property( 'padding', array( $settings['paddingTopMobile'], $settings['paddingRightMobile'], $settings['paddingBottomMobile'], $settings['paddingLeftMobile'] ), $settings['paddingUnit'] );
$mobile_css->add_property( 'border-width', array( $settings['borderSizeTopMobile'], $settings['borderSizeRightMobile'], $settings['borderSizeBottomMobile'], $settings['borderSizeLeftMobile'] ), 'px' );
if ( $settings['inlineWidthMobile'] ) {
$mobile_css->add_property( 'display', 'inline-flex' );
}
}
if ( $settings['hasIcon'] ) {
$mobile_css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon' );
if ( ! $settings['removeText'] ) {
$mobile_css->add_property( 'padding', array( $settings['iconPaddingTopMobile'], $settings['iconPaddingRightMobile'], $settings['iconPaddingBottomMobile'], $settings['iconPaddingLeftMobile'] ), $settings['iconPaddingUnit'] );
}
if ( 'above' === $settings['iconLocationMobile'] || ( 'above' === $settings['iconLocation'] && '' == $settings['iconLocationMobile'] ) || ( 'above' === $settings['iconLocationTablet'] && '' == $settings['iconLocationMobile'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$mobile_css->add_property( 'align-self', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
}
$mobile_css->set_selector( '.gb-headline-wrapper-' . $id . ' .gb-icon svg' );
$mobile_css->add_property( 'width', $settings['iconSizeMobile'], $settings['iconSizeUnit'] );
$mobile_css->add_property( 'height', $settings['iconSizeMobile'], $settings['iconSizeUnit'] );
$mobile_css->set_selector( '.gb-headline-wrapper-' . $id );
$mobile_css->add_property( 'margin', array( $settings['marginTopMobile'], $settings['marginRightMobile'], $settings['marginBottomMobile'], $settings['marginLeftMobile'] ), $settings['marginUnit'] );
$mobile_css->add_property( 'padding', array( $settings['paddingTopMobile'], $settings['paddingRightMobile'], $settings['paddingBottomMobile'], $settings['paddingLeftMobile'] ), $settings['paddingUnit'] );
$mobile_css->add_property( 'justify-content', generateblocks_get_flexbox_alignment( $settings['alignmentMobile'] ) );
$defaultBlockStyles = generateblocks_get_default_styles();
if ( '' === $settings['marginBottomMobile'] && ! $settings['removeText'] && isset( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomMobile'] ) && is_numeric( $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomMobile'] ) ) {
$mobile_css->add_property( 'margin-bottom', $defaultBlockStyles['headline'][ $settings['element'] ]['marginBottomMobile'], $defaultBlockStyles['headline'][ $settings['element'] ]['marginUnit'] );
}
if ( '' === $settings['fontSizeMobile'] && ! $settings['removeText'] && ! empty( $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeMobile'] ) ) {
$mobile_css->add_property( 'font-size', $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeMobile'], $defaultBlockStyles['headline'][ $settings['element'] ]['fontSizeUnit'] );
} else {
$mobile_css->add_property( 'font-size', $settings['fontSizeMobile'], $settings['fontSizeUnit'] );
}
if ( $settings['inlineWidthMobile'] ) {
$mobile_css->add_property( 'display', 'inline-flex' );
}
if ( 'inline' === $settings['iconLocationMobile'] ) {
$mobile_css->add_property( 'align-items', generateblocks_get_flexbox_alignment( $settings['iconVerticalAlignmentMobile'] ) );
}
if ( 'above' === $settings['iconLocationMobile'] ) {
$mobile_css->add_property( 'flex-direction', 'column' );
}
}
}
// Store this block ID in memory.
self::store_block_id( $id );
/**
* Do generateblocks_block_css_data hook
*
* @since 1.0
*
* @param string $name The name of our block.
* @param array $settings The settings for the current block.
* @param object $css Our desktop/main CSS data.
* @param object $desktop_css Our desktop only CSS data.
* @param object $tablet_css Our tablet CSS data.
* @param object $tablet_only_css Our tablet only CSS data.
* @param object $mobile_css Our mobile CSS data.
*/
do_action(
'generateblocks_block_css_data',
'headline',
$settings,
$css,
$desktop_css,
$tablet_css,
$tablet_only_css,
$mobile_css
);
return [
'main' => $css->css_output(),
'desktop' => $desktop_css->css_output(),
'tablet' => $tablet_css->css_output(),
'tablet_only' => $tablet_only_css->css_output(),
'mobile' => $mobile_css->css_output(),
];
}
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
if ( strpos( trim( $content ), '<div class="gb-headline-wrapper' ) === 0 ) {
$attributes['hasWrapper'] = true;
}
if ( ! isset( $attributes['useDynamicData'] ) || ! $attributes['useDynamicData'] ) {
// Add styles to this block if needed.
$content = generateblocks_maybe_add_block_css(
$content,
[
'class_name' => 'GenerateBlocks_Block_Headline',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return $content;
}
$allow_empty_content = false;
if ( empty( $attributes['dynamicContentType'] ) ) {
$dynamic_content = GenerateBlocks_Dynamic_Content::get_static_content( $content );
if ( ! empty( $attributes['hasIcon'] ) && ! empty( $attributes['removeText'] ) ) {
// Allow icon-only items to continue.
$allow_empty_content = true;
}
} else {
$dynamic_content = GenerateBlocks_Dynamic_Content::get_content( $attributes, $block );
}
if ( ! $dynamic_content && '0' !== $dynamic_content && ! $allow_empty_content ) {
return '';
}
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['headline']
);
$classNames = array(
'gb-headline',
'gb-headline-' . $settings['uniqueId'],
);
if ( ! empty( $settings['className'] ) ) {
$classNames[] = $settings['className'];
}
if ( empty( $settings['hasIcon'] ) ) {
$classNames[] = 'gb-headline-text';
}
$tagName = apply_filters(
'generateblocks_dynamic_headline_tagname',
$settings['element'],
$attributes,
$block
);
$allowedTagNames = apply_filters(
'generateblocks_dynamic_headline_allowed_tagnames',
array(
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'div',
'p',
'figcaption',
),
$attributes,
$block
);
if ( ! in_array( $tagName, $allowedTagNames ) ) {
$tagName = 'div';
}
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => 'GenerateBlocks_Block_Headline',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<%1$s %2$s>',
$tagName,
generateblocks_attr(
'dynamic-headline',
array(
'id' => isset( $settings['anchor'] ) ? $settings['anchor'] : null,
'class' => implode( ' ', $classNames ),
),
$settings,
$block
)
);
$icon_html = '';
// Extract our icon from the static HTML.
if ( $settings['hasIcon'] ) {
$icon_html = GenerateBlocks_Dynamic_Content::get_icon_html( $content );
if ( $icon_html ) {
$output .= $icon_html;
$output .= '<span class="gb-headline-text">';
}
}
$dynamic_link = GenerateBlocks_Dynamic_Content::get_dynamic_url( $attributes, $block );
if ( $dynamic_link ) {
$dynamic_content = sprintf(
'<a href="%s">%s</a>',
esc_url( $dynamic_link ),
$dynamic_content
);
}
$output .= $dynamic_content;
if ( $icon_html ) {
$output .= '</span>';
}
$output .= sprintf(
'</%s>',
$tagName
);
return $output;
}
}
@@ -0,0 +1,334 @@
<?php
/**
* Handles the Image block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Image related functions.
*/
class GenerateBlocks_Block_Image {
/**
* Keep track of all blocks of this type on the page.
*
* @var array $block_ids The current block id.
*/
private static $block_ids = [];
/**
* Keep track of CSS we want to output once per block type.
*
* @var boolean
*/
private static $singular_css_added = false;
/**
* Block defaults.
*/
public static function defaults() {
return [
'mediaId' => '',
'sizeSlug' => '',
'width' => '',
'widthTablet' => '',
'widthMobile' => '',
'height' => '',
'heightTablet' => '',
'heightMobile' => '',
'borderColor' => '',
'objectFit' => '',
'objectFitTablet' => '',
'objectFitMobile' => '',
'align' => '',
'alignment' => '',
'alignmentTablet' => '',
'alignmentMobile' => '',
];
}
/**
* Store our block ID in memory.
*
* @param string $id The block ID to store.
*/
public static function store_block_id( $id ) {
self::$block_ids[] = $id;
}
/**
* Check if our block ID exists.
*
* @param string $id The block ID to store.
*/
public static function block_id_exists( $id ) {
return in_array( $id, (array) self::$block_ids );
}
/**
* Compile our CSS data based on our block attributes.
*
* @param array $attributes Our block attributes.
*/
public static function get_css_data( $attributes ) {
$css = new GenerateBlocks_Dynamic_CSS();
$desktop_css = new GenerateBlocks_Dynamic_CSS();
$tablet_css = new GenerateBlocks_Dynamic_CSS();
$tablet_only_css = new GenerateBlocks_Dynamic_CSS();
$mobile_css = new GenerateBlocks_Dynamic_CSS();
$css_data = [];
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['image']
);
$id = $attributes['uniqueId'];
// Only add this CSS once.
if ( ! self::$singular_css_added ) {
do_action(
'generateblocks_block_one_time_css_data',
'image',
$settings,
$css
);
self::$singular_css_added = true;
}
// Map deprecated settings.
$settings = GenerateBlocks_Map_Deprecated_Attributes::map_attributes( $settings );
$css->set_selector( '.gb-block-image-' . $id );
generateblocks_add_spacing_css( $css, $settings );
// Set a flag we'll update later if we disable floats.
$disable_float = false;
$has_desktop_float = 'floatLeft' === $settings['alignment'] || 'floatRight' === $settings['alignment'];
$has_tablet_float = 'floatLeft' === $settings['alignmentTablet'] || 'floatRight' === $settings['alignmentTablet'];
if ( $has_desktop_float ) {
$css->add_property( 'float', generateblocks_get_float_alignment( $settings['alignment'] ) );
} else {
$css->add_property( 'text-align', $settings['alignment'] );
}
$css->set_selector( '.gb-image-' . $id );
generateblocks_add_border_css( $css, $settings );
$css->add_property( 'width', $settings['width'] );
$css->add_property( 'height', $settings['height'] );
$css->add_property( 'object-fit', $settings['objectFit'] );
$css->add_property( 'vertical-align', 'middle' );
$tablet_css->set_selector( '.gb-block-image-' . $id );
generateblocks_add_spacing_css( $tablet_css, $settings, 'Tablet' );
if ( $has_tablet_float ) {
$tablet_css->add_property( 'float', generateblocks_get_float_alignment( $settings['alignmentTablet'] ) );
} else {
$tablet_css->add_property( 'text-align', $settings['alignmentTablet'] );
if ( $settings['alignmentTablet'] && $has_desktop_float ) {
$tablet_css->add_property( 'float', 'none' );
$disable_float = true;
}
}
$tablet_css->set_selector( '.gb-image-' . $id );
generateblocks_add_border_css( $tablet_css, $settings, 'Tablet' );
$tablet_css->add_property( 'width', $settings['widthTablet'] );
$tablet_css->add_property( 'height', $settings['heightTablet'] );
$tablet_css->add_property( 'object-fit', $settings['objectFitTablet'] );
$mobile_css->set_selector( '.gb-block-image-' . $id );
generateblocks_add_spacing_css( $mobile_css, $settings, 'Mobile' );
if ( 'floatLeft' === $settings['alignmentMobile'] || 'floatRight' === $settings['alignmentMobile'] ) {
$mobile_css->add_property( 'float', generateblocks_get_float_alignment( $settings['alignmentMobile'] ) );
} else {
$mobile_css->add_property( 'text-align', $settings['alignmentMobile'] );
if (
$settings['alignmentMobile'] &&
! $disable_float &&
(
$has_desktop_float ||
$has_tablet_float
)
) {
$mobile_css->add_property( 'float', 'none' );
}
}
$mobile_css->set_selector( '.gb-image-' . $id );
generateblocks_add_border_css( $mobile_css, $settings, 'Mobile' );
$mobile_css->add_property( 'width', $settings['widthMobile'] );
$mobile_css->add_property( 'height', $settings['heightMobile'] );
$mobile_css->add_property( 'object-fit', $settings['objectFitMobile'] );
// Store this block ID in memory.
self::store_block_id( $id );
/**
* Do generateblocks_block_css_data hook
*
* @since 1.0
*
* @param string $name The name of our block.
* @param array $settings The settings for the current block.
* @param object $css Our desktop/main CSS data.
* @param object $desktop_css Our desktop only CSS data.
* @param object $tablet_css Our tablet CSS data.
* @param object $tablet_only_css Our tablet only CSS data.
* @param object $mobile_css Our mobile CSS data.
*/
do_action(
'generateblocks_block_css_data',
'image',
$settings,
$css,
$desktop_css,
$tablet_css,
$tablet_only_css,
$mobile_css
);
return [
'main' => $css->css_output(),
'desktop' => $desktop_css->css_output(),
'tablet' => $tablet_css->css_output(),
'tablet_only' => $tablet_only_css->css_output(),
'mobile' => $mobile_css->css_output(),
];
}
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
if ( empty( $attributes['useDynamicData'] ) ) {
// Add styles to this block if needed.
$content = generateblocks_maybe_add_block_css(
$content,
[
'class_name' => 'GenerateBlocks_Block_Image',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
return generateblocks_filter_images( $content, $attributes );
}
$image = empty( $attributes['dynamicContentType'] )
? generateblocks_filter_images( GenerateBlocks_Dynamic_Content::get_static_content( $content ), $attributes )
: GenerateBlocks_Dynamic_Content::get_dynamic_image( $attributes, $block );
if ( ! $image ) {
return '';
}
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['image']
);
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => 'GenerateBlocks_Block_Image',
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<figure %s>',
generateblocks_attr(
'image-figure',
array(
'class' => implode(
' ',
array(
'gb-block-image',
'gb-block-image-' . $settings['uniqueId'],
)
),
),
$settings,
$block
)
);
$dynamic_link = GenerateBlocks_Dynamic_Content::get_dynamic_url( $attributes, $block );
if ( $dynamic_link ) {
$relAttributes = array();
if ( ! empty( $settings['relNoFollow'] ) ) {
$relAttributes[] = 'nofollow';
}
if ( ! empty( $settings['openInNewWindow'] ) ) {
$relAttributes[] = 'noopener';
$relAttributes[] = 'noreferrer';
}
if ( ! empty( $settings['relSponsored'] ) ) {
$relAttributes[] = 'sponsored';
}
$image = sprintf(
'<a %s>%s</a>',
generateblocks_attr(
'image-link',
array(
'class' => '',
'href' => $dynamic_link,
'rel' => ! empty( $relAttributes ) ? implode( ' ', $relAttributes ) : null,
'target' => ! empty( $settings['openInNewWindow'] ) ? '_blank' : null,
),
$settings,
$block
),
$image
);
}
$output .= $image;
if ( isset( $block->parsed_block['innerBlocks'][0]['attrs'] ) ) {
$image_id = GenerateBlocks_Dynamic_Content::get_dynamic_image_id( $attributes );
$block->parsed_block['innerBlocks'][0]['attrs']['dynamicImage'] = $image_id;
$caption = (
new WP_Block(
$block->parsed_block['innerBlocks'][0]
)
)->render( array( 'dynamic' => true ) );
if ( $caption ) {
$output .= $caption;
}
}
$output .= '</figure>';
return $output;
}
}
@@ -0,0 +1,67 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Element block.
*/
class GenerateBlocks_Block_Loop_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/loop-item';
/**
* Render the Element block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param object $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
$query_type = $block->context['generateblocks/queryType'] ?? null;
if ( GenerateBlocks_Block_Query::TYPE_WP_QUERY === $query_type ) {
$post_classes = get_post_class();
if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag( $attributes['tagName'] ) ) {
$existing_classes = $tags->get_attribute( 'class' );
$new_classes = $existing_classes . ' ' . implode( ' ', $post_classes );
$tags->set_attribute( 'class', $new_classes );
$block_content = $tags->get_updated_html();
}
}
}
// 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,198 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Element block.
*/
class GenerateBlocks_Block_Looper 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/looper';
/**
* Sanitize a loop item's data for security.
*
* @param array|object $loop_item The loop item to sanitize.
* @return array|object The sanitized loop item.
*/
public static function sanitize_loop_item( $loop_item ) {
$disallowed_keys = GenerateBlocks_Meta_Handler::DISALLOWED_KEYS ?? [];
foreach ( $disallowed_keys as $key ) {
if ( is_object( $loop_item ) ) {
if ( isset( $loop_item->$key ) ) {
unset( $loop_item->$key );
}
} elseif ( is_array( $loop_item ) ) {
if ( isset( $loop_item[ $key ] ) ) {
unset( $loop_item[ $key ] );
}
}
}
return $loop_item;
}
/**
* Render the repeater items for the Looper block.
*
* @param array $attributes Block attributes.
* @param WP_Block $block The block instance.
* @return string The rendered content.
*/
public static function render_loop_items( $attributes, $block ) {
$query_data = $block->context['generateblocks/queryData']['data'] ?? null;
$query_type = $block->context['generateblocks/queryData']['type'] ?? null;
$output = '';
if ( GenerateBlocks_Block_Query::TYPE_WP_QUERY === $query_type ) {
$output = self::render_wp_query( $query_data, $attributes, $block );
}
/**
* Allow users to filter the looper rendering of loop items.
*
* @param string $output The block output.
* @param string $query_type The query type.
* @param array|object $query_data The query data.
* @param array $attributes Block attributes.
* @param WP_Block $block The block instance.
*/
$output = apply_filters( 'generateblocks_looper_render_loop_items', $output, $query_type, $query_data, $block, $attributes );
return $output;
}
/**
* Render the repeater items for the Looper block.
*
* @param WP_Query $query The WP_Query instance.
* @param array $attributes Block attributes.
* @param WP_Block $block The block instance.
* @return string The rendered content.
*/
public static function render_wp_query( $query, $attributes, $block ) {
$query_id = $block->context['generateblocks/queryData']['id'] ?? null;
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$per_page = $query->query_vars['posts_per_page'] ?? get_option( 'posts_per_page', 10 );
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$page_index = $page - 1; // Zero based index for pages.
$offset = $page_index * $per_page;
$content = '';
$inner_blocks = $block->parsed_block['innerBlocks'] ?? [];
if ( empty( $inner_blocks ) ) {
return '';
}
// Fallback to support preview in Elements.
if ( ! $query ) {
return (
new WP_Block(
$inner_blocks[0],
array(
'postType' => 'post',
'postId' => 0,
'generateblocks/queryType' => GenerateBlocks_Block_Query::TYPE_WP_QUERY,
'generateblocks/loopIndex' => 1,
'generateblocks/loopItem' => [ 'ID' => 0 ],
)
)
)->render( array( 'dynamic' => false ) );
}
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
global $post;
// Get the current index of the Loop.
foreach ( $inner_blocks as $inner_block ) {
$content .= (
new WP_Block(
$inner_block,
array(
'postType' => get_post_type(),
'postId' => get_the_ID(),
'generateblocks/queryType' => GenerateBlocks_Block_Query::TYPE_WP_QUERY,
'generateblocks/loopIndex' => $offset + $query->current_post + 1,
'generateblocks/loopItem' => self::sanitize_loop_item( $post ),
)
)
)->render();
}
}
wp_reset_postdata();
}
return $content;
}
/**
* Render the Query 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 ) {
$loop_items = self::render_loop_items( $attributes, $block );
if ( ! $loop_items ) {
return '';
}
$html_attributes = generateblocks_get_processed_html_attributes( $block_content );
// If our processing returned nothing, let's try to build our attributes from the block attributes.
if ( empty( $html_attributes ) ) {
$html_attributes = generateblocks_get_backup_html_attributes( 'gb-looper', $attributes );
}
$tag_name = $attributes['tagName'] ?? 'div';
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<%1$s %2$s>%3$s</%1$s>',
$tag_name,
generateblocks_attr(
'looper',
$html_attributes,
$attributes,
$block
),
$loop_items
);
return $output;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Element block.
*/
class GenerateBlocks_Block_Media 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/media';
/**
* Render the Void 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,78 @@
<?php
/**
* Handles the Query Loop block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Add Query Loop related functions.
*/
class GenerateBlocks_Block_Query_Loop {
/**
* Wrapper function for our dynamic buttons.
*
* @since 1.6.0
* @param array $attributes The block attributes.
* @param string $content The dynamic text to display.
* @param WP_Block $block Block instance.
*/
public static function render_block( $attributes, $content, $block ) {
$page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$query_args = GenerateBlocks_Query_Loop::get_query_args( $block, $page );
// Override the custom query with the global query if needed.
$use_global_query = ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] );
if ( $use_global_query ) {
global $wp_query;
if ( $wp_query && isset( $wp_query->query_vars ) && is_array( $wp_query->query_vars ) ) {
// Unset `offset` because if is set, $wp_query overrides/ignores the paged parameter and breaks pagination.
unset( $query_args['offset'] );
$query_args = wp_parse_args( $wp_query->query_vars, $query_args );
if ( empty( $query_args['post_type'] ) && is_singular() ) {
$query_args['post_type'] = get_post_type( get_the_ID() );
}
}
}
$query_args = apply_filters(
'generateblocks_query_loop_args',
$query_args,
$attributes,
$block
);
$the_query = new WP_Query( $query_args );
$content = '';
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) {
$the_query->the_post();
$block_content = (
new WP_Block(
$block->parsed_block,
array(
'postType' => get_post_type(),
'postId' => get_the_ID(),
)
)
)->render( array( 'dynamic' => false ) );
$content .= $block_content;
}
}
wp_reset_postdata();
return $content;
}
}
@@ -0,0 +1,39 @@
<?php
/**
* Handles the No Results block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The No Results block.
*/
class GenerateBlocks_Block_Query_No_Results extends GenerateBlocks_Block {
/**
* Store our block name.
*
* @var string $block_name The block name.
*/
public static $block_name = 'generateblocks/query-no-results';
/**
* Render the Shape 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 ) {
$no_results = $block->context['generateblocks/queryData']['noResults'] ?? false;
if ( ! $no_results ) {
return '';
}
return $block_content;
}
}
@@ -0,0 +1,163 @@
<?php
/**
* Handles the No Results block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The No Results block.
*/
class GenerateBlocks_Block_Query_Page_Numbers 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/query-page-numbers';
/**
* Render the Shape block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param object $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
$query_id = $block->context['generateblocks/queryData']['id'] ?? null;
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$args = $block->context['generateblocks/queryData']['args'] ?? [];
$per_page = $args['posts_per_page'] ?? apply_filters( 'generateblocks_query_per_page_default', 10, $args );
$content = '';
$mid_size = isset( $block->attributes['midSize'] ) ? (int) $block->attributes['midSize'] : null;
$inherit_query = $block->context['generateblocks/queryData']['inherit'] ?? false;
if ( $inherit_query ) {
$paginate_args = [ 'prev_next' => false ];
if ( null !== $mid_size ) {
$paginate_args['mid_size'] = $mid_size;
}
$content = paginate_links( $paginate_args );
} else {
$query_data = $block->context['generateblocks/queryData']['data'] ?? null;
$max_pages = $block->context['generateblocks/queryData']['maxPages'] ?? 0;
if ( ! $query_data ) {
return '';
}
$paginate_args = array(
'base' => '%_%',
'format' => "?$page_key=%#%",
'current' => max( 1, $page ),
'total' => $max_pages,
'prev_next' => false,
);
if ( null !== $mid_size ) {
$paginate_args['mid_size'] = $mid_size;
}
if ( 1 !== $page ) {
/**
* `paginate_links` doesn't use the provided `format` when the page is `1`.
* This is great for the main query as it removes the extra query params
* making the URL shorter, but in the case of multiple custom queries is
* problematic. It results in returning an empty link which ends up with
* a link to the current page.
*
* A way to address this is to add a `fake` query arg with no value that
* is the same for all custom queries. This way the link is not empty and
* preserves all the other existent query args.
*
* @see https://developer.wordpress.org/reference/functions/paginate_links/
*
* The proper fix of this should be in core. Track Ticket:
* @see https://core.trac.wordpress.org/ticket/53868
*
* TODO: After two WP versions (starting from the WP version the core patch landed),
* we should remove this and call `paginate_links` with the proper new arg.
*/
$paginate_args['add_args'] = array( 'cst' => '' );
}
// We still need to preserve `paged` query param if exists, as is used
// for Queries that inherit from global context.
$paged = empty( $_GET['paged'] ) ? null : (int) $_GET['paged']; // phpcs:ignore -- No data processing happening.
if ( $paged ) {
$paginate_args['add_args'] = array( 'paged' => $paged );
}
$content = paginate_links( $paginate_args );
}
if ( empty( $content ) ) {
return '';
}
$pagination_type = $block->context['generateblocks/queryData']['paginationType'] ?? '';
$instant_pagination = GenerateBlocks_Block_Query::TYPE_INSTANT_PAGINATION === $pagination_type;
if ( $instant_pagination && class_exists( 'WP_HTML_Tag_Processor' ) ) {
$p = new WP_HTML_Tag_Processor( $content );
while ( $p->next_tag(
array( 'class_name' => 'page-numbers' )
) ) {
if ( 'A' === $p->get_tag() ) {
$p->set_attribute( 'data-gb-router-target', 'query-' . $query_id );
$p->set_attribute( 'data-gb-prefetch', true );
}
}
$content = $p->get_updated_html();
}
$html_attributes = generateblocks_get_processed_html_attributes( $block_content );
// If our processing returned nothing, let's try to build our attributes from the block attributes.
if ( empty( $html_attributes ) ) {
$html_attributes = generateblocks_get_backup_html_attributes( 'gb-query-page-numbers', $attributes );
}
$tag_name = $attributes['tagName'] ?? 'div';
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= sprintf(
'<%1$s %2$s>%3$s</%1$s>',
$tag_name,
generateblocks_attr(
'query-page-numbers',
$html_attributes,
$attributes,
$block
),
$content
);
return $output;
}
}
@@ -0,0 +1,182 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Query block.
*/
class GenerateBlocks_Block_Query extends GenerateBlocks_Block {
const TYPE_WP_QUERY = 'WP_Query';
const TYPE_INSTANT_PAGINATION = 'instant';
/**
* 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/query';
/**
* Get the query data based on the type.
*
* @param string $query_type The type of query (WP_Query, post_meta, etc).
* @param array $attributes Block attributes.
* @param object $block The block instance.
* @param int $page The current query page.
* @return array Array of query data including the data for looping and no_results.
*/
public static function get_query_data( $query_type, $attributes, $block, $page ) {
$original_args = $attributes['query'] ?? [];
$query_data = [
'data' => [],
'no_results' => true,
'args' => $original_args,
];
if ( self::TYPE_WP_QUERY === $query_type ) {
// Override the custom query with the global query if needed.
$use_global_query = ( isset( $attributes['inheritQuery'] ) && $attributes['inheritQuery'] );
if ( $use_global_query ) {
global $wp_query;
/*
* If already in the main query loop, duplicate the query instance to not tamper with the main instance.
* Since this is a nested query, it should start at the beginning, therefore rewind posts.
* Otherwise, the main query loop has not started yet and this block is responsible for doing so.
*/
if ( in_the_loop() ) {
$data = clone $wp_query;
$data->rewind_posts();
} else {
$data = $wp_query;
}
$query_args = $data->query_vars;
} else {
$query_args = GenerateBlocks_Query_Utils::get_wp_query_args(
$query_data['args'],
$page,
$attributes,
$block
);
// Make the new WP_Query with filtered args.
$data = new WP_Query( $query_args );
}
$query_data = [
'data' => $data,
'no_results' => 0 === $data->found_posts,
'args' => $query_args,
];
}
/**
* Modify the Query block's query data.
*
* @param array $query_data The current query data.
* @param string $query_type The type of query.
* @param array $attributes An array of block attributes.
* @param object $object The block instance.
* @param int $page The current page number.
*
* @return array An array of query data.
*/
return apply_filters( 'generateblocks_query_data', $query_data, $query_type, $attributes, $block, $page );
}
/**
* Render the Query block.
*
* @param array $attributes The block attributes.
* @param string $block_content The block content.
* @param object $block The block.
*/
public static function render_block( $attributes, $block_content, $block ) {
$query_id = isset( $attributes['uniqueId'] ) ? 'query-' . $attributes['uniqueId'] : 'query';
$page_key = $query_id . '-page';
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$pagination_type = $attributes['paginationType'] ?? '';
$instant_pagination = self::TYPE_INSTANT_PAGINATION === $pagination_type;
$query_type = $attributes['queryType'] ?? self::TYPE_WP_QUERY;
$query_data = self::get_query_data(
$query_type,
$attributes,
$block,
$page
);
if ( $instant_pagination ) {
if ( ! wp_script_is( 'generateblocks-looper', 'enqueued' ) ) {
$asset_info = generateblocks_get_enqueue_assets( 'generateblocks-looper' );
wp_enqueue_script(
'generateblocks-looper',
GENERATEBLOCKS_DIR_URL . 'dist/looper.js',
$asset_info['dependencies'],
$asset_info['version'],
true
);
}
}
$max_pages = $query_data['max_num_pages'] ?? $query_data['data']->max_num_pages ?? 0;
$parsed_content = (
new WP_Block(
$block->parsed_block,
array(
'generateblocks/queryData' => [
'id' => $attributes['uniqueId'] ?? '',
'noResults' => $query_data['no_results'],
'data' => $query_data['data'],
'args' => $query_data['args'],
'type' => $query_type,
'maxPages' => $max_pages,
'inherit' => $attributes['inheritQuery'] ?? false,
'paginationType' => $pagination_type,
],
)
)
)->render( array( 'dynamic' => false ) );
if ( $instant_pagination && class_exists( 'WP_HTML_Tag_Processor' ) ) {
$processor = new WP_HTML_Tag_Processor( $parsed_content );
if ( $processor->next_tag( $attributes['tagName'] ) ) {
$processor->set_attribute( 'data-gb-router-region', $query_id );
$parsed_content = $processor->get_updated_html();
}
}
// Add styles to this block if needed.
$output = generateblocks_maybe_add_block_css(
'',
[
'class_name' => __CLASS__,
'attributes' => $attributes,
'block_ids' => self::$block_ids,
]
);
$output .= $parsed_content;
return $output;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Handles the Element block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Element block.
*/
class GenerateBlocks_Block_Shape 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/shape';
/**
* Render the Shape 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 Text block.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The Text block.
*/
class GenerateBlocks_Block_Text 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/text';
/**
* Render the Text 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,232 @@
<?php
/**
* Builds our dynamic CSS.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Creates minified css via PHP.
*/
class GenerateBlocks_Dynamic_CSS {
/**
* The css selector that you're currently adding rules to
*
* @access protected
* @var string
*/
protected $_selector = ''; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* Stores the final css output with all of its rules for the current selector.
*
* @access protected
* @var string
*/
protected $_selector_output = ''; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* Stores all of the rules that will be added to the selector
*
* @access protected
* @var string
*/
protected $_css = ''; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* The string that holds all of the css to output
*
* @access protected
* @var array
*/
protected $_output = array(); // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/**
* Sets a selector to the object and changes the current selector to a new one
*
* @access public
* @since 1.0
*
* @param string $selector - the css identifier of the html that you wish to target.
* @return $this
*/
public function set_selector( $selector = '' ) {
// Render the css in the output string everytime the selector changes.
if ( '' !== $this->_selector ) {
$this->add_selector_rules_to_output();
}
$this->_selector = $selector;
return $this;
}
/**
* Adds a css property with value to the css output
*
* @access public
* @since 1.0
*
* @param string $property - the css property.
* @param string $value - the value to be placed with the property.
* @param string $unit - the unit for the value (px).
* @return $this
*/
public function add_property( $property, $value, $unit = false ) {
if ( empty( $value ) && ! is_numeric( $value ) ) {
return false;
}
if (
is_array( $value ) &&
! array_filter(
$value,
function( $v ) {
return is_numeric( $v ) || $v;
}
)
) {
return false;
}
if ( is_array( $value ) ) {
$valueTop = generateblocks_has_number_value( $value[0] );
$valueRight = generateblocks_has_number_value( $value[1] );
$valueBottom = generateblocks_has_number_value( $value[2] );
$valueLeft = generateblocks_has_number_value( $value[3] );
if ( $valueTop && $valueRight && $valueBottom && $valueLeft ) {
$value = generateblocks_get_shorthand_css( $value[0], $value[1], $value[2], $value[3], $unit );
if ( 'border-width' === $property ) {
$this->_css .= 'border-style: solid;';
}
$this->_css .= $property . ':' . $value . ';';
return $this;
} else {
if ( $valueTop ) {
$property_top = $property . '-top';
$unit_top = $unit;
if ( 'border-radius' === $property ) {
$property_top = 'border-top-left-radius';
} elseif ( 'border-width' === $property ) {
$property_top = 'border-top-width';
$this->_css .= 'border-top-style: solid;';
}
if ( ! is_numeric( $value[0] ) || 0 === $value[0] || '0' === $value[0] ) {
$unit_top = '';
}
$this->_css .= $property_top . ':' . $value[0] . $unit_top . ';';
}
if ( $valueRight ) {
$property_right = $property . '-right';
$unit_right = $unit;
if ( 'border-radius' === $property ) {
$property_right = 'border-top-right-radius';
} elseif ( 'border-width' === $property ) {
$property_right = 'border-right-width';
$this->_css .= 'border-right-style: solid;';
}
if ( ! is_numeric( $value[1] ) || 0 === $value[1] || '0' === $value[1] ) {
$unit_right = '';
}
$this->_css .= $property_right . ':' . $value[1] . $unit_right . ';';
}
if ( $valueBottom ) {
$property_bottom = $property . '-bottom';
$unit_bottom = $unit;
if ( 'border-radius' === $property ) {
$property_bottom = 'border-bottom-right-radius';
} elseif ( 'border-width' === $property ) {
$property_bottom = 'border-bottom-width';
$this->_css .= 'border-bottom-style: solid;';
}
if ( ! is_numeric( $value[2] ) || 0 === $value[2] || '0' === $value[2] ) {
$unit_bottom = '';
}
$this->_css .= $property_bottom . ':' . $value[2] . $unit_bottom . ';';
}
if ( $valueLeft ) {
$property_left = $property . '-left';
$unit_left = $unit;
if ( 'border-radius' === $property ) {
$property_left = 'border-bottom-left-radius';
} elseif ( 'border-width' === $property ) {
$property_left = 'border-left-width';
$this->_css .= 'border-left-style: solid;';
}
if ( ! is_numeric( $value[3] ) || 0 === $value[3] || '0' === $value[3] ) {
$unit_left = '';
}
$this->_css .= $property_left . ':' . $value[3] . $unit_left . ';';
}
return $this;
}
}
// Add our unit to our value if it exists.
if ( $unit && is_numeric( $value ) ) {
$value = $value . $unit;
}
$this->_css .= $property . ':' . $value . ';';
return $this;
}
/**
* Adds the current selector rules to the output variable
*
* @access private
* @since 1.0
*
* @return $this
*/
private function add_selector_rules_to_output() {
if ( ! empty( $this->_css ) ) {
$this->_selector_output = $this->_selector;
$this->_output[ $this->_selector_output ][] = $this->_css;
$this->_output[ $this->_selector_output ] = array_unique( $this->_output[ $this->_selector_output ] );
// Reset the css.
$this->_css = '';
}
return $this;
}
/**
* Returns the minified css in the $_output variable
*
* @access public
* @since 1.0
*
* @return string
*/
public function css_output() {
// Add current selector's rules to output.
$this->add_selector_rules_to_output();
return $this->_output;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,875 @@
<?php
/**
* Dynamic tag security utilities.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class Dynamic_Tag_Security
*/
class GenerateBlocks_Dynamic_Tag_Security extends GenerateBlocks_Singleton {
const DISALLOWED_KEYS = [
'post_password',
'password',
'user_pass',
'user_activation_key',
];
/**
* Initialize all hooks.
*
* @return void
*/
public function init() {
add_filter( 'wp_insert_post_data', [ $this, 'validate_content_before_save' ], 10, 2 );
add_action( 'init', [ $this, 'register_rest_validation_for_post_types' ], 999 );
add_filter( 'rest_pre_dispatch', [ $this, 'validate_autosave_rest_request' ], 10, 3 );
}
/**
* Validate dynamic tag usage in post content at save time.
*
* Prevents restricted meta keys from being saved inside GenerateBlocks dynamic tags.
*
* @since 2.2.0
*
* @param string $content The post content to validate.
* @return true|WP_Error True if valid, WP_Error if validation fails.
*/
public static function validate_content( $content ) {
return self::validate_content_with_existing_signatures( $content );
}
/**
* Validate content while allowing previously existing violations.
*
* @since 2.2.0
*
* @param string $content The post content to validate.
* @param array $existing_signatures List of violation signatures already stored for this post.
* @return true|WP_Error
*/
public static function validate_content_with_existing_signatures( $content, $existing_signatures = [] ) {
return self::validate_content_internal( $content, $existing_signatures );
}
/**
* Retrieve restricted reference signatures present in content.
*
* @since 2.2.0
*
* @param string $content Serialized post content.
* @return array<int, string>
*/
public static function get_restricted_reference_signatures( $content ) {
$violations = self::get_violation_errors( $content );
$signatures = [];
foreach ( $violations as $signature => $details ) {
$signatures[ $signature ] = (int) ( $details['count'] ?? 0 );
}
return $signatures;
}
/**
* Determine if a given piece of content should be scanned for dynamic tag usage.
*
* @since 2.2.0
*
* @param string $content The post content to evaluate.
* @return bool True when validation should proceed.
*/
public static function should_validate_content( $content ) {
$content = self::normalize_serialized_content( $content );
if ( '' === $content ) {
return false;
}
$has_dynamic_tag_syntax = false !== strpos( $content, '{{' );
$has_dynamic_attributes = false;
if ( class_exists( 'GenerateBlocks_Dynamic_Content' ) && method_exists( 'GenerateBlocks_Dynamic_Content', 'content_has_dynamic_attribute_markers' ) ) {
$has_dynamic_attributes = (bool) GenerateBlocks_Dynamic_Content::content_has_dynamic_attribute_markers( $content );
}
if ( ! $has_dynamic_tag_syntax ) {
if ( $has_dynamic_attributes ) {
return true;
}
return (bool) apply_filters( 'generateblocks_force_validate_content', false, $content );
}
$block_names = self::get_dynamic_tag_enabled_blocks();
if ( empty( $block_names ) ) {
return false;
}
foreach ( $block_names as $block_name ) {
if ( function_exists( 'has_block' ) && has_block( $block_name, $content ) ) {
return true;
}
}
return (bool) apply_filters( 'generateblocks_force_validate_content', false, $content );
}
/**
* Extract violation errors keyed by signature.
*
* @since 2.2.0
*
* @param string $content Serialized post content.
* @return array<string, array{error:WP_Error,count:int}>
*/
protected static function get_violation_errors( $content ) {
$content = self::normalize_serialized_content( $content );
if ( '' === $content ) {
return [];
}
return self::scan_content_for_violations( $content );
}
/**
* Validate content while considering existing violations.
*
* @param string $content Content to validate.
* @param array $existing_signatures Previously stored violation signatures.
* @return true|WP_Error
*/
protected static function validate_content_internal( $content, $existing_signatures = [] ) {
$content = self::normalize_serialized_content( $content );
$violations = self::get_violation_errors( $content );
if ( empty( $violations ) ) {
return true;
}
if ( ! empty( $existing_signatures ) ) {
$existing_counts = self::normalize_signature_counts( $existing_signatures );
foreach ( $violations as $signature => $details ) {
$count = (int) ( $details['count'] ?? 0 );
$allowance = isset( $existing_counts[ $signature ] ) ? (int) $existing_counts[ $signature ] : 0;
if ( $count > $allowance ) {
return $details['error'];
}
}
return true;
}
$first_violation = reset( $violations );
return $first_violation['error'];
}
/**
* Format a violation signature for comparison.
*
* @param string $type Type of violation (user_meta/post_meta/term_meta).
* @param string $field_name Field name involved.
* @return string
*/
protected static function format_violation_signature( $type, $field_name ) {
$type = is_string( $type ) ? strtolower( trim( $type ) ) : '';
$field_name = is_string( $field_name ) ? strtolower( trim( $field_name ) ) : '';
if ( '' === $type || '' === $field_name ) {
return '';
}
return "{$type}:{$field_name}";
}
/**
* Normalize stored signature data into signature => count pairs.
*
* @param array $signatures Raw signature data.
* @return array<string,int>
*/
protected static function normalize_signature_counts( $signatures ) {
if ( empty( $signatures ) || ! is_array( $signatures ) ) {
return [];
}
$normalized = [];
foreach ( $signatures as $key => $value ) {
if ( is_string( $key ) && is_numeric( $value ) ) {
$normalized[ $key ] = max( 0, (int) $value );
} elseif ( is_string( $value ) ) {
$normalized[ $value ] = isset( $normalized[ $value ] ) ? $normalized[ $value ] + 1 : 1;
}
}
return $normalized;
}
/**
* Store or increment a violation entry.
*
* @param array $violations Reference to violations array.
* @param string $signature Signature key.
* @param WP_Error $error Violation error.
* @param int $increment Optional increment amount.
* @return void
*/
protected static function add_violation_entry( array &$violations, $signature, $error, $increment = 1 ) {
if ( ! isset( $violations[ $signature ] ) ) {
$violations[ $signature ] = [
'error' => $error,
'count' => 0,
];
}
$violations[ $signature ]['count'] += max( 1, (int) $increment );
}
/**
* Scan content and collect violation errors.
*
* @param string $content Serialized block content.
* @return array<string, array{error:WP_Error,count:int}>
*/
protected static function scan_content_for_violations( $content ) {
$content = self::normalize_serialized_content( $content );
$violations = [];
$rules = self::get_dynamic_tag_validation_rules();
foreach ( $rules as $rule_key => $rule ) {
if ( ! self::should_enforce_rule( $rule ) ) {
continue;
}
if ( ! empty( $rule['pattern'] ) && preg_match_all( $rule['pattern'], $content, $matches ) ) {
$field_matches = ! empty( $matches[1] ) ? $matches[1] : ( $matches[0] ?? [] );
foreach ( $field_matches as $field_name ) {
if ( isset( $rule['fixed_field'] ) ) {
$field_name = $rule['fixed_field'];
}
$result = call_user_func( $rule['validator'], $field_name );
if ( is_wp_error( $result ) ) {
$signature = self::format_violation_signature( $rule_key, $field_name );
if ( $signature ) {
self::add_violation_entry( $violations, $signature, $result );
}
}
}
}
if ( ! empty( $rule['link_tokens'] ) ) {
$link_violations = self::collect_meta_link_target_violations(
$content,
$rule_key,
array_fill_keys( $rule['link_tokens'], $rule['validator'] )
);
foreach ( $link_violations as $signature => $details ) {
self::add_violation_entry(
$violations,
$signature,
$details['error'],
$details['count'] ?? 1
);
}
}
}
if (
class_exists( 'GenerateBlocks_Dynamic_Content' ) &&
method_exists( 'GenerateBlocks_Dynamic_Content', 'get_dynamic_attribute_violation_items' )
) {
$attribute_violations = GenerateBlocks_Dynamic_Content::get_dynamic_attribute_violation_items( $content );
foreach ( $attribute_violations as $violation ) {
$type = $violation['type'] ?? '';
$field_name = $violation['field'] ?? '';
$error = $violation['error'] ?? null;
if ( ! $error instanceof WP_Error ) {
continue;
}
$signature = self::format_violation_signature( $type, $field_name );
if ( $signature ) {
self::add_violation_entry( $violations, $signature, $error );
}
}
}
return $violations;
}
/**
* Retrieve dynamic tag validation rules.
*
* @return array
*/
protected static function get_dynamic_tag_validation_rules() {
$rules = [
'user_meta' => [
'pattern' => '/\{\{(?:user_meta|author_meta)(?:\s+|\|)+[^}]*key:([^|}]+)[^}]*}}/i',
'validator' => [ self::class, 'validate_user_meta_field_name' ],
'link_tokens' => [ 'author_meta', 'author_email' ],
'bypass_cap' => 'list_users',
],
'post_meta' => [
'pattern' => '/\{\{post_meta(?:\s+|\|)+[^}]*key:([^|}]+)[^}]*}}/i',
'validator' => [ self::class, 'validate_post_meta_field_name' ],
'link_tokens' => [ 'post_meta' ],
],
'term_meta' => [
'pattern' => '/\{\{term_meta(?:\s+|\|)+[^}]*key:([^|}]+)[^}]*}}/i',
'validator' => [ self::class, 'validate_term_meta_field_name' ],
'link_tokens' => [ 'term_meta' ],
],
];
return apply_filters( 'generateblocks_dynamic_tag_validation_rules', $rules );
}
/**
* Determine if current user should validate user meta fields.
*
* @since 2.2.0
*
* @return bool
*/
public static function should_validate_user_meta_fields() {
$rules = self::get_dynamic_tag_validation_rules();
$rule = $rules['user_meta'] ?? null;
if ( ! $rule ) {
return true;
}
return self::should_enforce_rule( $rule );
}
/**
* Determine whether the current user should be subject to a validation rule.
*
* @param array $rule Rule definition.
* @return bool
*/
protected static function should_enforce_rule( $rule ) {
if ( empty( $rule['bypass_cap'] ) ) {
return true;
}
return ! current_user_can( $rule['bypass_cap'] );
}
/**
* Validate a single user meta field name against safe/disallowed lists.
*
* @since 2.2.0
*
* @param string $field_name The meta key to validate.
* @return true|WP_Error True if valid, WP_Error on violation.
*/
public static function validate_user_meta_field_name( $field_name ) {
$field_name = is_string( $field_name ) ? trim( $field_name ) : '';
if ( '' === $field_name ) {
return true;
}
if ( is_protected_meta( $field_name, 'user' ) ) {
return new WP_Error(
'restricted_user_meta',
sprintf(
/* translators: %s: The restricted field name */
__( 'You do not have permission to use the field "%s" in dynamic tags. Fields starting with underscore are protected.', 'generateblocks' ),
esc_html( $field_name )
),
[ 'status' => 403 ]
);
}
if ( in_array( $field_name, self::DISALLOWED_KEYS, true ) ) {
return new WP_Error(
'restricted_user_meta',
sprintf(
/* translators: %s: The restricted field name */
__( 'You do not have permission to use the field "%s" in dynamic tags. This field is restricted for security reasons.', 'generateblocks' ),
esc_html( $field_name )
),
[ 'status' => 403 ]
);
}
if ( ! in_array( $field_name, self::get_safe_user_meta_keys(), true ) ) {
return new WP_Error(
'restricted_user_meta',
sprintf(
/* translators: %s: The restricted field name */
__( 'You do not have permission to use the field "%s" in dynamic tags. Only administrators can use custom user fields.', 'generateblocks' ),
esc_html( $field_name )
),
[ 'status' => 403 ]
);
}
return true;
}
/**
* Validate a post meta key (blocks underscore-prefixed fields).
*
* @since 2.2.0
*
* @param string $field_name Meta key to validate.
* @return true|WP_Error True if valid key, WP_Error if restricted.
*/
public static function validate_post_meta_field_name( $field_name ) {
$field_name = is_string( $field_name ) ? trim( $field_name ) : '';
if ( '' === $field_name ) {
return true;
}
if ( is_protected_meta( $field_name, 'post' ) ) {
return new WP_Error(
'restricted_post_meta',
sprintf(
/* translators: %s: The restricted field name */
__( 'You do not have permission to use the post meta field "%s" in dynamic tags. Fields starting with underscore are protected.', 'generateblocks' ),
esc_html( $field_name )
),
[ 'status' => 403 ]
);
}
return true;
}
/**
* Validate a term meta key (blocks underscore-prefixed fields).
*
* @since 2.2.0
*
* @param string $field_name Meta key to validate.
* @return true|WP_Error True if valid key, WP_Error if restricted.
*/
public static function validate_term_meta_field_name( $field_name ) {
$field_name = is_string( $field_name ) ? trim( $field_name ) : '';
if ( '' === $field_name ) {
return true;
}
if ( is_protected_meta( $field_name, 'term' ) ) {
return new WP_Error(
'restricted_term_meta',
sprintf(
/* translators: %s: The restricted field name */
__( 'You do not have permission to use the term meta field "%s" in dynamic tags. Fields starting with underscore are protected.', 'generateblocks' ),
esc_html( $field_name )
),
[ 'status' => 403 ]
);
}
return true;
}
/**
* Collect link target violations for dynamic tags.
*
* @since 2.2.0
*
* @param string $content Serialized post content.
* @param string $rule_key The base rule key (user_meta/post_meta/term_meta).
* @param callable[] $validators Validators keyed by link token.
* @return array<string, array{error:WP_Error,count:int}>
*/
protected static function collect_meta_link_target_violations( $content, $rule_key, $validators ) {
$content = self::normalize_serialized_content( $content );
$violations = [];
if ( '' === $content ) {
return $violations;
}
if ( empty( $validators ) || ! is_array( $validators ) ) {
return $violations;
}
if ( ! preg_match_all( '/\{\{([^}]+)\}\}/', $content, $tag_matches ) ) {
return $violations;
}
foreach ( $tag_matches[1] as $tag_body ) {
$parts = preg_split( '/(?:\s+|\|)+/', $tag_body, 2 );
if ( count( $parts ) < 2 ) {
continue;
}
$options_string = $parts[1];
if ( false === stripos( $options_string, 'link:' ) ) {
continue;
}
foreach ( explode( '|', $options_string ) as $option ) {
$option = trim( $option );
if ( '' === $option || stripos( $option, 'link:' ) !== 0 ) {
continue;
}
$link_value = trim( substr( $option, 5 ) );
if ( '' === $link_value ) {
continue;
}
$link_parts = array_map( 'trim', explode( ',', $link_value ) );
$target = strtolower( $link_parts[0] ?? '' );
$field_name = $link_parts[1] ?? '';
if ( 'author_email' === $target ) {
$field_name = 'user_email';
}
if ( ! isset( $validators[ $target ] ) || ! is_callable( $validators[ $target ] ) ) {
continue;
}
$result = call_user_func( $validators[ $target ], $field_name );
if ( is_wp_error( $result ) ) {
$signature = self::format_violation_signature( $rule_key, $field_name );
if ( $signature ) {
self::add_violation_entry( $violations, $signature, $result );
}
}
}
}
return $violations;
}
/**
* Get safe user meta keys accessible without list_users capability.
*
* @since 2.1.4
*
* @return array<string>
*/
public static function get_safe_user_meta_keys() {
$safe_keys = [
'description',
'first_name',
'last_name',
'nickname',
'display_name',
'user_nicename',
'user_url',
'locale',
'show_admin_bar_front',
];
$safe_keys = apply_filters( 'generateblocks_safe_user_meta_keys', $safe_keys );
return array_values( array_unique( array_filter( $safe_keys, 'is_string' ) ) );
}
/**
* Get the list of blocks that support GenerateBlocks dynamic tags.
*
* Mirrors the filter used when replacing tags so integrations stay in sync.
*
* @since 2.2.0
*
* @return array<int, string>
*/
protected static function get_dynamic_tag_enabled_blocks() {
$blocks = [];
if ( class_exists( 'GenerateBlocks_Dynamic_Tags' ) ) {
$dynamic_tags = GenerateBlocks_Dynamic_Tags::get_instance();
if ( method_exists( $dynamic_tags, 'get_allowed_blocks' ) ) {
$blocks = $dynamic_tags->get_allowed_blocks();
}
}
if ( ! is_array( $blocks ) ) {
return [];
}
return array_values(
array_filter(
$blocks,
function( $block_name ) {
return is_string( $block_name ) && '' !== $block_name;
}
)
);
}
/**
* Fetch violation signatures from an existing post.
*
* @param int $post_id Post ID.
* @return array
*/
protected static function get_existing_content_signatures( $post_id ) {
if ( ! $post_id ) {
return [];
}
$post = get_post( $post_id );
if ( ! $post || is_wp_error( $post ) ) {
return [];
}
return self::get_restricted_reference_signatures( $post->post_content );
}
/**
* Validate post content before database insert.
*
* This filter runs BEFORE the post is saved to the database, allowing us to
* prevent the save by triggering an error. Handles classic editor, quick edit,
* and other non-REST saves.
*
* @since 2.2.0
*
* @param array $data An array of slashed, sanitized, and processed post data.
* @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data.
* @return array The post data array (unmodified if validation passes).
*/
public function validate_content_before_save( $data, $postarr ) {
// Admins already have unfiltered_html, so skip enforcement.
if ( current_user_can( 'manage_options' ) ) {
return $data;
}
// REST requests are validated via rest_pre_insert hooks.
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return $data;
}
// Skip if no content.
if ( empty( $data['post_content'] ) ) {
return $data;
}
// Skip classic revisions but continue validating autosaves.
if ( ! empty( $postarr['ID'] ) ) {
$is_revision = wp_is_post_revision( $postarr['ID'] );
$is_autosave = wp_is_post_autosave( $postarr['ID'] );
if ( $is_revision && ! $is_autosave ) {
return $data;
}
}
if ( ! self::should_validate_content( $data['post_content'] ) ) {
return $data;
}
$existing_signatures = [];
if ( ! empty( $postarr['ID'] ) ) {
$existing_signatures = self::get_existing_content_signatures( (int) $postarr['ID'] );
}
// Validate the content.
$result = self::validate_content_with_existing_signatures( $data['post_content'], $existing_signatures );
if ( is_wp_error( $result ) ) {
// Block the save with a clear error message.
wp_die(
esc_html( $result->get_error_message() ),
esc_html( __( 'Content Validation Failed', 'generateblocks' ) ),
array(
'response' => 403,
'back_link' => true,
)
);
}
return $data;
}
/**
* Validate post content before REST API insert/update.
*
* This handles Gutenberg saves and autosaves via REST API.
*
* @since 2.2.0
*
* @param stdClass $prepared_post An object representing a single post prepared for inserting or updating the database.
* @param WP_REST_Request $request Request object.
* @return stdClass|WP_Error The prepared post object or WP_Error on validation failure.
*/
public function validate_content_rest( $prepared_post, $request ) {
// Admins already have unfiltered_html, so skip enforcement.
if ( current_user_can( 'manage_options' ) ) {
return $prepared_post;
}
// Skip if no content.
$content = $prepared_post->post_content ?? '';
if ( empty( $content ) ) {
return $prepared_post;
}
if ( ! self::should_validate_content( $content ) ) {
return $prepared_post;
}
$post_id = 0;
if ( ! empty( $prepared_post->ID ) ) {
$post_id = (int) $prepared_post->ID;
} elseif ( $request instanceof WP_REST_Request && $request->get_param( 'id' ) ) {
$post_id = (int) $request->get_param( 'id' );
}
$existing_signatures = self::get_existing_content_signatures( $post_id );
// Validate the content.
$result = self::validate_content_with_existing_signatures( $content, $existing_signatures );
if ( is_wp_error( $result ) ) {
// Return the error - Gutenberg will display it in the editor.
return $result;
}
return $prepared_post;
}
/**
* Register REST API validation hooks for all public post types.
*
* This ensures validation runs for posts, pages, and custom post types that support the block editor.
*
* @since 2.2.0
* @return void
*/
public function register_rest_validation_for_post_types() {
$post_types = get_post_types(
array(
'show_in_rest' => true,
),
'names'
);
foreach ( $post_types as $post_type ) {
add_filter( "rest_pre_insert_{$post_type}", [ $this, 'validate_content_rest' ], 10, 2 );
}
}
/**
* Intercept Gutenberg autosave REST requests and validate their content.
*
* Autosaves use the /wp/v2/{post-type}/{id}/autosaves endpoint, which bypasses
* the regular rest_pre_insert_{post_type} filters. Hooking rest_pre_dispatch allows
* us to run the same validation logic and return a proper WP_Error response so
* the editor surfaces the issue immediately.
*
* @since 2.2.0
*
* @param mixed $response Response to replace the requested version with. Default false.
* @param WP_REST_Server $server Server instance.
* @param WP_REST_Request $request Request used to generate the response.
* @return mixed Either the original response or a WP_Error to halt dispatch.
*/
public function validate_autosave_rest_request( $response, $server, $request ) {
if ( $response ) {
return $response;
}
if ( 'POST' !== $request->get_method() ) {
return $response;
}
if ( current_user_can( 'manage_options' ) ) {
return $response;
}
$route = $request->get_route();
// Only run on wp/v2 autosave endpoints.
if ( ! is_string( $route ) || ! preg_match( '#^/wp/v2/([a-z0-9_-]+)/(\d+)/autosaves/?$#', $route, $route_matches ) ) {
return $response;
}
$content_param = $request->get_param( 'content' );
$content = '';
if ( is_array( $content_param ) ) {
$content = isset( $content_param['raw'] ) ? (string) $content_param['raw'] : '';
} elseif ( is_string( $content_param ) ) {
$content = $content_param;
}
if ( '' === $content ) {
return $response;
}
if ( ! self::should_validate_content( $content ) ) {
return $response;
}
$post_id = (int) ( $route_matches[2] ?? 0 );
$existing_signatures = self::get_existing_content_signatures( $post_id );
$result = self::validate_content_with_existing_signatures( $content, $existing_signatures );
if ( is_wp_error( $result ) ) {
if ( ! $result->get_error_data() ) {
$result->add_data( array( 'status' => rest_authorization_required_code() ) );
}
return $result;
}
return $response;
}
/**
* Normalize serialized block content so validation sees unslashed values.
*
* @since 2.2.0
*
* @param mixed $content Possibly slashed content string.
* @return string Normalized content string.
*/
public static function normalize_serialized_content( $content ) {
if ( ! is_string( $content ) ) {
return '';
}
$normalized = function_exists( 'wp_unslash' ) ? wp_unslash( $content ) : stripslashes( $content );
return trim( (string) $normalized );
}
}
GenerateBlocks_Dynamic_Tag_Security::get_instance()->init();
@@ -0,0 +1,526 @@
<?php
/**
* Handles the CSS Output.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Enqueue our block CSS to the current page.
*/
class GenerateBlocks_Enqueue_CSS {
/**
* Instance.
*
* @access private
* @var object Instance
* @since 0.1
*/
private static $instance;
/**
* Check to see if we've made this CSS in this instance.
* This should only run on first load if needed.
*
* @access private
* @var boolean
*/
private static $has_made_css = false;
/**
* Check to see if we've enqueued our CSS.
*
* @access private
* @var boolean
*/
private static $has_enqueued_css = false;
/**
* Initiator.
*
* @since 0.1
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
public function __construct() {
$this->add_options();
add_action( 'save_post', array( $this, 'post_update_option' ), 10, 2 );
add_action( 'save_post_wp_block', array( $this, 'wp_block_update' ), 10, 2 );
add_action( 'init', array( $this, 'enqueue_assets' ) );
add_filter( 'widget_update_callback', array( $this, 'force_file_regen_on_widget_save' ) );
add_action( 'customize_save_after', array( $this, 'force_file_regen_on_customizer_save' ) );
}
/**
* Tell our system it's ok to generate CSS.
*/
private function enable_enqueue() {
self::$has_enqueued_css = true;
}
/**
* Check to see if we can generate CSS.
*/
public static function can_enqueue() {
return self::$has_enqueued_css || ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() );
}
/**
* Enqueue our front-end assets.
*/
public function enqueue_assets() {
$dynamic_css_priority = apply_filters( 'generateblocks_dynamic_css_priority', 25 );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_dynamic_css' ), $dynamic_css_priority );
add_action( 'wp_enqueue_scripts', array( $this, 'print_inline_css' ), $dynamic_css_priority );
}
/**
* Get the current page ID.
*/
public function page_id() {
global $post;
$id = isset( $post ) ? $post->ID : false;
$id = ( ! is_singular() ) ? false : $id;
$id = ( function_exists( 'is_shop' ) && is_shop() ) ? get_option( 'woocommerce_shop_page_id' ) : $id;
$id = ( is_home() ) ? get_option( 'page_for_posts' ) : $id;
return $id;
}
/**
* Determine if we're using file mode or inline mode.
*/
public function mode() {
// Check if we're using file mode or inline mode.
// Default to file mode and fallback to inline if file mode is not possible.
$mode = apply_filters( 'generateblocks_css_print_method', 'file' );
if (
( function_exists( 'is_customize_preview' ) && is_customize_preview() )
||
is_preview()
||
// AMP inlines all CSS, so inlining from the start improves CSS processing performance.
( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() )
) {
return 'inline';
}
// Additional checks for file mode.
if ( 'file' === $mode && $this->needs_update() ) {
// Only allow processing 1 file every 5 seconds.
$current_time = (int) time();
$last_time = (int) get_option( 'generateblocks_dynamic_css_time' );
if ( 5 <= ( $current_time - $last_time ) ) {
// Attempt to write to the file.
$mode = ( $this->can_write() && $this->make_css() ) ? 'file' : 'inline';
// Does again if the file exists.
if ( 'file' === $mode ) {
$mode = ( file_exists( $this->file( 'path' ) ) ) ? 'file' : 'inline';
}
}
}
return $mode;
}
/**
* Enqueue the dynamic CSS.
*/
public function enqueue_dynamic_css() {
$this->enable_enqueue();
$page_id = $this->page_id();
if ( ! $page_id ) {
return;
}
$has_generateblocks = get_post_meta( $page_id, '_generateblocks_dynamic_css_version', true );
if ( empty( $has_generateblocks ) ) {
return;
}
if ( 'file' === $this->mode() ) {
if ( ! self::$has_made_css ) {
// Store our block IDs based on the content we find.
generateblocks_get_dynamic_css( '', true );
}
wp_enqueue_style( 'generateblocks', esc_url( $this->file( 'uri' ) ), array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
}
}
/**
* Print our inline CSS.
*/
public function print_inline_css() {
$this->enable_enqueue();
if ( 'inline' === $this->mode() || ! wp_style_is( 'generateblocks', 'enqueued' ) ) {
// Build our CSS based on the content we find.
generateblocks_get_dynamic_css();
$css = generateblocks_get_frontend_block_css();
if ( empty( $css ) ) {
return;
}
// Add a "dummy" handle we can add inline styles to.
wp_register_style( 'generateblocks', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
wp_enqueue_style( 'generateblocks' );
$css = html_entity_decode( $css, ENT_QUOTES, 'UTF-8' );
wp_add_inline_style(
'generateblocks',
wp_strip_all_tags( $css ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
}
/**
* Make our CSS.
*/
public function make_css() {
$page_id = $this->page_id();
if ( ! $page_id ) {
return false;
}
$has_generateblocks = get_post_meta( $page_id, '_generateblocks_dynamic_css_version', true );
if ( empty( $has_generateblocks ) ) {
return false;
}
// Build our CSS based on the content we find.
generateblocks_get_dynamic_css();
$content = generateblocks_get_frontend_block_css();
if ( ! $content ) {
return false;
}
// If we only have a little CSS, we should inline it.
$css_size = strlen( $content );
if ( $css_size < (int) apply_filters( 'generateblocks_css_inline_length', 500 ) ) {
return false;
}
$filesystem = generateblocks_get_wp_filesystem();
if ( ! $filesystem ) {
return false;
}
// Take care of domain mapping.
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
$mapped_domain = domain_mapping_siteurl( false );
$original_domain = get_original_url( 'siteurl' );
$content = str_replace( $original_domain, $mapped_domain, $content );
}
}
if ( is_writable( $this->file( 'path' ) ) || ( ! file_exists( $this->file( 'path' ) ) && is_writable( dirname( $this->file( 'path' ) ) ) ) ) {
$chmod_file = 0644;
if ( defined( 'FS_CHMOD_FILE' ) ) {
$chmod_file = FS_CHMOD_FILE;
}
$content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
if ( ! $filesystem->put_contents( $this->file( 'path' ), wp_strip_all_tags( $content ), $chmod_file ) ) {
// Fail!
return false;
} else {
$option = get_option( 'generateblocks_dynamic_css_posts', array() );
$option[ $page_id ] = true;
update_option( 'generateblocks_dynamic_css_posts', $option );
// Update the 'generateblocks_dynamic_css_time' option.
$this->update_saved_time();
// Set flag.
self::$has_made_css = true;
// Success!
return true;
}
}
}
/**
* Determines if the CSS file is writable.
*/
public function can_write() {
global $blog_id;
// Get the upload directory for this site.
$upload_dir = wp_get_upload_dir();
// If this is a multisite installation, append the blogid to the filename.
$css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
$page_id = $this->page_id();
if ( ! $page_id ) {
return false;
}
$file_name = '/style' . $css_blog_id . '-' . $page_id . '.css';
$folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
// Does the folder exist?
if ( file_exists( $folder_path ) ) {
// Folder exists, but is the folder writable?
if ( ! is_writable( $folder_path ) ) {
// Folder is not writable.
// Does the file exist?
if ( ! file_exists( $folder_path . $file_name ) ) {
// File does not exist, therefore it can't be created
// since the parent folder is not writable.
return false;
} else {
// File exists, but is it writable?
if ( ! is_writable( $folder_path . $file_name ) ) {
// Nope, it's not writable.
return false;
}
}
} else {
// The folder is writable.
// Does the file exist?
if ( file_exists( $folder_path . $file_name ) ) {
// File exists.
// Is it writable?
if ( ! is_writable( $folder_path . $file_name ) ) {
// Nope, it's not writable.
return false;
}
}
}
} else {
// Can we create the folder?
// returns true if yes and false if not.
return wp_mkdir_p( $folder_path );
}
// all is well!
return true;
}
/**
* Gets the css path or url to the stylesheet
*
* @param string $target path/url.
*/
public function file( $target = 'path' ) {
global $blog_id;
// Get the upload directory for this site.
$upload_dir = wp_get_upload_dir();
// If this is a multisite installation, append the blogid to the filename.
$css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
$page_id = $this->page_id();
$file_name = 'style' . $css_blog_id . '-' . $page_id . '.css';
$folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
// The complete path to the file.
$file_path = $folder_path . DIRECTORY_SEPARATOR . $file_name;
// Get the URL directory of the stylesheet.
$css_uri_folder = $upload_dir['baseurl'];
$css_uri = trailingslashit( $css_uri_folder ) . 'generateblocks/' . $file_name;
// Take care of domain mapping.
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
$mapped_domain = domain_mapping_siteurl( false );
$original_domain = get_original_url( 'siteurl' );
$css_uri = str_replace( $original_domain, $mapped_domain, $css_uri );
}
}
$css_uri = set_url_scheme( $css_uri );
if ( 'path' === $target ) {
return $file_path;
} elseif ( 'url' === $target || 'uri' === $target ) {
$timestamp = ( file_exists( $file_path ) ) ? '?ver=' . filemtime( $file_path ) : '';
return $css_uri . $timestamp;
}
}
/**
* Create settings.
*/
public function add_options() {
/**
* The 'generateblocks_dynamic_css_posts' option will hold an array of posts that have had their css generated.
* We can use that to keep track of which pages need their CSS to be recreated and which don't.
*/
add_option( 'generateblocks_dynamic_css_posts', array(), '', 'yes' );
/**
* The 'generateblocks_dynamic_css_time' option holds the time the file writer was last used.
*/
add_option( 'generateblocks_dynamic_css_time', time(), '', 'yes' );
}
/**
* Update the generateblocks_dynamic_css_posts option when a post is saved.
*
* @param int $post_id The current post ID.
* @param object $post The current post.
*/
public function post_update_option( $post_id, $post ) {
$is_autosave = wp_is_post_autosave( $post_id );
$is_revision = wp_is_post_revision( $post_id );
if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
if ( isset( $post->post_content ) ) {
if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
update_post_meta( $post_id, '_generateblocks_dynamic_css_version', sanitize_text_field( GENERATEBLOCKS_VERSION ) );
} else {
delete_post_meta( $post_id, '_generateblocks_dynamic_css_version' );
}
// Store any re-usable block IDs on the page. We need this to regenerate CSS files later if the re-usable block is changed.
$reusable_blocks = preg_match_all( '/wp:block {"ref":([^}]*)}/', $post->post_content, $matches );
$stored_reusable_blocks = array();
foreach ( $matches[1] as $match ) {
$stored_reusable_blocks[] = $match;
}
if ( ! empty( $stored_reusable_blocks ) ) {
$stored_reusable_blocks = array_map( 'intval', $stored_reusable_blocks );
update_post_meta( $post_id, '_generateblocks_reusable_blocks', $stored_reusable_blocks );
} else {
delete_post_meta( $post_id, '_generateblocks_reusable_blocks' );
}
}
// Make a new CSS file for this post on next load.
$option = get_option( 'generateblocks_dynamic_css_posts', array() );
unset( $option[ $post_id ] );
update_option( 'generateblocks_dynamic_css_posts', $option );
}
/**
* Force regeneration of CSS files attached to pages with this re-usable block.
*
* @param int $post_id The current post ID.
* @param object $post The current post.
*/
public function wp_block_update( $post_id, $post ) {
$is_autosave = wp_is_post_autosave( $post_id );
$is_revision = wp_is_post_revision( $post_id );
if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
if ( isset( $post->post_content ) ) {
if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
global $wpdb;
$option = get_option( 'generateblocks_dynamic_css_posts', array() );
$posts = $wpdb->get_col( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_generateblocks_reusable_blocks'" );
foreach ( (array) $posts as $id ) {
unset( $option[ $id ] );
}
update_option( 'generateblocks_dynamic_css_posts', $option );
}
}
}
/**
* Do we need to update the CSS file?
*/
public function needs_update() {
$option = get_option( 'generateblocks_dynamic_css_posts', array() );
$page_id = $this->page_id();
// If the CSS file does not exist then we definitely need to regenerate the CSS.
if ( ! file_exists( $this->file( 'path' ) ) ) {
return true;
}
return ( ! isset( $option[ $page_id ] ) || ! $option[ $page_id ] ) ? true : false;
}
/**
* Update the 'generateblocks_dynamic_css_time' option.
*/
public function update_saved_time() {
update_option( 'generateblocks_dynamic_css_time', time() );
}
/**
* Force CSS files to regenerate after a widget has been saved.
*
* @param array $instance The current widget instance's settings.
*/
public function force_file_regen_on_widget_save( $instance ) {
if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
update_option( 'generateblocks_dynamic_css_posts', array() );
}
return $instance;
}
/**
* Force CSS files to regenerate after the Customizer has been saved.
* This is necessary because force_file_regen_on_widget_save() doesn't fire in the Customizer for some reason.
*
* @todo Make this only happen when the widgets have been changed.
*/
public function force_file_regen_on_customizer_save() {
if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
update_option( 'generateblocks_dynamic_css_posts', array() );
}
}
}
GenerateBlocks_Enqueue_CSS::get_instance();
@@ -0,0 +1,115 @@
<?php
/**
* Handles legacy attributes that have changed.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Provides a method to define old attributes and serve old settings based on them.
*/
class GenerateBlocks_Legacy_Attributes {
/**
* Get our old defaults that have changed.
*
* @param string $version The version to get defaults from.
*/
public static function get_defaults( $version ) {
if ( '1.4.0' === $version ) {
return apply_filters(
'generateblocks_defaults',
array(
'gridContainer' => array(
'horizontalGap' => 30,
),
'container' => array(
'paddingTop' => '40',
'paddingRight' => '40',
'paddingBottom' => '40',
'paddingLeft' => '40',
'width' => 50,
'widthMobile' => 100,
'gradientDirection' => 90,
'gradientColorOne' => '#ffffff',
'gradientColorOneOpacity' => 0.1,
'gradientColorTwo' => '#000000',
'gradientColorTwoOpacity' => 0.3,
),
'button' => array(
'gradientDirection' => 90,
'gradientColorOne' => '#ffffff',
'gradientColorOneOpacity' => 0.1,
'gradientColorTwo' => '#000000',
'gradientColorTwoOpacity' => 0.3,
),
)
);
}
}
/**
* Update our settings based on old defaults.
*
* @param string $version The version to target.
* @param string $block The name of the block we're targeting.
* @param array $settings The current settings.
* @param array $atts The block attributes.
*/
public static function get_settings( $version, $block, $settings, $atts ) {
$legacy_defaults = self::get_defaults( $version );
if ( empty( $legacy_defaults ) ) {
return $settings;
}
if ( '1.4.0' === $version ) {
if ( 'grid' === $block ) {
$legacy_settings = wp_parse_args(
$atts,
$legacy_defaults['gridContainer']
);
$settings['horizontalGap'] = $legacy_settings['horizontalGap'];
}
if ( 'container' === $block ) {
$legacy_settings = wp_parse_args(
$atts,
$legacy_defaults['container']
);
$settings['paddingTop'] = $legacy_settings['paddingTop'];
$settings['paddingRight'] = $legacy_settings['paddingRight'];
$settings['paddingBottom'] = $legacy_settings['paddingBottom'];
$settings['paddingLeft'] = $legacy_settings['paddingLeft'];
$settings['width'] = $legacy_settings['width'];
$settings['widthMobile'] = $legacy_settings['widthMobile'];
$settings['gradientDirection'] = $legacy_settings['gradientDirection'];
$settings['gradientColorOne'] = $legacy_settings['gradientColorOne'];
$settings['gradientColorOneOpacity'] = $legacy_settings['gradientColorOneOpacity'];
$settings['gradientColorTwo'] = $legacy_settings['gradientColorTwo'];
$settings['gradientColorTwoOpacity'] = $legacy_settings['gradientColorTwoOpacity'];
}
if ( 'button' === $block ) {
$button_legacy_settings = wp_parse_args(
$atts,
$legacy_defaults['button']
);
$settings['gradientColorOne'] = $button_legacy_settings['gradientColorOne'];
$settings['gradientColorOneOpacity'] = $button_legacy_settings['gradientColorOneOpacity'];
$settings['gradientColorTwo'] = $button_legacy_settings['gradientColorTwo'];
$settings['gradientColorTwoOpacity'] = $button_legacy_settings['gradientColorTwoOpacity'];
$settings['gradientDirection'] = $button_legacy_settings['gradientDirection'];
}
}
return $settings;
}
}
@@ -0,0 +1,243 @@
<?php
/**
* Maps our old attributes to their new attribute names.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Map our deprecated attributes.
*/
class GenerateBlocks_Map_Deprecated_Attributes {
/**
* Class instance.
*
* @access private
* @var $instance Class instance.
*/
private static $instance;
/**
* Our devices.
*
* @access private
* @var $devices List of devices.
*/
private static $devices = [ '', 'Tablet', 'Mobile' ];
/**
* Initiator
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get all of our mapped attributes.
*
* @param array $settings Existing settings.
* @return array Mapped settings.
*/
public static function map_attributes( $settings ) {
$settings = self::map_spacing( $settings );
$settings = self::map_borders( $settings );
$settings = self::map_typography( $settings );
return $settings;
}
/**
* Map our old spacing attributes.
*
* @param array $settings Existing settings.
* @return array Mapped spacing settings.
*/
public static function map_spacing( $settings ) {
if ( ! empty( $settings['spacing'] ) ) {
return $settings;
}
$padding_attributes = [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
];
$margin_attributes = [
'marginTop',
'marginRight',
'marginBottom',
'marginLeft',
];
foreach ( self::$devices as $device ) {
foreach ( $padding_attributes as $attribute ) {
$setting_name = $attribute . $device;
if ( $settings[ $setting_name ] || is_numeric( $settings[ $setting_name ] ) ) {
$unit = is_numeric( $settings[ $setting_name ] )
? $settings['paddingUnit']
: '';
$settings['spacing'][ $setting_name ] = $settings[ $setting_name ] . $unit;
}
}
foreach ( $margin_attributes as $attribute ) {
$setting_name = $attribute . $device;
if ( $settings[ $setting_name ] || is_numeric( $settings[ $setting_name ] ) ) {
$unit = is_numeric( $settings[ $setting_name ] )
? $settings['marginUnit']
: '';
$settings['spacing'][ $setting_name ] = $settings[ $setting_name ] . $unit;
}
}
}
return $settings;
}
/**
* Map our old border attributes.
*
* @param array $settings Existing settings.
* @return array Mapped border settings.
*/
public static function map_borders( $settings ) {
if ( ! empty( $settings['borders'] ) ) {
return $settings;
}
$border_radius_attributes = [
'borderRadiusTopLeft' => 'borderTopLeftRadius',
'borderRadiusTopRight' => 'borderTopRightRadius',
'borderRadiusBottomRight' => 'borderBottomRightRadius',
'borderRadiusBottomLeft' => 'borderBottomLeftRadius',
];
foreach ( self::$devices as $device ) {
foreach ( $border_radius_attributes as $old_attribute_name => $new_attribute_name ) {
$setting_name = $old_attribute_name . $device;
if ( $settings[ $setting_name ] || is_numeric( $settings[ $setting_name ] ) ) {
$unit = is_numeric( $settings[ $setting_name ] )
? $settings['borderRadiusUnit']
: '';
$settings['borders'][ $new_attribute_name . $device ] = $settings[ $setting_name ] . $unit;
}
}
}
$border_width_attributes = [
'borderSizeTop' => 'borderTopWidth',
'borderSizeRight' => 'borderRightWidth',
'borderSizeBottom' => 'borderBottomWidth',
'borderSizeLeft' => 'borderLeftWidth',
];
foreach ( self::$devices as $device ) {
foreach ( $border_width_attributes as $old_attribute_name => $new_attribute_name ) {
$setting_name = $old_attribute_name . $device;
if ( $settings[ $setting_name ] || is_numeric( $settings[ $setting_name ] ) ) {
$unit = is_numeric( $settings[ $setting_name ] )
? 'px'
: '';
$settings['borders'][ $new_attribute_name . $device ] = $settings[ $setting_name ] . $unit;
$border_style_name = str_replace( 'Width', 'Style', $new_attribute_name );
$settings['borders'][ $border_style_name . $device ] = 'solid';
if ( ! empty( $settings['borderColor'] ) ) {
$border_color_name = str_replace( 'Width', 'Color', $new_attribute_name );
$settings['borders'][ $border_color_name ] = isset( $settings['borderColorOpacity'] )
? generateblocks_hex2rgba( $settings['borderColor'], $settings['borderColorOpacity'] )
: $settings['borderColor'];
}
if ( ! empty( $settings['borderColorHover'] ) ) {
$border_color_hover_name = str_replace( 'Width', 'ColorHover', $new_attribute_name );
$settings['borders'][ $border_color_hover_name ] = isset( $settings['borderColorHoverOpacity'] )
? generateblocks_hex2rgba( $settings['borderColorHover'], $settings['borderColorHoverOpacity'] )
: $settings['borderColorHover'];
}
if ( ! empty( $settings['borderColorCurrent'] ) ) {
$border_color_current_name = str_replace( 'Width', 'ColorCurrent', $new_attribute_name );
$settings['borders'][ $border_color_current_name ] = $settings['borderColorCurrent'];
}
}
}
}
return $settings;
}
/**
* Map our old typography attributes.
*
* @param array $settings Existing settings.
* @return array Mapped spacing settings.
*/
public static function map_typography( $settings ) {
if ( ! empty( $settings['typography'] ) ) {
return $settings;
}
$old_attributes = [
'fontFamily',
'fontSize',
'lineHeight',
'letterSpacing',
'fontWeight',
'textTransform',
'alignment',
];
foreach ( self::$devices as $device ) {
foreach ( $old_attributes as $attribute ) {
$setting_name = $attribute . $device;
if ( isset( $settings[ $setting_name ] ) && ( $settings[ $setting_name ] || is_numeric( $settings[ $setting_name ] ) ) ) {
$unit = '';
switch ( $attribute ) {
case 'fontSize':
$unit = $settings['fontSizeUnit'];
break;
case 'lineHeight':
$unit = $settings['lineHeightUnit'];
break;
case 'letterSpacing':
$unit = 'em';
break;
}
// textAlign used to be called "alignment".
if ( 'alignment' === $attribute ) {
$settings['typography'][ 'textAlign' . $device ] = $settings[ $setting_name ];
continue;
}
$settings['typography'][ $setting_name ] = $settings[ $setting_name ] . $unit;
}
}
}
return $settings;
}
}
@@ -0,0 +1,627 @@
<?php
/**
* The retrieve metadata and options.
*
* @package GenerateBlocks\Meta_Handler
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for handling dynamic tags.
*
* @since 2.0.0
*/
class GenerateBlocks_Meta_Handler extends GenerateBlocks_Singleton {
/**
* Back-compat proxy so external code can keep referencing this constant.
*/
const DISALLOWED_KEYS = GenerateBlocks_Dynamic_Tag_Security::DISALLOWED_KEYS;
/**
* Initialize all hooks.
*
* @return void
*/
public function init() {
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
}
/**
* Register class REST routes.
*
* @return void
*/
public function register_rest_routes() {
register_rest_route(
'generateblocks/v1',
'/meta/get-post-meta',
[
'methods' => 'GET',
'callback' => [ $this, 'get_post_meta_rest' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
register_rest_route(
'generateblocks/v1',
'/meta/get-user-meta',
[
'methods' => 'GET',
'callback' => [ $this, 'get_user_meta_rest' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
register_rest_route(
'generateblocks/v1',
'/meta/get-term-meta',
[
'methods' => 'GET',
'callback' => [ $this, 'get_term_meta_rest' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
register_rest_route(
'generateblocks/v1',
'/meta/get-option',
[
'methods' => 'GET',
'callback' => [ $this, 'get_option_rest' ],
'permission_callback' => function( $request ) {
// Only allow users who can edit posts to access options.
if ( ! current_user_can( 'edit_posts' ) ) {
return false;
}
// Admins can access all options.
if ( current_user_can( 'manage_options' ) ) {
return true;
}
$allowed_keys = apply_filters(
'generateblocks_allowed_option_keys_rest_api',
[
'siteurl',
'blogname',
'blogdescription',
'home',
'time_format',
'user_count',
]
);
if ( ! is_array( $allowed_keys ) ) {
return false;
}
$key = $request->get_param( 'key' ) ?? '';
if ( ! is_string( $key ) ) {
return false;
}
// Allow access to allowed keys.
if ( in_array( $key, $allowed_keys, true ) ) {
return true;
}
// Fallback: check parent key for dot notation.
if ( strpos( $key, '.' ) !== false ) {
$parent_key = trim( explode( '.', $key )[0] );
if ( '' !== $parent_key && in_array( $parent_key, $allowed_keys, true ) ) {
return true;
}
}
return false;
},
]
);
}
/**
* Check to see if a value if array-like and if so, get the provided property from it.
*
* @param mixed $value The value to check the property against.
* @param mixed $property The property to retrieve from the value if it exists.
* @param bool $single_only If true, only return value if it's a string-like value.
* @return mixed The $property value if it exists, otherwise the $value.
*/
public static function maybe_get_property( $value, $property, $single_only = true ) {
if ( ! $property
&& ! $single_only
&& ( is_array( $value ) || is_object( $value ) )
) {
return $value;
}
if ( is_array( $value ) ) {
return $value[ $property ] ?? $value;
} elseif ( is_object( $value ) ) {
return $value->$property ?? $value;
}
// Return the value if it's not an array or object.
return $value;
}
/**
* Check if a value is an array or object.
*
* @param mixed $value The value to check.
* @return bool If the value is an array or object.
*/
public static function is_array_or_object( $value ) {
return is_array( $value ) || is_object( $value );
}
/**
* Recursive or single value retrieval.
*
* @param string $key The key from the parent value for retrieval.
* @param string|int $parent_value The parent value to check the key against.
* @param bool $single_only If true, only return value if it's a string-like value.
* @param string $fallback The fallback value if the return value is empty.
* @return string
*/
public static function get_value( $key, $parent_value, $single_only = true, $fallback = '' ) {
// Stop here if the key is empty, and not "0".
if ( empty( $key ) && ! is_numeric( $key ) ) {
if ( $single_only ) {
$parent_value = self::is_array_or_object( $parent_value ) ? $fallback : (string) $parent_value;
return '' !== $parent_value ? $parent_value : $fallback;
}
return self::is_array_or_object( $parent_value ) ? $parent_value : $fallback;
}
$parts = explode( '.', $key );
$sub_value = self::maybe_get_property( $parent_value, $parts[0], $single_only );
if ( self::is_array_or_object( $sub_value ) ) {
return self::get_value(
implode( '.', array_slice( $parts, 1 ) ),
$sub_value,
$single_only,
$fallback
);
}
// Coerce simple values to strings.
$value = (string) $sub_value;
return '' !== $value ? $value : $fallback;
}
/**
* Get a meta value.
*
* @param string|int $id The id of the entity to fetch meta from.
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @param string $callable Function name to call. Should be a native WordPress function (ex: get_post_meta).
* @param string $fallback The fallback value to show if the returned value is empty.
* @return string|array|object The returned value or an empty string if not found.
*/
public static function get_meta( $id, $key, $single_only = true, $callable = null, $fallback = '' ) {
if ( ! is_string( $callable ) || ! function_exists( $callable ) || ! is_string( $key ) ) {
return '';
}
$key_parts = array_map( 'trim', explode( '.', $key ) );
$parent_name = $key_parts[0];
if ( empty( $key ) || in_array( $parent_name, self::DISALLOWED_KEYS, true ) ) {
return '';
}
/**
* Allow a filter to set this meta value using some
* custom setter function (such as get_field in ACF). If this value returns
* something we can skip calling get_post_meta for it and return the value instead.
*
* @since 2.0.0
*
* @param string|null $pre_value The pre-filtered value, or null if unset.
* @param int $id The entity ID used to fetch the meta value.
* @param string $key The meta key to fetch.
* @param string $callable Function name to call. Should be a native WordPress function (ex: get_post_meta).
* @param bool $single_only If true, only return value if it's a string-like value.
*/
$pre_value = apply_filters(
'generateblocks_get_meta_pre_value',
null,
$id,
$key,
$callable,
$single_only
);
if ( is_numeric( $id ) ) {
$meta = $pre_value ? $pre_value : call_user_func( $callable, $id, $parent_name, true );
} else {
$meta = $pre_value ? $pre_value : call_user_func( $callable, $parent_name );
}
if ( defined( 'REST_REQUEST' ) && REST_REQUEST && ! current_user_can( 'manage_options' ) ) {
if ( 'get_user_meta' === $callable && is_numeric( $id ) ) {
if ( self::should_restrict_user_meta_access( $id, $key ) ) {
return '';
}
}
if ( 'get_post_meta' === $callable ) {
if ( is_numeric( $id ) && ! current_user_can( 'read_post', (int) $id ) ) {
return '';
}
if ( is_protected_meta( $key, 'post' ) ) {
return '';
}
}
if ( 'get_term_meta' === $callable ) {
if ( is_protected_meta( $key, 'term' ) ) {
return '';
}
}
}
// Some user meta is stored as user data.
// If we're looking for user meta and can't find it, let's check for user data as well.
if ( ! $meta && 'get_user_meta' === $callable ) {
$meta = get_the_author_meta( $parent_name, $id );
}
$meta = apply_filters(
'generateblocks_get_meta_object',
$meta,
$id,
$key,
$callable
);
// Only send the sub key(s) through. If they're empty this will return the value of $meta.
array_shift( $key_parts );
$sub_key = implode( '.', $key_parts );
$value = self::get_value( $sub_key, $meta, $single_only, $fallback );
/**
* Filter the result of get_value for entity meta.
*
* @since 2.0.0
* @param string|int $id The ID of the entity to fetch meta from.
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @param string $callable Function name to call. Should be a native WordPress function (ex: get_post_meta).
*/
return apply_filters( 'generateblocks_get_meta_value', $value, $id, $key, $single_only, $callable );
}
/**
* Get the post meta.
*
* @param string|int $id The id of the post to fetch meta from.
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @return string|array|object The returned value or an empty string if not found.
*/
public static function get_post_meta( $id, $key, $single_only = true ) {
return self::get_meta( $id, $key, $single_only, 'get_post_meta' );
}
/**
* Rest handler for get_post_meta
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error The response object.
*/
public function get_post_meta_rest( $request ) {
$id = (int) $request->get_param( 'id' );
$single_only = true;
// Verify user can read this post.
if ( ! current_user_can( 'read_post', $id ) ) {
return new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to read this post.', 'generateblocks' ),
array( 'status' => 403 )
);
}
$post = get_post( $id );
if ( $post ) {
$requires_password = ! empty( $post->post_password ) && post_password_required( $post );
$can_bypass_pw = current_user_can( 'edit_post', $post->ID ) || get_current_user_id() === (int) $post->post_author;
if ( $requires_password && ! $can_bypass_pw ) {
return new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to read meta for this password-protected post.', 'generateblocks' ),
array( 'status' => 403 )
);
}
}
$key = $request->get_param( 'key' );
// Block protected meta keys (WordPress convention for private/internal meta).
if ( is_string( $key ) && is_protected_meta( $key, 'post' ) ) {
return new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to access protected meta fields.', 'generateblocks' ),
array( 'status' => 403 )
);
}
if ( 'false' === $request->get_param( 'singleOnly' ) ) {
$single_only = false;
}
return rest_ensure_response( self::get_post_meta( $id, $key, $single_only ) );
}
/**
* Get the user meta.
*
* @param string|int $id The id of the user to fetch meta from.
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @return string|array|object The returned value or an empty string if not found.
*/
public static function get_user_meta( $id, $key, $single_only = true ) {
return self::get_meta( $id, $key, $single_only, 'get_user_meta' );
}
/**
* Determine if user meta access should be restricted.
*
* This checks whether security restrictions should apply based on:
* - User capabilities (list_users bypasses all restrictions)
* - Whether user is viewing their own data (allowed)
* - Whether the field is in the safe list
*
* @since 2.2.0
*
* @param int $user_id The user ID being accessed.
* @param string $key The meta key being accessed.
* @return bool True if access should be restricted, false if allowed.
*/
public static function should_restrict_user_meta_access( $user_id, $key ) {
$current_user_id = get_current_user_id();
$is_privileged = current_user_can( 'list_users' );
$is_viewing_own = (int) $user_id === $current_user_id && $current_user_id > 0;
// Privileged users or viewing own data - no restrictions.
if ( $is_privileged || $is_viewing_own ) {
return false;
}
// Check if field is in safe list.
$parent_key = '';
if ( is_string( $key ) ) {
$parent_key = trim( explode( '.', $key )[0] );
}
$safe_keys = [];
if (
class_exists( 'GenerateBlocks_Dynamic_Tag_Security' ) &&
method_exists( 'GenerateBlocks_Dynamic_Tag_Security', 'get_safe_user_meta_keys' )
) {
$safe_keys = GenerateBlocks_Dynamic_Tag_Security::get_safe_user_meta_keys();
}
// If key is in safe list, allow access.
if ( $parent_key && in_array( $parent_key, $safe_keys, true ) ) {
return false;
}
/**
* Filter whether to restrict user_meta access.
*
* WARNING: Returning false disables security restrictions and may
* expose sensitive user data to all visitors. Only disable if you
* have implemented your own access controls.
*
* @since 2.1.4
*
* @param bool $should_restrict Whether to apply restrictions.
* @param int $user_id The user ID being accessed.
* @param int $current_user_id Current user ID (0 if logged out).
*/
return apply_filters(
'generateblocks_restrict_user_meta_access',
true,
$user_id,
$current_user_id
);
}
/**
* Rest handler for get_user_meta
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error The response object.
*/
public function get_user_meta_rest( $request ) {
$requested_id = (int) $request->get_param( 'id' );
$current_id = get_current_user_id();
$id = $requested_id ? $requested_id : $current_id;
if ( ! $id ) {
return rest_ensure_response(
new WP_Error(
'invalid_user_id',
__( 'A valid user ID is required.', 'generateblocks' ),
array( 'status' => 400 )
)
);
}
// Require list_users capability to access other users' meta.
if ( $id !== $current_id && ! current_user_can( 'list_users' ) ) {
return rest_ensure_response(
new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to access this user\'s meta.', 'generateblocks' ),
array( 'status' => rest_authorization_required_code() )
)
);
}
$key = $request->get_param( 'key' );
$single_only = true;
// Block protected meta keys (WordPress convention for private/internal meta).
if ( is_string( $key ) && is_protected_meta( $key, 'user' ) ) {
return rest_ensure_response(
new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to access protected meta fields.', 'generateblocks' ),
array( 'status' => rest_authorization_required_code() )
)
);
}
// Check if access should be restricted (preview/draft context and safe list).
if ( self::should_restrict_user_meta_access( $id, $key ) ) {
return rest_ensure_response(
new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to view this user meta field.', 'generateblocks' ),
array( 'status' => rest_authorization_required_code() )
)
);
}
if ( 'false' === $request->get_param( 'singleOnly' ) ) {
$single_only = false;
}
return rest_ensure_response( self::get_user_meta( $id, $key, $single_only ) );
}
/**
* Get the term meta.
*
* @param string|int $id The id of the term to fetch meta from.
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @return string|array|object The returned value or an empty string if not found.
*/
public static function get_term_meta( $id, $key, $single_only = true ) {
return self::get_meta( $id, $key, $single_only, 'get_term_meta' );
}
/**
* Rest handler for get_term_meta
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error The response object.
*/
public function get_term_meta_rest( $request ) {
$id = (int) $request->get_param( 'id' );
$key = $request->get_param( 'key' );
$single_only = true;
if ( ! $id ) {
return rest_ensure_response(
new WP_Error(
'invalid_term_id',
__( 'A valid term ID is required.', 'generateblocks' ),
[ 'status' => 400 ]
)
);
}
$term = get_term( $id );
if ( ! $term || is_wp_error( $term ) ) {
return rest_ensure_response(
new WP_Error(
'term_not_found',
__( 'Term not found.', 'generateblocks' ),
[ 'status' => 404 ]
)
);
}
$can_manage_term = current_user_can( 'edit_term', $term->term_id );
$can_edit_posts = current_user_can( 'edit_posts' );
if ( ! $can_manage_term && ! $can_edit_posts ) {
return rest_ensure_response(
new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to read this term.', 'generateblocks' ),
[ 'status' => 403 ]
)
);
}
if ( is_string( $key ) && is_protected_meta( $key, 'term' ) ) {
return rest_ensure_response(
new WP_Error(
'rest_forbidden',
__( 'Sorry, you are not allowed to access protected term meta fields.', 'generateblocks' ),
[ 'status' => 403 ]
)
);
}
if ( 'false' === $request->get_param( 'singleOnly' ) ) {
$single_only = false;
}
return rest_ensure_response( self::get_term_meta( $id, $key, $single_only ) );
}
/**
* Get an option's value.
*
* @param string $key The meta key to fetch. May include one or more sub keys separated by a period.
* @param bool $single_only If true, only return value if it's a string-like value.
* @return string|array|object The returned value or an empty string if not found.
*/
public static function get_option( $key, $single_only = true ) {
return self::get_meta( 'option', $key, $single_only, 'get_option' );
}
/**
* Rest handler for get_option
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error The response object.
*/
public function get_option_rest( $request ) {
$key = $request->get_param( 'key' );
$single_only = true;
if ( 'false' === $request->get_param( 'singleOnly' ) ) {
$single_only = false;
}
return rest_ensure_response( self::get_option( $key, $single_only ) );
}
}
GenerateBlocks_Meta_Handler::get_instance()->init();
@@ -0,0 +1,77 @@
<?php
/**
* Handles option changes on plugin updates.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Process option updates if necessary.
*/
class GenerateBlocks_Plugin_Update extends GenerateBlocks_Singleton {
/**
* Constructor
*/
public function init() {
add_action( 'admin_init', [ $this, 'updates' ], 5 );
}
/**
* Implement plugin update logic.
*
* @since 1.1.0
*/
public function updates() {
if ( is_customize_preview() ) {
return;
}
$saved_version = get_option( 'generateblocks_version', false );
if ( false === $saved_version ) {
update_option( 'generateblocks_version', sanitize_text_field( GENERATEBLOCKS_VERSION ) );
// Not an existing install, so no need to proceed further.
return;
}
if ( version_compare( $saved_version, GENERATEBLOCKS_VERSION, '=' ) ) {
return;
}
if ( version_compare( $saved_version, '2.0.0-alpha.1', '<' ) ) {
self::v_2_0_0();
}
// Force regenerate our static CSS files.
update_option( 'generateblocks_dynamic_css_posts', array() );
// Last thing to do is update our version.
update_option( 'generateblocks_version', sanitize_text_field( GENERATEBLOCKS_VERSION ) );
}
/**
* Update options for version 2.0.0.
*/
public static function v_2_0_0() {
$settings = get_option( 'generateblocks', [] );
// `disable_google_fonts` use to be false by default.
// If the user has come from 1.x and they haven't set this option,
// set it back to false.
if ( empty( $settings['disable_google_fonts'] ) ) {
$settings['disable_google_fonts'] = false;
}
update_option( 'generateblocks', $settings );
// Turn on v1 blocks by default for users coming from 1.x.
update_option( 'gb_use_v1_blocks', true, false );
}
}
GenerateBlocks_Plugin_Update::get_instance()->init();
@@ -0,0 +1,300 @@
<?php
/**
* This file handles the Query Loop functions.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Query Loop functions.
*
* @since 1.5.0
*/
class GenerateBlocks_Query_Loop {
/**
* Instance.
*
* @access private
* @var object Instance
* @since 1.2.0
*/
private static $instance;
/**
* Initiator.
*
* @since 1.2.0
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
public function __construct() {
add_filter( 'generateblocks_attr_grid-wrapper', array( $this, 'add_grid_wrapper_attributes' ), 10, 2 );
add_filter( 'generateblocks_attr_container', array( $this, 'add_container_attributes' ), 10, 2 );
add_filter( 'generateblocks_attr_grid-item', array( $this, 'add_grid_item_attributes' ), 10, 2 );
add_filter( 'generateblocks_attr_button-container', array( $this, 'add_button_wrapper_attributes' ), 10, 2 );
add_filter( 'generateblocks_defaults', array( $this, 'add_block_defaults' ) );
add_filter( 'generateblocks_query_loop_args', array( $this, 'set_query_loop_defaults' ) );
}
/**
* Helper function that constructs a WP_Query args array from
* a `Query` block properties.
*
* @param WP_Block $block Block instance.
* @param int $page Current query's page.
*
* @todo: https://github.com/WordPress/wordpress-develop/blob/44e308c12e68b5c6b63845fd84369ba36985e193/src/wp-includes/blocks.php#L1126
*/
public static function get_query_args( $block, $page ) {
$query_attributes = ( is_array( $block->context ) && isset( $block->context['generateblocks/query'] ) )
? $block->context['generateblocks/query']
: array();
// Set up our pagination.
$query_attributes['paged'] = $page;
$query_args = self::map_post_type_attributes( $query_attributes );
if ( isset( $query_args['tax_query'] ) ) {
$query_args['tax_query'] = self::normalize_tax_query_attributes( $query_args['tax_query'] );
}
if ( isset( $query_args['date_query_after'] ) || isset( $query_args['date_query_before'] ) ) {
$query_args['date_query'] = self::normalize_date_query_attributes(
isset( $query_args['date_query_after'] ) ? $query_args['date_query_after'] : null,
isset( $query_args['date_query_before'] ) ? $query_args['date_query_before'] : null
);
unset( $query_args['date_query_after'] );
unset( $query_args['date_query_before'] );
}
if ( isset( $query_args['stickyPosts'] ) && 'ignore' === $query_args['stickyPosts'] ) {
$query_args['ignore_sticky_posts'] = true;
unset( $query_args['stickyPosts'] );
}
if ( isset( $query_args['stickyPosts'] ) && 'exclude' === $query_args['stickyPosts'] ) {
$sticky_posts = get_option( 'sticky_posts' );
$post_not_in = isset( $query_args['post__not_in'] ) && is_array( $query_args['post__not_in'] ) ? $query_args['post__not_in'] : array();
$query_args['post__not_in'] = array_merge( $sticky_posts, $post_not_in );
unset( $query_args['stickyPosts'] );
}
if ( isset( $query_args['stickyPosts'] ) && 'only' === $query_args['stickyPosts'] ) {
$sticky_posts = get_option( 'sticky_posts' );
$query_args['ignore_sticky_posts'] = true;
$query_args['post__in'] = $sticky_posts;
unset( $query_args['stickyPosts'] );
}
if ( isset( $query_args['tax_query_exclude'] ) ) {
$not_in_tax_query = self::normalize_tax_query_attributes( $query_args['tax_query_exclude'], 'NOT IN' );
$query_args['tax_query'] = isset( $query_args['tax_query'] )
? array_merge( $query_args['tax_query'], $not_in_tax_query )
: $not_in_tax_query;
unset( $query_args['tax_query_exclude'] );
}
if (
isset( $query_args['posts_per_page'] ) &&
is_numeric( $query_args['posts_per_page'] )
) {
$per_page = intval( $query_args['posts_per_page'] );
$offset = 0;
if (
isset( $query_args['offset'] ) &&
is_numeric( $query_args['offset'] )
) {
$offset = absint( $query_args['offset'] );
}
$query_args['offset'] = ( $per_page * ( $page - 1 ) ) + $offset;
$query_args['posts_per_page'] = $per_page;
}
if (
isset( $query_args['post_status'] ) &&
'publish' !== $query_args['post_status'] &&
! current_user_can( 'read_private_posts' )
) {
// If the user can't read private posts, we'll force the post status to be public.
$query_args['post_status'] = 'publish';
}
return $query_args;
}
/**
* Map query parameters to their correct query names.
*
* @param array $attributes Block attributes.
*/
public static function map_post_type_attributes( $attributes ) {
$attributes_map = array(
'page' => 'paged',
'per_page' => 'posts_per_page',
'search' => 's',
'after' => 'date_query_after',
'before' => 'date_query_before',
'author' => 'author__in',
'exclude' => 'post__not_in',
'include' => 'post__in',
'order' => 'order',
'orderby' => 'orderby',
'status' => 'post_status',
'parent' => 'post_parent__in',
'parent_exclude' => 'post_parent__not_in',
'author_exclude' => 'author__not_in',
);
return generateblocks_map_array_keys( $attributes, $attributes_map );
}
/**
* Normalize the tax query attributes to be used in the WP_Query
*
* @param array $raw_tax_query Tax query.
* @param string $operator Tax operator.
*
* @return array|array[]
*/
public static function normalize_tax_query_attributes( $raw_tax_query, $operator = 'IN' ) {
return array_map(
function( $tax ) use ( $operator ) {
return array(
'taxonomy' => $tax['taxonomy'],
'field' => 'term_id',
'terms' => $tax['terms'],
'operator' => $operator,
'include_children' => isset( $tax['includeChildren'] ) ? $tax['includeChildren'] : true,
);
},
$raw_tax_query
);
}
/**
* Normalize the date query attributes to be used in the WP_Query
*
* @param string|null $after The after date.
* @param string|null $before The before date.
*
* @return array
*/
public static function normalize_date_query_attributes( $after = null, $before = null ) {
$result = array( 'inclusive' => true );
if ( generateblocks_is_valid_date( $after ) ) {
$result['after'] = $after;
}
if ( generateblocks_is_valid_date( $before ) ) {
$result['before'] = $before;
}
return $result;
}
/**
* Add defaults for our Query settings.
*
* @param array $defaults Block defaults.
*/
public function add_block_defaults( $defaults ) {
$defaults['container']['isQueryLoopItem'] = false;
$defaults['container']['isPagination'] = false;
$defaults['gridContainer']['isQueryLoop'] = false;
$defaults['buttonContainer']['isPagination'] = false;
return $defaults;
}
/**
* Add HTML attributes to the Query Loop wrapper.
*
* @param array $attributes Existing HTML attributes.
* @param array $settings Block settings.
*/
public function add_grid_wrapper_attributes( $attributes, $settings ) {
if ( $settings['isQueryLoop'] ) {
$attributes['class'] .= ' gb-query-loop-wrapper';
}
return $attributes;
}
/**
* Add HTML attributes to the Query Loop Containers.
*
* @param array $attributes Existing HTML attributes.
* @param array $settings Block settings.
*/
public function add_container_attributes( $attributes, $settings ) {
if ( $settings['isPagination'] ) {
$attributes['class'] .= ' gb-query-loop-pagination';
}
return $attributes;
}
/**
* Add HTML attributes to the Query Loop Item wrapper.
*
* @param array $attributes Existing HTML attributes.
* @param array $settings Block settings.
*/
public function add_grid_item_attributes( $attributes, $settings ) {
if ( $settings['isQueryLoopItem'] ) {
$attributes['class'] .= ' ' . implode( ' ', get_post_class( 'gb-query-loop-item' ) );
}
return $attributes;
}
/**
* Add HTML attributes to the Button wrapper.
*
* @param array $attributes Existing HTML attributes.
* @param array $settings Block settings.
*/
public function add_button_wrapper_attributes( $attributes, $settings ) {
if ( $settings['isPagination'] ) {
$attributes['class'] .= ' gb-query-loop-pagination';
}
return $attributes;
}
/**
* Set the default query loop arguments.
*
* @param array $query_args The query loop arguments.
* @return array The query loop arguments with defaults.
*/
public function set_query_loop_defaults( $query_args ) {
if ( ! isset( $query_args['posts_per_page'] ) || '' === $query_args['posts_per_page'] ) {
$query_args['posts_per_page'] = 10;
}
return $query_args;
}
}
GenerateBlocks_Query_Loop::get_instance();
@@ -0,0 +1,372 @@
<?php
/**
* The Libraries class file.
*
* @package GenerateBlocks\Pattern_Library
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for handling with libraries.
*
* @since 1.9.0
*/
class GenerateBlocks_Query_Utils extends GenerateBlocks_Singleton {
/**
* Initialize the class.
*
* @return void
*/
public function init() {
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
}
/**
* Register REST routes.
*
* @return void
*/
public function register_rest_routes() {
register_rest_route(
'generateblocks/v1',
'/get-wp-query',
[
'methods' => 'POST',
'callback' => [ $this, 'get_wp_query' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
register_rest_route(
'generateblocks/v1',
'/get-user-query',
[
'methods' => 'POST',
'callback' => [ $this, 'get_user_query' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
}
/**
* Gets posts and returns an array of WP_Post objects.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response The response.
*/
public function get_user_query( WP_REST_Request $request ) {
$args = $request->get_param( 'args' ) ?? [];
if ( ! is_array( $args ) ) {
$args = [];
}
$args['number'] = min( max( 1, absint( $args['number'] ?? 150 ) ), 150 );
$args['paged'] = max( 1, (int) ( $args['paged'] ?? 1 ) );
$args['fields'] = 'all';
$can_list_users = current_user_can( 'list_users' );
$can_manage_options = current_user_can( 'manage_options' );
// Sanitize dangerous query args for users without list_users capability.
if ( ! $can_list_users ) {
unset( $args['meta_query'] );
unset( $args['meta_key'] );
unset( $args['meta_value'] );
unset( $args['meta_compare'] );
unset( $args['role'] );
unset( $args['role__in'] );
unset( $args['role__not_in'] );
unset( $args['capability'] );
unset( $args['capability__in'] );
unset( $args['capability__not_in'] );
$args['has_published_posts'] = true;
}
$number = $args['number'];
$query = new WP_User_Query( $args );
$max_pages = (int) ceil( $query->get_total() / $number );
// Filter sensitive data from user objects while maintaining structure.
$users = array_map(
function( $user ) use ( $can_manage_options, $can_list_users ) {
// Always remove critical security fields.
unset( $user->data->user_pass );
unset( $user->data->user_activation_key );
if ( ! $can_manage_options ) {
// Remove sensitive values for non-admin users.
unset( $user->data->user_login );
unset( $user->data->user_email );
// Remove capability data to prevent role enumeration.
unset( $user->caps );
unset( $user->allcaps );
}
if ( ! $can_list_users ) {
unset( $user->roles );
unset( $user->cap_key );
}
return $user;
},
$query->get_results()
);
return rest_ensure_response(
[
'users' => $users,
'total' => $query->get_total(),
'max_pages' => $max_pages,
]
);
}
/**
* Gets posts and returns an array of WP_Post objects.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response The response.
*/
public function get_wp_query( $request ) {
$args = $request->get_param( 'args' );
$args = is_array( $args ) ? $args : [];
$page = $args['paged'] ?? $request->get_param( 'page' ) ?? 1;
$page = is_scalar( $page ) ? (int) $page : 1;
$attributes = $request->get_param( 'attributes' ) ?? [];
$current_post = $request->get_param( 'postId' ) ?? null;
$current_author = $request->get_param( 'authorId' ) ?? null;
$context = $request->get_param( 'context' ) ?? [];
$query_type = $request->get_param( 'queryType' ) ?? GenerateBlocks_Block_Query::TYPE_WP_QUERY;
$current = [
'post_id' => $current_post,
'author_id' => $current_author,
];
/**
* Filter the args for get-wp-query calls before they're passed to get_wp_query_args.
*
* @param array $args The WP_Query args to parse.
* @param array $props Additional filter properties.
*
* @return array $args The optimized WP_Query args array.
*/
$args = apply_filters(
'generateblocks_rest_get_wp_query_args',
$args,
[
'page' => $page,
'attributes' => $attributes,
'context' => $context,
'current' => $current,
'query_type' => $query_type,
]
);
$query = new WP_Query(
self::get_wp_query_args(
$args,
$page,
$attributes,
null,
$current
)
);
// Filter sensitive data in-place so consumers still receive the full WP_Query object.
$query->posts = array_map(
function( $post ) {
$requires_password = ! empty( $post->post_password ) && post_password_required( $post );
$can_read_post = current_user_can( 'read_post', $post->ID );
$can_bypass_pw = current_user_can( 'edit_post', $post->ID ) || get_current_user_id() === (int) $post->post_author;
unset( $post->post_password );
unset( $post->guid );
unset( $post->post_content_filtered );
if ( ! $can_read_post || ( $requires_password && ! $can_bypass_pw ) ) {
unset( $post->post_content );
unset( $post->post_excerpt );
}
return $post;
},
$query->posts
);
return rest_ensure_response( $query );
}
/**
* Helper function that optimizes the query attribute from the Query block.
*
* @param array $args The WP_Query args to parse.
* @param int $page Current query's page.
* @param array $attributes The query block's attributes. Used for reference in the filters.
* @param WP_Block|array $block The current block.
* @param array $current Array of current entities (post, author, etc.).
*
* @return array $query_args The optimized WP_Query args array.
*/
public static function get_wp_query_args( $args = [], $page = 1, $attributes = [], $block = null, $current = [] ) {
$current_post_id = $current['post_id'] ?? get_the_ID();
$current_author_id = $current['author_id'] ?? get_the_author_meta( 'ID' );
// Set up our pagination.
if ( ! isset( $args['paged'] ) && -1 < (int) $page ) {
$args['paged'] = $page;
}
if ( isset( $args['tax_query'] ) && is_array( $args['tax_query'] ) ) {
if ( count( $args['tax_query'] ) > 1 ) {
$args['tax_query']['relation'] = 'AND';
}
}
// Sticky posts handling.
if ( isset( $args['stickyPosts'] ) ) {
if ( 'ignore' === $args['stickyPosts'] ) {
$args['ignore_sticky_posts'] = true;
}
if ( 'exclude' === $args['stickyPosts'] ) {
$sticky_posts = get_option( 'sticky_posts' );
$post_not_in = isset( $args['post__not_in'] ) && is_array( $args['post__not_in'] ) ? $args['post__not_in'] : array();
$args['post__not_in'] = array_merge( $sticky_posts, $post_not_in );
}
if ( 'only' === $args['stickyPosts'] ) {
$sticky_posts = get_option( 'sticky_posts' );
$args['ignore_sticky_posts'] = true;
$args['post__in'] = $sticky_posts;
}
unset( $args['stickyPosts'] );
}
// Ensure offset works correctly with pagination.
$posts_per_page = (int) ( $args['posts_per_page'] ?? get_option( 'posts_per_page', -1 ) );
if (
isset( $args['posts_per_page'] ) &&
is_numeric( $args['posts_per_page'] ) &&
$posts_per_page > -1
) {
$offset = 0;
if (
isset( $args['offset'] ) &&
is_numeric( $args['offset'] )
) {
$offset = absint( $args['offset'] );
}
$args['offset'] = ( $posts_per_page * ( $page - 1 ) ) + $offset;
$args['posts_per_page'] = $posts_per_page;
}
$post_status = $args['post_status'] ?? 'publish';
if (
'publish' !== $post_status &&
! current_user_can( 'read_private_posts' )
) {
// If the user can't read private posts, we'll force the post status to be public.
$args['post_status'] = 'publish';
}
$date_query = $args['date_query'] ?? false;
if ( is_array( $date_query ) ) {
$args['date_query'] = array_map(
function( $query ) {
$query = array_filter(
$query,
function( $value ) {
return ! empty( $value );
}
);
return $query;
},
$args['date_query'] ?? []
);
}
/**
* Legacy filter for v1 query args compatibility.
*
* @deprecated 2.0.0.
*
* @param array $query_args The query arguments.
* @param array $attributes An array of block attributes.
* @param WP_Block|object $block The block instance.
*
* @return array The modified query arguments.
*/
$args = apply_filters(
'generateblocks_query_loop_args',
$args,
$attributes,
null === $block ? new stdClass() : $block
);
/**
* Filter the final calculated query args.
*
* @param array @query_args The array of args for the WP_Query.
* @param array $attributes The block attributes.
* @param WP_Block|array $block The current block.
* @param array $current The current entities (post, author, etc.).
*
* @return $args The modified query arguments.
*/
return apply_filters(
'generateblocks_query_wp_query_args',
$args,
$attributes,
null === $block ? new stdClass() : $block,
[
'post_id' => $current_post_id,
'author_id' => $current_author_id,
]
);
}
/**
* Normalize the tax query attributes to be used in the WP_Query
*
* @param array $raw_tax_query Tax query.
* @param string $operator Tax operator.
*
* @return array|array[]
*/
public static function normalize_tax_query_attributes( $raw_tax_query, $operator = 'IN' ) {
return array_map(
function( $tax ) use ( $operator ) {
return array(
'taxonomy' => $tax['taxonomy'],
'field' => 'term_id',
'terms' => $tax['terms'],
'operator' => $operator,
'include_children' => isset( $tax['includeChildren'] ) ? $tax['includeChildren'] : true,
);
},
$raw_tax_query
);
}
}
GenerateBlocks_Query_Utils::get_instance()->init();
@@ -0,0 +1,281 @@
<?php
/**
* This file handles the dynamic parts of our blocks.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Render the dynamic aspects of our blocks.
*
* @since 1.2.0
*/
class GenerateBlocks_Render_Block {
/**
* Instance.
*
* @access private
* @var object Instance
* @since 1.2.0
*/
private static $instance;
/**
* Initiator.
*
* @since 1.2.0
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'register_blocks' ) );
if ( version_compare( $GLOBALS['wp_version'], '5.9', '>' ) ) {
add_filter( 'render_block', array( $this, 'filter_rendered_blocks' ), 10, 3 );
add_filter( 'render_block', array( $this, 'late_filter_rendered_blocks' ), 20, 3 );
}
}
/**
* Register our dynamic blocks.
*
* @since 1.2.0
*/
public function register_blocks() {
$container_args = [
'title' => esc_html__( 'Container', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Container', 'render_block' ],
];
if ( version_compare( $GLOBALS['wp_version'], '6.1.0', '<' ) ) {
$container_args['editor_script'] = 'generateblocks';
$container_args['editor_style'] = 'generateblocks';
} else {
$container_args['editor_script_handles'] = [ 'generateblocks' ];
$container_args['editor_style_handles'] = [ 'generateblocks' ];
}
register_block_type(
'generateblocks/container',
$container_args
);
register_block_type(
'generateblocks/grid',
array(
'title' => esc_html__( 'Grid', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Grid', 'render_block' ],
'uses_context' => array(
'generateblocks/query',
'generateblocks/queryId',
'generateblocks/inheritQuery',
),
)
);
register_block_type(
'generateblocks/query-loop',
array(
'title' => esc_html__( 'Query loop', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Grid', 'render_block' ],
'provides_context' => array(
'generateblocks/query' => 'query',
'generateblocks/queryId' => 'uniqueId',
'generateblocks/inheritQuery' => 'inheritQuery',
),
)
);
register_block_type(
'generateblocks/button-container',
array(
'title' => esc_html__( 'Buttons', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Button_Container', 'render_block' ],
)
);
register_block_type(
'generateblocks/headline',
array(
'title' => esc_html__( 'Headline', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Headline', 'render_block' ],
)
);
register_block_type(
'generateblocks/button',
array(
'title' => esc_html__( 'Button', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Button', 'render_block' ],
'uses_context' => array(
'generateblocks/query',
'generateblocks/queryId',
'generateblocks/inheritQuery',
),
)
);
register_block_type(
'generateblocks/image',
array(
'title' => esc_html__( 'Image', 'generateblocks' ),
'render_callback' => [ 'GenerateBlocks_Block_Image', 'render_block' ],
'uses_context' => array(
'generateblocks/query',
'generateblocks/queryId',
'postType',
'postId',
),
)
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/text',
[
'render_callback' => [ 'GenerateBlocks_Block_Text', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/element',
[
'render_callback' => [ 'GenerateBlocks_Block_Element', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/media',
[
'render_callback' => [ 'GenerateBlocks_Block_Media', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/shape',
[
'render_callback' => [ 'GenerateBlocks_Block_Shape', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/query',
[
'render_callback' => [ 'GenerateBlocks_Block_Query', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/looper',
[
'render_callback' => [ 'GenerateBlocks_Block_Looper', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/query-no-results',
[
'render_callback' => [ 'GenerateBlocks_Block_Query_No_Results', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/query-page-numbers',
[
'render_callback' => [ 'GenerateBlocks_Block_Query_Page_Numbers', 'render_block' ],
]
);
register_block_type_from_metadata(
GENERATEBLOCKS_DIR . '/dist/blocks/loop-item',
[
'render_callback' => [ 'GenerateBlocks_Block_Loop_Item', 'render_block' ],
]
);
}
/**
* Filter existing rendered blocks.
*
* @since 1.5.0
* @param string $block_content The block content.
* @param array $block The block data.
* @param WP_Block $instance Block instance.
*/
public function filter_rendered_blocks( $block_content, $block, $instance ) {
$attributes = isset( $block['attrs'] ) ? $block['attrs'] : null;
// Don't output if no dynamic link exists.
if ( isset( $attributes ) && ! empty( $attributes['dynamicLinkType'] ) && ! empty( $attributes['dynamicLinkRemoveIfEmpty'] ) ) {
$dynamic_link = GenerateBlocks_Dynamic_Content::get_dynamic_url( $attributes, $instance );
if ( ! $dynamic_link ) {
return '';
}
}
return $block_content;
}
/**
* Filter existing rendered blocks. Fires later than `filter_rendered_blocks`.
*
* @since 2.0.0
* @param string $block_content The block content.
* @param array $block The block data.
* @param WP_Block $instance Block instance.
*/
public function late_filter_rendered_blocks( $block_content, $block, $instance ) {
$block_name = $block['blockName'] ?? '';
$attributes = $block['attrs'] ?? [];
if ( 'generateblocks/media' === $block_name ) {
$attachment_id = $attributes['mediaId'] ?? 0;
if ( ! $attachment_id ) {
$p = new WP_HTML_Tag_Processor( $block_content );
if ( $p->next_tag(
[
'tag_name' => 'img',
]
) ) {
$attachment_id = (int) $p->get_attribute( 'data-media-id' ) ?? 0;
}
}
if ( $attachment_id > 0 && 'img' === $attributes['tagName'] ) {
$context = current_filter();
if ( ! generateblocks_str_contains( $block_content, ' width=' ) && ! generateblocks_str_contains( $block_content, ' height=' ) ) {
$block_content = wp_img_tag_add_width_and_height_attr( $block_content, $context, $attachment_id );
}
// Add 'srcset' and 'sizes' attributes if applicable.
if ( ! generateblocks_str_contains( $block_content, ' srcset=' ) ) {
$block_content = wp_img_tag_add_srcset_and_sizes_attr( $block_content, $context, $attachment_id );
}
// Add loading optimization attributes if applicable.
$block_content = wp_img_tag_add_loading_optimization_attrs( $block_content, $context );
}
}
return $block_content;
}
}
GenerateBlocks_Render_Block::get_instance();
@@ -0,0 +1,369 @@
<?php
/**
* Rest API functions
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class GenerateBlocks_Rest
*/
class GenerateBlocks_Rest extends WP_REST_Controller {
/**
* Instance.
*
* @access private
* @var object Instance
*/
private static $instance;
/**
* Namespace.
*
* @var string
*/
protected $namespace = 'generateblocks/v';
/**
* Version.
*
* @var string
*/
protected $version = '1';
/**
* Onboarding meta key.
*
* @var string
*/
const ONBOARDING_META_KEY = 'generateblocks_onboarding';
/**
* Initiator.
*
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* GenerateBlocks_Rest constructor.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
/**
* Register rest routes.
*/
public function register_routes() {
$namespace = $this->namespace . $this->version;
// Update Settings.
register_rest_route(
$namespace,
'/settings/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_settings' ),
'permission_callback' => array( $this, 'update_settings_permission' ),
)
);
// Update single setting.
register_rest_route(
$namespace,
'/setting/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_setting' ),
'permission_callback' => array( $this, 'update_settings_permission' ),
)
);
// Regenerate CSS Files.
register_rest_route(
$namespace,
'/regenerate_css_files/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'regenerate_css_files' ),
'permission_callback' => array( $this, 'update_settings_permission' ),
)
);
register_rest_route(
$namespace,
'/onboarding/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'onboarding' ),
'permission_callback' => array( $this, 'onboarding_permission' ),
)
);
register_rest_route(
$namespace,
'/get-attachment-by-url/',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_attachment_by_url' ),
'permission_callback' => array( $this, 'can_edit_posts' ),
)
);
}
/**
* Get edit options permissions.
*
* @return bool
*/
public function update_settings_permission() {
return current_user_can( 'manage_options' );
}
/**
* Sanitize our options.
*
* @since 1.2.0
* @param string $name The setting name.
* @param mixed $value The value to save.
*/
public function sanitize_value( $name, $value ) {
$callbacks = apply_filters(
'generateblocks_option_sanitize_callbacks',
array(
'container_width' => 'absint',
'css_print_method' => 'sanitize_text_field',
'sync_responsive_previews' => 'rest_sanitize_boolean',
'disable_google_fonts' => 'rest_sanitize_boolean',
'gb_use_v1_blocks' => 'rest_sanitize_boolean',
)
);
$callback = $callbacks[ $name ];
if ( ! is_callable( $callback ) ) {
return sanitize_text_field( $value );
}
return $callback( $value );
}
/**
* Update a single setting.
*
* @param WP_REST_Request $request Full data about the request.
*/
public function update_setting( WP_REST_Request $request ) {
$name = $request->get_param( 'name' );
$value = $request->get_param( 'value' );
$allowed_settings = [
'gb_use_v1_blocks',
];
if ( ! in_array( $name, $allowed_settings, true ) ) {
return $this->error( 'rest_forbidden', 'Sorry, you are not allowed to update this setting.' );
}
$value = $this->sanitize_value( $name, $value );
update_option( $name, $value );
return $this->success( __( 'Settings saved.', 'generateblocks' ) );
}
/**
* Update Settings.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function update_settings( WP_REST_Request $request ) {
$current_settings = get_option( 'generateblocks', array() );
$new_settings = $request->get_param( 'settings' );
foreach ( $new_settings as $name => $value ) {
// Skip if the option hasn't changed.
if ( isset( $current_settings[ $name ] ) && $current_settings[ $name ] === $new_settings[ $name ] ) {
unset( $new_settings[ $name ] );
continue;
}
// Only save options that we know about.
if ( ! array_key_exists( $name, generateblocks_get_option_defaults() ) ) {
unset( $new_settings[ $name ] );
continue;
}
// Sanitize our value.
$new_settings[ $name ] = $this->sanitize_value( $name, $value );
}
if ( empty( $new_settings ) ) {
return $this->success( __( 'No changes found.', 'generateblocks' ) );
}
if ( is_array( $new_settings ) ) {
update_option( 'generateblocks', array_merge( $current_settings, $new_settings ) );
}
return $this->success( __( 'Settings saved.', 'generateblocks' ) );
}
/**
* Regenerate CSS Files.
*
* @param WP_REST_Request $request request object.
*
* @return mixed
*/
public function regenerate_css_files( WP_REST_Request $request ) {
update_option( 'generateblocks_dynamic_css_posts', array() );
return $this->success( __( 'CSS files regenerated.', 'generateblocks' ) );
}
/**
* Mark an onboard as "viewed" by the user.
*
* @param WP_REST_Request $request request object.
*
* @return WP_REST_Response The response.
*/
public function onboarding( WP_REST_Request $request ) {
$user_id = get_current_user_id();
$onboard = get_user_meta( $user_id, self::ONBOARDING_META_KEY, true );
$key = $request->get_param( 'key' );
if ( ! $onboard ) {
$onboard = array();
}
$onboard[ $key ] = true;
update_user_meta( get_current_user_id(), self::ONBOARDING_META_KEY, $onboard );
return new WP_REST_Response( array( 'success' => true ), 200 );
}
/**
* Get the attachment object by its URL.
*
* @param WP_REST_Request $request request object.
*
* @return WP_REST_Response The response.
*/
public function get_attachment_by_url( WP_REST_Request $request ) {
$url = $request->get_param( 'url' );
if ( ! $url ) {
return $this->error( 'no_url', __( 'No URL provided.', 'generateblocks' ) );
}
// Regular expression to remove '-300x300' like size patterns from the URL.
$pattern = '/-\d+x\d+(?=\.\w{3,4}$)/';
$url = preg_replace( $pattern, '', $url );
$id = attachment_url_to_postid( $url );
if ( ! $id ) {
return $this->error( 'no_attachment', __( 'No attachment found.', 'generateblocks' ) );
}
// Get image metadata, such as width, height, and sizes.
$image_metadata = wp_get_attachment_metadata( $id );
// Get the full URL of the image.
$full_url = wp_get_attachment_url( $id );
// Get URLs for all available sizes.
$sizes = [];
$image_sizes = get_intermediate_image_sizes();
foreach ( $image_sizes as $size ) {
$image_src = wp_get_attachment_image_src( $id, $size );
if ( $image_src && $image_src[0] !== $full_url ) {
$sizes[ $size ] = [
'url' => $image_src[0],
'width' => $image_src[1],
'height' => $image_src[2],
];
}
}
$response = [
'id' => $id,
'full_url' => $full_url,
'width' => isset( $image_metadata['width'] ) ? $image_metadata['width'] : '',
'height' => isset( $image_metadata['height'] ) ? $image_metadata['height'] : '',
'sizes' => $sizes,
];
return $this->success( $response );
}
/**
* Get onboarding edit permission.
*
* @return bool
*/
public function onboarding_permission() {
return current_user_can( 'edit_posts' );
}
/**
* Check if the user can edit posts.
*/
public function can_edit_posts() {
return current_user_can( 'edit_posts' );
}
/**
* Success rest.
*
* @param mixed $response response data.
* @return mixed
*/
public function success( $response ) {
return new WP_REST_Response(
array(
'success' => true,
'response' => $response,
),
200
);
}
/**
* Error rest.
*
* @param mixed $code error code.
* @param mixed $response response data.
* @return mixed
*/
public function error( $code, $response ) {
return new WP_REST_Response(
array(
'error' => true,
'success' => false,
'error_code' => $code,
'response' => $response,
),
401
);
}
}
GenerateBlocks_Rest::get_instance();
@@ -0,0 +1,152 @@
<?php
/**
* Our settings page.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Build our settings page.
*/
class GenerateBlocks_Settings {
/**
* Instance.
*
* @access private
* @var object Instance
*/
private static $instance;
/**
* Initiator.
*
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_menu' ) );
add_action( 'generateblocks_settings_area', array( $this, 'add_settings_container' ) );
add_action( 'generateblocks_settings_area', array( $this, 'add_blocks_version_settings_container' ), 100 );
}
/**
* Add our Dashboard menu item.
*/
public function add_menu() {
$settings = add_submenu_page(
'generateblocks',
__( 'Settings', 'generateblocks' ),
__( 'Settings', 'generateblocks' ),
'manage_options',
'generateblocks-settings',
array( $this, 'settings_page' ),
1
);
remove_submenu_page( 'generateblocks', 'generateblocks' );
add_action( "admin_print_scripts-$settings", array( $this, 'enqueue_scripts' ) );
}
/**
* Enqueue our scripts.
*/
public function enqueue_scripts() {
$generateblocks_deps = array( 'wp-api', 'wp-i18n', 'wp-components', 'wp-element', 'wp-api-fetch' );
$assets_file = GENERATEBLOCKS_DIR . 'dist/settings.asset.php';
$compiled_assets = file_exists( $assets_file )
? require $assets_file
: false;
$assets =
isset( $compiled_assets['dependencies'] ) &&
isset( $compiled_assets['version'] )
? $compiled_assets
: [
'dependencies' => $generateblocks_deps,
'version' => filemtime( GENERATEBLOCKS_DIR . 'dist/settings.js' ),
];
wp_enqueue_script(
'generateblocks-settings',
GENERATEBLOCKS_DIR_URL . 'dist/settings.js',
$assets['dependencies'],
$assets['version'],
true
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'generateblocks-settings', 'generateblocks' );
}
wp_localize_script(
'generateblocks-settings',
'generateBlocksSettings',
array(
'settings' => wp_parse_args(
get_option( 'generateblocks', array() ),
generateblocks_get_option_defaults()
),
'gpContainerWidth' => function_exists( 'generate_get_option' ) ? generate_get_option( 'container_width' ) : false,
'gpContainerWidthLink' => function_exists( 'generate_get_option' ) ?
add_query_arg(
rawurlencode( 'autofocus[control]' ),
rawurlencode( 'generate_settings[container_width]' ),
wp_customize_url()
) :
false,
'useV1Blocks' => generateblocks_use_v1_blocks(),
)
);
}
/**
* Add settings container.
*
* @since 1.2.0
*/
public function add_settings_container() {
echo '<div id="gblocks-block-default-settings"></div>';
}
/**
* Add blocks version settings container.
*
* @since 2.0.0
*/
public function add_blocks_version_settings_container() {
echo '<div id="gblocks-blocks-version-settings"></div>';
}
/**
* Output our Dashboard HTML.
*
* @since 0.1
*/
public function settings_page() {
?>
<div class="wrap gblocks-dashboard-wrap">
<div class="generateblocks-settings-area">
<?php do_action( 'generateblocks_settings_area' ); ?>
</div>
</div>
<?php
}
}
GenerateBlocks_Settings::get_instance();
@@ -0,0 +1,264 @@
<?php
/**
* Our admin Dashboard.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
add_action( 'admin_menu', 'generateblocks_register_dashboard', 9 );
/**
* Register our Dashboard page.
*
* @since 0.1
*/
function generateblocks_register_dashboard() {
$dashboard = add_menu_page(
__( 'GenerateBlocks', 'generateblocks' ),
__( 'GenerateBlocks', 'generateblocks' ),
'manage_options',
'generateblocks',
'generateblocks_do_dashboard'
);
$show_upgrade_menu = apply_filters(
'generateblocks_show_upgrade_menu',
! defined( 'GENERATEBLOCKS_PRO_VERSION' )
);
if ( $show_upgrade_menu ) {
add_submenu_page(
'generateblocks',
__( 'Upgrade', 'generateblocks' ),
__( 'Upgrade', 'generateblocks' ),
'manage_options',
'generateblocks-upgrade',
'generateblocks_do_dashboard'
);
}
add_action( "admin_print_styles-$dashboard", 'generateblocks_enqueue_dashboard_scripts' );
}
/**
* Add our Dashboard-specific scripts.
*
* @since 1.0.0
*/
function generateblocks_enqueue_dashboard_scripts() {
$assets_file = GENERATEBLOCKS_DIR . 'dist/dashboard.asset.php';
$compiled_assets = file_exists( $assets_file )
? require $assets_file
: false;
$assets =
isset( $compiled_assets['dependencies'] ) &&
isset( $compiled_assets['version'] )
? $compiled_assets
: [
'dependencies' => [],
'version' => filemtime( GENERATEBLOCKS_DIR . 'dist/dashboard.js' ),
];
wp_enqueue_script(
'generateblocks-dashboard',
GENERATEBLOCKS_DIR_URL . 'dist/dashboard.js',
$assets['dependencies'],
$assets['version'],
true
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'generateblocks-dashboard', 'generateblocks' );
}
wp_localize_script(
'generateblocks-dashboard',
'generateblocksDashboard',
array(
'gpImage' => esc_url( GENERATEBLOCKS_DIR_URL ) . 'assets/images/generatepress-sites.png',
'gpLogo' => esc_url( GENERATEBLOCKS_DIR_URL ) . 'assets/images/generatepress.svg',
'gbVersion' => GENERATEBLOCKS_VERSION,
'gbpVersion' => defined( 'GENERATEBLOCKS_PRO_VERSION' )
? GENERATEBLOCKS_PRO_VERSION
: false,
)
);
wp_enqueue_style(
'generateblocks-dashboard',
GENERATEBLOCKS_DIR_URL . 'dist/dashboard.css',
array( 'wp-components' ),
GENERATEBLOCKS_VERSION
);
}
/**
* Get a list of our Dashboard pages.
*
* @since 1.2.0
*/
function generateblocks_get_dashboard_pages() {
return apply_filters(
'generateblocks_dashboard_screens',
array(
'generateblocks_page_generateblocks-settings',
)
);
}
add_filter( 'admin_body_class', 'generateblocks_set_admin_body_classes' );
/**
* Add admin body classes when needed.
*
* @since 1.2.0
* @param string $classes The existing classes.
*/
function generateblocks_set_admin_body_classes( $classes ) {
$dashboard_pages = generateblocks_get_dashboard_pages();
$current_screen = get_current_screen();
if ( in_array( $current_screen->id, $dashboard_pages ) ) {
$classes .= ' generateblocks-dashboard-page';
}
return $classes;
}
add_action( 'in_admin_header', 'generateblocks_do_dashboard_headers' );
/**
* Add our Dashboard headers.
*
* @since 1.3.0
*/
function generateblocks_do_dashboard_headers() {
$dashboard_pages = generateblocks_get_dashboard_pages();
$current_screen = get_current_screen();
if ( in_array( $current_screen->id, $dashboard_pages ) ) {
generateblocks_do_dashboard_header();
}
}
add_action( 'admin_enqueue_scripts', 'generateblocks_enqueue_global_dashboard_scripts' );
/**
* Add our scripts to the page.
*
* @since 0.1
*/
function generateblocks_enqueue_global_dashboard_scripts() {
wp_enqueue_style(
'generateblocks-dashboard-global',
GENERATEBLOCKS_DIR_URL . 'assets/css/dashboard-global.css',
array(),
GENERATEBLOCKS_VERSION
);
$dashboard_pages = generateblocks_get_dashboard_pages();
$current_screen = get_current_screen();
if ( in_array( $current_screen->id, $dashboard_pages ) ) {
wp_enqueue_style(
'generateblocks-settings-build',
GENERATEBLOCKS_DIR_URL . 'dist/settings.css',
array( 'wp-components' ),
GENERATEBLOCKS_VERSION
);
}
}
/**
* Build our Dashboard menu.
*/
function generateblocks_dashboard_navigation() {
$screen = get_current_screen();
$tabs = apply_filters(
'generateblocks_dashboard_tabs',
array(
'settings' => array(
'name' => __( 'Settings', 'generateblocks' ),
'url' => admin_url( 'admin.php?page=generateblocks-settings' ),
'class' => 'generateblocks_page_generateblocks-settings' === $screen->id ? 'active' : '',
),
)
);
if ( ! defined( 'GENERATEBLOCKS_PRO_VERSION' ) ) {
$tabs['pro'] = array(
'name' => __( 'Get Pro', 'generateblocks' ),
'url' => 'https://generatepress.com/blocks/',
'class' => '',
);
}
// Don't print any markup if we only have one tab.
if ( count( $tabs ) === 1 ) {
return;
}
?>
<div class="gblocks-navigation">
<?php
foreach ( $tabs as $tab ) {
printf(
'<a href="%1$s" class="%2$s">%3$s</a>',
esc_url( $tab['url'] ),
esc_attr( $tab['class'] ),
esc_html( $tab['name'] )
);
}
?>
</div>
<?php
}
/**
* Build our Dashboard header.
*
* @since 1.2.0
*/
function generateblocks_do_dashboard_header() {
?>
<div class="gblocks-dashboard-header">
<div class="gblocks-dashboard-header-title">
<h1>
<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 50 60.12" xml:space="preserve"><path class="st0" d="M6.686 31.622V18.918a.077.077 0 01.05-.072l6.5-2.313 6.5-2.313 9.682-3.445L39.1 7.33a.067.067 0 00.036-.028.074.074 0 00.014-.044V.076a.077.077 0 00-.032-.062.076.076 0 00-.069-.009l-13 4.625-13 4.625-6.5 2.313-6.5 2.313a.067.067 0 00-.036.028.097.097 0 00-.013.046V52.067c0 .026.013.048.032.062s.044.018.069.009l3.267-1.163 3.267-1.163c.015-.005.028-.015.036-.028s.014-.028.014-.044V37.999l.001-6.377c-.001 0 0 0 0 0z"/><path class="st0" d="M23.949 29.976l13-4.625 13-4.625c.015-.005.028-.015.036-.028s.015-.028.015-.044V8.056a.077.077 0 00-.032-.062.076.076 0 00-.069-.009l-13 4.625-13 4.625-6.5 2.313-6.5 2.313a.067.067 0 00-.036.028.074.074 0 00-.014.044V60.045c0 .026.013.048.032.062a.076.076 0 00.069.009l6.475-2.304 6.475-2.304 6.525-2.322 6.525-2.322 6.5-2.313 6.5-2.313c.015-.005.028-.015.036-.028s.014-.025.014-.041V27.193a.077.077 0 00-.032-.062.076.076 0 00-.069-.009l-6.45 2.295L37 31.711a.067.067 0 00-.036.028.074.074 0 00-.014.044v6.272a.077.077 0 01-.05.072l-6.45 2.295L24 42.715a.075.075 0 01-.101-.071V30.046c0-.016.005-.031.014-.044a.08.08 0 01.036-.026z"/></svg>
<?php echo esc_html( get_admin_page_title() ); ?>
</h1>
</div>
<?php generateblocks_dashboard_navigation(); ?>
</div>
<?php
}
/**
* Output our Dashboard HTML.
*
* @since 0.1
*/
function generateblocks_do_dashboard() {
?>
<div class="wrap gblocks-dashboard-wrap">
<div id="gblocks-dashboard" />
</div>
<?php
}
add_action( 'admin_init', 'generateblocks_do_upgrade_redirect' );
/**
* Redirect to the sales page when landing on the upgrade page.
*/
function generateblocks_do_upgrade_redirect() {
if ( empty( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
if ( 'generateblocks-upgrade' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
wp_redirect( 'https://generatepress.com/blocks' ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
exit;
}
}
@@ -0,0 +1,199 @@
<?php
/**
* Set our block attribute defaults.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Set our block defaults.
*
* @since 0.1
*
* @return array
*/
function generateblocks_get_block_defaults() {
$use_cache = apply_filters( 'generateblocks_use_block_defaults_cache', false );
if ( $use_cache ) {
$cached_data = wp_cache_get(
'generateblocks_defaults_cache',
'generateblocks_cache_group'
);
if ( $cached_data ) {
return $cached_data;
}
}
$defaults = apply_filters(
'generateblocks_defaults',
[
'container' => generateblocks_with_global_defaults( GenerateBlocks_Block_Container::defaults() ),
'buttonContainer' => generateblocks_with_global_defaults( GenerateBlocks_Block_Button_Container::defaults() ),
'button' => generateblocks_with_global_defaults( GenerateBlocks_Block_Button::defaults() ),
'gridContainer' => generateblocks_with_global_defaults( GenerateBlocks_Block_Grid::defaults() ),
'headline' => generateblocks_with_global_defaults( GenerateBlocks_Block_Headline::defaults() ),
'image' => generateblocks_with_global_defaults( GenerateBlocks_Block_Image::defaults() ),
]
);
if ( $use_cache ) {
wp_cache_set(
'generateblocks_defaults_cache',
$defaults,
'generateblocks_cache_group'
);
}
return $defaults;
}
/**
* Get defaults for our general options.
*
* @since 0.1
*/
function generateblocks_get_option_defaults() {
return apply_filters(
'generateblocks_option_defaults',
array(
'container_width' => 1100,
'css_print_method' => 'file',
'sync_responsive_previews' => true,
'disable_google_fonts' => true,
)
);
}
/**
* Styles to use in the editor/font-end when needed.
*
* @since 1.0
*/
function generateblocks_get_default_styles() {
$defaults = generateblocks_get_block_defaults();
$defaultBlockStyles = array(
'button' => array(
'backgroundColor' => $defaults['button']['backgroundColor'] ? $defaults['button']['backgroundColor'] : '#0366d6',
'textColor' => $defaults['button']['textColor'] ? $defaults['button']['textColor'] : '#ffffff',
'backgroundColorHover' => $defaults['button']['backgroundColorHover'] ? $defaults['button']['backgroundColorHover'] : '#222222',
'textColorHover' => $defaults['button']['textColorHover'] ? $defaults['button']['textColorHover'] : '#ffffff',
'spacing' => [
'paddingTop' => $defaults['button']['paddingTop'] ? $defaults['button']['paddingTop'] : '15px',
'paddingRight' => $defaults['button']['paddingRight'] ? $defaults['button']['paddingRight'] : '20px',
'paddingBottom' => $defaults['button']['paddingBottom'] ? $defaults['button']['paddingBottom'] : '15px',
'paddingLeft' => $defaults['button']['paddingLeft'] ? $defaults['button']['paddingLeft'] : '20px',
],
'display' => 'inline-flex',
),
'container' => array(
'gridItemPaddingTop' => '',
'gridItemPaddingRight' => '',
'gridItemPaddingBottom' => '',
'gridItemPaddingLeft' => '',
'bgImageSize' => 'full',
'shapeDividers' => array(
'shape' => 'gb-waves-1',
'location' => 'bottom',
'height' => 200,
'heightTablet' => '',
'heightMobile' => '',
'width' => 100,
'widthTablet' => '',
'widthMobile' => '',
'flipHorizontally' => false,
'zindex' => '',
'color' => '#000000',
'colorOpacity' => 1,
),
),
);
if ( function_exists( 'generate_get_default_fonts' ) ) {
$font_settings = wp_parse_args(
get_option( 'generate_settings', array() ),
generate_get_default_fonts()
);
$defaultBlockStyles['headline'] = array(
'p' => array(
'marginBottom' => $font_settings['paragraph_margin'],
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'em',
'fontSize' => $font_settings['body_font_size'],
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
),
'h1' => array(
'marginBottom' => $font_settings['heading_1_margin_bottom'],
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => $font_settings['heading_1_font_size'],
'fontSizeTablet' => '',
'fontSizeMobile' => $font_settings['mobile_heading_1_font_size'],
'fontSizeUnit' => 'px',
),
'h2' => array(
'marginBottom' => $font_settings['heading_2_margin_bottom'],
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => $font_settings['heading_2_font_size'],
'fontSizeTablet' => '',
'fontSizeMobile' => $font_settings['mobile_heading_1_font_size'],
'fontSizeUnit' => 'px',
),
'h3' => array(
'marginBottom' => $font_settings['heading_3_margin_bottom'],
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => $font_settings['heading_3_font_size'],
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
),
'h4' => array(
'marginBottom' => '20',
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => '',
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
),
'h5' => array(
'marginBottom' => '20',
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => '',
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
),
'h6' => array(
'marginBottom' => '20',
'marginBottomTablet' => '',
'marginBottomMobile' => '',
'marginUnit' => 'px',
'fontSize' => '',
'fontSizeTablet' => '',
'fontSizeMobile' => '',
'fontSizeUnit' => 'px',
),
);
}
return apply_filters( 'generateblocks_default_block_styles', $defaultBlockStyles );
}
@@ -0,0 +1,69 @@
<?php
/**
* Deprecated functions.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Redirect to the Dashboard page on single plugin activation.
*
* @since 0.1
* @deprecated 2.0.0
*/
function generateblocks_dashboard_redirect() {
$do_redirect = apply_filters( 'generateblocks_do_activation_redirect', get_option( 'generateblocks_do_activation_redirect', false ) );
if ( $do_redirect ) {
delete_option( 'generateblocks_do_activation_redirect' );
wp_safe_redirect( esc_url( admin_url( 'admin.php?page=generateblocks' ) ) );
exit;
}
}
/**
* Adds a redirect option during plugin activation on non-multisite installs.
*
* @since 0.1
* @deprecated 2.0.0
*
* @param bool $network_wide Whether or not the plugin is being network activated.
*/
function generateblocks_do_activate( $network_wide = false ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only used to do a redirect. False positive.
if ( ! $network_wide && ! isset( $_GET['activate-multi'] ) ) {
update_option( 'generateblocks_do_activation_redirect', true );
}
}
/**
* Output permissions for use in admin objects.
*
* @deprecated 2.1.0
* @return void
*/
function generateblocks_admin_head_scripts() {
$permissions = apply_filters(
'generateblocks_permissions',
[
'isAdminUser' => current_user_can( 'manage_options' ),
'canEditPosts' => current_user_can( 'edit_posts' ),
'isGbProActive' => is_plugin_active( 'generateblocks-pro/plugin.php' ),
'isGpPremiumActive' => is_plugin_active( 'gp-premium/gp-premium.php' ),
]
);
$permission_object = wp_json_encode( $permissions );
echo sprintf(
'<script>
const gbPermissions = %s;
Object.freeze( gbPermissions );
</script>',
$permission_object // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
@@ -0,0 +1,833 @@
<?php
/**
* The Dynamic Tag Callbacks class file.
*
* @package GenerateBlocks\Dynamic_Tags
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for dynamic tag callbacks.
*
* @since 2.0.0
*/
class GenerateBlocks_Dynamic_Tag_Callbacks extends GenerateBlocks_Singleton {
const DATE_FORMAT_KEY = 'dateFormat';
/**
* Wrap a link around the output.
*
* @param string $output The output.
* @param array $options The options.
* @param object $instance The block instance.
*/
private static function with_link( $output, $options, $instance ) {
if ( empty( $options['link'] ) ) {
return $output;
}
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
$link = $options['link'];
$parts = explode( ',', $link );
$link_to = $parts[0] ?? 'post';
$key = $parts[1] ?? null;
$url = '';
switch ( $link_to ) {
case 'post':
$url = get_permalink( $id );
break;
case 'post_meta':
$url = $key ? GenerateBlocks_Meta_Handler::get_post_meta( $id, $key, true ) : '';
break;
case 'comments':
$url = get_comments_link( $id );
break;
case 'author_meta':
$user_id = get_post_field( 'post_author', $id );
$url = $key ? GenerateBlocks_Meta_Handler::get_user_meta( $user_id, $key, true ) : '';
break;
case 'author_archive':
$user_id = get_post_field( 'post_author', $id );
$url = get_author_posts_url( $user_id );
break;
case 'author_email':
if ( defined( 'REST_REQUEST' ) && REST_REQUEST && ! current_user_can( 'list_users' ) ) {
break;
}
$user_id = get_post_field( 'post_author', $id );
$url = 'mailto:' . get_the_author_meta( 'user_email', $user_id );
break;
}
if ( $url ) {
$output = sprintf(
'<a href="%s">%s</a>',
esc_url( $url ),
$output
);
}
return $output;
}
/**
* Truncate the output by character length.
*
* @param string $output The tag output.
* @param array $options The options.
*/
private static function with_trunc( $output, $options ) {
if ( empty( $options['trunc'] ) ) {
return $output;
}
$trunc_parts = explode( ',', $options['trunc'] );
$trunc_words = ! empty( $trunc_parts[1] ) && strpos( $trunc_parts[1], 'words' ) === 0;
if ( $trunc_words ) {
$output = wp_trim_words( $output, (int) $trunc_parts[0], '' );
return $output;
}
return substr( $output, 0, (int) $options['trunc'] );
}
/**
* Remove leading and trailing whitespace from the output.
*
* @param string $output The tag output.
* @param array $options The options.
*/
private static function with_trim( $output, $options ) {
if ( empty( $options['trim'] ) ) {
return $output;
}
switch ( $options['trim'] ) {
case 'left':
return ltrim( $output );
case 'right':
return rtrim( $output );
default:
return trim( $output ); }
return $output;
}
/**
* Transform the case of the output.
*
* @param string $output The tag output.
* @param array $options The options.
*/
private static function with_case( $output, $options ) {
if ( empty( $options['case'] ) ) {
return $output;
}
switch ( $options['case'] ) {
case 'lower':
return strtolower( $output );
case 'upper':
return strtoupper( $output );
case 'title':
return ucwords( $output );
default:
return $output;
}
}
/**
* Transform the case of the output.
*
* @param string $output The tag output.
* @param array $options The options.
*/
private static function with_replace( $output, $options ) {
if ( empty( $options['replace'] ) ) {
return $output;
}
$replace_parts = array_map(
function ( $str ) {
$result = '';
$escaped = false;
$length = strlen( $str );
for ( $i = 0; $i < $length; $i++ ) {
$char = $str[ $i ];
if ( $escaped ) {
$result .= $char;
$escaped = false;
} elseif ( '\\' === $char ) {
$escaped = true;
} elseif ( '"' !== $char && "'" !== $char ) {
$result .= $char;
}
}
return $result;
},
explode( ',', $options['replace'] )
);
if ( count( $replace_parts ) !== 2 ) {
return $output;
}
$search = (string) $replace_parts[0] ?? '';
$replace = (string) $replace_parts[1] ?? '';
return str_replace( $search, $replace, $output );
}
/**
* Add line breaks to content like the classic editor.
*
* @param string $output The tag output.
* @param array $options The options.
*/
private static function with_wpautop( $output, $options ) {
if ( empty( $options['wpautop'] ) ) {
return $output;
}
return wpautop( $output );
}
/**
* Output the dynamic tag.
*
* @param string $output The output.
* @param array $options The options.
* @param object $instance The block instance.
*
* @return string The tag output.
*/
public static function output( $output, $options, $instance = null ) {
// Store original output for filtering.
$raw_output = $output;
$output = self::with_trunc( $output, $options );
$output = self::with_replace( $output, $options );
$output = self::with_trim( $output, $options );
$output = self::with_case( $output, $options );
// Any wrapping output filters should go after direct string transformations.
$output = self::with_wpautop( $output, $options );
$output = self::with_link( $output, $options, $instance );
$output = apply_filters( 'generateblocks_dynamic_tag_output', $output, $options, $raw_output );
return $output;
}
/**
* Get the title.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_the_title( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$output = get_the_title( $id );
return self::output( $output, $options, $instance );
}
/**
* Get the permalink.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_the_permalink( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$output = get_permalink( $id );
return self::output( $output, $options, $instance );
}
/**
* Get the post date.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_post_date( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$type = $options['type'] ?? 'published';
$format = $options[ self::DATE_FORMAT_KEY ] ?? '';
$output = get_the_date( $format, $id );
if ( 'modified' === $type ) {
$output = get_the_modified_date( $format, $id );
}
if ( 'modifiedOnly' === $type ) {
$modified_date = get_the_modified_date( $format, $id );
if ( $modified_date === $output ) {
$output = '';
}
}
return self::output( $output, $options, $instance );
}
/**
* Get the featured image.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_featured_image( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$image_id = get_post_thumbnail_id( $id );
if ( ! $image_id ) {
return self::output( '', $options, $instance );
}
$size = $options['size'] ?? 'full';
$image = wp_get_attachment_image_src( $image_id, $size );
if ( ! $image ) {
return self::output( '', $options, $instance );
}
switch ( $options['key'] ?? '' ) {
case 'id':
$output = $image_id;
break;
case 'alt':
$output = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
break;
case 'caption':
$output = wp_get_attachment_caption( $image_id );
break;
case 'description':
$output = get_post_field( 'post_content', $image_id );
break;
case 'url':
default:
$output = $image[0];
break;
}
return self::output( $output, $options, $instance );
}
/**
* Get the post meta.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_post_meta( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$key = $options['key'] ?? '';
if ( ! $key ) {
return self::output( '', $options, $instance );
}
$value = GenerateBlocks_Meta_Handler::get_post_meta( $id, $key, true );
if ( ! $value ) {
return self::output( '', $options, $instance );
}
add_filter( 'wp_kses_allowed_html', [ 'GenerateBlocks_Dynamic_Tags', 'expand_allowed_html' ], 10, 2 );
$output = wp_kses_post( $value );
remove_filter( 'wp_kses_allowed_html', [ 'GenerateBlocks_Dynamic_Tags', 'expand_allowed_html' ], 10, 2 );
return self::output( $output, $options, $instance );
}
/**
* Get the comments count.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return int
*/
public static function get_the_comments_count( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$none = $options['none'] ?? __( 'No comments', 'generateblocks' );
$single = $options['single'] ?? __( '1 comment', 'generateblocks' );
$multi = $options['multiple'] ?? __( '% comments', 'generateblocks' );
$output = '';
if ( ! post_password_required( $id ) && ( comments_open( $id ) || get_comments_number( $id ) ) ) {
if ( '' === $none && get_comments_number( $id ) < 1 ) {
return self::output( $none, $options, $instance );
}
$output = get_comments_number_text(
$none,
$single,
$multi,
$id
);
} else {
$output = $none;
}
return self::output( $output, $options, $instance );
}
/**
* Get the comments URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_the_comments_url( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$output = get_comments_link( $id );
return self::output( $output, $options, $instance );
}
/**
* Get filtered userdata with only the relevant keys.
*
* @param int $user_id The user ID to get data for.
* @return array Filtered data array
*/
private static function get_userdata( $user_id ) {
$userdata = get_userdata( $user_id )->data ?? new stdClass();
return [
'user_nicename' => $userdata->user_nicename ?? '',
'user_email' => $userdata->user_email ?? '',
'display_name' => $userdata->display_name ?? '',
'ID' => $userdata->ID ?? '',
];
}
/**
* Get the author meta.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_author_meta( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$user_id = get_post_field( 'post_author', $id );
$key = $options['key'] ?? '';
$output = '';
if ( ! $user_id || ! $key ) {
return self::output( $output, $options, $instance );
}
$value = GenerateBlocks_Meta_Handler::get_user_meta( $user_id, $key );
add_filter( 'wp_kses_allowed_html', [ 'GenerateBlocks_Dynamic_Tags', 'expand_allowed_html' ], 10, 2 );
$output = wp_kses_post( $value );
remove_filter( 'wp_kses_allowed_html', [ 'GenerateBlocks_Dynamic_Tags', 'expand_allowed_html' ], 10, 2 );
return self::output( $output, $options, $instance );
}
/**
* Get the author archive URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_author_archive_url( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$user_id = get_post_field( 'post_author', $id );
$output = '';
if ( ! $user_id ) {
return self::output( $output, $options, $instance );
}
$output = get_author_posts_url( $user_id );
return self::output( $output, $options, $instance );
}
/**
* Get the author avatar URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_author_avatar_url( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
$size = $options['size'] ?? null;
$default = $options['default'] ?? null;
$user_id = get_post_field( 'post_author', $id );
$force_default = $options['forceDefault'] ?? null;
$rating = $options['rating'] ?? null;
$output = '';
if ( ! $user_id ) {
return self::output( $output, $options, $instance );
}
$args = [];
if ( $size ) {
$args['size'] = $size;
}
if ( $default ) {
$args['default'] = $default;
}
// This is false by default so only add the arg if it's true.
if ( $force_default ) {
$args['force_default'] = $force_default;
}
if ( $rating ) {
$args['rating'] = $rating;
}
$output = get_avatar_url( $user_id, $args );
return self::output( $output, $options, $instance );
}
/**
* Get the site tagline from settings.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_term_list( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
$taxonomy = $options['tax'] ?? '';
$separator = ! empty( $options['sep'] ) ? $options['sep'] : '';
$link = empty( $options['link'] ) ? false : (bool) $options['link'];
$terms = get_the_terms( $id, $taxonomy );
if ( ! $terms ) {
return self::output( '', $options, $instance );
}
$list_items = [];
foreach ( $terms as $term ) {
if ( $link ) {
$link = get_term_link( $term, $taxonomy );
if ( is_wp_error( $link ) ) {
continue;
}
$list_items[] = '<a href="' . esc_url( $link ) . '" rel="tag">' . $term->name . '</a>';
$list_items = apply_filters( "term_links-{$taxonomy}", $list_items ); // phpcs:ignore
} else {
$list_items[] = "<span>{$term->name}</span>";
}
}
$output = implode( $separator, $list_items );
return self::output( $output, $options, $instance );
}
/**
* Get the post's excerpt, optionally with a custom read more link.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_post_excerpt( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
static $seen_ids = [];
if ( isset( $seen_ids[ $id ] ) ) {
return self::output( '', $options, $instance );
}
$seen_ids[ $id ] = true;
$read_more = $options['readMore'] ?? '';
$pre_read_more = $options['pre'] ?? '';
$use_theme_read_more = isset( $options['useTheme'] ) ? true : false;
$filter_excerpt_length = function( $length ) use ( $options ) {
return $options['length'] ?? $length;
};
add_filter(
'excerpt_length',
$filter_excerpt_length,
100
);
if ( ! $use_theme_read_more ) {
$filter_more_text = function() use ( $read_more, $pre_read_more ) {
if ( ! $read_more ) {
return $pre_read_more;
}
return apply_filters(
'generateblocks_dynamic_excerpt_more_link',
sprintf(
'%1$s<a class="gb-dynamic-read-more" href="%2$s" aria-label="%3$s">%4$s</a>',
wp_kses_post( $pre_read_more ),
esc_url( get_permalink( get_the_ID() ) ),
sprintf(
/* translators: Aria-label describing the read more button */
_x( 'More on %s', 'more on post title', 'generateblocks' ),
the_title_attribute( 'echo=0' )
),
wp_kses_post( $read_more )
)
);
};
add_filter(
'excerpt_more',
$filter_more_text,
100
);
}
$output = get_the_excerpt( $id );
if ( isset( $filter_excerpt_length ) ) {
remove_filter(
'excerpt_length',
$filter_excerpt_length,
100
);
}
if ( isset( $filter_more_text ) ) {
remove_filter(
'excerpt_more',
$filter_more_text,
100
);
}
unset( $seen_ids[ $id ] );
return self::output( $output, $options, $instance );
}
/**
* Get the previous post page URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_previous_posts_page_url( $options, $block, $instance ) {
$query_id = $instance->context['generateblocks/queryData']['id'] ?? '';
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$inherit_query = $instance->context['generateblocks/queryData']['inherit'] ?? false;
$has_query = isset( $instance->context['generateblocks/queryData'] );
$output = '';
if ( $inherit_query || ! $has_query ) {
global $paged;
if ( $paged > 1 ) {
$output = previous_posts( false );
}
} elseif ( 1 !== $page ) {
$output = esc_url( add_query_arg( $page_key, $page - 1 ) );
}
return self::output( $output, $options, $instance );
}
/**
* Get the next post page URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_next_posts_page_url( $options, $block, $instance ) {
$query_id = $instance->context['generateblocks/queryData']['id'] ?? '';
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
$args = $instance->context['generateblocks/queryData']['args'] ?? [];
$inherit_query = $instance->context['generateblocks/queryData']['inherit'] ?? false;
$has_query = isset( $instance->context['generateblocks/queryData'] );
$per_page = $args['posts_per_page'] ?? apply_filters( 'generateblocks_query_per_page_default', 10, $args );
$output = '';
if ( $inherit_query || ! $has_query ) {
global $wp_query, $paged;
if ( ! $paged ) {
$paged = 1; // phpcs:ignore -- Need to overrite global here.
}
$next_page = (int) $paged + 1;
if ( $next_page <= $wp_query->max_num_pages ) {
$output = next_posts( $wp_query->max_num_pages, false );
}
} else {
$query_data = $instance->context['generateblocks/queryData']['data'] ?? null;
$query_type = $instance->context['generateblocks/queryData']['type'] ?? null;
$is_wp_query = GenerateBlocks_Block_Query::TYPE_WP_QUERY === $query_type;
if ( ! $query_data || ( ! $is_wp_query && ! is_array( $query_data ) ) ) {
return self::output( $output, $options, $instance );
}
$next_page = $page + 1;
$custom_query_max_pages = $is_wp_query
? (int) $query_data->max_num_pages
: ceil( count( $query_data ) / $per_page );
if ( $custom_query_max_pages < $next_page ) {
return self::output( $output, $options, $instance );
}
if ( $custom_query_max_pages && $custom_query_max_pages !== $page ) {
$output = esc_url( add_query_arg( $page_key, $page + 1 ) );
}
wp_reset_postdata(); // Restore original Post Data.
}
return self::output( $output, $options, $instance );
}
/**
* Get the media.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_media( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'post', $instance );
if ( ! $id ) {
return self::output( '', $options, $instance );
}
switch ( $options['key'] ?? '' ) {
case 'title':
$output = get_the_title( $id );
break;
case 'id':
$output = $id;
break;
case 'alt':
$output = get_post_meta( $id, '_wp_attachment_image_alt', true );
break;
case 'caption':
$output = wp_get_attachment_caption( $id );
break;
case 'description':
$output = get_post_field( 'post_content', $id );
break;
case 'url':
default:
$output = wp_get_attachment_url( $id );
break;
}
return self::output( $output, $options, $instance );
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
<?php
/**
* The Register Dynamic Tag class file.
*
* @package GenerateBlocks\Dynamic_Tags
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for registering dynamic tags.
*
* @since 1.9.0
*/
class GenerateBlocks_Register_Dynamic_Tag {
/**
* The tags.
*
* @var array
*/
private static $tags = [];
/**
* Constructor.
*
* @param array $args The arguments.
*/
public function __construct( $args ) {
if ( ! isset( $args['tag'] ) || ! isset( $args['return'] ) || ! isset( $args['title'] ) ) {
return;
}
if ( ! isset( $args['type'] ) ) {
$args['type'] = 'post';
}
self::$tags[ $args['tag'] ] = $args;
}
/**
* Parse options.
*
* @param string $options_string The options string.
* @param string $tag_name The tag name.
* @return array
*/
public static function parse_options( $options_string, $tag_name ) {
$pairs = $options_string ? preg_split( '/(?<!\\\\)\|/', $options_string, -1, PREG_SPLIT_NO_EMPTY ) : [];
$result = [
'tag_name' => $tag_name, // Make it so the tag name is available to us in $options.
];
if ( empty( $pairs ) ) {
return $result;
}
foreach ( $pairs as $pair ) {
$pair = str_replace( [ '\\:', '\\|' ], [ ':', '|' ], $pair );
if ( generateblocks_str_contains( $pair, ':' ) ) {
list( $key, $value ) = explode( ':', $pair, 2 );
} else {
$key = $pair;
$value = true; // Default value if no colon is present.
}
$result[ $key ] = $value;
}
return $result;
}
/**
* Get the tags.
*
* @return array
*/
public static function get_tags() {
return self::$tags;
}
/**
* Find matches.
*
* @param string $content The content.
* @param array $availableTags The available tags.
* @return array
*/
public static function find_matches( $content, $availableTags ) {
$pattern = '/\{{(' . implode( '|', array_keys( $availableTags ) ) . ')(\s+[^}]+)?}}/';
preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER );
return $matches;
}
/**
* Replace tags.
*
* @param string $content The content.
* @param array $block The block.
* @param Object $instance The block instance.
* @return string
*/
public static function replace_tags( $content, $block, $instance ) {
$block_html = ! empty( $block['innerHTML'] )
? $block['innerHTML']
: $content;
if ( ! generateblocks_str_contains( $block_html, '{{' ) ) {
return $content;
}
$matches = self::find_matches( $block_html, self::$tags );
foreach ( $matches as $match ) {
$tag_name = $match[1] ?? '';
if ( ! isset( self::$tags[ $tag_name ] ) ) {
continue;
}
$data = self::$tags[ $tag_name ];
$full_tag = $match[0];
$full_tag = self::maybe_prepend_protocol( $block_html, $full_tag );
$options_string = isset( $match[2] ) ? ltrim( $match[2], ' ' ) : '';
$options = self::parse_options( $options_string, $tag_name );
$replacement = $data['return']( $options, $block, $instance );
$og_replacement = $replacement;
$supports = $data['supports'];
$required = ! isset( $options['required'] ) || 'false' !== $options['required'];
/**
* Allow developers to filter the replacement.
*
* @since 2.0.0
*
* @param string $replacement The replacement.
* @param string $full_tag The full tag.
* @param mixed $content The replacement.
* @param array $block The block.
* @param Object $instance The block instance.
* @param array $options The options.
* @param array $supports The supports.
*/
$replacement = apply_filters(
'generateblocks_dynamic_tag_replacement',
$replacement,
[
'tag' => $tag_name,
'full_tag' => $full_tag,
'content' => $content,
'block' => $block,
'instance' => $instance,
'options' => $options,
'supports' => $supports,
]
);
// If this tag is required for the block to render and there is no replacement, bail.
if ( $required && ! $replacement ) {
return '';
}
/**
* Allow developers to filter the content before dynamic tag replacement.
*
* @since 2.0.0
*
* @param string $content The content.
* @param string $full_tag The full tag.
* @param string $tag The tag.
* @param mixed $replacement The replacement.
* @param mixed $og_replacement The original replacement.
* @param array $block The block.
* @param Object $instance The block instance.
* @param array $options The options.
* @param array $supports The supports.
*/
$content = apply_filters(
'generateblocks_before_dynamic_tag_replace',
$content,
[
'full_tag' => $full_tag,
'tag' => $tag_name,
'replacement' => $replacement,
'og_replacement' => $og_replacement,
'block' => $block,
'instance' => $instance,
'options' => $options,
'supports' => $supports,
]
);
$content = str_replace( $full_tag, (string) $replacement, $content );
}
return $content;
}
/**
* Maybe prepend the protocol to our dynamic tag.
* Some core blocks automatically prepend the protocol to URLs, so we need to account for that.
* This function checks if the protocol is already prepended and if so, prepends it to the tag so the entire thing is replaced.
*
* @param string $content The content.
* @param string $tag The tag.
* @return string
*/
public static function maybe_prepend_protocol( $content, $tag ) {
if ( generateblocks_str_contains( $content, 'http://' . $tag ) ) {
$tag = 'http://' . $tag;
}
if ( generateblocks_str_contains( $content, 'https://' . $tag ) ) {
$tag = 'https://' . $tag;
}
return $tag;
}
/**
* Get the details of a specific registered tag.
*
* @param string $tag The dynamic tag used for lookup.
* @return array|null The tag details or null if not found.
*/
public static function get_tag_details( $tag ) {
return self::$tags[ $tag ] ?? null;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,734 @@
<?php
/**
* General actions and filters.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
add_action( 'enqueue_block_editor_assets', 'generateblocks_do_block_editor_assets' );
/**
* Enqueue Gutenberg block assets for backend editor.
*
* @uses {wp-blocks} for block type registration & related functions.
* @uses {wp-element} for WP Element abstraction — structure of blocks.
* @uses {wp-i18n} to internationalize the block's text.
* @uses {wp-editor} for WP editor styles.
* @since 0.1
*/
function generateblocks_do_block_editor_assets() {
wp_localize_script(
'generateblocks-media-editor-script',
'generateblocksBlockMedia',
[
'standardPlaceholder' => GENERATEBLOCKS_DIR_URL . 'assets/images/placeholder1280x720.png',
'squarePlaceholder' => GENERATEBLOCKS_DIR_URL . 'assets/images/placeholder800x.png',
]
);
wp_localize_script(
'generateblocks-text-editor-script',
'generateblocksBlockText',
[
'defaultButtonAttributes' => apply_filters(
'generateblocks_default_button_attributes',
[
'styles' => [
'display' => 'inline-flex',
'alignItems' => 'center',
'backgroundColor' => '#215bc2',
'color' => '#ffffff',
'paddingTop' => '1rem',
'paddingRight' => '2rem',
'paddingBottom' => '1rem',
'paddingLeft' => '2rem',
'textDecoration' => 'none',
'&:is(:hover, :focus)' => [
'backgroundColor' => '#1a4a9b',
'color' => '#ffffff',
],
],
]
),
]
);
global $pagenow;
$generateblocks_deps = array( 'wp-blocks', 'wp-i18n', 'wp-editor', 'wp-element', 'wp-compose', 'wp-data' );
if ( 'widgets.php' === $pagenow ) {
unset( $generateblocks_deps[2] );
}
$assets_file = GENERATEBLOCKS_DIR . 'dist/blocks.asset.php';
$compiled_assets = file_exists( $assets_file )
? require $assets_file
: false;
$assets =
isset( $compiled_assets['dependencies'] ) &&
isset( $compiled_assets['version'] )
? $compiled_assets
: [
'dependencies' => $generateblocks_deps,
'version' => filemtime( GENERATEBLOCKS_DIR . 'dist/blocks.js' ),
];
wp_enqueue_script(
'generateblocks',
GENERATEBLOCKS_DIR_URL . 'dist/blocks.js',
$assets['dependencies'],
$assets['version'],
true
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'generateblocks', 'generateblocks' );
}
wp_enqueue_style(
'generateblocks',
GENERATEBLOCKS_DIR_URL . 'dist/blocks.css',
array( 'wp-edit-blocks', 'generateblocks-packages' ),
filemtime( GENERATEBLOCKS_DIR . 'dist/blocks.css' )
);
$image_sizes = get_intermediate_image_sizes();
$image_sizes = array_diff( $image_sizes, array( '1536x1536', '2048x2048' ) );
$image_sizes[] = 'full';
wp_localize_script(
'generateblocks',
'generateBlocksInfo',
array(
'imageSizes' => $image_sizes,
'svgShapes' => generateblocks_get_svg_shapes(),
'syncResponsivePreviews' => generateblocks_get_option( 'sync_responsive_previews' ),
'excerptLength' => apply_filters( 'excerpt_length', 55 ), // phpcs:ignore -- Core filter.
'excerptMore' => apply_filters( 'excerpt_more', ' ' . '[&hellip;]' ), // phpcs:ignore -- Core filter.
'imagePlaceholders' => array(
'standard' => GENERATEBLOCKS_DIR_URL . 'assets/images/image-placeholder.png',
'square' => GENERATEBLOCKS_DIR_URL . 'assets/images/square-image-placeholder.png',
),
'globalContainerWidth' => generateblocks_get_global_container_width(),
'queryLoopEditorPostsCap' => apply_filters( 'generateblocks_query_loop_editor_posts_cap', 50 ),
'disableGoogleFonts' => generateblocks_get_option( 'disable_google_fonts' ),
'typographyFontFamilyList' => generateblocks_get_font_family_list(),
'useV1Blocks' => generateblocks_use_v1_blocks(),
)
);
if ( function_exists( 'generate_get_color_defaults' ) ) {
$color_settings = wp_parse_args(
get_option( 'generate_settings', array() ),
generate_get_color_defaults()
);
$generatepressDefaultStyling = apply_filters(
'generateblocks_gp_default_styling',
array(
'buttonBackground' => $color_settings['form_button_background_color'],
'buttonBackgroundHover' => $color_settings['form_button_background_color_hover'],
'buttonText' => $color_settings['form_button_text_color'],
'buttonTextHover' => $color_settings['form_button_text_color_hover'],
'buttonPaddingTop' => '10px',
'buttonPaddingRight' => '20px',
'buttonPaddingBottom' => '10px',
'buttonPaddingLeft' => '20px',
)
);
$css = sprintf(
'.gb-button.button {
background-color: %1$s;
color: %2$s;
padding-top: %3$s;
padding-right: %4$s;
padding-bottom: %5$s;
padding-left: %6$s;
}',
$generatepressDefaultStyling['buttonBackground'],
$generatepressDefaultStyling['buttonText'],
$generatepressDefaultStyling['buttonPaddingTop'],
$generatepressDefaultStyling['buttonPaddingRight'],
$generatepressDefaultStyling['buttonPaddingBottom'],
$generatepressDefaultStyling['buttonPaddingLeft']
);
$css .= sprintf(
'.gb-button.button:active, .gb-button.button:hover, .gb-button.button:focus {
background-color: %1$s;
color: %2$s;
}',
$generatepressDefaultStyling['buttonBackgroundHover'],
$generatepressDefaultStyling['buttonTextHover']
);
wp_add_inline_style( 'generateblocks', $css );
}
$defaults = generateblocks_get_block_defaults();
wp_localize_script(
'generateblocks',
'generateBlocksDefaults',
$defaults
);
wp_localize_script(
'generateblocks',
'generateBlocksStyling',
generateblocks_get_default_styles()
);
wp_localize_script(
'generateblocks',
'generateBlocksLegacyDefaults',
array(
'v_1_4_0' => GenerateBlocks_Legacy_Attributes::get_defaults( '1.4.0' ),
)
);
$editor_sidebar_assets = generateblocks_get_enqueue_assets( 'editor-sidebar' );
wp_enqueue_script(
'generateblocks-editor-sidebar',
GENERATEBLOCKS_DIR_URL . 'dist/editor-sidebar.js',
$editor_sidebar_assets['dependencies'],
$editor_sidebar_assets['version'],
true
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'generateblocks-editor-sidebar', 'generateblocks' );
}
wp_enqueue_style(
'generateblocks-editor-sidebar',
GENERATEBLOCKS_DIR_URL . 'dist/editor-sidebar.css',
array( 'wp-components' ),
filemtime( GENERATEBLOCKS_DIR . 'dist/editor-sidebar.css' )
);
$packages_asset_info = generateblocks_get_enqueue_assets( 'packages' );
wp_register_style(
'generateblocks-packages',
GENERATEBLOCKS_DIR_URL . 'dist/packages.css',
'',
$packages_asset_info['version']
);
$edge22_packages = [
'block-styles',
'components',
'styles-builder',
];
foreach ( $edge22_packages as $name ) {
$package_asset_info = generateblocks_get_enqueue_assets( "{$name}-imported" );
$script_asset_info = generateblocks_get_enqueue_assets( $name );
wp_register_script(
"generateblocks-$name",
GENERATEBLOCKS_DIR_URL . 'dist/' . $name . '.js',
$package_asset_info['dependencies'],
$script_asset_info['version'],
true
);
wp_register_style(
"generateblocks-$name",
false,
[ 'generateblocks-packages' ],
$packages_asset_info['version']
);
}
$editor_assets = generateblocks_get_enqueue_assets( 'editor' );
wp_enqueue_script(
'generateblocks-editor',
GENERATEBLOCKS_DIR_URL . 'dist/editor.js',
$editor_assets['dependencies'],
$editor_assets['version'],
true
);
$tags = GenerateBlocks_Register_Dynamic_Tag::get_tags();
$tag_list = [];
foreach ( $tags as $tag => $data ) {
$relevant_data = $data;
unset( $relevant_data['return'] );
if ( $data ) {
$tag_list[] = $relevant_data;
}
}
wp_localize_script(
'generateblocks-editor',
'generateBlocksEditor',
[
'useV1Blocks' => generateblocks_use_v1_blocks(),
/**
* Filters whether WordPress 7.0's core "Additional CSS" textarea is
* shown on GenerateBlocks blocks. We disable it by default because our
* blocks have their own CSS editing; return true to re-enable it.
*
* @since 2.3.0
*
* @param bool $enabled Whether to allow the core Additional CSS field.
*/
'enableCoreAdditionalCss' => (bool) apply_filters( 'generateblocks_enable_core_additional_css', false ),
'dynamicTags' => $tag_list,
'hasGPFontLibrary' => function_exists( 'generatepress_is_module_active' )
? generatepress_is_module_active( 'generate_package_font_library', 'GENERATE_FONT_LIBRARY' )
: false,
'dateFormat' => get_option( 'date_format' ),
'wpContentUrl' => content_url(),
'typographyFontFamilyList' => generateblocks_get_font_family_list(),
'dynamicTagsPreview' => apply_filters( 'generateblocks_dynamic_tags_preview', true ) ? 'enabled' : 'disabled',
]
);
wp_enqueue_style(
'generateblocks-editor',
GENERATEBLOCKS_DIR_URL . 'dist/editor.css',
array( 'wp-edit-blocks', 'generateblocks-packages' ),
filemtime( GENERATEBLOCKS_DIR . 'dist/editor.css' )
);
}
add_filter( 'block_categories_all', 'generateblocks_do_category' );
/**
* Add GeneratePress category to Gutenberg.
*
* @param array $categories Existing categories.
* @since 0.1
*/
function generateblocks_do_category( $categories ) {
array_unshift(
$categories,
[
'slug' => 'generateblocks',
'title' => __( 'GenerateBlocks', 'generateblocks' ),
]
);
return $categories;
}
add_action( 'wp_enqueue_scripts', 'generateblocks_do_google_fonts' );
add_action( 'enqueue_block_editor_assets', 'generateblocks_do_google_fonts' );
/**
* Do Google Fonts.
*
* @since 0.1
*/
function generateblocks_do_google_fonts() {
if ( generateblocks_get_option( 'disable_google_fonts' ) ) {
return;
}
$fonts_url = generateblocks_get_google_fonts_uri();
if ( $fonts_url ) {
wp_enqueue_style( 'generateblocks-google-fonts', $fonts_url, array(), null, 'all' ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
}
}
add_filter( 'generateblocks_css_print_method', 'generateblocks_set_css_print_method' );
/**
* Set our CSS print method.
*
* @param string $method Existing method.
*/
function generateblocks_set_css_print_method( $method ) {
$method = generateblocks_get_option( 'css_print_method' );
if ( is_single() ) {
$method = 'inline';
}
return $method;
}
add_filter( 'excerpt_allowed_blocks', 'generateblocks_set_excerpt_allowed_blocks' );
/**
* Add blocks that can be displayed in post excerpts.
*
* @param array $allowed Existing allowed blocks.
* @since 1.0
*/
function generateblocks_set_excerpt_allowed_blocks( $allowed ) {
$allowed[] = 'generateblocks/headline';
$allowed[] = 'generateblocks/container';
$allowed[] = 'generateblocks/text';
$allowed[] = 'generateblocks/element';
return $allowed;
}
add_filter( 'excerpt_allowed_wrapper_blocks', 'generateblocks_set_excerpt_allowed_wrapper_blocks' );
/**
* Allows excerpts to be generated from the `innerBlocks` of these wrappers.
*
* @param array $allowed Existing allowed wrapper blocks.
* @since 1.5.0
*/
function generateblocks_set_excerpt_allowed_wrapper_blocks( $allowed ) {
$allowed[] = 'generateblocks/container';
$allowed[] = 'generateblocks/element';
return $allowed;
}
add_filter( 'generateblocks_before_container_close', 'generateblocks_do_shape_divider', 10, 2 );
/**
* Add shape divider to Container.
*
* @since 1.2.0
* @param string $output The current block output.
* @param array $attributes The current block attributes.
*/
function generateblocks_do_shape_divider( $output, $attributes ) {
$defaults = generateblocks_get_block_defaults();
$settings = wp_parse_args(
$attributes,
$defaults['container']
);
if ( ! empty( $settings['shapeDividers'] ) ) {
$shapes = generateblocks_get_svg_shapes();
$shape_values = array();
foreach ( $shapes as $group => $data ) {
if ( ! empty( $data['svgs'] ) && is_array( $data['svgs'] ) ) {
foreach ( $data['svgs'] as $key => $shape ) {
$shape_values[ $key ] = $shape['icon'];
}
}
}
$output .= '<div class="gb-shapes">';
foreach ( (array) $settings['shapeDividers'] as $index => $option ) {
if ( ! empty( $option['shape'] ) ) {
if ( isset( $shape_values[ $option['shape'] ] ) ) {
$shapeNumber = $index + 1;
$output .= sprintf(
'<div class="gb-shape gb-shape-' . $shapeNumber . '">%s</div>',
$shape_values[ $option['shape'] ]
);
}
}
}
$output .= '</div>';
}
return $output;
}
add_filter( 'generateblocks_do_content', 'generateblocks_do_widget_styling' );
/**
* Process all widget content for potential styling.
*
* @since 1.3.4
* @param string $content The existing content to process.
*/
function generateblocks_do_widget_styling( $content ) {
$widget_blocks = get_option( 'widget_block' );
foreach ( (array) $widget_blocks as $block ) {
if ( isset( $block['content'] ) ) {
$content .= $block['content'];
}
}
return $content;
}
add_filter( 'generateblocks_attr_container', 'generateblocks_set_inline_background_style', 10, 2 );
/**
* Add our background image attribute to the Container.
*
* @since 1.5.0
* @param array $attributes Existing attributes.
* @param array $settings Block settings.
*/
function generateblocks_set_inline_background_style( $attributes, $settings ) {
if ( generateblocks_has_background_image( $settings ) && $settings['bgImageInline'] ) {
$url = generateblocks_get_background_image_url( $settings );
if ( $url ) {
$attribute_name = 'background-image';
if ( 'element' !== $settings['bgOptions']['selector'] ) {
$attribute_name = '--' . $attribute_name;
}
$attributes['style'] = $attribute_name . ': url(' . esc_url( $url ) . ');';
}
}
return $attributes;
}
add_filter( 'generateblocks_block_css_selector', 'generateblocks_set_block_css_selectors', 10, 3 );
/**
* Change our block selectors if needed.
*
* @param string $selector Existing selector.
* @param string $name The block name.
* @param array $attributes The block attributes.
*/
function generateblocks_set_block_css_selectors( $selector, $name, $attributes ) {
$blockVersion = ! empty( $attributes['blockVersion'] ) ? $attributes['blockVersion'] : 1;
$defaults = generateblocks_get_block_defaults();
if ( 'button' === $name ) {
$settings = wp_parse_args(
$attributes,
$defaults['button']
);
if ( $blockVersion < 3 ) {
// Old versions of the this block used this backwards logic
// to determine whether to remove the "a" to the selector.
$clean_selector = $selector;
$selector = 'a' . $selector;
if ( isset( $attributes['hasUrl'] ) && ! $attributes['hasUrl'] ) {
$selector = $clean_selector;
}
} else {
$is_link = (
! empty( $settings['hasUrl'] ) ||
! empty( $settings['dynamicLinkType'] )
) && 'link' === $settings['buttonType'];
if ( $is_link ) {
$selector = 'a' . $selector;
}
if ( 'button' === $settings['buttonType'] ) {
$selector = 'button' . $selector;
}
}
if ( $settings['hasButtonContainer'] || $blockVersion < 3 ) {
$selector = '.gb-button-wrapper ' . $selector;
} elseif ( isset( $settings['isPagination'] ) && $settings['isPagination'] ) {
$selector = '.gb-query-loop-pagination ' . $selector;
}
}
if ( 'headline' === $name ) {
$settings = wp_parse_args(
$attributes,
$defaults['headline']
);
if ( apply_filters( 'generateblocks_headline_selector_tagname', true, $attributes ) ) {
$selector = $settings['element'] . $selector;
}
}
return $selector;
}
add_action( 'init', 'generateblocks_register_user_meta' );
/**
* Register GenerateBlocks custom user meta fields.
*
* @return void
*/
function generateblocks_register_user_meta() {
register_meta(
'user',
GenerateBlocks_Rest::ONBOARDING_META_KEY,
array(
'type' => 'object',
'single' => true,
'show_in_rest' => array(
'schema' => array(
'type' => 'object',
'properties' => array(
'insert_inner_container' => array( 'type' => 'boolean' ),
),
'additionalProperties' => array(
'type' => 'boolean',
),
),
),
)
);
}
add_filter( 'block_editor_settings_all', 'generateblocks_do_block_css_reset', 15 );
/**
* This resets the `max-width`, `margin-left`, and `margin-right` properties for our blocks in the editor.
* We have to do this as most themes use `.wp-block` to set a `max-width` and auto margins.
*
* We used to do this directly in the block CSS if those block attributes didn't exist, but this allows us
* to overwrite the reset in the `block_editor_settings_all` filter with a later priority.
*
* @param array $editor_settings The existing editor settings.
*/
function generateblocks_do_block_css_reset( $editor_settings ) {
$css = '.gb-container, .gb-headline, .gb-button {max-width:unset;margin-left:0;margin-right:0;}';
$editor_settings['styles'][] = [ 'css' => $css ];
$blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-text:where(:not(h1, h2, h3, h4, h5, h6, p))',
'.editor-styles-wrapper .wp-block-generateblocks-element',
'.editor-styles-wrapper .wp-block-generateblocks-shape',
'.editor-styles-wrapper .wp-block-generateblocks-media',
'.editor-styles-wrapper .wp-block-generateblocks-query',
'.editor-styles-wrapper .wp-block-generateblocks-query-no-results',
'.editor-styles-wrapper .wp-block-generateblocks-query-page-numbers',
'.editor-styles-wrapper .wp-block-generateblocks-looper',
'.editor-styles-wrapper .wp-block-generateblocks-loop-item',
];
$heading_blocks_to_reset = [
'.editor-styles-wrapper .wp-block-generateblocks-text:where(h1, h2, h3, h4, h5, h6, p)',
];
$css = implode( ',', $blocks_to_reset ) . '{max-width:unset;margin:0;}';
$css .= implode( ',', $heading_blocks_to_reset ) . '{max-width:unset;margin-left:0;margin-right:0;}';
$editor_settings['styles'][] = [ 'css' => $css ];
return $editor_settings;
}
add_filter( 'generateblocks_css_output', 'generateblocks_add_general_css' );
/**
* Add general CSS that doesn't apply to our own blocks.
*
* @param string $css Existing CSS.
*/
function generateblocks_add_general_css( $css ) {
$container_width = generateblocks_get_global_container_width();
if ( $container_width ) {
$css .= ':root{--gb-container-width:' . $container_width . ';}';
}
$css .= '.gb-container .wp-block-image img{vertical-align:middle;}';
$css .= '.gb-grid-wrapper .wp-block-image{margin-bottom:0;}';
$css .= '.gb-highlight{background:none;}';
$css .= '.gb-shape{line-height:0;}';
return $css;
}
add_filter( 'block_editor_settings_all', 'generateblocks_do_block_editor_styles', 15 );
/**
* Add our block editor styles.
*
* @param array $editor_settings The existing editor settings.
*/
function generateblocks_do_block_editor_styles( $editor_settings ) {
$container_width = generateblocks_get_global_container_width();
$editor_settings['styles'][] = array(
'css' => ':root{--gb-container-width:' . $container_width . ';}',
);
$editor_settings['styles'][] = array(
'css' => '.gb-shape{line-height:0;}',
);
return $editor_settings;
}
add_action( 'enqueue_block_editor_assets', 'generateblocks_set_editor_permissions', 0 );
/**
* Output permissions for use in the editor.
*
* @return void
*/
function generateblocks_set_editor_permissions() {
$permissions = apply_filters(
'generateblocks_permissions',
[
'isAdminUser' => current_user_can( 'manage_options' ),
'canEditPosts' => current_user_can( 'edit_posts' ),
'isGbProActive' => is_plugin_active( 'generateblocks-pro/plugin.php' ),
'isGpPremiumActive' => is_plugin_active( 'gp-premium/gp-premium.php' ),
]
);
$permission_object = wp_json_encode( $permissions );
wp_register_script( 'generateblocks-editor-permissions', '', [], '1.0', false );
wp_enqueue_script( 'generateblocks-editor-permissions' );
$script = sprintf(
'const gbPermissions = %s;
Object.freeze( gbPermissions );',
$permission_object
);
wp_add_inline_script( 'generateblocks-editor-permissions', $script );
}
add_filter( 'render_block', 'generateblocks_do_html_attributes_escaping', 20, 2 );
/**
* Filter the rendered block content and escape HTML attributes.
*
* @param string $content The block content about to be appended to the post content.
* @param array $block The full block, including name and attributes.
* @return string
*/
function generateblocks_do_html_attributes_escaping( $content, $block ) {
$html_attributes = $block['attrs']['htmlAttributes'] ?? [];
$link_attributes = $block['attrs']['linkHtmlAttributes'] ?? [];
if ( empty( $html_attributes ) && empty( $link_attributes ) ) {
return $content;
}
$v1_block_names = generateblocks_get_v1_block_names();
$block_name = $block['blockName'] ?? '';
// Only do this for our non-v1 blocks.
if (
! generateblocks_str_starts_with( $block_name, 'generateblocks' ) ||
in_array( $block_name, $v1_block_names, true )
) {
return $content;
}
$content = generateblocks_with_escaped_attributes(
$content,
[
'block_html_attrs' => $html_attributes,
'link_html_attrs' => $link_attributes,
]
);
return $content;
}
add_filter( 'generateblocks_allowed_option_keys_rest_api', 'generateblocks_allow_additional_option_keys_rest_api' );
/**
* Allow additional option keys to be accessible via the REST API.
*
* @param array $allowed_keys Existing allowed keys.
*/
function generateblocks_allow_additional_option_keys_rest_api( $allowed_keys ) {
if ( ! is_array( $allowed_keys ) ) {
$allowed_keys = [];
}
$acf_option_keys = generateblocks_get_acf_option_field_keys();
$allowed_keys = array_merge( $allowed_keys, $acf_option_keys );
return $allowed_keys;
}
@@ -0,0 +1,536 @@
<?php
/**
* The Libraries class file.
*
* @package GenerateBlocks\Pattern_Library
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for handling with libraries.
*
* @since 1.9.0
*/
class GenerateBlocks_Libraries extends GenerateBlocks_Singleton {
/**
* Initialize all hooks.
*
* @return void
*/
public function init() {
add_filter( 'query_vars', [ $this, 'add_query_vars' ] );
add_action( 'init', [ $this, 'rewrite_endpoints' ] );
add_action( 'template_include', [ $this, 'template_viewer' ] );
add_filter( 'show_admin_bar', [ $this, 'hide_admin_bar' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
}
/**
* Enqueue block editor assets.
*
* @return void
*/
public function enqueue_block_editor_assets() {
$assets = generateblocks_get_enqueue_assets( 'pattern-library' );
wp_enqueue_script(
'generateblocks-pattern-library',
GENERATEBLOCKS_DIR_URL . 'dist/pattern-library.js',
$assets['dependencies'],
$assets['version'],
true
);
wp_set_script_translations(
'generateblocks-pattern-library',
'generateblocks'
);
wp_localize_script(
'generateblocks-pattern-library',
'generateBlocksPatternLibrary',
array(
'patternPreviewUrl' => site_url() . '?gb-template-viewer=1',
'defaultOpenLibrary' => apply_filters(
'generateblocks_default_open_pattern_library',
'gb_default_pro_library'
),
'defaultOpenCategory' => apply_filters(
'generateblocks_default_open_pattern_category',
''
),
)
);
wp_enqueue_style(
'generateblocks-pattern-library',
GENERATEBLOCKS_DIR_URL . 'dist/pattern-library.css',
array( 'wp-components' ),
GENERATEBLOCKS_VERSION
);
}
/**
* The default id.
*
* @var string The default library id.
*/
protected $default_library_id = 'gb_default_free_library';
/**
* Returns all the registered libraries.
*
* @param bool $enabled_only Filter disabled libraries.
*
* @return array
*/
public function get_all( bool $enabled_only = true ): array {
// Get saved library data.
$saved_libraries = get_option( 'generateblocks_pattern_libraries', [] );
if ( ! is_array( $saved_libraries ) ) {
$saved_libraries = [];
}
// Create library instances for any remote sites that provide the complete set of data.
$remote_libraries = array_map(
[ $this, 'create' ],
array_filter(
$saved_libraries,
function( $saved_library ) {
return is_array( $saved_library ) &&
isset( $saved_library['isLocal'] ) &&
! rest_sanitize_boolean( $saved_library['isLocal'] ) &&
! empty( $saved_library['id'] ) &&
is_scalar( $saved_library['id'] ) &&
! empty( $saved_library['domain'] ) &&
is_string( $saved_library['domain'] );
}
)
);
// Allow other libraries to be added.
$libraries = apply_filters(
'generateblocks_pattern_libraries',
$remote_libraries
);
if ( ! is_array( $libraries ) ) {
$libraries = [];
}
$libraries = array_filter(
$libraries,
function( $library ) {
return $this->is_valid_library( $library );
}
);
// Add our default library at the start of the list.
if ( ! self::exists( $libraries, $this->default_library_id ) ) {
$default_library = self::get_default();
$libraries = array_merge( array( $default_library ), $libraries );
}
// Loop our libraries and set their status based on their saved value.
foreach ( $libraries as $key => $library ) {
$saved_data = array_filter(
$saved_libraries,
function( $saved_library ) use ( $library ) {
if ( ! is_array( $saved_library ) || ! isset( $saved_library['id'] ) ) {
return false;
}
return $saved_library['id'] === $library->id;
}
);
$saved_data = array_values( $saved_data );
if ( isset( $saved_data[0]['isEnabled'] ) ) {
$library->setStatus( $this->normalize_boolean_value( $saved_data[0]['isEnabled'] ) );
}
}
return array_filter(
$libraries,
function( GenerateBlocks_Library_DTO $library ) use ( $enabled_only ) {
if ( $enabled_only ) {
return $library->is_enabled;
}
return true;
}
);
}
/**
* Returns a single library by id.
*
* @param string $id The id.
*
* @return GenerateBlocks_Library_DTO|null
*/
public function get_one( string $id ): ?GenerateBlocks_Library_DTO {
$libraries = self::get_all( false );
return array_reduce(
$libraries,
function( $result, GenerateBlocks_Library_DTO $library ) use ( $id ) {
if ( $id === $library->id ) {
return $library;
}
return $result;
},
null
);
}
/**
* Creates a library from array.
*
* @param array $data The library in array format.
*
* @return GenerateBlocks_Library_DTO
*/
public function create( array $data ): GenerateBlocks_Library_DTO {
$data = wp_parse_args(
$data,
array(
'id' => '',
'name' => '',
'domain' => '',
'publicKey' => '',
'isEnabled' => false,
'isDefault' => false,
'isLocal' => false,
)
);
return ( new GenerateBlocks_Library_DTO() )
->set( 'id', $this->normalize_string_value( $data['id'] ) )
->set( 'name', $this->normalize_string_value( $data['name'] ) )
->set( 'domain', $this->normalize_string_value( $data['domain'] ) )
->set( 'public_key', $this->normalize_string_value( $data['publicKey'] ) )
->set( 'is_enabled', $this->normalize_boolean_value( $data['isEnabled'] ) )
->set( 'is_default', $this->normalize_boolean_value( $data['isDefault'] ) )
->set( 'is_local', $this->normalize_boolean_value( $data['isLocal'] ) );
}
/**
* Normalize values that are expected to be strings.
*
* @param mixed $value The value to normalize.
* @return string
*/
private function normalize_string_value( $value ): string {
if ( ! is_scalar( $value ) ) {
return '';
}
return (string) $value;
}
/**
* Normalize values that are expected to be booleans.
*
* @param mixed $value The value to normalize.
* @return bool
*/
private function normalize_boolean_value( $value ): bool {
if ( ! is_scalar( $value ) ) {
return false;
}
return rest_sanitize_boolean( $value );
}
/**
* Check that a library has values safe to access through the DTO.
*
* @param mixed $library The library object to validate.
* @return bool
*/
private function is_valid_library( $library ): bool {
if ( ! $library instanceof GenerateBlocks_Library_DTO ) {
return false;
}
$data = $library->serialize();
if ( empty( $data['id'] ) || ! is_scalar( $data['id'] ) ) {
return false;
}
foreach ( array( 'name', 'domain', 'publicKey' ) as $key ) {
if ( isset( $data[ $key ] ) && ! is_scalar( $data[ $key ] ) ) {
return false;
}
}
foreach ( array( 'isEnabled', 'isDefault', 'isLocal' ) as $key ) {
if ( isset( $data[ $key ] ) && ! is_scalar( $data[ $key ] ) ) {
return false;
}
}
return true;
}
/**
* Return the default library.
*
* @return GenerateBlocks_Library_DTO
*/
protected function get_default(): GenerateBlocks_Library_DTO {
$use_legacy_pattern_library = generateblocks_use_v1_blocks();
$domain = 'https://patterns.generatepress.com';
$public_key = 'betRRDVi8W0Td2eKAS52oVkG4HxqEj9r';
if (
$use_legacy_pattern_library ||
apply_filters( 'generateblocks_force_v1_pattern_library', false )
) {
$domain = 'https://patterns.generateblocks.com';
$public_key = 'GxroZpidKoLZ2ofWNJdXtanAK9ZozWKo';
}
return ( new GenerateBlocks_Library_DTO() )
->set( 'id', $this->default_library_id )
->set( 'name', __( 'Free', 'generateblocks' ) )
->set( 'domain', $domain )
->set( 'public_key', $public_key )
->set( 'is_enabled', true )
->set( 'is_default', true );
}
/**
* Checks if exists the library.
*
* @param array $libraries Array of libraries.
* @param string $id The library id.
*
* @return bool
*/
public function exists( array $libraries, string $id ): bool {
return array_reduce(
$libraries,
function( $result, GenerateBlocks_Library_DTO $library ) use ( $id ) {
if ( $id === $library->id ) {
return true;
}
return $result;
},
false
);
}
/**
* Get our cached library data by collection.
*
* @param string $cache_key The key to look up.
* @param array $query_args Args to filter the results with.
* @param string $collection The collection to check.
*/
public static function get_cached_data( $cache_key = '', $query_args = [], $collection = '' ) {
if ( ! $cache_key ) {
return [];
}
if ( 'patterns' !== $collection ) {
return get_transient( $cache_key );
}
$cached_data = [];
$has_cache = false;
$index = 0;
while ( true ) {
$option_key = $cache_key . '_' . $index;
$chunk = get_transient( $option_key );
if ( false === $chunk ) {
// No more chunks found, exit the loop.
break;
}
// Merge the chunk into the cached data.
$cached_data += $chunk;
$has_cache = true;
// Increment the index for the next iteration.
$index++;
}
// If we have no cache, return false.
// This allows empty arrays to be cached.
if ( ! $has_cache ) {
return false;
}
if ( ! empty( $query_args['categoryId'] ) ) {
$cached_data = array_filter(
$cached_data,
function( $data ) use ( $query_args ) {
if ( ! isset( $data['categories'] ) || ! is_array( $data['categories'] ) ) {
return false;
}
return in_array( $query_args['categoryId'], $data['categories'] );
}
);
}
if ( ! empty( $query_args['search'] ) ) {
$cached_data = array_filter(
$cached_data,
function( $data ) use ( $query_args ) {
if ( ! is_array( $data ) ) {
return false;
}
foreach ( $data as $key => $value ) {
if ( is_string( $value ) && stripos( $value, $query_args['search'] ) !== false ) {
return true;
}
continue;
}
return false;
}
);
}
$cached_data = array_values( $cached_data );
return $cached_data;
}
/**
* Set the collection cache expiry.
*/
public static function get_cache_expiry() {
return 86400;
}
/**
* Set our cached data. This function will split our patterns into chunks.
*
* @param array $data The data to cache.
* @param string $cache_key The key to set.
* @param string $collection The collection to check.
*/
public static function set_cached_data( $data = [], $cache_key = '', $collection = '' ) {
if ( ! $cache_key ) {
return;
}
if ( ! is_array( $data ) ) {
return;
}
$expiration = self::get_cache_expiry();
if ( 'patterns' === $collection ) {
self::delete_cached_data( $cache_key, $collection );
if ( ! empty( $data ) ) {
$chunks = array_chunk( $data, 20, true );
foreach ( $chunks as $index => $chunk ) {
$option_key = $cache_key . '_' . $index;
set_transient( $option_key, $chunk, $expiration );
}
} else {
set_transient( $cache_key . '_0', $data, $expiration );
}
} else {
set_transient( $cache_key, $data, $expiration );
}
}
/**
* Delete cached library data by collection.
*
* @param string $cache_key The key to delete.
* @param string $collection The collection to delete.
*/
public static function delete_cached_data( $cache_key = '', $collection = '' ) {
if ( ! $cache_key ) {
return;
}
if ( 'patterns' !== $collection ) {
delete_transient( $cache_key );
return;
}
$index = 0;
while ( false !== get_transient( $cache_key . '_' . $index ) ) {
delete_transient( $cache_key . '_' . $index );
$index++;
}
}
/**
* Adds GB custom query variables.
*
* @param array $vars The variables.
*
* @return array
*/
public function add_query_vars( array $vars ): array {
$vars[] = 'gb-template-viewer';
return $vars;
}
/**
* Adds GB custom rewrite endpoints.
*
* @return void
*/
public function rewrite_endpoints(): void {
add_rewrite_endpoint( 'gb-template-viewer', EP_ROOT );
}
/**
* Register the template viewer template.
*
* @param string $template The current template.
*
* @return string
*/
public function template_viewer( string $template ): string {
if ( false !== get_query_var( 'gb-template-viewer', false ) ) {
return GENERATEBLOCKS_DIR . 'includes/pattern-library/templates/gb-template-viewer.php';
}
return $template;
}
/**
* Hide the admin bar if we are on the template viewer.
*
* @param bool $show_admin_bar Whether to show the admin bar.
* @return bool
*/
public function hide_admin_bar( $show_admin_bar ) {
if ( ! isset( $GLOBALS['wp_query'] ) ) {
return $show_admin_bar;
}
if ( false !== get_query_var( 'gb-template-viewer', false ) ) {
$show_admin_bar = false;
}
return $show_admin_bar;
}
}
GenerateBlocks_Libraries::get_instance()->init();
@@ -0,0 +1,49 @@
<?php
/**
* The Libraries class file.
*
* @package GenerateBlocks\Pattern_Library
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Library data transfer object.
*
* @property string id The library id.
* @property string name The library name.
* @property string domain The library domain.
* @property string public_key The library public key.
* @property bool is_default The library is default.
* @property bool is_local The library is local.
* @property bool is_enabled The library is enabled.
*
* @since 1.9
*/
class GenerateBlocks_Library_DTO extends GenerateBlocks_DTO {
/**
* The data.
*
* @var array The library data.
*/
protected $data = array(
'id' => '',
'name' => '',
'domain' => '',
'public_key' => '',
'is_enabled' => false,
'is_default' => false,
'is_local' => false,
);
/**
* Set the status for a library.
*
* @param boolean $newStatus The status to set.
*/
public function setStatus( $newStatus ) {
$this->data['is_enabled'] = $newStatus;
}
}
@@ -0,0 +1,508 @@
<?php
/**
* The Pattern library class file.
*
* @package GenerateBlocks\Pattern_Library
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Main class for the Pattern Library Rest functions.
*
* @since 1.9
*/
class GenerateBlocks_Pattern_Library_Rest extends GenerateBlocks_Singleton {
/**
* Initialize all hooks.
*
* @return void
*/
public function init() {
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
/**
* Register the REST routes.
*
* @return void
*/
public function register_routes(): void {
$namespace = 'generateblocks/v1';
register_rest_route(
$namespace,
'/pattern-library/libraries',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'list_libraries' ),
'permission_callback' => function() {
return apply_filters(
'generateblocks_can_view_pattern_library',
$this->edit_posts_permission()
);
},
)
);
register_rest_route(
$namespace,
'/pattern-library/libraries/save',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'save_libraries' ),
'permission_callback' => array( $this, 'manage_options_permission' ),
)
);
register_rest_route(
$namespace,
'/pattern-library/categories',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'list_categories' ),
'permission_callback' => function() {
return apply_filters(
'generateblocks_can_view_pattern_library',
$this->edit_posts_permission()
);
},
)
);
register_rest_route(
$namespace,
'/pattern-library/patterns',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'list_patterns' ),
'permission_callback' => function() {
return apply_filters(
'generateblocks_can_view_pattern_library',
$this->edit_posts_permission()
);
},
)
);
register_rest_route(
$namespace,
'/pattern-library/get-cache-data',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_cache_data' ),
'permission_callback' => function() {
return apply_filters(
'generateblocks_can_view_pattern_library',
$this->edit_posts_permission()
);
},
)
);
register_rest_route(
$namespace,
'/pattern-library/clear-cache',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'clear_cache' ),
'permission_callback' => array( $this, 'edit_posts_permission' ),
)
);
}
/**
* Manage options permission callback.
*
* @return bool
*/
public function manage_options_permission(): bool {
return current_user_can( 'manage_options' );
}
/**
* Manage options permission callback.
*
* @return bool
*/
public function edit_posts_permission(): bool {
return current_user_can( 'edit_posts' );
}
/**
* Returns a list of registered libraries.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function list_libraries( WP_REST_Request $request ): WP_REST_Response {
$is_enabled = $request->get_param( 'is_enabled' );
$libraries = GenerateBlocks_Libraries::get_instance();
$data = $libraries->get_all( rest_sanitize_boolean( $is_enabled ) );
$data = array_values( $data ); // Fix indexes.
return new WP_REST_Response(
array(
'error' => false,
'data' => $data,
),
200
);
}
/**
* Saves the list of libraries.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function save_libraries( WP_REST_Request $request ): WP_REST_Response {
$data = $request->get_param( 'data' );
if ( ! is_array( $data ) ) {
return $this->error( 400, __( 'Invalid library data.', 'generateblocks' ) );
}
$libraries = array_values(
array_filter(
array_map(
[ $this, 'sanitize_library_data' ],
$data
)
)
);
update_option( 'generateblocks_pattern_libraries', $libraries );
return $this->success( $libraries );
}
/**
* Returns a list of categories.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function list_categories( WP_REST_Request $request ): WP_REST_Response {
$library_id = $this->sanitize_request_string( $request->get_param( 'libraryId' ) );
if ( ! $library_id ) {
return $this->error( 404, "Library of id \"$library_id\" was not found." );
}
$libraries = GenerateBlocks_Libraries::get_instance();
$library = $libraries->get_one( $library_id );
if ( ! is_null( $library ) ) {
return self::remote_fetch( $library, 'categories' );
}
return $this->error( 404, "Library of id \"$library_id\" was not found." );
}
/**
* Returns a list of patterns.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function list_patterns( WP_REST_Request $request ): WP_REST_Response {
$library_id = $this->sanitize_request_string( $request->get_param( 'libraryId' ) );
$search = $this->sanitize_request_string( $request->get_param( 'search' ) );
$category_id = $this->sanitize_request_string( $request->get_param( 'categoryId' ) );
if ( ! $library_id ) {
return $this->error( 404, "Library of id \"$library_id\" was not found." );
}
$libraries = GenerateBlocks_Libraries::get_instance();
$library = $libraries->get_one( $library_id );
if ( ! is_null( $library ) ) {
return self::remote_fetch(
$library,
'patterns',
array(
'search' => $search,
'categoryId' => $category_id,
)
);
}
return $this->error( 404, "Library of id \"$library_id\" was not found." );
}
/**
* Fetch pattern library remotely.
*
* @param GenerateBlocks_Library_DTO $library The library to fetch data.
* @param string $collection The collection type. Either 'categories' or 'patterns'.
* @param array $query_args The extra query arguments.
*
* @return WP_REST_Response
*/
private function remote_fetch(
GenerateBlocks_Library_DTO $library,
string $collection,
array $query_args = array()
): WP_REST_Response {
if ( ! in_array( $collection, array( 'categories', 'patterns' ), true ) ) {
return $this->error( 400, __( 'Invalid library collection.', 'generateblocks' ) );
}
$domain = esc_url_raw( $library->domain );
if ( ! $domain ) {
return $this->error( 400, __( 'Invalid library domain.', 'generateblocks' ) );
}
$endpoint = trailingslashit( $domain ) . "wp-json/generateblocks-pro/v1/pattern-library/$collection";
$url = add_query_arg( $query_args, $endpoint );
$cache_key = $library->id . '-' . $collection;
$cache = GenerateBlocks_Libraries::get_cached_data( $cache_key, $query_args, $collection );
if ( false !== $cache ) {
return $this->success( $cache );
}
$request = wp_remote_get(
$url,
array(
'headers' => array(
'X-GB-Public-Key' => $library->public_key,
),
'timeout' => 15,
)
);
if ( is_wp_error( $request ) ) {
return $this->error( 500, "Unable to request from $endpoint" );
}
$response_code = (int) wp_remote_retrieve_response_code( $request );
if ( 200 !== $response_code ) {
return $this->error( $response_code ? $response_code : 500, "Unable to request from $endpoint" );
}
$body = wp_remote_retrieve_body( $request );
$body = json_decode( $body, true );
if ( ! is_array( $body ) ) {
return $this->error( 500, __( 'Invalid library response.', 'generateblocks' ) );
}
$data = $body['response']['data'] ?? [];
if ( ! is_array( $data ) ) {
return $this->error( 500, __( 'Invalid library response data.', 'generateblocks' ) );
}
// Cache our data.
GenerateBlocks_Libraries::set_cached_data( $data, $cache_key, $collection );
return $this->success( $data );
}
/**
* Get the expiry time of a cache.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function get_cache_data( WP_REST_Request $request ): WP_REST_Response {
$id = $this->sanitize_request_string( $request->get_param( 'id' ) );
if ( ! $id ) {
return $this->error( 400, __( 'Invalid library id.', 'generateblocks' ) );
}
if ( is_null( GenerateBlocks_Libraries::get_instance()->get_one( $id ) ) ) {
return $this->error( 404, __( 'Library not found.', 'generateblocks' ) );
}
$expiration_time = get_option( '_transient_timeout_' . $id . '-patterns_0' );
if ( ! $expiration_time ) {
return $this->failed( 'no_cache' );
}
$current_time = time();
$cache_made_time = $expiration_time - GenerateBlocks_Libraries::get_cache_expiry();
$can_clear_cache = $current_time > ( $cache_made_time + 300 );
return $this->success(
[
'expiry_time_raw' => $expiration_time,
'expiry_time' => gmdate( 'Y-m-d H:i:s', $expiration_time ),
'can_clear' => $can_clear_cache,
]
);
}
/**
* Clear caches for a specific collection.
*
* @param WP_REST_Request $request The request.
*
* @return WP_REST_Response
*/
public function clear_cache( WP_REST_Request $request ): WP_REST_Response {
$id = $this->sanitize_request_string( $request->get_param( 'id' ) );
if ( ! $id ) {
return $this->error( 400, __( 'Invalid library id.', 'generateblocks' ) );
}
if ( is_null( GenerateBlocks_Libraries::get_instance()->get_one( $id ) ) ) {
return $this->error( 404, __( 'Library not found.', 'generateblocks' ) );
}
GenerateBlocks_Libraries::delete_cached_data( $id . '-categories' );
GenerateBlocks_Libraries::delete_cached_data( $id . '-patterns', 'patterns' );
GenerateBlocks_Libraries::delete_cached_data( $id . '_global-style-data' );
return $this->success( [] );
}
/**
* Sanitize scalar REST request values.
*
* @param mixed $value The value to sanitize.
* @return string
*/
private function sanitize_request_string( $value ): string {
if ( ! is_scalar( $value ) ) {
return '';
}
return sanitize_text_field( (string) $value );
}
/**
* Sanitize scalar REST request URLs.
*
* @param mixed $value The value to sanitize.
* @return string
*/
private function sanitize_request_url( $value ): string {
if ( ! is_string( $value ) ) {
return '';
}
return esc_url_raw( $value, array( 'http', 'https' ) );
}
/**
* Sanitize a saved library row.
*
* @param mixed $library The library data.
* @return array
*/
private function sanitize_library_data( $library ): array {
if ( ! is_array( $library ) ) {
return [];
}
$id = $this->sanitize_request_string( $library['id'] ?? '' );
if ( ! $id ) {
return [];
}
$is_local = rest_sanitize_boolean( $library['isLocal'] ?? false );
$is_default = rest_sanitize_boolean( $library['isDefault'] ?? false );
$is_enabled = rest_sanitize_boolean( $library['isEnabled'] ?? false );
if ( ! $is_local && ! $is_default ) {
$domain = $this->sanitize_request_url( $library['domain'] ?? '' );
if ( ! $domain ) {
return [];
}
// Save all data if this is a remote library.
return [
'id' => $id,
'name' => $this->sanitize_request_string( $library['name'] ?? '' ),
'domain' => $domain,
'publicKey' => $this->sanitize_request_string( $library['publicKey'] ?? '' ),
'isEnabled' => $is_enabled,
'isDefault' => $is_default,
'isLocal' => $is_local,
];
}
// Only save the ID and status for local and default libraries.
// The rest of the data will be supplied via the PHP filter.
return [
'id' => $id,
'isEnabled' => $is_enabled,
];
}
/**
* Returns a success response.
*
* @param array $data The data.
*
* @return WP_REST_Response
*/
private function success( array $data ): WP_REST_Response {
return new WP_REST_Response(
array(
'success' => true,
'response' => array(
'data' => $data,
),
),
200
);
}
/**
* Returns a success response.
*
* @param string $message The error message.
*
* @return WP_REST_Response
*/
private function failed( string $message ): WP_REST_Response {
return new WP_REST_Response(
array(
'success' => false,
'response' => $message,
),
200
);
}
/**
* Returns a error response.
*
* @param int $code Error code.
* @param string $message Error message.
*
* @return WP_REST_Response
*/
private function error( int $code, string $message ): WP_REST_Response {
return new WP_REST_Response(
array(
'error' => true,
'success' => false,
'error_code' => $code,
'response' => $message,
),
$code
);
}
}
GenerateBlocks_Pattern_Library_Rest::get_instance()->init();
@@ -0,0 +1,20 @@
<?php
/**
* The GB template viewer file.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?><!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<?php wp_head(); ?>
</head>
<body style="overflow-y: hidden;">
<?php do_action( 'wp_footer' ); // phpcs:ignore -- Need to use core action. ?>
</body>
</html>
@@ -0,0 +1,90 @@
<?php
/**
* The data transfer object class file.
*
* @package GenerateBlocks\Utils
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The GenerateBlocks Data Transfer Object class.
*
* @since 1.9.0
*/
class GenerateBlocks_DTO implements JsonSerializable {
/**
* The data.
*
* @var array The DTO data.
*/
protected $data = array();
/**
* Returns the data values if exists.
*
* @param string $name The called name.
*
* @return string|null
*/
public function __get( string $name ): ?string {
return $this->data[ $name ] ?? null;
}
/**
* Set a value for the DTO data.
*
* @param string $key The name.
* @param mixed $value The value.
*
* @return GenerateBlocks_DTO
*/
public function set( string $key, $value ): GenerateBlocks_DTO {
if ( isset( $this->data[ $key ] ) ) {
$this->data[ $key ] = $value;
}
return $this;
}
/**
* Serialize this class.
*
* @return array
*/
public function serialize(): array {
$result = array();
foreach ( $this->data as $key => $value ) {
$k = generateblocks_to_camel_case( $key );
$result[ $k ] = $value;
}
return $result;
}
/**
* Unserialize this class.
*
* @param array $data The data.
*
* @return void
*/
public function unserialize( array $data ): void {
foreach ( $data as $key => $value ) {
$k = generateblocks_to_snake_case( $key );
$this->data[ $k ] = $value;
}
}
/**
* JSON serialize function.
*
* @return array
*/
public function JsonSerialize(): array {
return $this->serialize();
}
}
@@ -0,0 +1,61 @@
<?php
/**
* The singleton class file.
*
* @package GenerateBlocks\Utils
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The GenerateBlocks Singleton class.
*
* @since 1.9.0
*/
class GenerateBlocks_Singleton {
/**
* Child class instances.
*
* @var array<static>
*/
private static $instances = [];
/**
* The singleton constructor can not be public.
*/
final protected function __construct() {
}
/**
* Not allowed to clone a singleton.
*/
protected function __clone() {
}
/**
* Not allowed to un-serialize a singleton.
*
* @throws Exception Cannot un-serialize a singleton.
*/
public function __wakeup() {
throw new Exception( 'Cannot un-serialize singleton' );
}
/**
* Get the class instance.
*
* @return static
*/
public static function get_instance(): GenerateBlocks_Singleton {
$subclass = static::class;
if ( ! isset( self::$instances[ $subclass ] ) ) {
self::$instances[ $subclass ] = new static();
}
return self::$instances[ $subclass ];
}
}