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,907 @@
<?php
/**
* Abstract Condition Base Class.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract base class for conditions.
*/
abstract class GenerateBlocks_Pro_Condition_Abstract implements GenerateBlocks_Pro_Condition_Interface {
/**
* Get default rule metadata.
*
* @return array
*/
protected function get_default_rule_metadata() {
return [
'needs_value' => true,
'value_type' => 'text',
];
}
/**
* Parse custom field value structure with edge case handling.
*
* @param string $value The value to parse.
* @return array
*/
protected function parse_custom_value( $value ) {
// Handle null, empty, or non-string values.
if ( null === $value || '' === $value ) {
return [
'field_name' => '',
'comparison_value' => '',
];
}
// Convert to string if not already (handles numbers, booleans).
if ( ! is_string( $value ) ) {
if ( is_scalar( $value ) ) {
$value = (string) $value;
} else {
// Arrays, objects, resources - return empty.
return [
'field_name' => '',
'comparison_value' => '',
];
}
}
// Handle extremely long values to prevent memory issues.
if ( 10000 < strlen( $value ) ) {
$value = substr( $value, 0, 10000 );
}
if ( false === strpos( $value, '|' ) ) {
return [
'field_name' => sanitize_text_field( $value ),
'comparison_value' => '',
];
}
$parts = explode( '|', $value, 2 );
return [
'field_name' => sanitize_text_field( $parts[0] ),
'comparison_value' => sanitize_text_field( $parts[1] ?? '' ),
];
}
/**
* Sanitize custom field value with comprehensive validation.
*
* @param mixed $value The value to sanitize.
* @return string
*/
protected function sanitize_custom_value( $value ) {
if ( null === $value || '' === $value ) {
return '';
}
// Handle arrays or objects - reject them for custom fields.
if ( ! is_scalar( $value ) ) {
return '';
}
$value = (string) $value;
// Prevent extremely long values.
if ( 10000 < strlen( $value ) ) {
$value = substr( $value, 0, 10000 );
}
if ( false !== strpos( $value, '|' ) ) {
$parts = $this->parse_custom_value( $value );
// Validate field name (basic WordPress meta key validation).
$field_name = $parts['field_name'];
if ( empty( $field_name ) || 255 < strlen( $field_name ) ) {
return '';
}
// Only rebuild if we have a valid field name.
return $field_name . '|' . $parts['comparison_value'];
}
return sanitize_text_field( $value );
}
/**
* Standardized meta field parsing with edge case handling.
*
* @param string $rule The condition rule.
* @param mixed $value The condition value.
* @return array Parsed field data with 'field_name' and 'comparison_value'.
*/
protected function parse_meta_field( $rule, $value ) {
if ( 'custom' === $rule ) {
return $this->parse_custom_value( $value );
}
// Ensure rule is a valid string.
if ( ! is_string( $rule ) || empty( $rule ) ) {
return [
'field_name' => '',
'comparison_value' => '',
];
}
// Handle non-scalar values.
if ( ! is_scalar( $value ) && null !== $value ) {
$comparison_value = '';
} else {
$comparison_value = is_scalar( $value ) ? sanitize_text_field( $value ) : '';
}
return [
'field_name' => sanitize_text_field( $rule ),
'comparison_value' => $comparison_value,
];
}
/**
* Enhanced meta existence evaluation with comprehensive validation.
*
* @param string $meta_type The meta type ('post', 'user', 'option').
* @param int $object_id The object ID (post ID, user ID, etc.).
* @param string $meta_key The meta key to check.
* @param string $operator The operator ('exists' or 'not_exists').
* @return bool
*/
protected function evaluate_meta_existence( $meta_type, $object_id, $meta_key, $operator ) {
// Validate meta type.
if ( ! in_array( $meta_type, [ 'post', 'user', 'option' ], true ) ) {
return false;
}
// Validate meta key.
if ( empty( $meta_key ) || ! is_string( $meta_key ) || 255 < strlen( $meta_key ) ) {
return false;
}
// Validate object ID for non-option types.
if ( 'option' !== $meta_type ) {
if ( ! is_numeric( $object_id ) || 1 > $object_id || PHP_INT_MAX < $object_id ) {
return false;
}
$object_id = absint( $object_id );
}
// Validate operator.
if ( ! in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return false;
}
try {
if ( 'option' === $meta_type ) {
$exists = false !== get_option( $meta_key, false );
} else {
$exists = metadata_exists( $meta_type, $object_id, $meta_key );
}
} catch ( Exception $e ) {
// Database errors, invalid meta types, etc.
return false;
}
return 'not_exists' === $operator ? ! $exists : $exists;
}
/**
* Enhanced meta value retrieval with comprehensive validation.
*
* @param string $meta_type The meta type ('post', 'user', 'option').
* @param int $object_id The object ID (post ID, user ID, etc.).
* @param string $meta_key The meta key.
* @param string $operator The condition operator.
* @param mixed $comparison_value The value to compare against.
* @return bool
*/
protected function evaluate_meta_value( $meta_type, $object_id, $meta_key, $operator, $comparison_value ) {
// Validate inputs using same validation as existence check.
if ( ! in_array( $meta_type, [ 'post', 'user', 'option' ], true ) ) {
return false;
}
if ( empty( $meta_key ) || ! is_string( $meta_key ) || 255 < strlen( $meta_key ) ) {
return false;
}
if ( 'option' !== $meta_type ) {
if ( ! is_numeric( $object_id ) || 1 > $object_id || PHP_INT_MAX < $object_id ) {
return false;
}
$object_id = absint( $object_id );
}
// Handle multi-value operators first.
if ( $this->is_multi_value_operator( $operator ) ) {
try {
if ( 'option' === $meta_type ) {
$meta_value = get_option( $meta_key );
} elseif ( 'post' === $meta_type ) {
$meta_value = get_post_meta( $object_id, $meta_key, true );
} elseif ( 'user' === $meta_type ) {
$meta_value = get_user_meta( $object_id, $meta_key, true );
} else {
return false;
}
} catch ( Exception $e ) {
return false;
}
return $this->evaluate_multi_value_meta( $operator, $meta_value, $comparison_value );
}
// Get single meta value with error handling.
try {
if ( 'option' === $meta_type ) {
$meta_value = get_option( $meta_key );
} elseif ( 'post' === $meta_type ) {
$meta_value = get_post_meta( $object_id, $meta_key, true );
} elseif ( 'user' === $meta_type ) {
$meta_value = get_user_meta( $object_id, $meta_key, true );
} else {
return false;
}
} catch ( Exception $e ) {
return false;
}
// Evaluate based on operator.
switch ( $operator ) {
case 'equals':
return $this->compare_values_equals( $meta_value, $comparison_value );
case 'contains':
return is_string( $meta_value ) && is_string( $comparison_value ) &&
false !== strpos( $meta_value, $comparison_value );
case 'not_contains':
return ! is_string( $meta_value ) || ! is_string( $comparison_value ) ||
false === strpos( $meta_value, $comparison_value );
case 'greater_than':
return $this->compare_numeric( $meta_value, $comparison_value, '>' );
case 'less_than':
return $this->compare_numeric( $meta_value, $comparison_value, '<' );
default:
return false;
}
}
/**
* Check if operator supports multiple values.
*
* @param string $operator The operator to check.
* @return bool
*/
protected function is_multi_value_operator( $operator ) {
$multi_operators = [ 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
return in_array( $operator, $multi_operators, true );
}
/**
* Parse value as array for multi-select operators with comprehensive validation.
*
* @param mixed $value The value to parse.
* @return array
*/
protected function parse_multi_value( $value ) {
if ( is_array( $value ) ) {
// Limit array size to prevent memory issues.
if ( 1000 < count( $value ) ) {
$value = array_slice( $value, 0, 1000 );
}
return array_filter( array_map( 'sanitize_text_field', $value ) );
}
if ( null === $value || '' === $value ) {
return [];
}
// Handle non-scalar values.
if ( ! is_scalar( $value ) ) {
return [];
}
$value = (string) $value;
// Prevent extremely long JSON strings.
if ( 50000 < strlen( $value ) ) {
return [];
}
// Try to decode as JSON first.
if ( '{' === $value[0] || '[' === $value[0] ) {
$decoded = json_decode( $value, true );
if ( is_array( $decoded ) ) {
// Limit decoded array size.
if ( 1000 < count( $decoded ) ) {
$decoded = array_slice( $decoded, 0, 1000 );
}
return array_filter( array_map( 'sanitize_text_field', $decoded ) );
}
}
// Fallback to single value.
return [ sanitize_text_field( $value ) ];
}
/**
* Generic multi-value condition evaluator with validation.
*
* @param string $operator The operator (includes_any, includes_all, etc.).
* @param mixed $condition_values The condition values (array or string).
* @param callable $match_callback Callback function to check if a value matches.
* @return bool
*/
protected function evaluate_multi_value_generic( $operator, $condition_values, $match_callback ) {
if ( ! is_callable( $match_callback ) ) {
return false;
}
if ( ! in_array( $operator, [ 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ], true ) ) {
return false;
}
$values = $this->parse_multi_value( $condition_values );
if ( empty( $values ) ) {
return false;
}
$matches = [];
foreach ( $values as $value ) {
try {
$matches[] = call_user_func( $match_callback, $value );
} catch ( Exception $e ) {
$matches[] = false;
}
}
// Apply operator logic.
switch ( $operator ) {
case 'includes_any':
return in_array( true, $matches, true );
case 'includes_all':
return ! in_array( false, $matches, true );
case 'excludes_any':
return ! in_array( true, $matches, true );
case 'excludes_all':
return in_array( false, $matches, true );
default:
return false;
}
}
/**
* Evaluate multi-value condition.
*
* @param string $operator The operator (includes_any, includes_all, etc.).
* @param mixed $target_value The current value to check against.
* @param mixed $condition_values The condition values (array or string).
* @return bool
*/
protected function evaluate_multi_value( $operator, $target_value, $condition_values ) {
return $this->evaluate_multi_value_generic(
$operator,
$condition_values,
function( $value ) use ( $target_value ) {
return $this->values_match( $target_value, $value );
}
);
}
/**
* Evaluate multi-value condition for arrays (like post terms).
*
* @param string $operator The operator (includes_any, includes_all, etc.).
* @param array $target_values Array of current values to check against.
* @param mixed $condition_values The condition values (array or string).
* @return bool
*/
protected function evaluate_multi_value_array( $operator, $target_values, $condition_values ) {
if ( ! is_array( $target_values ) ) {
return false;
}
return $this->evaluate_multi_value_generic(
$operator,
$condition_values,
function( $value ) use ( $target_values ) {
foreach ( $target_values as $target_value ) {
if ( $this->values_match( $target_value, $value ) ) {
return true;
}
}
return false;
}
);
}
/**
* Enhanced multi-value meta evaluation with error handling.
*
* @param string $operator The operator (includes_any, includes_all, etc.).
* @param mixed $meta_value The meta value (could be string, array, or object).
* @param mixed $condition_values The condition values (array or string).
* @return bool
*/
protected function evaluate_multi_value_meta( $operator, $meta_value, $condition_values ) {
// Convert meta value to array if it's not already.
$meta_values = [];
if ( is_array( $meta_value ) ) {
// Limit meta array size.
if ( 1000 < count( $meta_value ) ) {
$meta_value = array_slice( $meta_value, 0, 1000 );
}
$meta_values = array_map( 'sanitize_text_field', $meta_value );
} elseif ( null !== $meta_value && '' !== $meta_value ) {
if ( is_scalar( $meta_value ) ) {
$meta_value_string = (string) $meta_value;
// Try to decode JSON if it looks like JSON.
if ( ( '{' === $meta_value_string[0] || '[' === $meta_value_string[0] ) && 50000 > strlen( $meta_value_string ) ) {
$decoded = json_decode( $meta_value_string, true );
if ( is_array( $decoded ) ) {
if ( 1000 < count( $decoded ) ) {
$decoded = array_slice( $decoded, 0, 1000 );
}
$meta_values = array_map( 'sanitize_text_field', $decoded );
} else {
$meta_values = [ sanitize_text_field( $meta_value_string ) ];
}
} else {
$meta_values = [ sanitize_text_field( $meta_value_string ) ];
}
}
}
return $this->evaluate_multi_value_generic(
$operator,
$condition_values,
function( $condition_value ) use ( $meta_values ) {
foreach ( $meta_values as $current_meta_value ) {
if ( $this->values_match( $current_meta_value, $condition_value ) ) {
return true;
}
}
return false;
}
);
}
/**
* Check if two values match (with loose comparison).
*
* @param mixed $value1 First value.
* @param mixed $value2 Second value.
* @return bool
*/
protected function values_match( $value1, $value2 ) {
// Convert both to strings for comparison.
return (string) $value1 === (string) $value2;
}
/**
* Compare values with type juggling for equals operator.
*
* @param mixed $value1 First value.
* @param mixed $value2 Second value.
* @return bool
*/
protected function compare_values_equals( $value1, $value2 ) {
return $value1 == $value2; // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
}
/**
* Compare numeric values with enhanced validation.
*
* @param mixed $value1 First value.
* @param mixed $value2 Second value.
* @param string $operator Comparison operator (>, <).
* @return bool
*/
protected function compare_numeric( $value1, $value2, $operator ) {
if ( ! in_array( $operator, [ '>', '<' ], true ) ) {
return false;
}
// Handle edge cases for numeric comparison.
if ( null === $value1 || null === $value2 ) {
return false;
}
if ( ! is_numeric( $value1 ) || ! is_numeric( $value2 ) ) {
return false;
}
// Check for potential overflow issues.
if ( ! is_finite( (float) $value1 ) || ! is_finite( (float) $value2 ) ) {
return false;
}
$float1 = floatval( $value1 );
$float2 = floatval( $value2 );
if ( '>' === $operator ) {
return $float1 > $float2;
} elseif ( '<' === $operator ) {
return $float1 < $float2;
}
return false;
}
/**
* Enhanced value sanitization with better validation.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
if ( 1000 < count( $value ) ) {
$value = array_slice( $value, 0, 1000 );
}
return array_filter( array_map( 'sanitize_text_field', $value ) );
}
return sanitize_text_field( $value );
}
/**
* Enhanced query parameter validation.
*
* @param string $param Parameter name.
* @return mixed
*/
protected function get_query_param( $param ) {
if ( empty( $param ) || ! is_string( $param ) || 255 < strlen( $param ) ) {
return null;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET[ $param ] ) ) {
return null;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$value = wp_unslash( $_GET[ $param ] );
// Handle arrays in query params.
if ( is_array( $value ) ) {
if ( 100 < count( $value ) ) {
$value = array_slice( $value, 0, 100 );
}
return array_map( 'sanitize_text_field', $value );
}
// Prevent extremely long query values.
if ( is_string( $value ) && 10000 < strlen( $value ) ) {
$value = substr( $value, 0, 10000 );
}
return sanitize_text_field( $value );
}
/**
* Enhanced input validation for server variables.
*
* @param string $var Variable name.
* @return string
*/
protected function get_server_var( $var ) {
if ( empty( $var ) || ! is_string( $var ) || 100 < strlen( $var ) ) {
return '';
}
// Whitelist allowed server variables for security.
$allowed_vars = [
'HTTP_USER_AGENT',
'HTTP_REFERER',
'REQUEST_METHOD',
'REMOTE_ADDR',
'HTTP_HOST',
'REQUEST_URI',
'QUERY_STRING',
];
if ( ! in_array( $var, $allowed_vars, true ) ) {
return '';
}
if ( ! isset( $_SERVER[ $var ] ) ) {
return '';
}
$value = wp_unslash( $_SERVER[ $var ] );
// Type-specific validation for enhanced security.
switch ( $var ) {
case 'HTTP_REFERER':
// Validate and sanitize as URL.
$value = esc_url_raw( $value );
break;
case 'HTTP_USER_AGENT':
// Remove control characters and normalize.
$value = preg_replace( '/[\x00-\x1F\x7F]/', '', $value );
$value = sanitize_text_field( $value );
break;
default:
$value = sanitize_text_field( $value );
}
// Prevent extremely long values.
if ( is_string( $value ) && 10000 < strlen( $value ) ) {
$value = substr( $value, 0, 10000 );
}
return $value;
}
/**
* Enhanced cookie validation.
*
* @param string $cookie_name Cookie name.
* @return string|null
*/
protected function get_cookie_value( $cookie_name ) {
if ( empty( $cookie_name ) || ! is_string( $cookie_name ) || 255 < strlen( $cookie_name ) ) {
return null;
}
// Basic cookie name validation (prevent obvious XSS attempts).
// Also reject browser-reserved cookie prefixes (__Host-, __Secure-).
if ( ! preg_match( '/^[a-zA-Z0-9_-]+$/', $cookie_name ) || 0 === strpos( $cookie_name, '__' ) ) {
return null;
}
if ( ! isset( $_COOKIE[ $cookie_name ] ) ) {
return null;
}
$value = wp_unslash( $_COOKIE[ $cookie_name ] );
// Prevent extremely long cookie values.
if ( is_string( $value ) && 4096 < strlen( $value ) ) {
$value = substr( $value, 0, 4096 );
}
return sanitize_text_field( $value );
}
/**
* Check if cookie exists with validation.
*
* @param string $cookie_name Cookie name.
* @return bool
*/
protected function cookie_exists( $cookie_name ) {
if ( empty( $cookie_name ) || ! is_string( $cookie_name ) || 255 < strlen( $cookie_name ) ) {
return false;
}
// Reject browser-reserved cookie prefixes.
if ( ! preg_match( '/^[a-zA-Z0-9_-]+$/', $cookie_name ) || 0 === strpos( $cookie_name, '__' ) ) {
return false;
}
return isset( $_COOKIE[ $cookie_name ] );
}
/**
* Check if query parameter exists with validation.
*
* @param string $param_name Parameter name.
* @return bool
*/
protected function query_param_exists( $param_name ) {
if ( empty( $param_name ) || ! is_string( $param_name ) || 255 < strlen( $param_name ) ) {
return false;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return isset( $_GET[ $param_name ] );
}
/**
* Enhanced meta value check - determines if meta has a non-empty value.
*
* @param string $meta_type The meta type ('post', 'user', 'option').
* @param int $object_id The object ID (post ID, user ID, etc.).
* @param string $meta_key The meta key.
* @return bool
*/
protected function evaluate_meta_has_value( $meta_type, $object_id, $meta_key ) {
// Validate inputs using same validation as existence check.
if ( ! in_array( $meta_type, [ 'post', 'user', 'option' ], true ) ) {
return false;
}
if ( empty( $meta_key ) || ! is_string( $meta_key ) || 255 < strlen( $meta_key ) ) {
return false;
}
if ( 'option' !== $meta_type ) {
if ( ! is_numeric( $object_id ) || 1 > $object_id || PHP_INT_MAX < $object_id ) {
return false;
}
$object_id = absint( $object_id );
}
// Get the value.
try {
if ( 'option' === $meta_type ) {
// For options, false means it doesn't exist.
if ( false === get_option( $meta_key, false ) ) {
return false;
}
$value = get_option( $meta_key );
} elseif ( 'post' === $meta_type ) {
// First check if it exists.
if ( ! metadata_exists( $meta_type, $object_id, $meta_key ) ) {
return false;
}
$value = get_post_meta( $object_id, $meta_key, true );
} elseif ( 'user' === $meta_type ) {
// First check if it exists.
if ( ! metadata_exists( $meta_type, $object_id, $meta_key ) ) {
return false;
}
$value = get_user_meta( $object_id, $meta_key, true );
} else {
return false;
}
} catch ( Exception $e ) {
return false;
}
// Check for various "empty" states.
if ( null === $value || '' === $value || false === $value ) {
return false;
}
// Empty array is also "no value".
if ( is_array( $value ) && 0 === count( $value ) ) {
return false;
}
// Everything else is considered "has value" (including 0 and "0").
return true;
}
/**
* Check if cookie has a non-empty value.
*
* @param string $cookie_name Cookie name.
* @return bool
*/
protected function cookie_has_value( $cookie_name ) {
if ( ! $this->cookie_exists( $cookie_name ) ) {
return false;
}
$value = $this->get_cookie_value( $cookie_name );
// Check for various "empty" states.
if ( null === $value || '' === $value || false === $value ) {
return false;
}
// Everything else is considered "has value" (including 0 and "0").
return true;
}
/**
* Check if query parameter has a non-empty value.
*
* @param string $param_name Parameter name.
* @return bool
*/
protected function query_param_has_value( $param_name ) {
if ( ! $this->query_param_exists( $param_name ) ) {
return false;
}
$value = $this->get_query_param( $param_name );
// Check for various "empty" states.
if ( null === $value || '' === $value || false === $value ) {
return false;
}
// Empty array is also "no value".
if ( is_array( $value ) && 0 === count( $value ) ) {
return false;
}
// Everything else is considered "has value" (including 0 and "0").
return true;
}
/**
* Check if referrer has a non-empty value.
*
* @return bool
*/
protected function referrer_has_value() {
$referrer = $this->get_server_var( 'HTTP_REFERER' );
// Check for various "empty" states.
if ( null === $referrer || '' === $referrer || false === $referrer ) {
return false;
}
// Everything else is considered "has value".
return true;
}
/**
* Check if operator needs a value.
*
* @param string $operator The operator.
* @return bool
*/
protected function operator_needs_value( $operator ) {
$no_value_operators = [ 'exists', 'not_exists', 'has_value', 'no_value' ];
return ! in_array( $operator, $no_value_operators, true );
}
/**
* Check if a rule actually supports multi-value selection.
*
* @param string $rule The rule key.
* @return bool
*/
protected function rule_supports_multi_select( $rule ) {
$metadata = $this->get_rule_metadata( $rule );
// Only support multi-select if we have object selectors.
$multi_select_types = [ 'object_selector', 'hierarchical_object_selector' ];
return in_array( $metadata['value_type'] ?? '', $multi_select_types, true );
}
/**
* Get operators with intelligent filtering based on actual multi-select support.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Get base operators for this condition type.
$types = GenerateBlocks_Pro_Conditions::get_condition_types();
$class_name = get_class( $this );
$base_operators = [];
foreach ( $types as $type_data ) {
if ( $type_data['class'] === $class_name ) {
$base_operators = $type_data['operators'];
break;
}
}
// If this rule doesn't support multi-select, remove ALL "any/all" operators.
if ( ! $this->rule_supports_multi_select( $rule ) ) {
$multi_operators = [ 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
$base_operators = array_diff( $base_operators, $multi_operators );
}
return $base_operators;
}
}
@@ -0,0 +1,152 @@
<?php
/**
* Conditions Dashboard Admin Page
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class GenerateBlocks_Pro_Conditions_Dashboard
*/
class GenerateBlocks_Pro_Conditions_Dashboard {
/**
* 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', [ $this, 'add_admin_menu' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'generateblocks_dashboard_tabs', [ $this, 'add_tab' ] );
add_filter( 'generateblocks_dashboard_screens', [ $this, 'add_to_dashboard_pages' ] );
}
/**
* Add admin menu page.
*/
public function add_admin_menu() {
// Get the required capability.
$capability = GenerateBlocks_Pro_Conditions::get_conditions_capability( 'manage' );
// Only add menu if user has permission.
if ( ! current_user_can( $capability ) ) {
return;
}
add_submenu_page(
'generateblocks',
__( 'Conditions', 'generateblocks-pro' ),
__( 'Conditions', 'generateblocks-pro' ),
$capability,
'generateblocks-conditions',
[ $this, 'render_dashboard' ],
4
);
}
/**
* Render the dashboard page.
*/
public function render_dashboard() {
// Double-check permission before rendering.
if ( ! GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to access this page.', 'generateblocks-pro' ) );
}
?>
<div class="wrap">
<div id="gb-conditions-dashboard"></div>
</div>
<?php
}
/**
* Enqueue scripts for the dashboard.
*
* @param string $hook_suffix The current admin page.
*/
public function enqueue_scripts( $hook_suffix ) {
if ( 'generateblocks_page_generateblocks-conditions' !== $hook_suffix ) {
return;
}
$assets = generateblocks_pro_get_enqueue_assets( 'conditions-dashboard' );
wp_enqueue_script(
'gb-conditions-dashboard',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/conditions-dashboard.js',
$assets['dependencies'],
$assets['version'],
true
);
wp_enqueue_style(
'gb-conditions-dashboard',
GENERATEBLOCKS_PRO_DIR_URL . 'dist/conditions-dashboard.css',
[ 'wp-components', 'generateblocks-pro-dashboard-table' ],
GENERATEBLOCKS_PRO_VERSION
);
wp_localize_script(
'gb-conditions-dashboard',
'gbConditionsDashboard',
[
'apiUrl' => rest_url( 'wp/v2/' ),
'nonce' => wp_create_nonce( 'wp_rest' ),
]
);
}
/**
* Add a Local Templates tab to the GB Dashboard tabs.
*
* @param array $tabs The existing tabs.
*/
public function add_tab( $tabs ) {
$screen = get_current_screen();
$tabs['conditions'] = array(
'name' => __( 'Conditions', 'generateblocks-pro' ),
'url' => admin_url( 'admin.php?page=generateblocks-conditions' ),
'class' => 'generateblocks_page_generateblocks-conditions' === $screen->id ? 'active' : '',
);
return $tabs;
}
/**
* Add to our Dashboard pages.
*
* @since 1.0.0
* @param array $pages The existing pages.
*/
public function add_to_dashboard_pages( $pages ) {
$pages[] = 'generateblocks_page_generateblocks-conditions';
return $pages;
}
}
GenerateBlocks_Pro_Conditions_Dashboard::get_instance();
@@ -0,0 +1,305 @@
<?php
/**
* Conditions Post Type Registration
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class GenerateBlocks_Pro_Conditions_Post_Type
*/
class GenerateBlocks_Pro_Conditions_Post_Type {
/**
* 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( 'init', [ $this, 'register_post_type' ] );
add_action( 'init', [ $this, 'register_taxonomy' ] );
add_action( 'init', [ $this, 'register_post_meta' ] );
}
/**
* Build the register_post_type() args.
*
* Capabilities are pinned to the 'manage' context of the conditions
* capability filter so WP core REST writes (/wp/v2/gblocks-conditions)
* require the same capability as the custom /advanced-conditions/v1/* routes.
* Without this, any edit_posts user (Author+) could create published
* condition posts via the core REST endpoint and bypass the 2.4.0
* manage_options hardening.
*
* @return array
*/
public function get_conditions_cpt_args() {
$labels = [
'name' => __( 'Conditions', 'generateblocks-pro' ),
'singular_name' => __( 'Condition', 'generateblocks-pro' ),
'menu_name' => __( 'Conditions', 'generateblocks-pro' ),
'add_new' => __( 'Add New', 'generateblocks-pro' ),
'add_new_item' => __( 'Add New Condition', 'generateblocks-pro' ),
'edit_item' => __( 'Edit Condition', 'generateblocks-pro' ),
'new_item' => __( 'New Condition', 'generateblocks-pro' ),
'view_item' => __( 'View Condition', 'generateblocks-pro' ),
'search_items' => __( 'Search Conditions', 'generateblocks-pro' ),
'not_found' => __( 'No conditions found.', 'generateblocks-pro' ),
'not_found_in_trash' => __( 'No conditions found in Trash.', 'generateblocks-pro' ),
];
$manage_cap = GenerateBlocks_Pro_Conditions::get_conditions_capability( 'manage' );
$use_cap = GenerateBlocks_Pro_Conditions::get_conditions_capability( 'use' );
return [
'labels' => $labels,
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'show_in_admin_bar' => false,
'show_in_nav_menus' => false,
'can_export' => true,
'has_archive' => false,
'exclude_from_search' => true,
'publicly_queryable' => false,
'capability_type' => 'post',
'map_meta_cap' => true,
// Override only primitive capabilities — leaving meta caps
// (edit_post/read_post/delete_post) at their defaults so WP's
// map_meta_cap() resolves them via these primitives. Do not map
// meta caps directly to a global cap like 'manage_options': WP's
// _post_type_meta_capabilities() would then register that global
// cap in $post_type_meta_caps and recursive-rewrite it to a meta
// cap with no post ID, which always resolves to do_not_allow.
'capabilities' => [
'edit_posts' => $manage_cap,
'edit_others_posts' => $manage_cap,
'delete_posts' => $manage_cap,
'delete_others_posts' => $manage_cap,
'delete_private_posts' => $manage_cap,
'delete_published_posts' => $manage_cap,
'edit_private_posts' => $manage_cap,
'edit_published_posts' => $manage_cap,
'publish_posts' => $manage_cap,
'read_private_posts' => $manage_cap,
'create_posts' => $manage_cap,
'read' => $use_cap,
],
'show_in_rest' => true,
'rest_base' => 'gblocks-conditions',
'supports' => [ 'title' ],
];
}
/**
* Register the conditions post type.
*/
public function register_post_type() {
register_post_type( 'gblocks_condition', $this->get_conditions_cpt_args() );
}
/**
* Build the register_taxonomy() args for the condition category taxonomy.
*
* The manage_terms/edit_terms/delete_terms caps are pinned to the manage context so
* WP core REST writes on /wp/v2/condition-categories (see WP_REST_Terms_Controller)
* require the same capability as the custom conditions routes. assign_terms
* stays at the use context so edit_posts users can still read the category
* list from the block editor UI.
*
* @return array
*/
public function get_conditions_taxonomy_args() {
$labels = [
'name' => __( 'Condition Categories', 'generateblocks-pro' ),
'singular_name' => __( 'Condition Category', 'generateblocks-pro' ),
'search_items' => __( 'Search Categories', 'generateblocks-pro' ),
'all_items' => __( 'All Categories', 'generateblocks-pro' ),
'parent_item' => __( 'Parent Category', 'generateblocks-pro' ),
'parent_item_colon' => __( 'Parent Category:', 'generateblocks-pro' ),
'edit_item' => __( 'Edit Category', 'generateblocks-pro' ),
'update_item' => __( 'Update Category', 'generateblocks-pro' ),
'add_new_item' => __( 'Add New Category', 'generateblocks-pro' ),
'new_item_name' => __( 'New Category Name', 'generateblocks-pro' ),
'menu_name' => __( 'Categories', 'generateblocks-pro' ),
];
$manage_cap = GenerateBlocks_Pro_Conditions::get_conditions_capability( 'manage' );
$use_cap = GenerateBlocks_Pro_Conditions::get_conditions_capability( 'use' );
return [
'labels' => $labels,
'hierarchical' => true,
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'show_admin_column' => false,
'show_in_nav_menus' => false,
'show_tagcloud' => false,
'show_in_rest' => true,
'rest_base' => 'condition-categories',
'capabilities' => [
'manage_terms' => $manage_cap,
'edit_terms' => $manage_cap,
'delete_terms' => $manage_cap,
'assign_terms' => $use_cap,
],
];
}
/**
* Register the condition category taxonomy.
*/
public function register_taxonomy() {
register_taxonomy( 'gblocks_condition_cat', [ 'gblocks_condition' ], $this->get_conditions_taxonomy_args() );
}
/**
* Build the register_post_meta() args for _gb_conditions.
*
* The sanitize callback must be callable by is_callable() — register_meta
* silently skips filter registration when that check fails. The previous
* [class-string, instance-method] form failed is_callable() and caused
* _gb_conditions meta to be stored without sanitization.
*
* auth_callback checks the manage capability so the REST meta endpoint
* matches the CPT-level capability requirements.
*
* @return array
*/
public function get_conditions_meta_args() {
return [
'single' => true,
'type' => 'object',
'auth_callback' => static function() {
return GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' );
},
'sanitize_callback' => [ GenerateBlocks_Pro_Conditions::get_instance(), 'sanitize_conditions' ],
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
'logic' => [
'type' => 'string',
'enum' => [ 'AND', 'OR' ],
],
'groups' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'logic' => [
'type' => 'string',
'enum' => [ 'AND', 'OR' ],
],
'conditions' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'type' => [ 'type' => 'string' ],
'rule' => [ 'type' => 'string' ],
'operator' => [ 'type' => 'string' ],
'value' => [ 'type' => 'string' ],
],
],
],
],
],
],
],
],
],
];
}
/**
* Register post meta for conditions.
*/
public function register_post_meta() {
register_post_meta( 'gblocks_condition', '_gb_conditions', $this->get_conditions_meta_args() );
register_rest_field(
'gblocks_condition',
'gbConditions',
[
'get_callback' => function( $data ) {
$conditions = get_post_meta( $data['id'], '_gb_conditions', true );
return $conditions ? $conditions : [
'logic' => 'OR',
'groups' => [],
];
},
'update_callback' => function( $value, $post ) {
if ( ! GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' ) ) {
return new \WP_Error(
'rest_cannot_update',
__( 'Sorry, you are not allowed to edit conditions.', 'generateblocks-pro' ),
[ 'status' => rest_authorization_required_code() ]
);
}
$sanitized = GenerateBlocks_Pro_Conditions::get_instance()->sanitize_conditions( $value );
update_post_meta( $post->ID, '_gb_conditions', $sanitized );
},
'schema' => [
'type' => 'object',
'properties' => [
'logic' => [
'type' => 'string',
'enum' => [ 'AND', 'OR' ],
],
'groups' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'logic' => [
'type' => 'string',
'enum' => [ 'AND', 'OR' ],
],
'conditions' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'type' => [ 'type' => 'string' ],
'rule' => [ 'type' => 'string' ],
'operator' => [ 'type' => 'string' ],
'value' => [ 'type' => 'string' ],
],
],
],
],
],
],
],
],
]
);
}
}
GenerateBlocks_Pro_Conditions_Post_Type::get_instance();
@@ -0,0 +1,338 @@
<?php
/**
* Conditions Registry
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Registry for condition types.
*/
class GenerateBlocks_Pro_Conditions_Registry {
/**
* Registered condition types.
*
* @var array
*/
private static $condition_types = [];
/**
* Condition instances cache.
*
* @var array
*/
private static $instances = [];
/**
* Evaluation results cache (per-request).
*
* @var array
*/
private static $evaluation_cache = [];
/**
* Register a condition type.
*
* @param string $type Condition type identifier.
* @param array $args Condition arguments.
* @param string $classname Condition evaluator class name.
* @return bool
*/
public static function register( $type, $args, $classname ) {
if ( empty( $type ) || empty( $classname ) ) {
return false;
}
if ( ! class_exists( $classname ) ) {
return false;
}
// Ensure the class implements our interface.
$interfaces = class_implements( $classname );
if ( ! isset( $interfaces['GenerateBlocks_Pro_Condition_Interface'] ) ) {
return false;
}
$defaults = [
'label' => '',
'operators' => [],
'priority' => 10,
];
$args = wp_parse_args( $args, $defaults );
self::$condition_types[ $type ] = [
'label' => $args['label'],
'class' => $classname,
'operators' => $args['operators'],
'priority' => $args['priority'],
];
return true;
}
/**
* Unregister a condition type.
*
* @param string $type Condition type identifier.
* @return bool
*/
public static function unregister( $type ) {
if ( ! isset( self::$condition_types[ $type ] ) ) {
return false;
}
unset( self::$condition_types[ $type ] );
if ( isset( self::$instances[ $type ] ) ) {
unset( self::$instances[ $type ] );
}
return true;
}
/**
* Get all registered condition types.
*
* @return array
*/
public static function get_all() {
// Sort by priority.
uasort(
self::$condition_types,
function( $a, $b ) {
return $a['priority'] <=> $b['priority'];
}
);
return self::$condition_types;
}
/**
* Get a specific condition type.
*
* @param string $type Condition type identifier.
* @return array|null
*/
public static function get( $type ) {
return isset( self::$condition_types[ $type ] ) ? self::$condition_types[ $type ] : null;
}
/**
* Get condition instance.
*
* @param string $type Condition type identifier.
* @return GenerateBlocks_Pro_Condition_Interface|null
*/
public static function get_instance( $type ) {
if ( ! isset( self::$condition_types[ $type ] ) ) {
return null;
}
if ( ! isset( self::$instances[ $type ] ) ) {
$classname = self::$condition_types[ $type ]['class'];
self::$instances[ $type ] = new $classname();
}
return self::$instances[ $type ];
}
/**
* Evaluate a condition.
*
* @param string $type Condition type.
* @param string $rule Condition rule.
* @param string $operator Condition operator.
* @param mixed $value Condition value.
* @param array $context Additional context.
* @return bool
*/
public static function evaluate( $type, $rule, $operator, $value, $context = [] ) {
$instance = self::get_instance( $type );
if ( ! $instance ) {
return false;
}
// Cache key for performance - per-request only using class property.
$cache_data = wp_json_encode( [ $type, $rule, $operator, $value, $context ] );
$cache_key = md5( $cache_data );
if ( isset( self::$evaluation_cache[ $cache_key ] ) ) {
return self::$evaluation_cache[ $cache_key ];
}
$result = $instance->evaluate( $rule, $operator, $value, $context );
// Cache for current request only using class property.
self::$evaluation_cache[ $cache_key ] = $result;
return $result;
}
/**
* Clear the evaluation cache.
* Used primarily for testing purposes to reset state between tests.
*
* @return void
*/
public static function clear_evaluation_cache() {
self::$evaluation_cache = [];
}
/**
* Register core condition types.
*/
public static function register_core_types() {
// Location.
self::register(
'location',
[
'label' => __( 'Location', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ],
'priority' => 10,
],
'GenerateBlocks_Pro_Condition_Location'
);
// Query Parameter.
self::register(
'query_arg',
[
'label' => __( 'Query Parameter', 'generateblocks-pro' ),
'operators' => [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ],
'priority' => 20,
],
'GenerateBlocks_Pro_Condition_Query_Arg'
);
// User Role.
self::register(
'user_role',
[
'label' => __( 'User Role', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not', 'includes_any', 'includes_all' ],
'priority' => 30,
],
'GenerateBlocks_Pro_Condition_User_Role'
);
// Date & Time.
self::register(
'date_time',
[
'label' => __( 'Date & Time', 'generateblocks-pro' ),
'operators' => [ 'before', 'after', 'between', 'on' ],
'priority' => 40,
],
'GenerateBlocks_Pro_Condition_Date_Time'
);
// Device.
self::register(
'device',
[
'label' => __( 'Device', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not' ],
'priority' => 50,
],
'GenerateBlocks_Pro_Condition_Device'
);
// Referrer.
self::register(
'referrer',
[
'label' => __( 'Referrer', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not', 'contains', 'not_contains', 'equals', 'starts_with', 'ends_with' ],
'priority' => 60,
],
'GenerateBlocks_Pro_Condition_Referrer'
);
// Post Meta.
self::register(
'post_meta',
[
'label' => __( 'Post Meta', 'generateblocks-pro' ),
'operators' => [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all' ],
'priority' => 70,
],
'GenerateBlocks_Pro_Condition_Post_Meta'
);
// User Meta.
self::register(
'user_meta',
[
'label' => __( 'User Meta', 'generateblocks-pro' ),
'operators' => [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all' ],
'priority' => 80,
],
'GenerateBlocks_Pro_Condition_User_Meta'
);
// Cookie.
self::register(
'cookie',
[
'label' => __( 'Cookie', 'generateblocks-pro' ),
'operators' => [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ],
'priority' => 90,
],
'GenerateBlocks_Pro_Condition_Cookie'
);
// Language.
self::register(
'language',
[
'label' => __( 'Language', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not' ],
'priority' => 95,
],
'GenerateBlocks_Pro_Condition_Language'
);
// Options.
self::register(
'options',
[
'label' => __( 'Site Options', 'generateblocks-pro' ),
'operators' => [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all' ],
'priority' => 100,
],
'GenerateBlocks_Pro_Condition_Options'
);
// Author.
self::register(
'author',
[
'label' => __( 'Author', 'generateblocks-pro' ),
'operators' => [ 'is', 'is_not', 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'includes_any', 'excludes_any' ],
'priority' => 110,
],
'GenerateBlocks_Pro_Condition_Author'
);
}
/**
* Reset registry for testing purposes only.
*
* @since 2.2.0
*/
public static function reset_for_testing() {
// Only allow in testing environment.
if ( ! defined( 'GB_TESTING' ) || ! GB_TESTING ) {
return;
}
self::$condition_types = [];
self::$instances = [];
}
}
@@ -0,0 +1,390 @@
<?php
/**
* The Advanced Conditions class file.
*
* @package GenerateBlocksTheme
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Main class for advanced conditions system.
*
* @since 1.0.0
*/
class GenerateBlocks_Pro_Conditions extends GenerateBlocks_Pro_Singleton {
/**
* Initialize the conditions system.
*/
public function init() {
// Register core condition types early for REST API, but after init for translations.
add_action( 'init', [ $this, 'register_core_conditions' ], 0 );
// Allow third-party registrations after core.
add_action(
'init',
function() {
do_action( 'generateblocks_register_conditions' );
},
10
);
}
/**
* Get the capability required for conditions.
*
* @since 2.4.0
*
* Breaking changes in 2.4.0:
* - Previously all operations required only 'edit_posts' capability
* - Now 'manage' context (create/edit/delete) requires 'manage_options' by default
* - Use the 'generateblocks_conditions_capability' filter to customize
*
* @param string $context The context: 'use' (select existing) or 'manage' (create/edit/dashboard).
* @return string The capability required.
*/
public static function get_conditions_capability( $context = 'use' ) {
// Default capabilities.
if ( 'manage' === $context ) {
// Can create/edit/access dashboard - default to manage_options.
$capability = 'manage_options';
} else {
// Can select/use existing conditions - anyone who can edit posts.
$capability = 'edit_posts';
}
/**
* Filter the capability required for conditions.
*
* @since 2.4.0
* @param string $capability The capability required.
* @param string $context The context: 'use' or 'manage'.
*/
return apply_filters( 'generateblocks_conditions_capability', $capability, $context );
}
/**
* Check if current user can use conditions.
*
* @since 2.4.0
* @param string $context The context: 'use' (select existing) or 'manage' (create/edit/dashboard).
* @return bool
*/
public static function current_user_can_use_conditions( $context = 'use' ) {
$capability = self::get_conditions_capability( $context );
return current_user_can( $capability );
}
/**
* Register core condition types.
*/
public function register_core_conditions() {
// Check if already registered to avoid duplicates.
if ( ! empty( GenerateBlocks_Pro_Conditions_Registry::get_all() ) ) {
return;
}
GenerateBlocks_Pro_Conditions_Registry::register_core_types();
}
/**
* Sanitize conditions data.
*
* @param array $meta_value The conditions array.
* @return array
*/
public function sanitize_conditions( $meta_value ) {
if ( ! is_array( $meta_value ) ) {
return [
'logic' => 'OR',
'groups' => [],
];
}
$sanitized = [
'logic' => in_array( $meta_value['logic'] ?? 'OR', [ 'AND', 'OR' ], true ) ? $meta_value['logic'] : 'OR',
'groups' => [],
];
if ( isset( $meta_value['groups'] ) && is_array( $meta_value['groups'] ) ) {
foreach ( $meta_value['groups'] as $group ) {
if ( ! is_array( $group ) ) {
continue;
}
$sanitized_group = [
'logic' => in_array( $group['logic'] ?? 'AND', [ 'AND', 'OR' ], true ) ? $group['logic'] : 'AND',
'conditions' => [],
];
if ( isset( $group['conditions'] ) && is_array( $group['conditions'] ) ) {
foreach ( $group['conditions'] as $condition ) {
if ( ! is_array( $condition ) ) {
continue;
}
$sanitized_condition = [
'type' => sanitize_text_field( $condition['type'] ?? '' ),
'rule' => sanitize_text_field( $condition['rule'] ?? '' ),
'operator' => sanitize_text_field( $condition['operator'] ?? '' ),
'value' => $this->sanitize_condition_value( $condition ),
];
$sanitized_group['conditions'][] = $sanitized_condition;
}
}
$sanitized['groups'][] = $sanitized_group;
}
}
return $sanitized;
}
/**
* Sanitize condition value - handles custom field two-part values and arrays
*
* @param array $condition The condition array.
* @return string|array
*/
private function sanitize_condition_value( $condition ) {
$value = $condition['value'] ?? '';
if ( empty( $value ) ) {
return '';
}
// Handle array values for multi-select operators.
if ( is_array( $value ) ) {
// Filter out empty values to improve data quality.
return array_filter( array_map( 'sanitize_text_field', $value ) );
}
// Try to decode JSON array.
$decoded = json_decode( $value, true );
if ( is_array( $decoded ) ) {
// Filter out empty values to improve data quality.
return wp_json_encode( array_filter( array_map( 'sanitize_text_field', $decoded ) ) );
}
// Extended condition types support.
$is_custom_field = ( 'custom' === ( $condition['rule'] ?? '' ) ) &&
in_array( $condition['type'] ?? '', [ 'post_meta', 'user_meta', 'query_arg', 'referrer', 'cookie', 'options' ], true );
if ( $is_custom_field && false !== strpos( $value, '|' ) ) {
$parts = explode( '|', $value, 2 );
$field_name = sanitize_text_field( $parts[0] );
$comparison_value = sanitize_text_field( $parts[1] ?? '' );
// Rebuild the value with sanitized parts.
return $field_name . '|' . $comparison_value;
}
return sanitize_text_field( $value );
}
/**
* Get all available condition types.
*
* @return array
*/
public static function get_condition_types() {
// Ensure conditions are registered if called early.
if ( empty( GenerateBlocks_Pro_Conditions_Registry::get_all() ) && did_action( 'init' ) ) {
self::get_instance()->register_core_conditions();
}
$types = GenerateBlocks_Pro_Conditions_Registry::get_all();
// Format for backward compatibility.
$formatted = [];
foreach ( $types as $key => $type ) {
$formatted[ $key ] = [
'label' => $type['label'],
'class' => $type['class'],
'operators' => $type['operators'],
];
}
return apply_filters( 'generateblocks_condition_types', $formatted );
}
/**
* Get rules for a specific condition type.
*
* @param string $type The condition type.
* @return array
*/
public static function get_condition_rules( $type = '' ) {
// Ensure conditions are registered if called early.
if ( empty( GenerateBlocks_Pro_Conditions_Registry::get_all() ) && did_action( 'init' ) ) {
self::get_instance()->register_core_conditions();
}
$instance = GenerateBlocks_Pro_Conditions_Registry::get_instance( $type );
if ( ! $instance ) {
return [];
}
$rules = $instance->get_rules();
return apply_filters( 'generateblocks_condition_rules', $rules, $type );
}
/**
* Get location rules (from your existing system).
*
* @return array
*/
private static function get_location_rules() {
$rules = [
'general:site' => __( 'Entire Site', 'generateblocks-pro' ),
'general:front_page' => __( 'Front Page', 'generateblocks-pro' ),
'general:blog' => __( 'Blog', 'generateblocks-pro' ),
'general:singular' => __( 'All Singular', 'generateblocks-pro' ),
'general:archive' => __( 'All Archives', 'generateblocks-pro' ),
'general:author' => __( 'Author Archives', 'generateblocks-pro' ),
'general:date' => __( 'Date Archives', 'generateblocks-pro' ),
'general:search' => __( 'Search Results', 'generateblocks-pro' ),
'general:404' => __( '404 Template', 'generateblocks-pro' ),
];
// Add post types.
$post_types = get_post_types( [ 'public' => true ], 'objects' );
foreach ( $post_types as $post_type_slug => $post_type ) {
$rules[ 'post:' . $post_type_slug ] = $post_type->labels->singular_name;
if ( $post_type->has_archive ) {
// translators: %s is the singular name of the post type.
$rules[ 'archive:' . $post_type_slug ] = sprintf( __( '%s Archive', 'generateblocks-pro' ), $post_type->labels->singular_name );
}
}
return $rules;
}
/**
* Get user role rules.
*
* @return array
*/
private static function get_user_role_rules() {
$rules = [
'general:all' => __( 'All Users', 'generateblocks-pro' ),
'general:logged_in' => __( 'Logged In', 'generateblocks-pro' ),
'general:logged_out' => __( 'Logged Out', 'generateblocks-pro' ),
];
if ( function_exists( 'get_editable_roles' ) ) {
$roles = get_editable_roles();
foreach ( $roles as $slug => $data ) {
$rules[ $slug ] = $data['name'];
}
}
return $rules;
}
/**
* Get metadata for a specific rule.
*
* @param string $type The condition type.
* @param string $rule The rule key.
* @return array
*/
public static function get_rule_metadata( $type, $rule ) {
// Ensure conditions are registered if called early.
if ( empty( GenerateBlocks_Pro_Conditions_Registry::get_all() ) && did_action( 'init' ) ) {
self::get_instance()->register_core_conditions();
}
$instance = GenerateBlocks_Pro_Conditions_Registry::get_instance( $type );
if ( ! $instance ) {
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => false,
];
}
$metadata = $instance->get_rule_metadata( $rule );
return apply_filters( 'generateblocks_rule_metadata', $metadata, $type, $rule );
}
/**
* Main function to determine if conditions are met.
*
* @param array $conditions The conditions array.
* @param array $context Optional context data (e.g., post_id for loop context).
* @return bool
*/
public static function show( $conditions, $context = [] ) {
if ( empty( $conditions ) || empty( $conditions['groups'] ) ) {
return true;
}
$group_results = [];
foreach ( $conditions['groups'] as $group ) {
if ( empty( $group['conditions'] ) ) {
$group_results[] = true;
continue;
}
$condition_results = [];
foreach ( $group['conditions'] as $condition ) {
$condition_results[] = self::evaluate_single_condition( $condition, $context );
}
// Apply group logic (AND/OR within group).
if ( 'OR' === $group['logic'] ) {
$group_results[] = in_array( true, $condition_results, true );
} else {
$group_results[] = ! in_array( false, $condition_results, true );
}
}
// Apply top-level logic to combine group results.
$top_logic = $conditions['logic'] ?? 'OR';
if ( 'AND' === $top_logic ) {
return ! in_array( false, $group_results, true );
} else {
return in_array( true, $group_results, true );
}
}
/**
* Evaluate a single condition.
*
* @param array $condition The condition to evaluate.
* @param array $context Optional context data (e.g., post_id for loop context).
* @return bool
*/
private static function evaluate_single_condition( $condition, $context = [] ) {
// Ensure conditions are registered.
if ( empty( GenerateBlocks_Pro_Conditions_Registry::get_all() ) && did_action( 'init' ) ) {
self::get_instance()->register_core_conditions();
}
if ( empty( $condition['type'] ) || empty( $condition['rule'] ) || empty( $condition['operator'] ) ) {
return false;
}
return GenerateBlocks_Pro_Conditions_Registry::evaluate(
$condition['type'],
$condition['rule'],
$condition['operator'],
$condition['value'] ?? '',
$context // Pass context through to conditions.
);
}
}
// Initialize the singleton instance.
GenerateBlocks_Pro_Conditions::get_instance()->init();
@@ -0,0 +1,41 @@
<?php
/**
* Conditions.
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Core files - order matters!
require_once 'interface-condition.php';
require_once 'class-condition-abstract.php';
require_once 'class-conditions-registry.php';
// Individual condition types.
require_once 'conditions/class-condition-location.php';
require_once 'conditions/class-condition-query-arg.php';
require_once 'conditions/class-condition-user-role.php';
require_once 'conditions/class-condition-date-time.php';
require_once 'conditions/class-condition-device.php';
require_once 'conditions/class-condition-referrer.php';
require_once 'conditions/class-condition-post-meta.php';
require_once 'conditions/class-condition-user-meta.php';
require_once 'conditions/class-condition-cookie.php';
require_once 'conditions/class-condition-language.php';
require_once 'conditions/class-condition-options.php';
require_once 'conditions/class-condition-author.php';
// Main conditions class.
require_once 'class-conditions.php';
// REST API.
require_once 'class-conditions-rest.php';
// Post type registration.
require_once 'class-conditions-post-type.php';
// Admin dashboard.
require_once 'class-conditions-dashboard.php';
@@ -0,0 +1,245 @@
<?php
/**
* Author Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Author condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Author extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$author_id = null;
// Use context if provided, otherwise get current post.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( $post_id ) {
$post = get_post( $post_id );
if ( $post ) {
$author_id = $post->post_author;
}
} elseif ( is_author() ) {
// Special case for author archive pages.
$author_id = get_queried_object_id();
}
if ( ! $author_id ) {
return false;
}
// Handle author meta custom fields.
if ( 'author_meta' === $rule ) {
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $author_id, $meta_key, $operator );
}
// Handle other operators using standardized method.
return $this->evaluate_meta_value( 'user', $author_id, $meta_key, $operator, $comparison_value );
}
// Handle multi-value operators for author selection.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $author_id ) {
return $this->check_single_author_match( $rule, $check_value, $author_id );
}
);
}
$is_match = false;
switch ( $rule ) {
case 'author_name':
// Check author display name or login.
if ( empty( $value ) ) {
return false;
}
$author = get_userdata( $author_id );
if ( $author ) {
$is_match = ( $author->display_name === $value ) || ( $author->user_login === $value );
}
break;
case 'author_id':
if ( empty( $value ) || '' === $value ) {
return false;
}
// Check specific author ID.
$is_match = ( $author_id == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
break;
case 'first_name':
case 'last_name':
case 'nickname':
case 'description':
// Check specific user meta fields using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $author_id, $rule, $operator );
}
return $this->evaluate_meta_value( 'user', $author_id, $rule, $operator, $value );
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if a single author value matches the current author.
*
* @param string $rule The condition rule.
* @param mixed $value The value to check.
* @param int $author_id Current author ID.
* @return bool
*/
private function check_single_author_match( $rule, $value, $author_id ) {
switch ( $rule ) {
case 'author_id':
if ( empty( $value ) || '' === $value ) {
return false;
}
return ( $author_id == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
case 'author_name':
if ( empty( $value ) || '' === $value ) {
return false;
}
$author = get_userdata( $author_id );
if ( $author ) {
return ( $author->display_name === $value ) || ( $author->user_login === $value );
}
break;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'author_id' => __( 'Author ID', 'generateblocks-pro' ),
'author_name' => __( 'Author Name', 'generateblocks-pro' ),
'author_meta' => __( 'Author Meta Key', 'generateblocks-pro' ),
];
// Add common author meta fields.
$author_fields = [
'first_name' => __( 'First Name', 'generateblocks-pro' ),
'last_name' => __( 'Last Name', 'generateblocks-pro' ),
'nickname' => __( 'Nickname', 'generateblocks-pro' ),
'description' => __( 'Biographical Info', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $author_fields );
return apply_filters( 'generateblocks_author_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'author_meta' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
if ( 'author_id' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'object_selector',
'supports_multi' => true,
];
}
// Text fields don't need greater_than/less_than.
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => false,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'author_meta' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific author rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Author meta supports all operators.
if ( 'author_meta' === $rule ) {
return [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
}
// Author ID supports multi-select.
if ( 'author_id' === $rule ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Text fields - no greater_than/less_than.
if ( in_array( $rule, [ 'author_name', 'first_name', 'last_name', 'nickname', 'description' ], true ) ) {
return [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains' ];
}
return [ 'is', 'is_not' ];
}
}
@@ -0,0 +1,153 @@
<?php
/**
* Cookie Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Cookie condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Cookie extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the cookie name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$cookie_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $cookie_name ) ) {
return false;
}
switch ( $operator ) {
case 'exists':
return $this->cookie_exists( $cookie_name );
case 'not_exists':
return ! $this->cookie_exists( $cookie_name );
case 'has_value':
return $this->cookie_has_value( $cookie_name );
case 'no_value':
return ! $this->cookie_has_value( $cookie_name );
case 'equals':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && $cookie_value === $comparison_value;
case 'contains':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && false !== strpos( $cookie_value, $comparison_value );
case 'not_contains':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null === $cookie_value || false === strpos( $cookie_value, $comparison_value );
case 'starts_with':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && 0 === strpos( $cookie_value, $comparison_value );
case 'ends_with':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && substr( $cookie_value, -strlen( $comparison_value ) ) === $comparison_value;
default:
return false;
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Cookie', 'generateblocks-pro' ),
];
// Add common cookie rules that might be useful.
$common_cookies = [
'wordpress_logged_in' => __( 'WordPress Logged In Cookie', 'generateblocks-pro' ),
'comment_author' => __( 'Comment Author Cookie', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_cookies );
// Allow filtering to add predefined cookies.
return apply_filters( 'generateblocks_cookie_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
// WordPress cookies typically need existence checks only.
if ( in_array( $rule, [ 'wordpress_logged_in', 'comment_author' ], true ) ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
return [
'needs_value' => true,
'value_type' => 'text',
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific cookie rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// WordPress cookies typically only need existence checks.
if ( in_array( $rule, [ 'wordpress_logged_in', 'comment_author' ], true ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Custom cookies support all text operators including not_contains.
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ];
}
}
@@ -0,0 +1,399 @@
<?php
/**
* Date Time Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Date/time condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Date_Time extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Handle day of week separately with multi-select support.
if ( 'day_of_week' === $rule ) {
return $this->evaluate_day_of_week( $operator, $value );
}
// Get current time as DateTime object for proper timezone handling.
$current_datetime = current_datetime();
if ( empty( $value ) ) {
return false;
}
// Handle time-only rules (current_time and time_of_day).
if ( in_array( $rule, [ 'current_time', 'time_of_day' ], true ) ) {
return $this->evaluate_time_only( $current_datetime, $operator, $value );
}
// For date-based rules, use full timestamp comparison.
$current_time = $current_datetime->getTimestamp();
switch ( $operator ) {
case 'before':
return $this->is_before( $current_time, $value );
case 'after':
return $this->is_after( $current_time, $value );
case 'on':
return $this->is_on( $current_time, $value, $rule );
case 'between':
return $this->is_between( $current_time, $value );
default:
return false;
}
}
/**
* Evaluate time-only condition (ignoring date).
*
* @param DateTime $current_datetime Current DateTime object.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @return bool
*/
private function evaluate_time_only( $current_datetime, $operator, $value ) {
// Convert current time to seconds since midnight.
$current_hours = (int) $current_datetime->format( 'H' );
$current_minutes = (int) $current_datetime->format( 'i' );
$current_seconds_component = (int) $current_datetime->format( 's' );
$current_seconds = ( $current_hours * HOUR_IN_SECONDS ) +
( $current_minutes * MINUTE_IN_SECONDS ) +
$current_seconds_component;
switch ( $operator ) {
case 'before':
$target_seconds = $this->parse_time_to_seconds( $value );
return false !== $target_seconds && $current_seconds < $target_seconds;
case 'after':
$target_seconds = $this->parse_time_to_seconds( $value );
return false !== $target_seconds && $current_seconds > $target_seconds;
case 'on':
// For time "on", check if within the same hour.
$target_seconds = $this->parse_time_to_seconds( $value );
if ( false === $target_seconds ) {
return false;
}
$target_hour = intval( $target_seconds / HOUR_IN_SECONDS );
return $current_hours === $target_hour;
case 'between':
// Handle time range (e.g., "09:00, 17:00" or "22:00, 02:00" for overnight).
$times = array_map( 'trim', explode( ',', $value ) );
if ( 2 !== count( $times ) ) {
return false;
}
$start_seconds = $this->parse_time_to_seconds( $times[0] );
$end_seconds = $this->parse_time_to_seconds( $times[1] );
if ( false === $start_seconds || false === $end_seconds ) {
return false;
}
// Handle overnight ranges (e.g., 22:00 to 02:00).
if ( $start_seconds > $end_seconds ) {
// Current time is either after start OR before end.
return $current_seconds >= $start_seconds || $current_seconds <= $end_seconds;
} else {
// Normal range within same day.
return $current_seconds >= $start_seconds && $current_seconds <= $end_seconds;
}
default:
return false;
}
}
/**
* Parse time string to seconds since midnight.
*
* @param string $time_str Time string (HH:MM or full datetime).
* @return int|false Seconds since midnight or false on failure.
*/
private function parse_time_to_seconds( $time_str ) {
if ( empty( $time_str ) ) {
return false;
}
// For time-only values (HH:MM format), parse directly in WordPress timezone
// to avoid timezone conversion issues.
try {
// Create DateTime in WordPress timezone directly.
$datetime = new DateTime( $time_str, wp_timezone() );
} catch ( Exception $e ) {
// If parsing fails, try with today's date prepended.
try {
$today = current_datetime()->format( 'Y-m-d' );
$datetime = new DateTime( $today . ' ' . $time_str, wp_timezone() );
} catch ( Exception $e2 ) {
return false;
}
}
// Extract time components.
$hours = (int) $datetime->format( 'H' );
$minutes = (int) $datetime->format( 'i' );
$seconds = (int) $datetime->format( 's' );
// Calculate seconds since midnight.
$result = ( $hours * HOUR_IN_SECONDS ) + ( $minutes * MINUTE_IN_SECONDS ) + $seconds;
return $result;
}
/**
* Evaluate day of week condition.
*
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @return bool
*/
private function evaluate_day_of_week( $operator, $value ) {
// Get current day of week (1 = Monday, 7 = Sunday).
$current_day = intval( current_datetime()->format( 'N' ) );
// Handle multi-value operators.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value( $operator, $current_day, $value );
}
// Parse value for single operators.
$target_day = intval( $value );
switch ( $operator ) {
case 'is':
return $current_day === $target_day;
case 'is_not':
return $current_day !== $target_day;
default:
return false;
}
}
/**
* Check if current time is before target time.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @return bool
*/
private function is_before( $current_time, $target_time ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$compare_time = $target_datetime->getTimestamp();
return $current_time < $compare_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is after target time.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @return bool
*/
private function is_after( $current_time, $target_time ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$compare_time = $target_datetime->getTimestamp();
return $current_time > $compare_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is on target date.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @param string $rule The rule type.
* @return bool
*/
private function is_on( $current_time, $target_time, $rule ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$current_datetime = new DateTime();
$current_datetime->setTimestamp( $current_time );
$current_datetime->setTimezone( wp_timezone() );
if ( 'current_date' === $rule ) {
// Compare dates in WordPress timezone.
return $current_datetime->format( 'Y-m-d' ) === $target_datetime->format( 'Y-m-d' );
}
// For time comparison, check if within the same hour.
return $current_datetime->format( 'Y-m-d H' ) === $target_datetime->format( 'Y-m-d H' );
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is between two times.
*
* @param int $current_time Current timestamp.
* @param string $value Comma-separated start and end times.
* @return bool
*/
private function is_between( $current_time, $value ) {
$dates = array_map( 'trim', explode( ',', $value ) );
if ( 2 !== count( $dates ) ) {
return false;
}
try {
// Parse both dates using WordPress timezone.
$start_datetime = new DateTime( $dates[0], wp_timezone() );
$end_datetime = new DateTime( $dates[1], wp_timezone() );
$start_time = $start_datetime->getTimestamp();
$end_time = $end_datetime->getTimestamp();
return $current_time >= $start_time && $current_time <= $end_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'current_date' => __( 'Date', 'generateblocks-pro' ),
'current_time' => __( 'Time', 'generateblocks-pro' ),
'day_of_week' => __( 'Day of Week', 'generateblocks-pro' ),
'time_of_day' => __( 'Time of Day', 'generateblocks-pro' ), // Hidden in UI, kept for backward compatibility.
];
return apply_filters( 'generateblocks_date_time_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'day_of_week' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'day_selector',
'supports_multi' => true,
];
}
// Time-only rules use time picker.
if ( in_array( $rule, [ 'current_time', 'time_of_day' ], true ) ) {
return [
'needs_value' => true,
'value_type' => 'time',
];
}
// Date rule uses datetime picker.
return [
'needs_value' => true,
'value_type' => 'datetime',
];
}
/**
* Get operators available for a specific date/time rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
if ( 'day_of_week' === $rule ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Other date/time rules use temporal operators.
return [ 'before', 'after', 'between', 'on' ];
}
/**
* Get available day options.
*
* @return array
*/
public function get_day_options() {
return [
'1' => __( 'Monday', 'generateblocks-pro' ),
'2' => __( 'Tuesday', 'generateblocks-pro' ),
'3' => __( 'Wednesday', 'generateblocks-pro' ),
'4' => __( 'Thursday', 'generateblocks-pro' ),
'5' => __( 'Friday', 'generateblocks-pro' ),
'6' => __( 'Saturday', 'generateblocks-pro' ),
'7' => __( 'Sunday', 'generateblocks-pro' ),
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'day_of_week' === $rule ) {
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'intval', $value );
}
// Try to decode JSON array.
$decoded = json_decode( $value, true );
if ( is_array( $decoded ) ) {
return wp_json_encode( array_map( 'intval', $decoded ) );
}
// Single value.
return intval( $value );
}
// For between operator, ensure proper format.
if ( false !== strpos( $value, ',' ) ) {
$dates = array_map( 'trim', explode( ',', $value ) );
$sanitized_dates = array_map( 'sanitize_text_field', $dates );
return implode( ', ', $sanitized_dates );
}
return sanitize_text_field( $value );
}
}
@@ -0,0 +1,116 @@
<?php
/**
* Device Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Device condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Device extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$is_match = false;
if ( ! function_exists( 'wp_is_mobile' ) ) {
return false;
}
switch ( $rule ) {
case 'mobile':
$is_match = wp_is_mobile() && ! $this->is_tablet();
break;
case 'tablet':
$is_match = $this->is_tablet();
break;
case 'desktop':
$is_match = ! wp_is_mobile();
break;
case 'ios':
$is_match = $this->is_ios();
break;
case 'android':
$is_match = $this->is_android();
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Basic tablet detection.
*
* @return bool
*/
private function is_tablet() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/(tablet|ipad|playbook|silk)|(android(?!.*mobile))/i', $user_agent );
}
/**
* Check if device is iOS.
*
* @return bool
*/
private function is_ios() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/(iPhone|iPod|iPad)/i', $user_agent );
}
/**
* Check if device is Android.
*
* @return bool
*/
private function is_android() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/Android/i', $user_agent );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'mobile' => __( 'Mobile', 'generateblocks-pro' ),
'tablet' => __( 'Tablet', 'generateblocks-pro' ),
'desktop' => __( 'Desktop', 'generateblocks-pro' ),
'ios' => __( 'iOS', 'generateblocks-pro' ),
'android' => __( 'Android', 'generateblocks-pro' ),
];
return apply_filters( 'generateblocks_device_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
}
@@ -0,0 +1,199 @@
<?php
/**
* Language Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Language condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Language extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$current_locale = $this->get_current_locale();
$is_match = false;
switch ( $rule ) {
case 'locale':
// Full locale match (e.g., en_US).
$is_match = $current_locale === $value;
break;
case 'language':
// Language code match (e.g., en).
$current_lang = substr( $current_locale, 0, 2 );
$is_match = $current_lang === $value;
break;
case 'rtl':
// Check if Right-to-Left language.
$is_match = is_rtl();
break;
case 'custom':
// Custom locale check.
$is_match = $current_locale === $value;
break;
default:
// For predefined locales (en_US, fr_FR, etc.), check directly.
$is_match = $current_locale === $rule;
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Get the current locale.
*
* @return string
*/
private function get_current_locale() {
// Check for WPML.
if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
global $sitepress;
if ( $sitepress && method_exists( $sitepress, 'get_locale' ) ) {
$locale = $sitepress->get_locale( ICL_LANGUAGE_CODE );
if ( $locale ) {
return $locale;
}
}
}
// Check for Polylang.
if ( function_exists( 'pll_current_language' ) ) {
$locale = pll_current_language( 'locale' );
if ( $locale ) {
return $locale;
}
}
// Default WordPress locale.
return get_locale();
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'locale' => __( 'Full Locale (e.g., en_US)', 'generateblocks-pro' ),
'language' => __( 'Language Code (e.g., en)', 'generateblocks-pro' ),
'rtl' => __( 'RTL Language', 'generateblocks-pro' ),
'custom' => __( 'Custom Locale', 'generateblocks-pro' ),
];
// Add common locales.
$common_locales = [
'en_US' => __( 'English (United States)', 'generateblocks-pro' ),
'en_GB' => __( 'English (UK)', 'generateblocks-pro' ),
'en_CA' => __( 'English (Canada)', 'generateblocks-pro' ),
'en_AU' => __( 'English (Australia)', 'generateblocks-pro' ),
'es_ES' => __( 'Spanish (Spain)', 'generateblocks-pro' ),
'es_MX' => __( 'Spanish (Mexico)', 'generateblocks-pro' ),
'fr_FR' => __( 'French (France)', 'generateblocks-pro' ),
'fr_CA' => __( 'French (Canada)', 'generateblocks-pro' ),
'de_DE' => __( 'German', 'generateblocks-pro' ),
'it_IT' => __( 'Italian', 'generateblocks-pro' ),
'pt_BR' => __( 'Portuguese (Brazil)', 'generateblocks-pro' ),
'pt_PT' => __( 'Portuguese (Portugal)', 'generateblocks-pro' ),
'nl_NL' => __( 'Dutch', 'generateblocks-pro' ),
'ru_RU' => __( 'Russian', 'generateblocks-pro' ),
'ja' => __( 'Japanese', 'generateblocks-pro' ),
'zh_CN' => __( 'Chinese (Simplified)', 'generateblocks-pro' ),
'zh_TW' => __( 'Chinese (Traditional)', 'generateblocks-pro' ),
'ko_KR' => __( 'Korean', 'generateblocks-pro' ),
'ar' => __( 'Arabic', 'generateblocks-pro' ),
'he_IL' => __( 'Hebrew', 'generateblocks-pro' ),
];
// Get installed languages if available.
if ( function_exists( 'get_available_languages' ) ) {
$installed = get_available_languages();
foreach ( $installed as $locale ) {
if ( ! isset( $common_locales[ $locale ] ) && 'en_US' !== $locale ) {
$common_locales[ $locale ] = $locale;
}
}
}
$rules = array_merge( $rules, $common_locales );
return apply_filters( 'generateblocks_language_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
// RTL check doesn't need a value.
if ( 'rtl' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
// Language code needs a 2-letter code.
if ( 'language' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'text',
];
}
// Custom and locale need full locale codes.
if ( in_array( $rule, [ 'locale', 'custom' ], true ) ) {
return [
'needs_value' => true,
'value_type' => 'text',
];
}
// Predefined locales don't need values.
return [
'needs_value' => false,
'value_type' => 'none',
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
// For language codes, ensure lowercase and 2 characters.
if ( 'language' === $rule ) {
return strtolower( substr( sanitize_text_field( $value ), 0, 2 ) );
}
// For locales, allow underscores and dashes.
if ( in_array( $rule, [ 'locale', 'custom' ], true ) ) {
return preg_replace( '/[^a-zA-Z0-9_-]/', '', sanitize_text_field( $value ) );
}
return sanitize_text_field( $value );
}
}
@@ -0,0 +1,537 @@
<?php
/**
* Location Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Location condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Location extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$current_location = $this->get_current_location();
// Handle parent/child relationships first (they have their own multi-value handling).
if ( in_array( $rule, [ 'child_of', 'parent_of' ], true ) ) {
return $this->evaluate_hierarchy( $rule, $operator, $value, $context );
}
// Handle post taxonomy terms.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return $this->evaluate_post_terms( $rule, $operator, $value, $context );
}
// Handle multi-value operators for non-hierarchical rules.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $current_location ) {
return $this->check_single_location_match( $rule, $check_value, $current_location );
}
);
}
$is_match = false;
// Check if current location matches the rule.
if ( in_array( $rule, $current_location['location'], true ) ) {
if ( empty( $value ) ) {
$is_match = true;
} else {
// Check specific object (post ID, term ID, etc.).
$is_match = ( $current_location['object'] == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
}
}
// Special cases - use true original query to check page context, not loop context.
if ( ! $is_match ) {
global $wp_the_query;
switch ( $rule ) {
case 'general:site':
$is_match = true;
break;
case 'general:singular':
$is_match = $wp_the_query->is_singular ?? is_singular();
break;
case 'general:archive':
$is_match = $wp_the_query->is_archive ?? is_archive();
break;
}
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if a single location value matches the current location.
*
* @param string $rule The condition rule.
* @param mixed $check_value The value to check.
* @param array $current_location Current location data.
* @return bool
*/
private function check_single_location_match( $rule, $check_value, $current_location ) {
// Check if current location rule matches.
if ( in_array( $rule, $current_location['location'], true ) ) {
if ( empty( $check_value ) ) {
return true;
} else {
// Check specific object (post ID, term ID, etc.).
return ( $current_location['object'] == $check_value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
}
}
// Special cases for general rules - use true original query.
global $wp_the_query;
switch ( $rule ) {
case 'general:site':
return true;
case 'general:singular':
return $wp_the_query->is_singular ?? is_singular();
case 'general:archive':
return $wp_the_query->is_archive ?? is_archive();
}
return false;
}
/**
* Evaluate post taxonomy terms condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
private function evaluate_post_terms( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Extract taxonomy from rule.
$taxonomy = str_replace( 'post_terms:', '', $rule );
if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
return false;
}
// Get post terms.
$post_terms = get_the_terms( $post->ID, $taxonomy );
if ( is_wp_error( $post_terms ) ) {
return false;
}
// Convert to array of term IDs.
$post_term_ids = [];
if ( $post_terms && is_array( $post_terms ) ) {
$post_term_ids = wp_list_pluck( $post_terms, 'term_id' );
$post_term_ids = array_map( 'strval', $post_term_ids ); // Convert to strings for comparison.
}
// Handle multi-value operators.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_array( $operator, $post_term_ids, $value );
}
// For single value operators.
if ( empty( $value ) ) {
// No specific term - just check if post has any terms.
return 'is_not' === $operator ? empty( $post_term_ids ) : ! empty( $post_term_ids );
}
$has_term = in_array( strval( $value ), $post_term_ids, true );
return 'is_not' === $operator ? ! $has_term : $has_term;
}
/**
* Evaluate hierarchical relationships.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
private function evaluate_hierarchy( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Check if post type is hierarchical.
$post_type_object = get_post_type_object( $post->post_type );
if ( ! $post_type_object || ! $post_type_object->hierarchical ) {
return false;
}
// Handle empty value - check for any parent/child relationship.
if ( empty( $value ) ) {
return $this->check_any_hierarchy( $rule, $operator, $post );
}
// Handle multi-value for hierarchy.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $post ) {
return $this->check_single_hierarchy( $rule, $check_value, $post );
}
);
}
$is_match = $this->check_single_hierarchy( $rule, $value, $post );
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check single hierarchy relationship.
*
* @param string $rule The hierarchy rule.
* @param mixed $value The value to check.
* @param WP_Post $post The post object to check.
* @return bool
*/
private function check_single_hierarchy( $rule, $value, $post ) {
if ( 'child_of' === $rule ) {
// Check if current post is a child of the specified post.
$ancestors = get_post_ancestors( $post->ID );
return in_array( intval( $value ), $ancestors, true );
} elseif ( 'parent_of' === $rule ) {
// Check if current post is a parent of the specified post.
$target_post = get_post( intval( $value ) );
if ( $target_post ) {
$target_ancestors = get_post_ancestors( $target_post->ID );
return in_array( $post->ID, $target_ancestors, true );
}
}
return false;
}
/**
* Check if post has any hierarchical relationship.
*
* @param string $rule The hierarchy rule.
* @param string $operator The condition operator.
* @param WP_Post $post The post object to check.
* @return bool
*/
private function check_any_hierarchy( $rule, $operator, $post ) {
if ( 'child_of' === $rule ) {
// Check if current post has any parent.
$has_parent = 0 < $post->post_parent;
return 'is_not' === $operator ? ! $has_parent : $has_parent;
} elseif ( 'parent_of' === $rule ) {
// Check if current post has any children.
$children = get_children(
array(
'post_parent' => $post->ID,
'post_type' => $post->post_type,
'numberposts' => 1,
'post_status' => array( 'publish', 'private', 'draft' ),
)
);
$has_children = ! empty( $children );
return 'is_not' === $operator ? ! $has_children : $has_children;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'general:site' => __( 'Entire Site', 'generateblocks-pro' ),
'general:front_page' => __( 'Front Page', 'generateblocks-pro' ),
'general:blog' => __( 'Blog', 'generateblocks-pro' ),
'general:singular' => __( 'All Singular', 'generateblocks-pro' ),
'general:archive' => __( 'All Archives', 'generateblocks-pro' ),
'general:author' => __( 'Author Archives', 'generateblocks-pro' ),
'general:date' => __( 'Date Archives', 'generateblocks-pro' ),
'general:search' => __( 'Search Results', 'generateblocks-pro' ),
'general:no_results' => __( 'No Results', 'generateblocks-pro' ),
'general:404' => __( '404 Template', 'generateblocks-pro' ),
];
// Add hierarchical relationships.
$rules['child_of'] = __( 'Child of', 'generateblocks-pro' );
$rules['parent_of'] = __( 'Parent of', 'generateblocks-pro' );
// Add post types.
$post_types = get_post_types( [ 'public' => true ], 'objects' );
foreach ( $post_types as $post_type_slug => $post_type ) {
$rules[ 'post:' . $post_type_slug ] = $post_type->labels->singular_name;
if ( $post_type->has_archive ) {
$rules[ 'archive:' . $post_type_slug ] = sprintf(
/* translators: %s: post type singular name */
__( '%s Archive', 'generateblocks-pro' ),
$post_type->labels->singular_name
);
}
}
// Add taxonomies for archive pages.
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
foreach ( $taxonomies as $taxonomy_slug => $taxonomy ) {
$rules[ 'taxonomy:' . $taxonomy_slug ] = sprintf(
/* translators: %s: taxonomy singular name */
__( '%s Archive', 'generateblocks-pro' ),
$taxonomy->labels->singular_name
);
}
// Add post taxonomy terms - for checking if current post has specific terms.
foreach ( $taxonomies as $taxonomy_slug => $taxonomy ) {
// Use name if available, fallback to singular_name for tests.
$taxonomy_name = isset( $taxonomy->labels->name ) ? $taxonomy->labels->name : $taxonomy->labels->singular_name;
$rules[ 'post_terms:' . $taxonomy_slug ] = sprintf(
/* translators: %s: taxonomy name */
__( 'Post %s', 'generateblocks-pro' ),
$taxonomy_name
);
}
return apply_filters( 'generateblocks_location_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
$general_location_rules = array(
'general:site',
'general:front_page',
'general:blog',
'general:singular',
'general:archive',
'general:author',
'general:date',
'general:search',
'general:no_results',
'general:404',
);
if ( in_array( $rule, $general_location_rules, true ) ) {
return array(
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
);
}
// Parent/child relationships need object selection but allow empty values.
if ( in_array( $rule, array( 'child_of', 'parent_of' ), true ) ) {
return array(
'needs_value' => false,
'value_type' => 'hierarchical_object_selector',
'supports_multi' => true,
);
}
// Archive rules typically don't need values.
if ( 0 === strpos( $rule, 'archive:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
);
}
// Post taxonomy terms need term selection.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'object_selector',
'supports_multi' => true,
);
}
// Post and taxonomy rules can optionally have specific object selection.
if ( 0 === strpos( $rule, 'post:' ) || 0 === strpos( $rule, 'taxonomy:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'object_selector',
'supports_multi' => true,
);
}
return $this->get_default_rule_metadata();
}
/**
* Get current location data.
*
* @return array
*/
private function get_current_location() {
global $wp_the_query;
$location = [];
$object = null;
// Always use the true original query to determine location, not the current loop item.
// This ensures Location conditions check "where you are" not "what you're looking at".
// Using $wp_the_query is bulletproof against query_posts() and other query manipulations.
// Falls back to global functions if $wp_the_query is not available.
if ( $wp_the_query->is_front_page ?? is_front_page() ) {
$location[] = 'general:front_page';
// When "Your homepage displays" is set to "Your latest posts",
// both is_front_page() and is_home() are true.
if ( $wp_the_query->is_home ?? is_home() ) {
$location[] = 'general:blog';
}
if ( $wp_the_query->is_page ?? is_page() ) {
$location[] = 'post:page';
$location[] = 'general:singular';
// Get the front page ID for specific page targeting.
$front_page_id = get_option( 'page_on_front' );
if ( $front_page_id ) {
$object = $front_page_id;
}
}
} elseif ( $wp_the_query->is_home ?? is_home() ) {
$location[] = 'general:blog';
} elseif ( $wp_the_query->is_singular ?? is_singular() ) {
$location[] = 'general:singular';
$queried_object = isset( $wp_the_query ) ? $wp_the_query->get_queried_object() : get_queried_object();
if ( $queried_object && isset( $queried_object->post_type ) ) {
$location[] = 'post:' . $queried_object->post_type;
$object = $queried_object->ID;
}
} elseif ( $wp_the_query->is_archive ?? is_archive() ) {
$location[] = 'general:archive';
if ( ( $wp_the_query->is_category ?? is_category() ) || ( $wp_the_query->is_tag ?? is_tag() ) || ( $wp_the_query->is_tax ?? is_tax() ) ) {
$queried_object = isset( $wp_the_query ) ? $wp_the_query->get_queried_object() : get_queried_object();
if ( $queried_object && isset( $queried_object->taxonomy ) ) {
$location[] = 'taxonomy:' . $queried_object->taxonomy;
$object = $queried_object->term_id;
}
} elseif ( $wp_the_query->is_post_type_archive ?? is_post_type_archive() ) {
$post_type = isset( $wp_the_query ) ? $wp_the_query->get( 'post_type' ) : get_query_var( 'post_type' );
if ( $post_type ) {
$location[] = 'archive:' . $post_type;
}
} elseif ( $wp_the_query->is_author ?? is_author() ) {
$location[] = 'general:author';
} elseif ( $wp_the_query->is_date ?? is_date() ) {
$location[] = 'general:date';
}
} elseif ( $wp_the_query->is_search ?? is_search() ) {
$location[] = 'general:search';
// Check if search has no results.
if ( ( $wp_the_query->found_posts ?? 0 ) === 0 ) {
$location[] = 'general:no_results';
}
} elseif ( $wp_the_query->is_404 ?? is_404() ) {
$location[] = 'general:404';
}
// Always include site.
$location[] = 'general:site';
return [
'location' => $location,
'object' => $object,
];
}
/**
* Get operators available for a specific location rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// General location rules - only basic operators (you're either there or not).
$general_rules = array(
'general:site',
'general:front_page',
'general:blog',
'general:singular',
'general:archive',
'general:author',
'general:date',
'general:search',
'general:no_results',
'general:404',
);
if ( in_array( $rule, $general_rules, true ) ) {
return array( 'is', 'is_not' );
}
// Archive rules - only basic operators.
if ( 0 === strpos( $rule, 'archive:' ) ) {
return array( 'is', 'is_not' );
}
// Hierarchical relationships - simplified operators (no includes_all/excludes_all).
if ( in_array( $rule, array( 'child_of', 'parent_of' ), true ) ) {
return array( 'is', 'is_not', 'includes_any', 'excludes_any' );
}
// Post taxonomy terms - full multi-value support.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return array( 'is', 'is_not', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' );
}
// For all other rules, check if multi-select is actually supported.
if ( $this->rule_supports_multi_select( $rule ) ) {
// Multi-select interface available.
if ( 0 === strpos( $rule, 'post:' ) ) {
// Posts: can select multiple but can't be on all simultaneously.
return array( 'is', 'is_not', 'includes_any', 'excludes_any' );
}
// Taxonomies: full support.
return array( 'is', 'is_not', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' );
} else {
// Single input only: no any/all operators.
return array( 'is', 'is_not' );
}
}
}
@@ -0,0 +1,195 @@
<?php
/**
* Options Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Options condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Options extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the option name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$option_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $option_name ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'option', 0, $option_name, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'option', 0, $option_name );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'option', 0, $option_name );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'option', 0, $option_name, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Option', 'generateblocks-pro' ),
];
// Add common WordPress options.
$common_options = [
'blogname' => __( 'Site Title', 'generateblocks-pro' ),
'blogdescription' => __( 'Site Tagline', 'generateblocks-pro' ),
'timezone_string' => __( 'Timezone', 'generateblocks-pro' ),
'date_format' => __( 'Date Format', 'generateblocks-pro' ),
'time_format' => __( 'Time Format', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_options );
// Allow themes/plugins to add their options.
return apply_filters( 'generateblocks_options_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Boolean options need special handling.
$boolean_options = [
'users_can_register',
'comment_registration',
'close_comments_for_old_posts',
'comment_moderation',
'moderation_notify',
'comments_notify',
];
if ( in_array( $rule, $boolean_options, true ) ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
// Numeric options.
$numeric_options = [
'posts_per_page',
'comments_per_page',
'start_of_week',
];
if ( in_array( $rule, $numeric_options, true ) ) {
return [
'needs_value' => true,
'value_type' => 'number',
'supports_multi' => true,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific option rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Boolean options only need existence checks.
$boolean_options = [
'users_can_register',
'comment_registration',
'close_comments_for_old_posts',
'comment_moderation',
'moderation_notify',
'comments_notify',
];
if ( in_array( $rule, $boolean_options, true ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Numeric options support all operators.
$numeric_options = [
'posts_per_page',
'comments_per_page',
'start_of_week',
];
if ( in_array( $rule, $numeric_options, true ) ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'greater_than', 'less_than' ];
}
}
// Text options support text operators.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
}
@@ -0,0 +1,154 @@
<?php
/**
* Post Meta Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Post meta condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Post_Meta extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Parse the meta key and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'post', $post->ID, $meta_key, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'post', $post->ID, $meta_key );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'post', $post->ID, $meta_key );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'post', $post->ID, $meta_key, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Meta Key', 'generateblocks-pro' ),
];
// Add common meta keys.
$common_keys = [
'_thumbnail_id' => __( 'Has Featured Image', 'generateblocks-pro' ),
'_wp_page_template' => __( 'Page Template', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_keys );
// Allow themes/plugins to add their meta keys.
return apply_filters( 'generateblocks_post_meta_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Special handling for specific meta keys.
if ( '_thumbnail_id' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
// Page template is text-based.
if ( '_wp_page_template' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Get operators available for a specific post meta rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Special case for thumbnail - only existence checks.
if ( '_thumbnail_id' === $rule ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Page template - only text operations, no greater_than/less_than.
if ( '_wp_page_template' === $rule ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
// Custom meta key supports all operators including numeric.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than' ];
}
}
}
@@ -0,0 +1,160 @@
<?php
/**
* Query Argument Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Query argument condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Query_Arg extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the parameter name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$param_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $param_name ) ) {
return false;
}
switch ( $operator ) {
case 'exists':
return $this->query_param_exists( $param_name );
case 'not_exists':
return ! $this->query_param_exists( $param_name );
case 'has_value':
return $this->query_param_has_value( $param_name );
case 'no_value':
return ! $this->query_param_has_value( $param_name );
case 'equals':
$param_value = $this->get_query_param_raw( $param_name );
// For arrays, check if comparison value is in the array.
if ( is_array( $param_value ) ) {
return in_array( $comparison_value, $param_value, true );
}
return null !== $param_value && $param_value === $comparison_value;
case 'contains':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return false !== strpos( $param_value, $comparison_value );
case 'not_contains':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return true for arrays (they don't "contain" the string).
if ( ! is_string( $param_value ) ) {
return true;
}
return false === strpos( $param_value, $comparison_value );
case 'starts_with':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return 0 === strpos( $param_value, $comparison_value );
case 'ends_with':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return substr( $param_value, -strlen( $comparison_value ) ) === $comparison_value;
default:
return false;
}
}
/**
* Get raw query parameter value without sanitization to preserve arrays.
*
* @param string $param_name Parameter name.
* @return mixed|null
*/
private function get_query_param_raw( $param_name ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET[ $param_name ] ) ) {
return null;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$value = wp_unslash( $_GET[ $param_name ] );
// Sanitize based on type.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Parameter', 'generateblocks-pro' ),
];
// Allow filtering to add predefined parameters.
return apply_filters( 'generateblocks_query_arg_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
return [
'needs_value' => true,
'value_type' => 'text',
];
}
/**
* Get operators available for a specific query arg rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// All query args support the same text operators.
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ];
}
}
@@ -0,0 +1,138 @@
<?php
/**
* Referrer Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Referrer condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Referrer extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$referrer = $this->get_server_var( 'HTTP_REFERER' );
// Handle direct traffic (no referrer).
if ( 'direct' === $rule ) {
$has_referrer = ! empty( $referrer );
return 'is_not' === $operator ? $has_referrer : ! $has_referrer;
}
// Handle custom referrer.
if ( 'custom' === $rule ) {
// Handle has_value/no_value operators first.
if ( 'has_value' === $operator ) {
return $this->referrer_has_value();
}
if ( 'no_value' === $operator ) {
return ! $this->referrer_has_value();
}
$parsed = $this->parse_custom_value( $value );
$comparison_value = $parsed['comparison_value'] ? $parsed['comparison_value'] : $parsed['field_name'];
if ( empty( $comparison_value ) ) {
return false;
}
$is_match = false;
switch ( $operator ) {
case 'contains':
$is_match = false !== strpos( $referrer, $comparison_value );
break;
case 'not_contains':
$is_match = false === strpos( $referrer, $comparison_value );
break;
case 'equals':
$is_match = $referrer === $comparison_value;
break;
case 'starts_with':
$is_match = 0 === strpos( $referrer, $comparison_value );
break;
case 'ends_with':
$is_match = substr( $referrer, -strlen( $comparison_value ) ) === $comparison_value;
break;
}
return $is_match;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'direct' => __( 'Direct Traffic (No Referrer)', 'generateblocks-pro' ),
'custom' => __( 'Custom Referrer', 'generateblocks-pro' ),
];
return apply_filters( 'generateblocks_referrer_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
if ( 'direct' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
return $this->get_default_rule_metadata();
}
/**
* Get operators available for a specific referrer rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
if ( 'direct' === $rule ) {
// Direct traffic only supports is/is_not.
return [ 'is', 'is_not' ];
}
if ( 'custom' === $rule ) {
// Custom referrer supports all text comparison operators including not_contains.
return [ 'has_value', 'no_value', 'contains', 'not_contains', 'equals', 'starts_with', 'ends_with' ];
}
return [ 'contains', 'not_contains', 'equals', 'starts_with' ];
}
}
@@ -0,0 +1,166 @@
<?php
/**
* User Meta Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* User meta condition evaluator.
*/
class GenerateBlocks_Pro_Condition_User_Meta extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// User must be logged in for user meta conditions.
if ( ! is_user_logged_in() ) {
return false;
}
$user_id = get_current_user_id();
if ( ! $user_id ) {
return false;
}
// Parse the meta key and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $user_id, $meta_key, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'user', $user_id, $meta_key );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'user', $user_id, $meta_key );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'user', $user_id, $meta_key, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Meta Key', 'generateblocks-pro' ),
];
// Add common user meta keys.
$common_keys = [
'first_name' => __( 'First Name', 'generateblocks-pro' ),
'last_name' => __( 'Last Name', 'generateblocks-pro' ),
'nickname' => __( 'Nickname', 'generateblocks-pro' ),
'description' => __( 'Biographical Info', 'generateblocks-pro' ),
'show_admin_bar_front' => __( 'Show Admin Bar', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_keys );
// Allow themes/plugins to add their meta keys.
return apply_filters( 'generateblocks_user_meta_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Show admin bar is a boolean stored as string.
if ( 'show_admin_bar_front' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific user meta rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Show admin bar only needs existence checks.
if ( 'show_admin_bar_front' === $rule ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Text fields - no greater_than/less_than as per feedback.
if ( in_array( $rule, [ 'first_name', 'last_name', 'nickname', 'description' ], true ) ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
// Custom meta key supports all operators.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than' ];
}
}
}
@@ -0,0 +1,179 @@
<?php
/**
* User Role Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* User role condition evaluator.
*/
class GenerateBlocks_Pro_Condition_User_Role extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Handle multi-value operators for roles and capabilities only.
if ( $this->is_multi_value_operator( $operator ) && $this->rule_supports_multi_select( $rule ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) {
return $this->check_single_user_role( $check_value );
}
);
}
$is_match = false;
switch ( $rule ) {
case 'general:logged_in':
$is_match = is_user_logged_in();
break;
case 'general:logged_out':
$is_match = ! is_user_logged_in();
break;
default:
// Check specific role or capability.
$user = wp_get_current_user();
if ( $user && $user->exists() ) {
// Check if it's a capability (prefixed with 'cap:').
if ( 0 === strpos( $rule, 'cap:' ) ) {
// Remove prefix.
$capability = substr( $rule, 4 );
$is_match = user_can( $user, $capability );
} else {
// It's a role.
$is_match = in_array( $rule, (array) $user->roles, true );
}
}
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if current user matches a single role/capability.
*
* @param string $check_value The role or capability to check.
* @return bool
*/
private function check_single_user_role( $check_value ) {
$user = wp_get_current_user();
if ( ! $user || ! $user->exists() ) {
// For logged out users, only check against general rules.
return 'general:logged_out' === $check_value;
}
switch ( $check_value ) {
case 'general:logged_in':
return true; // User is logged in if we reach this point.
case 'general:logged_out':
return false; // User is logged in.
default:
// Check specific role or capability.
if ( 0 === strpos( $check_value, 'cap:' ) ) {
// Remove prefix.
$capability = substr( $check_value, 4 );
return user_can( $user, $capability );
} else {
// It's a role.
$user_roles = (array) $user->roles;
return in_array( $check_value, $user_roles, true );
}
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'general:logged_in' => __( 'Logged In', 'generateblocks-pro' ),
'general:logged_out' => __( 'Logged Out', 'generateblocks-pro' ),
];
// Ensure get_editable_roles() is available.
if ( ! function_exists( 'get_editable_roles' ) ) {
require_once ABSPATH . 'wp-admin/includes/user.php';
}
// Add WordPress roles.
if ( function_exists( 'get_editable_roles' ) ) {
$roles = get_editable_roles();
foreach ( $roles as $slug => $data ) {
$rules[ $slug ] = translate_user_role( $data['name'] );
}
}
// Add capabilities - keep the most commonly used ones.
$capabilities = [
'edit_posts' => __( 'Can Edit Posts', 'generateblocks-pro' ),
'edit_pages' => __( 'Can Edit Pages', 'generateblocks-pro' ),
'edit_published_posts' => __( 'Can Edit Published Posts', 'generateblocks-pro' ),
'edit_others_posts' => __( 'Can Edit Others Posts', 'generateblocks-pro' ),
'publish_posts' => __( 'Can Publish Posts', 'generateblocks-pro' ),
'manage_options' => __( 'Can Manage Options', 'generateblocks-pro' ),
'moderate_comments' => __( 'Can Moderate Comments', 'generateblocks-pro' ),
];
// Capabilities are prefixed with 'cap:' to distinguish from roles.
foreach ( $capabilities as $cap => $label ) {
$rules[ 'cap:' . $cap ] = $label;
}
return apply_filters( 'generateblocks_user_role_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
/**
* Get operators available for a specific user role rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// General status rules - simple is/is_not only.
if ( in_array( $rule, [ 'general:logged_in', 'general:logged_out' ], true ) ) {
return [ 'is', 'is_not' ];
}
// For individual roles and capabilities, support multi-select if UI supports it.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Fallback to simple operators.
return [ 'is', 'is_not' ];
}
}
@@ -0,0 +1,59 @@
<?php
/**
* Condition Interface
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Interface for condition evaluators.
*/
interface GenerateBlocks_Pro_Condition_Interface {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] );
/**
* Get available rules for this condition type.
*
* @return array Array of rule_key => rule_label pairs.
*/
public function get_rules();
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array Rule metadata.
*/
public function get_rule_metadata( $rule );
/**
* Get operators available for a specific rule.
* This method should be public since the REST API calls it directly.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule );
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule );
}