initial
This commit is contained in:
+907
@@ -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;
|
||||
}
|
||||
}
|
||||
+152
@@ -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();
|
||||
+305
@@ -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();
|
||||
+338
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
+245
@@ -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' ];
|
||||
}
|
||||
}
|
||||
+153
@@ -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' ];
|
||||
}
|
||||
}
|
||||
+399
@@ -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 );
|
||||
}
|
||||
}
|
||||
+116
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
+199
@@ -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 );
|
||||
}
|
||||
}
|
||||
+537
@@ -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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
+195
@@ -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' ];
|
||||
}
|
||||
}
|
||||
}
|
||||
+154
@@ -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' ];
|
||||
}
|
||||
}
|
||||
}
|
||||
+160
@@ -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' ];
|
||||
}
|
||||
}
|
||||
+138
@@ -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' ];
|
||||
}
|
||||
}
|
||||
+166
@@ -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' ];
|
||||
}
|
||||
}
|
||||
}
|
||||
+179
@@ -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 );
|
||||
}
|
||||
Reference in New Issue
Block a user