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,159 @@
<?php
/**
* Block Conditions functionality.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class GenerateBlocks_Pro_Block_Conditions
*/
class GenerateBlocks_Pro_Block_Conditions extends GenerateBlocks_Pro_Singleton {
/**
* Constructor.
*/
public function init() {
// Don't initialize if block conditions are disabled.
if ( ! generateblocks_pro_block_conditions_enabled() ) {
return;
}
add_filter( 'render_block', [ $this, 'check_block_conditions' ], 10, 2 );
add_filter( 'generateblocks_condition_usage_handlers', [ $this, 'add_block_usage_handler' ], 10, 2 );
add_filter( 'register_block_type_args', [ $this, 'add_condition_attributes' ], 10, 2 );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
}
/**
* Enqueue block editor assets for block conditions.
*/
public function enqueue_editor_assets() {
$assets = generateblocks_pro_get_enqueue_assets( 'block-conditions' );
wp_enqueue_script(
'generateblocks-pro-block-conditions',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/block-conditions.js',
$assets['dependencies'],
$assets['version'],
true
);
}
/**
* Check block conditions and prevent rendering if conditions are not met.
*
* @param string $block_content The block content.
* @param array $block The block data.
* @return string The block content or empty string if conditions are not met.
*/
public function check_block_conditions( $block_content, $block ) {
// Early return if no attributes.
if ( empty( $block['attrs'] ) ) {
return $block_content;
}
$attributes = $block['attrs'];
// Check if this block has a condition set.
if ( empty( $attributes['gbBlockCondition'] ) ) {
return $block_content;
}
$condition_id = absint( $attributes['gbBlockCondition'] );
// Validate condition ID.
if ( ! $condition_id ) {
return $block_content;
}
// Check if the condition post exists and is published.
$condition_post = get_post( $condition_id );
if ( ! $condition_post || 'publish' !== $condition_post->post_status ) {
return $block_content;
}
// Get the condition data.
$display_conditions = get_post_meta( $condition_id, '_gb_conditions', true );
if ( empty( $display_conditions ) ) {
return $block_content;
}
// Pass the current post context to the conditions system.
// This ensures conditions are evaluated for the correct post in loops.
$context = array( 'post_id' => get_the_ID() );
// Use the existing conditions system to evaluate.
$show = GenerateBlocks_Pro_Conditions::show( $display_conditions, $context );
// If invert is enabled, flip the result.
if ( ! empty( $attributes['gbBlockConditionInvert'] ) ) {
$show = ! $show;
}
// Return empty string to prevent block from rendering if condition is not met.
if ( ! $show ) {
return '';
}
return $block_content;
}
/**
* Add block conditions handler to usage search.
*
* @param array $handlers Existing handlers.
* @param int $condition_id The condition ID.
* @return array Modified handlers.
*/
public function add_block_usage_handler( $handlers, $condition_id ) {
$handlers['block_conditions'] = [
'method' => 'search_block_conditions_usage',
'label' => __( 'Block Conditions', 'generateblocks-pro' ),
];
return $handlers;
}
/**
* Add condition attributes to all blocks during server-side registration.
* This ensures ServerSideRender calls don't fail validation.
*
* @param array $args The block registration arguments.
* @param string $block_type The block type name.
* @return array Modified arguments.
*/
public function add_condition_attributes( $args, $block_type ) {
// Ensure attributes array exists.
if ( ! isset( $args['attributes'] ) ) {
$args['attributes'] = [];
}
// Add gbBlockCondition attribute if not already defined.
if ( ! isset( $args['attributes']['gbBlockCondition'] ) ) {
$args['attributes']['gbBlockCondition'] = [
'type' => 'string',
'default' => '',
];
}
// Add gbBlockConditionInvert attribute if not already defined.
if ( ! isset( $args['attributes']['gbBlockConditionInvert'] ) ) {
$args['attributes']['gbBlockConditionInvert'] = [
'type' => 'boolean',
'default' => false,
];
}
return $args;
}
}
// Initialize the class.
GenerateBlocks_Pro_Block_Conditions::get_instance()->init();
@@ -0,0 +1,158 @@
<?php
/**
* The class to integrate advance custom fields to dynamic content post meta.
*
* @package Generateblocks/Extend/DynamicContent
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* GenerateBlocks Pro Advanced custom fields integration
*
* @since 1.4.0
*/
class GenerateBlocks_Pro_Advanced_Custom_Fields extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
protected function __construct() {
parent::__construct();
if ( class_exists( 'ACF' ) ) {
add_filter(
'generateblocks_dynamic_content_post_meta',
'GenerateBlocks_Pro_Advanced_Custom_Fields::load_acf_fields',
10,
3
);
add_filter(
'generateblocks_dynamic_url_post_meta',
'GenerateBlocks_Pro_Advanced_Custom_Fields::load_link_acf_fields',
10,
3
);
add_filter(
'generateblocks_dynamic_content_author_meta',
'GenerateBlocks_Pro_Advanced_Custom_Fields::load_author_acf_fields',
10,
3
);
add_filter(
'generateblocks_dynamic_url_author_meta',
'GenerateBlocks_Pro_Advanced_Custom_Fields::load_author_link_acf_fields',
10,
3
);
}
}
/**
* Load ACF fields in the frontend.
*
* @param mixed $value The field value.
* @param string|int $id The object id.
* @param array $attributes The block attributes.
* @param string $metaFieldKey The meta field key.
* @param string $metaFieldPropertyNameKey The property key.
*
* @return mixed|string The field value.
*/
public static function load_acf_fields(
$value,
$id,
$attributes,
$metaFieldKey = 'metaFieldName',
$metaFieldPropertyNameKey = 'metaFieldPropertyName'
) {
$field = get_field( $attributes[ $metaFieldKey ], $id );
if ( is_array( $field ) ) {
if (
isset( $attributes[ $metaFieldPropertyNameKey ] ) &&
is_string( $attributes[ $metaFieldPropertyNameKey ] ) &&
isset( $field[ $attributes[ $metaFieldPropertyNameKey ] ] )
) {
return $field[ $attributes[ $metaFieldPropertyNameKey ] ];
}
// This is for backwards compatibility for dynamic images using ACF image field before 1.6.
if ( array_key_exists( 'ID', $field ) ) {
return $field['ID'];
}
return '';
}
if (
is_object( $field ) &&
isset( $attributes[ $metaFieldPropertyNameKey ] ) &&
isset( $field->{$attributes[ $metaFieldPropertyNameKey ]} )
) {
return $field->{$attributes[ $metaFieldPropertyNameKey ]};
} elseif ( is_object( $field ) ) {
return '';
}
return $field;
}
/**
* Load the author custom fields.
*
* @param mixed $value The field value.
* @param string $author_id The author id.
* @param array $attributes The block attributes.
*
* @return mixed|string The field value.
*/
public static function load_author_acf_fields( $value, $author_id, $attributes ) {
return self::load_acf_fields( $value, 'user_' . $author_id, $attributes );
}
/**
* Load the link custom field.
*
* @param mixed $value The field value.
* @param string $id The object id.
* @param array $attributes The block attributes.
*
* @return mixed|string The field value.
*/
public static function load_link_acf_fields( $value, $id, $attributes ) {
return self::load_acf_fields(
$value,
$id,
$attributes,
'linkMetaFieldName',
'linkMetaFieldPropertyName'
);
}
/**
* Load the author link custom field.
*
* @param mixed $value The field value.
* @param string $author_id The author id.
* @param array $attributes The block attributes.
*
* @return mixed|string The field value.
*/
public static function load_author_link_acf_fields( $value, $author_id, $attributes ) {
return self::load_acf_fields(
$value,
'user_' . $author_id,
$attributes,
'linkMetaFieldName',
'linkMetaFieldPropertyName'
);
}
}
GenerateBlocks_Pro_Advanced_Custom_Fields::get_instance();
@@ -0,0 +1,361 @@
<?php
/**
* The class to integrate advance custom fields to dynamic content post meta.
*
* @package Generateblocks/Extend/DynamicTags
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* GenerateBlocks Pro Advanced custom fields integration
*
* @since 1.4.0
*/
class GenerateBlocks_Pro_Dynamic_Tags_ACF extends GenerateBlocks_Pro_Singleton {
/**
* Stored ACF option keys.
*
* @var array
*/
public $acf_option_fields = [];
/**
* Init function
* The post meta value.
*/
public function init() {
if ( ! class_exists( 'ACF' ) ) {
return; // Exit if Advanced Custom Fields plugin is not active.
}
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
add_filter(
'generateblocks_get_meta_pre_value',
[ $this, 'get_meta_pre_value' ],
10,
5
);
add_filter(
'generateblocks_dynamic_tags_post_record_response',
[ $this, 'add_acf_meta_to_post_record' ],
10,
3
);
add_filter(
'generateblocks_dynamic_tags_user_record_response',
[ $this, 'add_acf_meta_to_user_record' ],
10,
3
);
}
/**
* Add appropriate meta pre values.
*
* @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).
*/
public function get_meta_pre_value( $pre_value, $id, $key, $callable ) {
switch ( $callable ) {
case 'get_post_meta':
$value = self::get_post_meta_pre_value( $pre_value, $id, $key );
break;
case 'get_user_meta':
$value = self::get_user_meta_pre_value( $pre_value, $id, $key );
break;
case 'get_term_meta':
$value = self::get_term_meta_pre_value( $pre_value, $id, $key );
break;
case 'get_option':
$value = self::get_option_pre_value( $pre_value, $id, $key );
break;
default:
}
return $value;
}
/**
* Register REST routes.
*
* @return void
*/
public function register_rest_routes() {
register_rest_route(
'generateblocks-pro/v1',
'/get-acf-option-fields',
[
'methods' => 'GET',
'callback' => [ $this, 'get_acf_option_fields_rest' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
]
);
}
/**
*
* This function checks if the provided key is an Advanced Custom Fields (ACF) field key.
* If it is, the function retrieves the ACF field value using the `get_field` function. Sub fields
* can be passed to the $key param using a "." to separate the parent and sub fields (ex: parent_child.sub_field).
*
* @since 1.8.0
*
* @param string $value The current post meta value.
* @param int $id The ID of the post.
* @param string $key The post meta key.
* @param string $type The post meta type.
*
* @return mixed The updated post meta value. If the key is not an ACF field key, the original value is returned.
*/
public function maybe_get_field( $value, $id, $key, $type = '' ) {
// Bail if the ACF functions are undefined.
if ( ! function_exists( 'get_field' ) || ! function_exists( 'acf_get_meta' ) ) {
return $value;
}
$acf_id = $type ? "{$type}_{$id}" : $id;
$key_parts = array_map( 'trim', explode( '.', $key ) );
$parent_name = $key_parts[0];
$is_acf_field = false;
$parent_is_option = 'option' === $type ? $this->is_acf_option( $parent_name ) : false;
$acf_keys = [];
if ( $parent_is_option ) {
$acf_id = 'option';
$is_acf_field = true;
} else {
$acf_keys = acf_get_meta( $acf_id );
$is_acf_field = isset( $acf_keys[ $parent_name ] );
}
/**
* Filter the ACF field check to allow for custom logic.
*
* @param bool $is_acf_field Whether the provided key is an ACF field key.
* @param string|int $acf_id The ACF ID for the post. All options have the value of 'option'.
* @param array $acf_keys The ACF keys for the $acf_id.
* @param array $args Additional arguments: $value (the current post meta value), $id (the ID of the post), $key (the post meta key), and $type (the post meta type).
*
* @return bool Whether the provided key is an ACF field key.
*/
$is_acf_field = apply_filters(
'generateblocks_pro_dynamic_tags_is_acf_field',
$is_acf_field,
$acf_id,
$acf_keys,
[
$value,
$id,
$key,
$type,
]
);
if ( ! $is_acf_field ) {
return $value;
}
return get_field( $parent_name, $acf_id );
}
/**
* Filters the post meta value before it's returned.
*
* This function checks if the provided key is an Advanced Custom Fields( ACF ) field key.
* if it is, the function retrieves the ACF field value using the `get_field` function.
*
* @since 1.8.0
*
* @param string $value The current post meta value.
* @param int $post_id The ID of the post.
* @param string $key The post meta key.
*
* @return mixed The updated post meta value. if the key is not an ACF field key, the original value is returned.
*/
public function get_post_meta_pre_value( $value, $post_id, $key ) {
return $this->maybe_get_field( $value, $post_id, $key );
}
/**
* Filters the term meta value before it's returned.
*
* This function checks if the provided key is an Advanced Custom Fields (ACF) field key.
* If it is, the function retrieves the ACF field value using the `get_field` function.
*
* @param string $value The current term meta value.
* @param int $id The ID of the term to query meta from.
* @param string $key The term meta key.
*
* @return mixed The updated post meta value. If the key is not an ACF field key, the original value is returned.
*/
public function get_term_meta_pre_value( $value, $id, $key ) {
return $this->maybe_get_field( $value, $id, $key, 'term' );
}
/**
* Filters the user meta value before it's returned.
*
* This function checks if the provided key is an Advanced Custom Fields (ACF) field key.
* If it is, the function retrieves the ACF field value using the `get_field` function.
*
* @param string $value The current user meta value.
* @param int $id The ID of the user to query meta from.
* @param string $key The user meta key.
*
* @return mixed The updated post meta value. If the key is not an ACF field key, the original value is returned.
*/
public function get_user_meta_pre_value( $value, $id, $key ) {
return $this->maybe_get_field( $value, $id, $key, 'user' );
}
/**
* Filters the option value before it's returned.
*
* This function checks if the provided key is an Advanced Custom Fields (ACF) field key.
* If it is, the function retrieves the ACF field value using the `get_field` function.
*
* @param string $value The current option meta value.
* @param int $id The ACF ID of the option to retrieve.
* @param string $key The option key.
*
* @return mixed The updated post meta value. If the key is not an ACF field key, the original value is returned.
*/
public function get_option_pre_value( $value, $id, $key ) {
return $this->maybe_get_field( $value, $id, $key, 'option' );
}
/**
* Filters the post record to include ACF meta field keys and values.
*
* @param object $response Post object from the response.
* @param int $id ID of the post record.
*
* @return mixed
*/
public function add_acf_meta_to_post_record( $response, $id ) {
// Stop here if the ACF functions are undefined.
if ( ! function_exists( 'acf_get_meta' ) ) {
return $response;
}
$acf_meta = acf_get_meta( $id );
if ( $acf_meta ) {
$response->acf = array_filter(
$acf_meta,
function ( $key ) {
return strpos( $key, '_' ) !== 0;
},
ARRAY_FILTER_USE_KEY
);
}
return $response;
}
/**
* Filters the user record to include ACF meta field keys and values.
*
* @param object $response Post object from the response.
* @param int $id ID of the post record.
*
* @return mixed
*/
public function add_acf_meta_to_user_record( $response, $id ) {
// Stop here if the ACF functions are undefined.
if ( ! function_exists( 'acf_get_meta' ) ) {
return $response;
}
$acf_meta = acf_get_meta( "user_$id" );
if ( $acf_meta ) {
$response->acf = array_filter(
$acf_meta,
function ( $key ) {
return strpos( $key, '_' ) !== 0;
},
ARRAY_FILTER_USE_KEY
);
}
return $response;
}
/**
* Retrieves all ACf option fields using the default option prefix.
*
* @return array
*/
public function get_acf_option_fields() {
if ( ! function_exists( 'acf_get_option_meta' ) ) {
return [];
}
$options = array_filter(
acf_get_option_meta( 'options' ),
function( $key ) {
return strpos( $key, '_' ) !== 0;
},
ARRAY_FILTER_USE_KEY
);
$response = [];
foreach ( $options as $key => $array_value ) {
$response[ $key ] = $array_value[0];
}
return $response;
}
/**
* Rest controller for get-acf-option-fields.
*
* @return WP_REST_Response|WP_Error
*/
public function get_acf_option_fields_rest() {
if ( ! $this->acf_option_fields ) {
$this->acf_option_fields = $this->get_acf_option_fields();
}
return rest_ensure_response( $this->acf_option_fields );
}
/**
* Check if a given field name is an ACF option or not.
*
* @param string $field_name The name of the field to check.
* @return bool Return true if the option is an ACF option.
*/
public function is_acf_option( $field_name ) {
if ( ! $this->acf_option_fields ) {
$this->acf_option_fields = $this->get_acf_option_fields();
}
return isset( $this->acf_option_fields[ $field_name ] );
}
}
GenerateBlocks_Pro_Dynamic_Tags_ACF::get_instance()->init();
@@ -0,0 +1,62 @@
<?php
/**
* The class to integrate adjacent post dynamic tags.
*
* @package Generateblocks/Extend/DynamicTags
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* GenerateBlocks Pro adjacted post dynamic tags.
*
* @since 1.4.0
*/
class GenerateBlocks_Pro_Dynamic_Tags_Adjacent_Posts extends GenerateBlocks_Pro_Singleton {
/**
* Init.
*/
public function init() {
add_filter( 'generateblocks_dynamic_tag_id', array( $this, 'set_adjacent_post_ids' ), 10, 2 );
}
/**
* Set adjacent post ids.
*
* @param int $id The post id.
* @param array $options The options.
*/
public function set_adjacent_post_ids( $id, $options ) {
$source = $options['source'] ?? '';
if ( 'next-post' === $source ) {
$in_same_term = $options['inSameTerm'] ?? false;
$term_taxonomy = $options['sameTermTaxonomy'] ?? 'category';
$next_post = get_next_post( $in_same_term, '' );
if ( ! is_object( $next_post ) ) {
return false;
}
return $next_post->ID;
}
if ( 'previous-post' === $source ) {
$in_same_term = $options['inSameTerm'] ?? false;
$term_taxonomy = $options['sameTermTaxonomy'] ?? 'category';
$previous_post = get_previous_post( $in_same_term, '', $term_taxonomy );
if ( ! is_object( $previous_post ) ) {
return false;
}
return $previous_post->ID;
}
return $id;
}
}
GenerateBlocks_Pro_Dynamic_Tags_Adjacent_Posts::get_instance()->init();
@@ -0,0 +1,502 @@
<?php
/**
* The Dynamic Tags class file.
*
* @package GenerateBlocks_Pro\Dynamic_Tags
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class for handling dynamic tags.
*
* @since 2.0.0
*/
class GenerateBlocks_Pro_Dynamic_Tags_Register extends GenerateBlocks_Pro_Singleton {
/**
* Initialize all hooks.
*
* @return void
*/
public function init() {
if ( ! class_exists( 'GenerateBlocks_Register_Dynamic_Tag' ) ) {
return;
}
add_action( 'init', [ $this, 'register' ] );
}
/**
* Register the tags.
*
* @return void
*/
public function register() {
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Archive Title', 'generateblocks-pro' ),
'tag' => 'archive_title',
'type' => 'archive',
'supports' => [],
'description' => __( 'Get the title for the current archive being viewed.', 'generateblocks-pro' ),
'return' => [ $this, 'get_archive_title' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Archive Description', 'generateblocks-pro' ),
'tag' => 'archive_description',
'type' => 'archive',
'supports' => [],
'description' => __( 'Get the description for the current archive being viewed.', 'generateblocks-pro' ),
'return' => [ $this, 'get_archive_description' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Option', 'generateblocks-pro' ),
'tag' => 'option',
'type' => 'option',
'supports' => [ 'meta' ],
'return' => [ $this, 'get_option' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Term Meta', 'generateblocks-pro' ),
'tag' => 'term_meta',
'type' => 'term',
'supports' => [ 'meta', 'source' ],
'description' => __( 'Access term meta by key for the specified term. Return value must be a string.', 'generateblocks-pro' ),
'return' => [ $this, 'get_term_meta' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'User Meta', 'generateblocks-pro' ),
'tag' => 'user_meta',
'type' => 'user',
'supports' => [ 'meta', 'source' ],
'description' => __( 'Access user meta by key for the specified user. Return value must be a string.', 'generateblocks-pro' ),
'return' => [ $this, 'get_user_meta' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Current year', 'generateblocks-pro' ),
'tag' => 'current_year',
'type' => 'site',
'supports' => [],
'return' => [ $this, 'get_current_year' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Site Title', 'generateblocks-pro' ),
'tag' => 'site_title',
'type' => 'site',
'supports' => [],
'return' => [ $this, 'get_site_title' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Site Tagline', 'generateblocks-pro' ),
'tag' => 'site_tagline',
'type' => 'site',
'supports' => [],
'return' => [ $this, 'get_site_tagline' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Site Logo URL', 'generateblocks-pro' ),
'tag' => 'site_logo_url',
'type' => 'site',
'supports' => [],
'return' => [ $this, 'get_site_logo_url' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Site URL', 'generateblocks-pro' ),
'tag' => 'site_url',
'type' => 'site',
'supports' => [],
'return' => [ $this, 'get_site_url' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Loop Index', 'generateblocks-pro' ),
'tag' => 'loop_index',
'type' => 'looper',
'supports' => [],
'visibility' => [
'context' => [
'generateblocks/loopIndex',
],
],
'options' => [
'zeroBased' => [
'type' => 'checkbox',
'label' => __( 'Use zero-based index', 'generateblocks-pro' ),
'help' => __( 'Enable this to start the loop index count from 0.', 'generateblocks-pro' ),
],
],
'description' => __( 'The numbered index of the loop item.', 'generateblocks-pro' ),
'return' => [ $this, 'get_loop_index' ],
]
);
new GenerateBlocks_Register_Dynamic_Tag(
[
'title' => __( 'Loop Item', 'generateblocks-pro' ),
'tag' => 'loop_item',
'type' => 'looper',
'supports' => [ 'properties' ],
'visibility' => [
'context' => [
'generateblocks/loopItem',
],
],
'description' => __( 'The current loop item data.', 'generateblocks-pro' ),
'return' => [ $this, 'get_loop_item' ],
]
);
}
/**
* Get the archive title.
*
* @param array $options The options.
* @param object $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_archive_title( $options, $block, $instance ) {
$output = '';
$id = $options['id'] ?? 0;
if ( is_category() ) {
$output = single_cat_title( '', false );
} elseif ( is_tag() ) {
$output = single_tag_title( '', false );
} elseif ( is_author() ) {
$output = get_the_author();
} elseif ( is_post_type_archive() ) {
$output = post_type_archive_title( '', false );
} elseif ( is_tax() ) {
$output = single_term_title( '', false );
} elseif ( is_home() ) {
$page = get_option( 'page_for_posts' ) ?? 0;
if ( $page ) {
$output = get_the_title( $page );
} else {
$output = __( 'Blog', 'generateblocks-pro' );
}
} elseif ( is_search() ) {
$output = get_search_query();
} elseif ( $id ) {
if ( term_exists( (int) $id ) ) {
$term = get_term( $id );
$output = $term->name;
} elseif ( is_string( $id ) ) {
// Assume it's a post type archive title.
$post_type_obj = get_post_type_object( $id );
$title = $post_type_obj->labels->name ?? '';
if ( $title ) {
/**
* Core Filter. Filters the post type archive title.
*
* @param string $post_type_name Post type 'name' label.
* @param string $post_type Post type.
*/
$output = apply_filters( 'post_type_archive_title', $title, $id );
}
}
}
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the archive description.
*
* @param array $options The options.
* @param object $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_archive_description( $options, $block, $instance ) {
$output = get_the_archive_description();
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the option.
*
* @param array $options The options.
* @return string
*/
public static function get_option( $options ) {
$default = $options['default'] ?? '';
$key = $options['key'] ?? '';
$key_parts = array_map( 'trim', explode( '.', $key ) );
$parent_name = $key_parts[0];
$output = '';
if ( empty( $key ) ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options );
}
$allowed_options = [
'siteurl',
'blogname',
'blogdescription',
'home',
'time_format',
'user_count',
];
$acf_keys = array_keys(
GenerateBlocks_Pro_Dynamic_Tags_ACF::get_instance()->get_acf_option_fields()
);
/**
* $allowed_options contains an array of keys from the options table along with the
* acf option keys. Disallowed keys will return an empty string.
*
* @since 2.0.0
* @param array $allowed_options Array of allowed option keys.
*/
$allowed_options = apply_filters(
'generateblocks_dynamic_tags_allowed_options',
array_merge( $allowed_options, $acf_keys )
);
if ( ! in_array( $parent_name, $allowed_options, true ) ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options );
}
$value = GenerateBlocks_Meta_Handler::get_option( $key, true, $default );
if ( ! $value ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options );
}
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 GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options );
}
/**
* Get the term meta.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_term_meta( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'term', $instance );
if ( ! $id ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( '', $options, $instance );
}
$key = $options['key'] ?? '';
$output = '';
if ( empty( $key ) ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
$value = GenerateBlocks_Meta_Handler::get_term_meta( $id, $key, true );
if ( ! $value ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $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 GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the user meta.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_user_meta( $options, $block, $instance ) {
$id = GenerateBlocks_Dynamic_Tags::get_id( $options, 'user', $instance );
if ( ! $id ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( '', $options, $instance );
}
$key = $options['key'] ?? '';
$output = '';
if ( empty( $key ) ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
$value = GenerateBlocks_Meta_Handler::get_user_meta( $id, $key, true );
if ( ! $value ) {
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $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 GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the current year.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
*
* @return string
*/
public static function get_current_year( $options, $block, $instance ) {
$output = wp_date( 'Y' );
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the site title from settings.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_site_title( $options, $block, $instance ) {
$output = get_option( 'blogname' );
return GenerateBlocks_Dynamic_Tag_Callbacks::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_site_tagline( $options, $block, $instance ) {
$output = get_option( 'blogdescription' );
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the site logo URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_site_logo_url( $options, $block, $instance ) {
$custom_logo_id = get_theme_mod( 'custom_logo' );
$output = '';
if ( $custom_logo_id ) {
$url = wp_get_attachment_url( $custom_logo_id );
if ( $url ) {
$output = esc_url( $url );
}
}
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the site URL.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string
*/
public static function get_site_url( $options, $block, $instance ) {
$output = esc_url( site_url() );
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
/**
* Get the index of the current looper block loop.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return int The loop index number.
*/
public static function get_loop_index( $options, $block, $instance ) {
$use_zero_based = $options['zeroBased'] ?? false;
$loop_index = (int) isset( $instance->context['generateblocks/loopIndex'] )
? $instance->context['generateblocks/loopIndex']
: -1;
if ( $use_zero_based ) {
--$loop_index;
}
if ( $loop_index > -1 ) {
return (string) $loop_index;
}
}
/**
* Get the current loop item.
*
* @param array $options The options.
* @param array $block The block.
* @param object $instance The block instance.
* @return string Value of the loop item or a given key's value from the loop item.
*/
public static function get_loop_item( $options, $block, $instance ) {
$key = $options['key'] ?? '';
$fallback = $options['fallback'] ?? '';
$loop_item = $instance->context['generateblocks/loopItem'] ?? [];
$output = GenerateBlocks_Meta_Handler::get_value( $key, $loop_item, true, $fallback );
return GenerateBlocks_Dynamic_Tag_Callbacks::output( $output, $options, $instance );
}
}
GenerateBlocks_Pro_Dynamic_Tags_Register::get_instance()->init();
@@ -0,0 +1,455 @@
<?php
/**
* This file handles the Accordion functions.
*
* @package GenerateBlocksPro/Extend/Interactions/Accordion
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Accordion functions.
*
* @since 1.5.0
*/
class GenerateBlocks_Pro_Block_Variant_Accordion extends GenerateBlocks_Pro_Singleton {
/**
* Constructor.
*/
public function __construct() {
parent::__construct();
if ( ! version_compare( GENERATEBLOCKS_VERSION, '1.7.0-alpha.1', '>=' ) ) {
return;
}
add_filter( 'generateblocks_defaults', [ $this, 'set_defaults' ] );
add_filter( 'generateblocks_after_container_open', [ $this, 'enqueue_scripts' ], 10, 2 );
add_filter( 'generateblocks_attr_container', [ $this, 'set_container_attributes' ], 10, 4 );
add_filter( 'generateblocks_attr_dynamic-button', [ $this, 'set_button_attributes' ], 10, 2 );
add_action( 'generateblocks_block_one_time_css_data', [ $this, 'generate_css' ], 10, 3 );
add_action( 'generateblocks_block_css_data', [ $this, 'do_dynamic_css' ], 10, 3 );
add_filter( 'generateblocks_before_container_open', [ $this, 'open_accordion_content_container' ], 1, 3 );
add_filter( 'generateblocks_after_container_close', [ $this, 'close_accordion_content_container' ], 100, 3 );
add_filter( 'generateblocks_onboarding_user_meta_properties', [ $this, 'define_add_accordion_item_onboarding_property' ], 10, 1 );
add_filter( 'register_block_type_args', [ $this, 'block_type_args' ], 10, 2 );
add_action( 'wp_footer', [ $this, 'faq_schema_script' ] );
add_filter( 'render_block', [ $this, 'gather_schema_data' ], 10, 3 );
}
/**
* Set our attribute defaults.
*
* @param array $defaults Existing defaults.
*/
public function set_defaults( $defaults ) {
$defaults['container']['accordionItemOpen'] = false;
$defaults['container']['accordionMultipleOpen'] = false;
$defaults['container']['accordionTransition'] = '';
$defaults['button']['accordionItemOpen'] = false;
return $defaults;
}
/**
* Enqueue our accordion script.
*
* @param string $content Block content.
* @param array $attributes Block attributes.
*/
public function enqueue_scripts( $content, $attributes ) {
if ( ! empty( $attributes['variantRole'] ) && 'accordion' === $attributes['variantRole'] ) {
wp_enqueue_script(
'generateblocks-accordion',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/accordion.js',
array(),
GENERATEBLOCKS_PRO_VERSION,
true
);
}
return $content;
}
/**
* Set our Container block HTML attributes.
*
* @param array $attributes HTML attributes.
* @param array $settings Block settings.
* @param string $context Context of the filter.
* @param WP_Block $block Block instance.
*/
public function set_container_attributes( $attributes, $settings, $context, $block ) {
if ( isset( $settings['variantRole'] ) && 'accordion' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-accordion';
if ( $settings['accordionMultipleOpen'] ) {
$attributes['data-accordion-multiple-open'] = true;
}
}
if ( isset( $settings['variantRole'] ) && 'accordion-item' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-accordion__item';
if ( $settings['accordionItemOpen'] ) {
$attributes['class'] .= ' gb-accordion__item-open';
}
if ( $settings['accordionTransition'] ) {
$attributes['data-transition'] = $settings['accordionTransition'];
}
}
if ( isset( $settings['variantRole'] ) && 'accordion-content' === $settings['variantRole'] ) {
$transition = isset( $block->context['generateblocks-pro/accordionTransition'] )
? $block->context['generateblocks-pro/accordionTransition']
: '';
if ( 'slide' !== $transition ) {
$attributes['class'] .= ' gb-accordion__content';
}
// Unset the accordion content ID. This ID is added to the wrapping div
// in the open_accordion_content_container function.
$attributes['id'] = '';
}
if ( isset( $settings['variantRole'] ) && 'accordion-toggle' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-accordion__toggle';
if ( $settings['accordionItemOpen'] ) {
$attributes['class'] .= ' gb-block-is-current';
}
$attributes['role'] = 'button';
$attributes['tabindex'] = '0';
}
return $attributes;
}
/**
* Set our dynamic Button block HTML attributes.
*
* @param array $attributes HTML attributes.
* @param array $settings Block settings.
*/
public function set_button_attributes( $attributes, $settings ) {
if ( isset( $settings['variantRole'] ) && 'accordion-toggle' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-accordion__toggle';
if ( $settings['accordionItemOpen'] ) {
$attributes['class'] .= ' gb-block-is-current';
}
}
return $attributes;
}
/**
* Generate our one-time accordion CSS.
*
* @param string $name The block name.
* @param array $settings Block settings.
* @param object $css The CSS object.
*/
public function generate_css( $name, $settings, $css ) {
if ( 'button' === $name ) {
$css->set_selector( '.gb-accordion__item:not(.gb-accordion__item-open) > .gb-button .gb-accordion__icon-open' );
$css->add_property( 'display', 'none' );
$css->set_selector( '.gb-accordion__item.gb-accordion__item-open > .gb-button .gb-accordion__icon' );
$css->add_property( 'display', 'none' );
}
}
/**
* Generate our CSS for our options.
*
* @since 1.0
* @param string $name Name of the block.
* @param array $settings Our available settings.
* @param object $css Current desktop CSS object.
*/
public function do_dynamic_css( $name, $settings, $css ) {
if ( 'container' === $name && isset( $settings['variantRole'] ) ) {
$accordion_transition = ! empty( $settings['accordionTransition'] ) ? $settings['accordionTransition'] : '';
$selector = function_exists( 'generateblocks_get_css_selector' )
? generateblocks_get_css_selector( 'container', $settings )
: '.gb-container-' . $settings['uniqueId'];
if ( 'accordion-item' === $settings['variantRole'] ) {
if ( '' === $accordion_transition || 'fade' === $accordion_transition ) {
$css->set_selector( $selector . ':not(.gb-accordion__item-open) > .gb-accordion__content' );
$css->add_property( 'display', 'none' );
if ( 'fade' === $accordion_transition ) {
$css->set_selector( $selector . '.gb-accordion__item-open > .gb-accordion__content' );
$css->add_property( 'opacity', 1 );
$css->set_selector( $selector . '.gb-accordion__item-transition > .gb-accordion__content' );
$css->add_property( 'opacity', 0 );
}
}
if ( 'slide' === $accordion_transition ) {
$css->set_selector( $selector . ' > .gb-accordion__content' );
$css->add_property( 'will-change', 'max-height' );
$css->add_property( 'max-height', 0 );
$css->add_property( 'overflow', 'hidden' );
$css->add_property( 'visibility', 'hidden' );
$css->set_selector( $selector . '.gb-accordion__item-open > .gb-accordion__content' );
$css->add_property( 'max-height', 'inherit' );
$css->add_property( 'visibility', 'visible' );
}
}
if ( 'accordion-toggle' === $settings['variantRole'] ) {
$css->set_selector( $selector );
$css->add_property( 'cursor', 'pointer' );
}
}
}
/**
* Inject our opening accordion content div.
*
* @param string $content Block content.
* @param array $attributes Block attributes.
* @param WP_Block $block Block instance.
*/
public function open_accordion_content_container( $content, $attributes, $block ) {
$transition = isset( $block->context['generateblocks-pro/accordionTransition'] )
? $block->context['generateblocks-pro/accordionTransition']
: '';
if ( isset( $attributes['variantRole'] ) && 'accordion-content' === $attributes['variantRole'] && 'slide' === $transition ) {
if ( ! empty( $attributes['anchor'] ) ) {
$content = '<div id="' . esc_attr( $attributes['anchor'] ) . '" class="gb-accordion__content">' . $content;
} else {
$content = '<div class="gb-accordion__content">' . $content;
}
}
return $content;
}
/**
* Inject our closing accordion content div.
*
* @param string $content Block content.
* @param array $attributes Block attributes.
* @param WP_Block $block Block instance.
*/
public function close_accordion_content_container( $content, $attributes, $block ) {
$transition = isset( $block->context['generateblocks-pro/accordionTransition'] )
? $block->context['generateblocks-pro/accordionTransition']
: '';
if ( isset( $attributes['variantRole'] ) && 'accordion-content' === $attributes['variantRole'] && 'slide' === $transition ) {
$content .= '</div>';
}
return $content;
}
/**
* Define the onboarding key for adding accordion items.
*
* @param array $properties The registered keys.
*
* @return array
*/
public function define_add_accordion_item_onboarding_property( $properties ) {
$properties['add_accordion_item'] = array( 'type' => 'boolean' );
return $properties;
}
/**
* Filter block args.
*
* @param array $args Existing args.
* @param string $name The block name.
*/
public function block_type_args( $args, $name ) {
if ( 'generateblocks/container' === $name ) {
if ( ! isset( $args['provides_context'] ) || ! is_array( $args['provides_context'] ) ) {
$args['provides_context'] = [];
}
$args['provides_context'] = array_merge(
$args['provides_context'],
[
'generateblocks-pro/accordionTransition' => 'accordionTransition',
'generateblocks-pro/faqSchema' => 'faqSchema',
]
);
if ( ! isset( $args['uses_context'] ) || ! is_array( $args['uses_context'] ) ) {
$args['uses_context'] = [];
}
$args['uses_context'] = array_merge(
$args['uses_context'],
[
'generateblocks-pro/accordionTransition',
'generateblocks-pro/faqSchema',
]
);
}
if ( 'generateblocks/button' === $name ) {
if ( ! isset( $args['provides_context'] ) || ! is_array( $args['provides_context'] ) ) {
$args['provides_context'] = [];
}
$args['provides_context'] = array_merge(
$args['provides_context'],
[
'generateblocks-pro/faqSchema' => 'faqSchema',
]
);
if ( ! isset( $args['uses_context'] ) || ! is_array( $args['uses_context'] ) ) {
$args['uses_context'] = [];
}
$args['uses_context'] = array_merge(
$args['uses_context'],
[
'generateblocks-pro/faqSchema',
]
);
}
return $args;
}
/**
* Remove all HTML and line-breaks/whitespace from the content.
*
* @param string $content The block content.
*/
public static function strip_html( $content ) {
$allowed_tags = [ '<h1>', '<h2>', '<h3>', '<h4>', '<h5>', '<h6>', '<br>', '<ol>', '<ul>', '<li>', '<a>', '<p>', '<b>', '<strong>', '<i>', '<em>' ];
$allowed_tags = implode( '', $allowed_tags );
$block_text = strip_tags( $content, $allowed_tags );
$block_text = preg_replace( "/\n/", '', $block_text ); // Remove new lines.
return $block_text;
}
/**
* Gather FAQ schema from accordions.
*
* @since 1.6.0
* @param string $block_content The block content.
* @param array $block The block attributes/data.
* @param WP_Block $instance The block instance.
* @return array
*/
public function gather_schema_data( $block_content, $block, $instance ) {
if (
isset( $block['attrs']['variantRole'] ) &&
isset( $instance->context ) &&
! empty( $instance->context['generateblocks-pro/faqSchema'] )
) {
if ( 'accordion-toggle' === $block['attrs']['variantRole'] ) {
add_filter(
'generateblocks_faq_schema_data',
function( $data ) use ( $block_content ) {
$data[] = [ 'question' => self::strip_html( $block_content ) ];
return $data;
},
1
);
}
if ( 'accordion-content' === $block['attrs']['variantRole'] ) {
add_filter(
'generateblocks_faq_schema_data',
function( $data ) use ( $block_content ) {
foreach ( $data as $key => $info ) {
// If we have a question but no answer, assume this is the answer.
if ( ! empty( $info['question'] ) && empty( $info['answer'] ) ) {
preg_match( '/<img.+src=[\'"](?P<src>.+?)[\'"].*>/i', $block_content, $image );
if ( ! empty( $image['src'] ) ) {
$data[ $key ]['image'] = $image['src'];
}
$data[ $key ]['answer'] = self::strip_html( $block_content );
break;
}
}
return $data;
},
2
);
}
}
return $block_content;
}
/**
* Output the accordion FAQ schema to the footer.
*
* @since 1.6.0
*/
public function faq_schema_script() {
$faq_schema_data = apply_filters(
'generateblocks_faq_schema_data',
[]
);
if ( empty( $faq_schema_data ) ) {
return;
}
// Remove any duplicated questions/answers.
// @see https://stackoverflow.com/a/308955.
$serialized = array_map( 'json_encode', $faq_schema_data );
$unique = array_unique( $serialized );
$faq_schema_data = array_values( array_intersect_key( $faq_schema_data, $unique ) );
$faq_schema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'name' => get_the_title(),
'mainEntity' => [],
];
foreach ( $faq_schema_data as $data ) {
$question = [
'@type' => 'Question',
'name' => $data['question'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $data['answer'],
],
];
if ( ! empty( $data['image'] ) ) {
$question['acceptedAnswer']['image'] = [
'@type' => 'ImageObject',
'contentUrl' => esc_url_raw( $data['image'] ),
];
}
$faq_schema['mainEntity'][] = $question;
}
if ( count( $faq_schema['mainEntity'] ) > 0 ) {
printf( '<script type="application/ld+json">%s</script>', wp_json_encode( $faq_schema ) );
}
}
}
GenerateBlocks_Pro_Block_Variant_Accordion::get_instance();
@@ -0,0 +1,196 @@
<?php
/**
* Our Tabs block.
*
* @package GenerateBlocks/Extend/Interactions/Tabs
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* GenerateBlocks Pro Tabs.
*
* @since 1.4.0
*/
class GenerateBlocks_Pro_Tabs_Variation extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
protected function __construct() {
parent::__construct();
if ( ! version_compare( GENERATEBLOCKS_VERSION, '1.7.0-alpha.1', '>=' ) ) {
return;
}
add_filter( 'generateblocks_defaults', [ $this, 'set_defaults' ] );
add_action( 'generateblocks_block_one_time_css_data', [ $this, 'generate_css' ], 10, 3 );
add_action( 'generateblocks_block_css_data', [ $this, 'do_dynamic_css' ], 10, 3 );
add_filter( 'generateblocks_attr_container', [ $this, 'set_container_attributes' ], 10, 2 );
add_filter( 'generateblocks_attr_dynamic-button', [ $this, 'set_button_attributes' ], 10, 2 );
add_filter( 'generateblocks_after_container_open', [ $this, 'enqueue_scripts' ], 10, 2 );
add_filter( 'generateblocks_onboarding_user_meta_properties', [ $this, 'define_add_tab_item_onboarding_property' ], 10, 1 );
}
/**
* Set our attribute defaults.
*
* @param array $defaults Existing defaults.
*/
public function set_defaults( $defaults ) {
$defaults['container']['defaultOpenedTab'] = '';
$defaults['container']['tabItemOpen'] = false;
$defaults['container']['tabTransition'] = '';
$defaults['container']['borderColorCurrent'] = false;
$defaults['container']['backgroundColorCurrent'] = '';
$defaults['container']['textColorCurrent'] = '';
$defaults['button']['tabItemOpen'] = false;
return $defaults;
}
/**
* Enqueue our tabs script.
*
* @param string $content Block content.
* @param array $attributes Block attributes.
*/
public function enqueue_scripts( $content, $attributes ) {
if ( ! empty( $attributes['variantRole'] ) && 'tabs' === $attributes['variantRole'] ) {
wp_enqueue_script(
'generateblocks-tabs',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/tabs.js',
array(),
GENERATEBLOCKS_PRO_VERSION,
true
);
}
return $content;
}
/**
* Generate our one-time tabs CSS.
*
* @param string $name The block name.
* @param array $settings Block settings.
* @param object $css The CSS object.
*/
public function generate_css( $name, $settings, $css ) {
if ( 'container' === $name ) {
$css->set_selector( '.gb-container.gb-tabs__item:not(.gb-tabs__item-open)' );
$css->add_property( 'display', 'none' );
}
}
/**
* Generate our CSS for our options.
*
* @since 1.0
* @param string $name Name of the block.
* @param array $settings Our available settings.
* @param object $css Current desktop CSS object.
*/
public function do_dynamic_css( $name, $settings, $css ) {
if ( 'container' === $name && isset( $settings['variantRole'] ) ) {
$selector = function_exists( 'generateblocks_get_css_selector' )
? generateblocks_get_css_selector( 'container', $settings )
: '.gb-container-' . $settings['uniqueId'];
if ( 'tab-button' === $settings['variantRole'] ) {
$css->set_selector( $selector . ':not(.gb-block-is-current)' );
$css->add_property( 'cursor', 'pointer' );
}
if ( 'tab-items' === $settings['variantRole'] && 'fade' === $settings['tabTransition'] ) {
$css->set_selector( $selector . ' > .gb-tabs__item-open' );
$css->add_property( 'opacity', 1 );
$css->set_selector( $selector . ' > .gb-tabs__item-transition' );
$css->add_property( 'opacity', 0 );
}
}
}
/**
* Set our Container block HTML attributes.
*
* @param array $attributes HTML attributes.
* @param array $settings Block settings.
*/
public function set_container_attributes( $attributes, $settings ) {
if ( isset( $settings['variantRole'] ) ) {
if ( 'tabs' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs';
$attributes['data-opened-tab'] = $settings['defaultOpenedTab'];
}
if ( 'tab-items' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs__items';
}
if ( 'tab-buttons' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs__buttons';
}
if ( 'tab-button' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs__button';
if ( true === $settings['tabItemOpen'] ) {
$attributes['class'] .= ' gb-block-is-current';
}
$attributes['role'] = 'button';
$attributes['tabindex'] = '0';
}
if ( 'tab-item' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs__item';
if ( $settings['tabItemOpen'] ) {
$attributes['class'] .= ' gb-tabs__item-open';
}
}
}
return $attributes;
}
/**
* Set our Button block HTML attributes.
*
* @param array $attributes HTML attributes.
* @param array $settings Block settings.
*/
public function set_button_attributes( $attributes, $settings ) {
if ( isset( $settings['variantRole'] ) ) {
if ( 'tab-button' === $settings['variantRole'] ) {
$attributes['class'] .= ' gb-tabs__button';
if ( $settings['tabItemOpen'] ) {
$attributes['class'] .= ' gb-block-is-current';
}
}
}
return $attributes;
}
/**
* Define the onboarding key for adding tab items.
*
* @param array $properties The registered keys.
*
* @return array
*/
public function define_add_tab_item_onboarding_property( $properties ) {
$properties['add_tab_item'] = array( 'type' => 'boolean' );
return $properties;
}
}
GenerateBlocks_Pro_Tabs_Variation::get_instance();
@@ -0,0 +1,186 @@
<?php
/**
* Extend the Looper block.
*
* @package GenerateBlocksPro\Extend\Looper
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Extend the default Query block.
*
* @since 2.0.0
*/
class GenerateBlocks_Pro_Block_Looper extends GenerateBlocks_Pro_Singleton {
/**
* Init class.
*
* @return void
*/
public function init() {
add_filter( 'generateblocks_looper_render_loop_items', [ $this, 'render_loop_items' ], 10, 5 );
}
/**
* Render loop items based on the query type.
*
* @param string $output The block output.
* @param string $query_type The query type.
* @param array|object $query_data The query data.
* @param WP_Block $block The block instance.
*
* @return string The render content.
*/
public function render_loop_items( $output, $query_type, $query_data, $block ) {
if ( GenerateBlocks_Pro_Block_Query::TYPE_POST_META === $query_type ) {
return self::render_post_meta_loop_items( $query_data, $block );
}
if ( GenerateBlocks_Pro_Block_Query::TYPE_OPTION === $query_type ) {
return self::render_option_loop_items( $query_data, $block );
}
return $output;
}
/**
* Render the repeater items for the Looper block.
*
* @param array $items The items to loop over.
* @param WP_Block $block The block instance.
* @return string The rendered content.
*/
public static function render_post_meta_loop_items( $items, $block ) {
$query_id = $block->context['generateblocks/queryData']['id'] ?? null;
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$args = $block->context['generateblocks/queryData']['args'] ?? [];
$per_page = $args['posts_per_page'] ?? get_option( 'posts_per_page' );
$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 = '';
$index = $offset + 1;
// Adjust value to support array_slice.
if ( '-1' === $per_page ) {
$per_page = count( $items );
}
if ( is_array( $items ) ) {
$items = array_slice( $items, $offset, $per_page );
foreach ( $items as $item ) {
// Get the current index of the Loop.
$content .= (
new WP_Block(
$block->parsed_block['innerBlocks'][0],
array(
'postType' => get_post_type(),
'postId' => get_the_ID(),
'generateblocks/queryType' => GenerateBlocks_Pro_Block_Query::TYPE_POST_META,
'generateblocks/loopIndex' => $index,
'generateblocks/loopItem' => $item,
)
)
)->render( array( 'dynamic' => false ) );
$index++;
}
return $content;
}
// Fallback to support previews in Elements.
$content = (
new WP_Block(
$block->parsed_block['innerBlocks'][0],
array(
'postType' => 'post',
'postId' => 0,
'generateblocks/queryType' => GenerateBlocks_Pro_Block_Query::TYPE_POST_META,
'generateblocks/loopIndex' => 1,
'generateblocks/loopItem' => [ 'ID' => 0 ],
)
)
)->render( array( 'dynamic' => false ) );
return $content;
}
/**
* Render the repeater items for the Looper block.
*
* @param array $items The items to loop over.
* @param WP_Block $block The block instance.
* @return string The rendered content.
*/
public static function render_option_loop_items( $items, $block ) {
$query_id = $block->context['generateblocks/queryData']['id'] ?? null;
$page_key = $query_id ? 'query-' . $query_id . '-page' : 'query-page';
$args = $block->context['generateblocks/queryData']['args'] ?? [];
$per_page = $args['posts_per_page'] ?? get_option( 'posts_per_page' );
$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 = '';
$index = $offset + 1;
// Adjust value to support array_slice.
if ( '-1' === $per_page ) {
$per_page = count( $items );
}
$inner_blocks = $block->parsed_block['innerBlocks'];
if ( ! $inner_blocks ) {
return $content;
}
if ( $items ) {
$items = array_slice( $items, $offset, $per_page );
foreach ( $items as $item ) {
// Get the current index of the Loop.
$content .= (
new WP_Block(
$inner_blocks[0],
array(
'postType' => $item['post_type'] ?? null,
'postId' => $item['ID'] ?? $item['id'] ?? 0,
'generateblocks/queryType' => GenerateBlocks_Pro_Block_Query::TYPE_OPTION,
'generateblocks/loopIndex' => $index,
'generateblocks/loopItem' => $item,
)
)
)->render( array( 'dynamic' => false ) );
$index++;
}
return $content;
}
// Fallback to support previews in Elements.
$content = (
new WP_Block(
$inner_blocks[0],
array(
'postType' => 'post',
'postId' => 0,
'generateblocks/queryType' => GenerateBlocks_Pro_Block_Query::TYPE_OPTION,
'generateblocks/loopIndex' => 1,
'generateblocks/loopItem' => [ 'ID' => 0 ],
)
)
)->render( array( 'dynamic' => false ) );
return $content;
}
}
GenerateBlocks_Pro_Block_Looper::get_instance()->init();
@@ -0,0 +1,489 @@
<?php
/**
* Menu Item Conditions functionality.
*
* @package GenerateBlocksPro
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class GenerateBlocks_Pro_Menu_Item_Conditions
*/
class GenerateBlocks_Pro_Menu_Item_Conditions extends GenerateBlocks_Pro_Singleton {
/**
* Cache for evaluated conditions to avoid repeated checks.
*
* @var array
*/
private $condition_cache = [];
/**
* Initialize the class.
*/
public function init() {
// Don't initialize if block conditions are disabled.
if ( ! generateblocks_pro_block_conditions_enabled() ) {
return;
}
// Register post meta for menu items.
add_action( 'init', [ $this, 'register_post_meta' ] );
// Add fields to menu item editor.
add_action( 'wp_nav_menu_item_custom_fields', [ $this, 'add_condition_fields' ], 20, 5 );
// Save menu item conditions.
add_action( 'wp_update_nav_menu_item', [ $this, 'save_menu_item_conditions' ], 10, 2 );
// Filter menu items based on conditions.
add_filter( 'wp_nav_menu_objects', [ $this, 'filter_menu_items_by_conditions' ], 10, 2 );
// Add to condition usage tracking.
add_filter( 'generateblocks_condition_usage_handlers', [ $this, 'add_menu_usage_handler' ], 10, 2 );
// Add admin scripts for dynamic UI.
add_action( 'admin_enqueue_scripts', [ $this, 'add_admin_scripts' ] );
// AJAX handler for condition search.
add_action( 'wp_ajax_gb_search_conditions', [ $this, 'ajax_search_conditions' ] );
// AJAX handler for loading more conditions.
add_action( 'wp_ajax_gb_load_more_conditions', [ $this, 'ajax_load_more_conditions' ] );
}
/**
* Register the post meta.
*/
public function register_post_meta() {
register_post_meta(
'nav_menu_item',
'_gb_menu_condition',
[
'show_in_rest' => true,
'single' => true,
'type' => 'string',
]
);
register_post_meta(
'nav_menu_item',
'_gb_menu_condition_invert',
[
'show_in_rest' => true,
'single' => true,
'type' => 'boolean',
]
);
}
/**
* Get the conditions limit per page.
*/
private function get_conditions_limit() {
return apply_filters( 'generateblocks_menu_conditions_limit', 100 );
}
/**
* Add condition fields to menu items.
*
* @param string $item_id Menu item ID as a numeric string.
* @param WP_Post $menu_item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass|null $args An object of menu item arguments.
* @param int $current_object_id Nav menu ID.
*/
public function add_condition_fields( $item_id, $menu_item, $depth, $args, $current_object_id ) {
$condition_id = get_post_meta( $item_id, '_gb_menu_condition', true );
$invert = get_post_meta( $item_id, '_gb_menu_condition_invert', true );
// Get conditions for dropdown.
static $conditions_cache = null;
if ( null === $conditions_cache ) {
// Get the most recent conditions.
$conditions_cache = get_posts(
[
'post_type' => 'gblocks_condition',
'posts_per_page' => $this->get_conditions_limit(),
'orderby' => 'date',
'order' => 'DESC',
'post_status' => 'publish',
'suppress_filters' => false,
'no_found_rows' => true,
]
);
}
$conditions = $conditions_cache;
// Always include the currently selected condition if it's not in the list.
if ( $condition_id && 'gblocks_condition' === get_post_type( $condition_id ) ) {
$found = false;
foreach ( $conditions as $condition ) {
if ( $condition->ID === $condition_id ) {
$found = true;
break;
}
}
if ( ! $found ) {
$selected_condition = get_post( $condition_id );
if ( $selected_condition ) {
// Add it to the list for this menu item only.
$conditions = array_merge( [ $selected_condition ], $conditions );
}
}
}
// Check if there are more conditions than we're showing.
$total_conditions = wp_count_posts( 'gblocks_condition' )->publish;
$has_more = $total_conditions > count( $conditions_cache );
?>
<p class="field-gb-menu-condition description description-wide">
<label for="gb-menu-condition-<?php echo esc_attr( $item_id ); ?>">
<?php esc_html_e( 'Display Condition', 'generateblocks-pro' ); ?>
</label>
<div class="gb-menu-condition-wrapper" style="display: flex; align-items: center; gap: 10px;">
<select
name="gb-menu-condition[<?php echo esc_attr( $item_id ); ?>]"
id="gb-menu-condition-<?php echo esc_attr( $item_id ); ?>"
class="gb-menu-condition-select"
style="flex: 1;"
data-item-id="<?php echo esc_attr( $item_id ); ?>"
data-page="1"
>
<option value=""><?php esc_html_e( 'No condition', 'generateblocks-pro' ); ?></option>
<?php foreach ( $conditions as $condition ) : ?>
<option value="<?php echo esc_attr( $condition->ID ); ?>" <?php selected( $condition_id, $condition->ID ); ?>>
<?php echo esc_html( $condition->post_title ); ?>
</option>
<?php endforeach; ?>
</select>
<?php if ( $has_more ) : ?>
<button
type="button"
class="button gb-load-more-conditions"
data-item-id="<?php echo esc_attr( $item_id ); ?>"
>
<?php esc_html_e( 'Load More', 'generateblocks-pro' ); ?>
</button>
<?php endif; ?>
</div>
<span class="description" style="display: block; margin-top: 5px;">
<?php esc_html_e( 'Choose a condition to control when this menu item appears.', 'generateblocks-pro' ); ?>
</span>
</p>
<p class="field-gb-menu-condition-invert description description-wide" style="<?php echo $condition_id ? '' : 'display: none;'; ?>">
<label>
<input
type="checkbox"
name="gb-menu-condition-invert[<?php echo esc_attr( $item_id ); ?>]"
value="1"
<?php checked( $invert, '1' ); ?>
/>
<?php esc_html_e( 'Invert condition', 'generateblocks-pro' ); ?>
</label>
<span class="description" style="display: block"><?php esc_html_e( 'Hide the menu item when the condition is true instead of false.', 'generateblocks-pro' ); ?></span>
</p>
<?php wp_nonce_field( 'update-menu-item-condition', 'menu-item-condition-nonce' ); ?>
<?php
}
/**
* Save the menu item conditions.
*
* @param int $menu_id The menu ID.
* @param int $menu_item_id The menu item ID.
* @return mixed
*/
public function save_menu_item_conditions( $menu_id, $menu_item_id ) {
// Verify nonce.
if (
! isset( $_POST['menu-item-condition-nonce'] )
|| ! wp_verify_nonce( $_POST['menu-item-condition-nonce'], 'update-menu-item-condition' )
) {
return $menu_id;
}
// Save condition from dropdown.
$condition_id = isset( $_POST['gb-menu-condition'][ $menu_item_id ] )
? sanitize_text_field( $_POST['gb-menu-condition'][ $menu_item_id ] )
: '';
if ( $condition_id ) {
update_post_meta( $menu_item_id, '_gb_menu_condition', $condition_id );
} else {
delete_post_meta( $menu_item_id, '_gb_menu_condition' );
}
// Save invert setting.
$invert = isset( $_POST['gb-menu-condition-invert'][ $menu_item_id ] ) ? '1' : '0';
if ( $condition_id && '1' === $invert ) {
update_post_meta( $menu_item_id, '_gb_menu_condition_invert', '1' );
} else {
delete_post_meta( $menu_item_id, '_gb_menu_condition_invert' );
}
return $menu_id;
}
/**
* Filter menu items based on conditions.
*
* @param array $sorted_menu_items The menu items, sorted by menu order.
* @param stdClass $args The menu arguments.
* @return array Filtered menu items.
*/
public function filter_menu_items_by_conditions( $sorted_menu_items, $args ) {
if ( empty( $sorted_menu_items ) ) {
return $sorted_menu_items;
}
$removed_items = [];
// Build list of all items that should be removed (condition fails or parent removed).
// Note: WordPress guarantees menu items are in hierarchical order (parents before children).
foreach ( $sorted_menu_items as $item ) {
$item_id = (int) $item->ID;
// Skip if already marked for removal.
if ( in_array( $item_id, $removed_items, true ) ) {
continue;
}
// Check if parent was removed.
$parent_id = ! empty( $item->menu_item_parent ) ? (int) $item->menu_item_parent : 0;
if ( $parent_id && in_array( $parent_id, $removed_items, true ) ) {
$removed_items[] = $item_id;
continue;
}
// Check condition.
$condition_id = get_post_meta( $item_id, '_gb_menu_condition', true );
if ( $condition_id && ! $this->should_show_menu_item( $item_id, $condition_id ) ) {
$removed_items[] = $item_id;
}
}
// If no items were removed, return the original array.
if ( empty( $removed_items ) ) {
return $sorted_menu_items;
}
// Filter out removed items.
$final_items = [];
foreach ( $sorted_menu_items as $item ) {
if ( ! in_array( (int) $item->ID, $removed_items, true ) ) {
$final_items[] = $item;
}
}
return $final_items;
}
/**
* Check if a menu item should be shown based on its condition.
*
* @param int $menu_item_id The menu item ID.
* @param string $condition_id The condition ID.
* @return bool Whether to show the menu item.
*/
private function should_show_menu_item( $menu_item_id, $condition_id ) {
$condition_id = absint( $condition_id );
if ( ! $condition_id ) {
return true;
}
$invert_condition = get_post_meta( $menu_item_id, '_gb_menu_condition_invert', true ) === '1';
// Check cache first to avoid repeated evaluations.
$cache_key = $condition_id . '_' . ( $invert_condition ? '1' : '0' );
if ( isset( $this->condition_cache[ $cache_key ] ) ) {
return $this->condition_cache[ $cache_key ];
}
// Default to showing the menu item.
$show = true;
// Check if the condition post exists and is published.
$condition_post = get_post( $condition_id );
if ( $condition_post && 'publish' === $condition_post->post_status ) {
// Get the condition data.
$display_conditions = get_post_meta( $condition_id, '_gb_conditions', true );
if ( ! empty( $display_conditions ) ) {
// Use the existing conditions system to evaluate.
$show = GenerateBlocks_Pro_Conditions::show( $display_conditions );
// If invert is enabled, flip the result.
if ( $invert_condition ) {
$show = ! $show;
}
}
}
// Cache the result.
$this->condition_cache[ $cache_key ] = $show;
return $show;
}
/**
* Add menu item conditions handler to usage search.
*
* @param array $handlers Existing handlers.
* @param int $condition_id The condition ID.
* @return array Modified handlers.
*/
public function add_menu_usage_handler( $handlers, $condition_id ) {
$handlers['menu_item_conditions'] = [
'method' => 'search_menu_item_conditions_usage',
'label' => __( 'Menu Item Conditions', 'generateblocks-pro' ),
];
return $handlers;
}
/**
* AJAX handler for searching conditions.
*/
public function ajax_search_conditions() {
// Verify nonce.
if ( ! wp_verify_nonce( $_GET['_ajax_nonce'], 'gb_search_conditions' ) ) {
wp_die();
}
$search = isset( $_GET['search'] ) ? sanitize_text_field( $_GET['search'] ) : '';
$page = isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 1;
$per_page = $this->get_conditions_limit();
$args = [
'post_type' => 'gblocks_condition',
'posts_per_page' => $per_page,
'paged' => $page,
'orderby' => 'date',
'order' => 'DESC',
'post_status' => 'publish',
];
if ( $search ) {
$args['s'] = $search;
$args['orderby'] = 'relevance';
}
$conditions = get_posts( $args );
$total = wp_count_posts( 'gblocks_condition' )->publish;
$results = [];
foreach ( $conditions as $condition ) {
$results[] = [
'id' => $condition->ID,
'text' => $condition->post_title ? $condition->post_title : __( 'Untitled Condition', 'generateblocks-pro' ),
];
}
wp_send_json(
[
'results' => $results,
'pagination' => [
'more' => ( $page * $per_page ) < $total,
],
]
);
}
/**
* AJAX handler for loading more conditions.
*/
public function ajax_load_more_conditions() {
// Verify nonce.
if ( ! wp_verify_nonce( $_GET['_ajax_nonce'], 'gb_load_more_conditions' ) ) {
wp_die();
}
$page = isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 2;
$limit = $this->get_conditions_limit();
$offset = ( $page - 1 ) * $limit;
$conditions = get_posts(
[
'post_type' => 'gblocks_condition',
'posts_per_page' => $limit,
'offset' => $offset,
'orderby' => 'date',
'order' => 'DESC',
'post_status' => 'publish',
'suppress_filters' => false,
'no_found_rows' => false,
]
);
$formatted_conditions = [];
foreach ( $conditions as $condition ) {
$formatted_conditions[] = [
'id' => $condition->ID,
'title' => $condition->post_title ? $condition->post_title : __( 'Untitled Condition', 'generateblocks-pro' ),
];
}
$total = wp_count_posts( 'gblocks_condition' )->publish;
$has_more = ( $page * $limit ) < $total;
wp_send_json_success(
[
'conditions' => $formatted_conditions,
'has_more' => $has_more,
]
);
}
/**
* Add admin scripts for dynamic UI behavior.
*
* @param string $hook_suffix The current admin page.
*/
public function add_admin_scripts( $hook_suffix ) {
// Only load on nav-menus.php page.
if ( 'nav-menus.php' !== $hook_suffix ) {
return;
}
wp_enqueue_script(
'gb-menu-item-conditions',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/menu-item-conditions.js',
array(),
GENERATEBLOCKS_PRO_VERSION,
true
);
wp_localize_script(
'gb-menu-item-conditions',
'gbMenuConditions',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonces' => array(
'loadMore' => wp_create_nonce( 'gb_load_more_conditions' ),
'search' => wp_create_nonce( 'gb_search_conditions' ),
),
'strings' => array(
'loading' => __( 'Loading...', 'generateblocks-pro' ),
'loadMore' => __( 'Load More', 'generateblocks-pro' ),
),
)
);
}
}
// Initialize the class.
GenerateBlocks_Pro_Menu_Item_Conditions::get_instance()->init();
@@ -0,0 +1,79 @@
<?php
/**
* The class for related author.
*
* @package Generateblocks/Extend/QueryLoop
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The class for related author.
*
* @since 1.3.0
*/
class GenerateBlocks_Pro_Related_Author extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
public function __construct() {
parent::__construct();
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Author::include_current_author' );
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Author::exclude_current_author' );
}
/**
* Include posts of current post author to the query loop.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public static function include_current_author( $query_args ) {
return self::add_current_author( $query_args, 'author__in' );
}
/**
* Exclude posts of current post author to the query loop.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public static function exclude_current_author( $query_args ) {
return self::add_current_author( $query_args, 'author__not_in' );
}
/**
* Include current author to a query argument.
*
* @since 1.3.0
* @param array $query_args The query arguments.
* @param string $key The query argument key.
*
* @return array The query arguments.
*/
protected static function add_current_author( $query_args, $key ) {
if (
isset( $query_args[ $key ] ) &&
in_array( 'current-post-author', $query_args[ $key ] )
) {
$current_post_author_index = array_search( 'current-post-author', $query_args[ $key ] );
array_splice( $query_args[ $key ], $current_post_author_index, 1 );
if ( ! in_array( get_the_author_meta( 'ID' ), $query_args[ $key ] ) ) {
$query_args[ $key ][] = get_the_author_meta( 'ID' );
}
}
return $query_args;
}
}
GenerateBlocks_Pro_Related_Author::get_instance();
@@ -0,0 +1,80 @@
<?php
/**
* The class for related parent.
*
* @package Generateblocks/Extend/QueryLoop
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The class for related parent.
*
* @since 1.3.0
*/
class GenerateBlocks_Pro_Related_Parent extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
public function __construct() {
parent::__construct();
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Parent::include_current_post' );
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Parent::exclude_current_post' );
}
/**
* Include current post as parent to the query loop.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public static function include_current_post( $query_args ) {
return self::add_current_post( $query_args, 'post_parent__in' );
}
/**
* Exclude current post as parent to the query loop.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public static function exclude_current_post( $query_args ) {
return self::add_current_post( $query_args, 'post_parent__not_in' );
}
/**
* Include current post to a query argument.
*
* @since 1.3.0
* @param array $query_args The query arguments.
* @param string $key The query argument key.
*
* @return array The query arguments.
*/
protected static function add_current_post( $query_args, $key ) {
if (
isset( $query_args[ $key ] ) &&
in_array( 'current-post', $query_args[ $key ] ) &&
get_post_type() === $query_args['post_type']
) {
$current_post_index = array_search( 'current-post', $query_args[ $key ] );
array_splice( $query_args[ $key ], $current_post_index, 1 );
if ( ! in_array( get_the_ID(), $query_args[ $key ] ) ) {
$query_args[ $key ][] = get_the_ID();
}
}
return $query_args;
}
}
GenerateBlocks_Pro_Related_Parent::get_instance();
@@ -0,0 +1,63 @@
<?php
/**
* The class for related post.
*
* @package Generateblocks/Extend/QueryLoop
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The related posts class.
*
* @since 1.3.0
*/
class GenerateBlocks_Pro_Related_Post extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
public function __construct() {
parent::__construct();
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Post::exclude_current_post' );
}
/**
* Exclude current post from the query loop.
*
* @since 1.3.0
* @param Array $query_args The query arguments.
*
* @return Array The query arguments without current post.
*/
public static function exclude_current_post( $query_args ) {
if (
isset( $query_args['post__not_in'] ) &&
in_array( 'exclude-current', $query_args['post__not_in'] ) &&
get_post_type() === $query_args['post_type']
) {
if ( ! in_array( get_the_ID(), $query_args['post__not_in'] ) ) {
$query_args['post__not_in'][] = get_the_ID();
}
$exclude_current_index = array_search( 'exclude-current', $query_args['post__not_in'] );
array_splice( $query_args['post__not_in'], $exclude_current_index, 1 );
// This is to avoid current post being dynamically added to post__in which will show him in the result set.
if (
isset( $query_args['post__in'] ) &&
in_array( get_the_ID(), $query_args['post__in'] )
) {
$current_post_index = array_search( get_the_ID(), $query_args['post__in'] );
array_splice( $query_args['post__in'], $current_post_index, 1 );
}
}
return $query_args;
}
}
GenerateBlocks_Pro_Related_Post::get_instance();
@@ -0,0 +1,96 @@
<?php
/**
* The class for related terms.
*
* @package Generateblocks/Extend/QueryLoop
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* The class for related terms.
*
* @since 1.3.0
*/
class GenerateBlocks_Pro_Related_Terms extends GenerateBlocks_Pro_Singleton {
/**
* The class constructor.
*/
public function __construct() {
parent::__construct();
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Terms::include_current_post_terms' );
add_filter( 'generateblocks_query_loop_args', 'GenerateBlocks_Pro_Related_Terms::exclude_current_post_terms' );
}
/**
* Include current post terms to the query loop.
*
* @since 1.3.0
* @param Array $query_args The query arguments.
*
* @return Array The query arguments with current post terms.
*/
public static function include_current_post_terms( $query_args ) {
return self::add_current_post_terms( $query_args, 'tax_query' );
}
/**
* Exclude current post terms to the query loop.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments without current post terms.
*/
public static function exclude_current_post_terms( $query_args ) {
return self::add_current_post_terms( $query_args, 'tax_query_exclude' );
}
/**
* Include current post term to a query argument.
*
* @since 1.3.0
* @param array $query_args The query arguments.
* @param string $key The query argument key.
*
* @return array The query arguments.
*/
protected static function add_current_post_terms( $query_args, $key ) {
if (
is_singular() &&
isset( $query_args[ $key ] )
) {
$query_args[ $key ] = array_map(
function( $tax ) {
if ( isset( $tax['terms'] ) && in_array( 'current-terms', $tax['terms'] ) ) {
$registered_taxonomies = get_object_taxonomies( get_post_type() );
if ( in_array( $tax['taxonomy'], $registered_taxonomies ) ) {
$related_terms = wp_get_object_terms(
get_the_ID(),
$tax['taxonomy'],
array( 'fields' => 'ids' )
);
$tax['terms'] = array_merge( $tax['terms'], $related_terms );
}
$current_terms_index = array_search( 'current-terms', $tax['terms'] );
array_splice( $tax['terms'], $current_terms_index, 1 );
}
return $tax;
},
$query_args[ $key ]
);
}
return $query_args;
}
}
GenerateBlocks_Pro_Related_Terms::get_instance();
@@ -0,0 +1,311 @@
<?php
/**
* Extend the Query block.
*
* @package GenerateBlocksPro\Extend\Query
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Extend the default Query block.
*
* @since 2.0.0
*/
class GenerateBlocks_Pro_Block_Query extends GenerateBlocks_Pro_Singleton {
// Add new Query types here.
const TYPE_POST_META = 'post_meta';
const TYPE_OPTION = 'option';
/**
* Init function.
*/
public function init() {
if ( ! class_exists( 'GenerateBlocks_Meta_Handler' ) ) {
return;
}
add_filter( 'generateblocks_query_data', [ $this, 'set_query_data' ], 10, 5 );
add_filter( 'generateblocks_dynamic_tag_id', [ $this, 'set_dynamic_tag_id' ], 10, 3 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'exclude_current_post' ], 10, 4 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'include_current_author' ], 10, 4 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'exclude_current_author' ], 10, 4 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'current_post_terms' ], 10, 4 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'include_current_parent' ], 10, 4 );
add_filter( 'generateblocks_query_wp_query_args', [ $this, 'exclude_current_parent' ], 10, 4 );
}
/**
* Update the dynamic tag's ID if it's a post tag, it has a valid loop item ID, and no other ID is set in options.
*
* @param int $id The current ID value for the tag.
* @param array $options The tag options.
* @param object $instance The block instance for the block containing the tag.
* @return int The ID for the dynamic tag.
*/
public function set_dynamic_tag_id( $id, $options, $instance ) {
// If an ID is set in options, use the original ID.
if ( $options['id'] ?? false ) {
return $id;
}
$loop_item = $instance->context['generateblocks/loopItem'] ?? null;
if ( ! $loop_item ) {
return $id;
}
// Look for the ID or id keys and return the original $id if none can be found.
if ( is_array( $loop_item ) ) {
return $loop_item['ID'] ?? $loop_item['id'] ?? $id;
} elseif ( is_object( $loop_item ) ) {
return $loop_item->ID ?? $loop_item->id ?? $id;
}
return $id;
}
/**
* Set the query data for certain types.
*
* @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 WP_Block $block The block instance.
* @param int $page The page number.
*
* @return array An array of query data.
*/
public function set_query_data( $query_data, $query_type, $attributes, $block, $page ) {
$pro_types = [
self::TYPE_POST_META,
self::TYPE_OPTION,
];
if ( ! in_array( $query_type, $pro_types, true ) ) {
return $query_data;
}
$query = $attributes['query'] ?? [];
$id = $query['meta_key_id'] ?? '';
$meta_key = $query['meta_key'] ?? '';
if ( 'current' === $id || ! $id ) {
$id = get_the_ID();
}
if ( self::TYPE_POST_META === $query_type ) {
$value = GenerateBlocks_Meta_Handler::get_post_meta( $id, $meta_key, false );
} elseif ( self::TYPE_OPTION === $query_type ) {
$value = GenerateBlocks_Meta_Handler::get_option( $meta_key, false );
}
$data = is_array( $value ) ? $value : [];
// Handle pagination.
$posts_per_page = (int) ( $query['posts_per_page'] ?? get_option( 'posts_per_page' ) );
$offset = isset( $query['offset'] ) && is_numeric( $query['offset'] ) ? $query['offset'] : 0;
// Get the total number of items less the user specified offset.
$data = array_slice( $data, $offset );
$max_pages = $posts_per_page > 0
? (int) ceil( count( $data ) / $posts_per_page )
: 1;
if ( 0 === $posts_per_page ) {
$max_pages = 0;
}
return [
'data' => $data,
'no_results' => empty( $data ),
'args' => $query,
'max_num_pages' => $max_pages,
];
}
/**
* Exclude current post from the query.
*
* @param array $query_args The query arguments.
* @param array $attributes The block attributes.
* @param WP_Block|null $block The block instance.
* @param array $current The current post data.
*
* @return array The query arguments without current post.
*/
public function exclude_current_post( $query_args, $attributes, $block, $current ) {
$current_post_id = $current['post_id'] ?? get_the_ID();
return self::add_current_post( $query_args, $current_post_id, 'post__not_in' );
}
/**
* Include current parent post from the query.
*
* @param array $query_args The query arguments.
* @param array $attributes The block attributes.
* @param WP_Block|null $block The block instance.
* @param array $current The current post data.
*
* @return array The query arguments without current post.
*/
public function include_current_parent( $query_args, $attributes, $block, $current ) {
$current_post_id = $current['post_id'] ?? get_the_ID();
return self::add_current_post( $query_args, $current_post_id, 'post_parent__in' );
}
/**
* Exclude current parent post from the query.
*
* @param array $query_args The query arguments.
* @param array $attributes The block attributes.
* @param WP_Block|null $block The block instance.
* @param array $current The current post data.
*
* @return array The query arguments without current post.
*/
public function exclude_current_parent( $query_args, $attributes, $block, $current ) {
$current_post_id = $current['post_id'] ?? get_the_ID();
return self::add_current_post( $query_args, $current_post_id, 'post_parent__not_in' );
}
/**
* Adds the current post ID to the query args.
*
* @param array $query_args The query arguments.
* @param int $current_post_id The current post ID.
* @param string $key The key to check.
*
* @return array The query arguments without current post.
*/
public static function add_current_post( $query_args, $current_post_id, $key ) {
if (
isset( $query_args[ $key ] ) &&
in_array( 'current', $query_args[ $key ] )
) {
if ( ! in_array( $current_post_id, $query_args[ $key ] ) ) {
$query_args[ $key ][] = $current_post_id;
}
$exclude_current_index = array_search( 'current', $query_args[ $key ] );
array_splice( $query_args[ $key ], $exclude_current_index, 1 );
if ( 'post__not_in' === $key ) {
// This is to avoid current post being dynamically added to post__in which will show him in the result set.
if (
isset( $query_args['post__in'] ) &&
in_array( $current_post_id, $query_args['post__in'] )
) {
$current_post_index = array_search( $current_post_id, $query_args['post__in'] );
array_splice( $query_args['post__in'], $current_post_index, 1 );
}
}
}
return $query_args;
}
/**
* Include posts of current post author to the query.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public function include_current_author( $query_args ) {
return self::add_current_author( $query_args, 'author__in' );
}
/**
* Exclude posts of current post author to the query.
*
* @since 1.3.0
* @param array $query_args The query arguments.
*
* @return array The query arguments.
*/
public function exclude_current_author( $query_args ) {
return self::add_current_author( $query_args, 'author__not_in' );
}
/**
* Include current author to a query argument.
*
* @since 1.3.0
* @param array $query_args The query arguments.
* @param string $key The query argument key.
*
* @return array The query arguments.
*/
public function add_current_author( $query_args, $key ) {
if (
isset( $query_args[ $key ] ) &&
in_array( 'current', $query_args[ $key ] )
) {
$current_post_author_index = array_search( 'current', $query_args[ $key ] );
array_splice( $query_args[ $key ], $current_post_author_index, 1 );
if ( ! in_array( get_the_author_meta( 'ID' ), $query_args[ $key ] ) ) {
$query_args[ $key ][] = get_the_author_meta( 'ID' );
}
}
return $query_args;
}
/**
* Process the "current" post terms.
*
* @param array $query_args The query arguments.
* @param array $attributes The block attributes.
* @param WP_Block|null $block The block instance.
* @param array $current The current post data.
*
* @return array The query arguments.
*/
public function current_post_terms( $query_args, $attributes, $block, $current ) {
if (
$current['post_id'] &&
isset( $query_args['tax_query'] )
) {
$query_args['tax_query'] = array_map(
function( $tax ) use ( $current ) {
if ( ! isset( $tax['terms'] ) ) {
return $tax;
}
if ( in_array( 'current', $tax['terms'], true ) ) {
$registered_taxonomies = get_object_taxonomies( get_post_type( $current['post_id'] ) );
if ( in_array( $tax['taxonomy'], $registered_taxonomies, true ) ) {
$related_terms = wp_get_object_terms(
$current['post_id'],
$tax['taxonomy'],
array( 'fields' => 'ids' )
);
$tax['terms'] = array_merge( $tax['terms'], $related_terms );
}
$current_terms_index = array_search( 'current', $tax['terms'] );
array_splice( $tax['terms'], $current_terms_index, 1 );
}
return $tax;
},
$query_args['tax_query']
);
}
return $query_args;
}
}
GenerateBlocks_Pro_Block_Query::get_instance()->init();