This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,245 @@
<?php
/**
* Author Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Author condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Author extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$author_id = null;
// Use context if provided, otherwise get current post.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( $post_id ) {
$post = get_post( $post_id );
if ( $post ) {
$author_id = $post->post_author;
}
} elseif ( is_author() ) {
// Special case for author archive pages.
$author_id = get_queried_object_id();
}
if ( ! $author_id ) {
return false;
}
// Handle author meta custom fields.
if ( 'author_meta' === $rule ) {
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $author_id, $meta_key, $operator );
}
// Handle other operators using standardized method.
return $this->evaluate_meta_value( 'user', $author_id, $meta_key, $operator, $comparison_value );
}
// Handle multi-value operators for author selection.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $author_id ) {
return $this->check_single_author_match( $rule, $check_value, $author_id );
}
);
}
$is_match = false;
switch ( $rule ) {
case 'author_name':
// Check author display name or login.
if ( empty( $value ) ) {
return false;
}
$author = get_userdata( $author_id );
if ( $author ) {
$is_match = ( $author->display_name === $value ) || ( $author->user_login === $value );
}
break;
case 'author_id':
if ( empty( $value ) || '' === $value ) {
return false;
}
// Check specific author ID.
$is_match = ( $author_id == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
break;
case 'first_name':
case 'last_name':
case 'nickname':
case 'description':
// Check specific user meta fields using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $author_id, $rule, $operator );
}
return $this->evaluate_meta_value( 'user', $author_id, $rule, $operator, $value );
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if a single author value matches the current author.
*
* @param string $rule The condition rule.
* @param mixed $value The value to check.
* @param int $author_id Current author ID.
* @return bool
*/
private function check_single_author_match( $rule, $value, $author_id ) {
switch ( $rule ) {
case 'author_id':
if ( empty( $value ) || '' === $value ) {
return false;
}
return ( $author_id == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
case 'author_name':
if ( empty( $value ) || '' === $value ) {
return false;
}
$author = get_userdata( $author_id );
if ( $author ) {
return ( $author->display_name === $value ) || ( $author->user_login === $value );
}
break;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'author_id' => __( 'Author ID', 'generateblocks-pro' ),
'author_name' => __( 'Author Name', 'generateblocks-pro' ),
'author_meta' => __( 'Author Meta Key', 'generateblocks-pro' ),
];
// Add common author meta fields.
$author_fields = [
'first_name' => __( 'First Name', 'generateblocks-pro' ),
'last_name' => __( 'Last Name', 'generateblocks-pro' ),
'nickname' => __( 'Nickname', 'generateblocks-pro' ),
'description' => __( 'Biographical Info', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $author_fields );
return apply_filters( 'generateblocks_author_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'author_meta' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
if ( 'author_id' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'object_selector',
'supports_multi' => true,
];
}
// Text fields don't need greater_than/less_than.
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => false,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'author_meta' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific author rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Author meta supports all operators.
if ( 'author_meta' === $rule ) {
return [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
}
// Author ID supports multi-select.
if ( 'author_id' === $rule ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Text fields - no greater_than/less_than.
if ( in_array( $rule, [ 'author_name', 'first_name', 'last_name', 'nickname', 'description' ], true ) ) {
return [ 'exists', 'not_exists', 'equals', 'contains', 'not_contains' ];
}
return [ 'is', 'is_not' ];
}
}
@@ -0,0 +1,153 @@
<?php
/**
* Cookie Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Cookie condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Cookie extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the cookie name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$cookie_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $cookie_name ) ) {
return false;
}
switch ( $operator ) {
case 'exists':
return $this->cookie_exists( $cookie_name );
case 'not_exists':
return ! $this->cookie_exists( $cookie_name );
case 'has_value':
return $this->cookie_has_value( $cookie_name );
case 'no_value':
return ! $this->cookie_has_value( $cookie_name );
case 'equals':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && $cookie_value === $comparison_value;
case 'contains':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && false !== strpos( $cookie_value, $comparison_value );
case 'not_contains':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null === $cookie_value || false === strpos( $cookie_value, $comparison_value );
case 'starts_with':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && 0 === strpos( $cookie_value, $comparison_value );
case 'ends_with':
$cookie_value = $this->get_cookie_value( $cookie_name );
return null !== $cookie_value && substr( $cookie_value, -strlen( $comparison_value ) ) === $comparison_value;
default:
return false;
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Cookie', 'generateblocks-pro' ),
];
// Add common cookie rules that might be useful.
$common_cookies = [
'wordpress_logged_in' => __( 'WordPress Logged In Cookie', 'generateblocks-pro' ),
'comment_author' => __( 'Comment Author Cookie', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_cookies );
// Allow filtering to add predefined cookies.
return apply_filters( 'generateblocks_cookie_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
// WordPress cookies typically need existence checks only.
if ( in_array( $rule, [ 'wordpress_logged_in', 'comment_author' ], true ) ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
return [
'needs_value' => true,
'value_type' => 'text',
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific cookie rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// WordPress cookies typically only need existence checks.
if ( in_array( $rule, [ 'wordpress_logged_in', 'comment_author' ], true ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Custom cookies support all text operators including not_contains.
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ];
}
}
@@ -0,0 +1,399 @@
<?php
/**
* Date Time Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Date/time condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Date_Time extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Handle day of week separately with multi-select support.
if ( 'day_of_week' === $rule ) {
return $this->evaluate_day_of_week( $operator, $value );
}
// Get current time as DateTime object for proper timezone handling.
$current_datetime = current_datetime();
if ( empty( $value ) ) {
return false;
}
// Handle time-only rules (current_time and time_of_day).
if ( in_array( $rule, [ 'current_time', 'time_of_day' ], true ) ) {
return $this->evaluate_time_only( $current_datetime, $operator, $value );
}
// For date-based rules, use full timestamp comparison.
$current_time = $current_datetime->getTimestamp();
switch ( $operator ) {
case 'before':
return $this->is_before( $current_time, $value );
case 'after':
return $this->is_after( $current_time, $value );
case 'on':
return $this->is_on( $current_time, $value, $rule );
case 'between':
return $this->is_between( $current_time, $value );
default:
return false;
}
}
/**
* Evaluate time-only condition (ignoring date).
*
* @param DateTime $current_datetime Current DateTime object.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @return bool
*/
private function evaluate_time_only( $current_datetime, $operator, $value ) {
// Convert current time to seconds since midnight.
$current_hours = (int) $current_datetime->format( 'H' );
$current_minutes = (int) $current_datetime->format( 'i' );
$current_seconds_component = (int) $current_datetime->format( 's' );
$current_seconds = ( $current_hours * HOUR_IN_SECONDS ) +
( $current_minutes * MINUTE_IN_SECONDS ) +
$current_seconds_component;
switch ( $operator ) {
case 'before':
$target_seconds = $this->parse_time_to_seconds( $value );
return false !== $target_seconds && $current_seconds < $target_seconds;
case 'after':
$target_seconds = $this->parse_time_to_seconds( $value );
return false !== $target_seconds && $current_seconds > $target_seconds;
case 'on':
// For time "on", check if within the same hour.
$target_seconds = $this->parse_time_to_seconds( $value );
if ( false === $target_seconds ) {
return false;
}
$target_hour = intval( $target_seconds / HOUR_IN_SECONDS );
return $current_hours === $target_hour;
case 'between':
// Handle time range (e.g., "09:00, 17:00" or "22:00, 02:00" for overnight).
$times = array_map( 'trim', explode( ',', $value ) );
if ( 2 !== count( $times ) ) {
return false;
}
$start_seconds = $this->parse_time_to_seconds( $times[0] );
$end_seconds = $this->parse_time_to_seconds( $times[1] );
if ( false === $start_seconds || false === $end_seconds ) {
return false;
}
// Handle overnight ranges (e.g., 22:00 to 02:00).
if ( $start_seconds > $end_seconds ) {
// Current time is either after start OR before end.
return $current_seconds >= $start_seconds || $current_seconds <= $end_seconds;
} else {
// Normal range within same day.
return $current_seconds >= $start_seconds && $current_seconds <= $end_seconds;
}
default:
return false;
}
}
/**
* Parse time string to seconds since midnight.
*
* @param string $time_str Time string (HH:MM or full datetime).
* @return int|false Seconds since midnight or false on failure.
*/
private function parse_time_to_seconds( $time_str ) {
if ( empty( $time_str ) ) {
return false;
}
// For time-only values (HH:MM format), parse directly in WordPress timezone
// to avoid timezone conversion issues.
try {
// Create DateTime in WordPress timezone directly.
$datetime = new DateTime( $time_str, wp_timezone() );
} catch ( Exception $e ) {
// If parsing fails, try with today's date prepended.
try {
$today = current_datetime()->format( 'Y-m-d' );
$datetime = new DateTime( $today . ' ' . $time_str, wp_timezone() );
} catch ( Exception $e2 ) {
return false;
}
}
// Extract time components.
$hours = (int) $datetime->format( 'H' );
$minutes = (int) $datetime->format( 'i' );
$seconds = (int) $datetime->format( 's' );
// Calculate seconds since midnight.
$result = ( $hours * HOUR_IN_SECONDS ) + ( $minutes * MINUTE_IN_SECONDS ) + $seconds;
return $result;
}
/**
* Evaluate day of week condition.
*
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @return bool
*/
private function evaluate_day_of_week( $operator, $value ) {
// Get current day of week (1 = Monday, 7 = Sunday).
$current_day = intval( current_datetime()->format( 'N' ) );
// Handle multi-value operators.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value( $operator, $current_day, $value );
}
// Parse value for single operators.
$target_day = intval( $value );
switch ( $operator ) {
case 'is':
return $current_day === $target_day;
case 'is_not':
return $current_day !== $target_day;
default:
return false;
}
}
/**
* Check if current time is before target time.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @return bool
*/
private function is_before( $current_time, $target_time ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$compare_time = $target_datetime->getTimestamp();
return $current_time < $compare_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is after target time.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @return bool
*/
private function is_after( $current_time, $target_time ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$compare_time = $target_datetime->getTimestamp();
return $current_time > $compare_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is on target date.
*
* @param int $current_time Current timestamp.
* @param string $target_time Target time string.
* @param string $rule The rule type.
* @return bool
*/
private function is_on( $current_time, $target_time, $rule ) {
try {
// Parse the target time using WordPress timezone.
$target_datetime = new DateTime( $target_time, wp_timezone() );
$current_datetime = new DateTime();
$current_datetime->setTimestamp( $current_time );
$current_datetime->setTimezone( wp_timezone() );
if ( 'current_date' === $rule ) {
// Compare dates in WordPress timezone.
return $current_datetime->format( 'Y-m-d' ) === $target_datetime->format( 'Y-m-d' );
}
// For time comparison, check if within the same hour.
return $current_datetime->format( 'Y-m-d H' ) === $target_datetime->format( 'Y-m-d H' );
} catch ( Exception $e ) {
return false;
}
}
/**
* Check if current time is between two times.
*
* @param int $current_time Current timestamp.
* @param string $value Comma-separated start and end times.
* @return bool
*/
private function is_between( $current_time, $value ) {
$dates = array_map( 'trim', explode( ',', $value ) );
if ( 2 !== count( $dates ) ) {
return false;
}
try {
// Parse both dates using WordPress timezone.
$start_datetime = new DateTime( $dates[0], wp_timezone() );
$end_datetime = new DateTime( $dates[1], wp_timezone() );
$start_time = $start_datetime->getTimestamp();
$end_time = $end_datetime->getTimestamp();
return $current_time >= $start_time && $current_time <= $end_time;
} catch ( Exception $e ) {
return false;
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'current_date' => __( 'Date', 'generateblocks-pro' ),
'current_time' => __( 'Time', 'generateblocks-pro' ),
'day_of_week' => __( 'Day of Week', 'generateblocks-pro' ),
'time_of_day' => __( 'Time of Day', 'generateblocks-pro' ), // Hidden in UI, kept for backward compatibility.
];
return apply_filters( 'generateblocks_date_time_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'day_of_week' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'day_selector',
'supports_multi' => true,
];
}
// Time-only rules use time picker.
if ( in_array( $rule, [ 'current_time', 'time_of_day' ], true ) ) {
return [
'needs_value' => true,
'value_type' => 'time',
];
}
// Date rule uses datetime picker.
return [
'needs_value' => true,
'value_type' => 'datetime',
];
}
/**
* Get operators available for a specific date/time rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
if ( 'day_of_week' === $rule ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Other date/time rules use temporal operators.
return [ 'before', 'after', 'between', 'on' ];
}
/**
* Get available day options.
*
* @return array
*/
public function get_day_options() {
return [
'1' => __( 'Monday', 'generateblocks-pro' ),
'2' => __( 'Tuesday', 'generateblocks-pro' ),
'3' => __( 'Wednesday', 'generateblocks-pro' ),
'4' => __( 'Thursday', 'generateblocks-pro' ),
'5' => __( 'Friday', 'generateblocks-pro' ),
'6' => __( 'Saturday', 'generateblocks-pro' ),
'7' => __( 'Sunday', 'generateblocks-pro' ),
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'day_of_week' === $rule ) {
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'intval', $value );
}
// Try to decode JSON array.
$decoded = json_decode( $value, true );
if ( is_array( $decoded ) ) {
return wp_json_encode( array_map( 'intval', $decoded ) );
}
// Single value.
return intval( $value );
}
// For between operator, ensure proper format.
if ( false !== strpos( $value, ',' ) ) {
$dates = array_map( 'trim', explode( ',', $value ) );
$sanitized_dates = array_map( 'sanitize_text_field', $dates );
return implode( ', ', $sanitized_dates );
}
return sanitize_text_field( $value );
}
}
@@ -0,0 +1,116 @@
<?php
/**
* Device Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Device condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Device extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$is_match = false;
if ( ! function_exists( 'wp_is_mobile' ) ) {
return false;
}
switch ( $rule ) {
case 'mobile':
$is_match = wp_is_mobile() && ! $this->is_tablet();
break;
case 'tablet':
$is_match = $this->is_tablet();
break;
case 'desktop':
$is_match = ! wp_is_mobile();
break;
case 'ios':
$is_match = $this->is_ios();
break;
case 'android':
$is_match = $this->is_android();
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Basic tablet detection.
*
* @return bool
*/
private function is_tablet() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/(tablet|ipad|playbook|silk)|(android(?!.*mobile))/i', $user_agent );
}
/**
* Check if device is iOS.
*
* @return bool
*/
private function is_ios() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/(iPhone|iPod|iPad)/i', $user_agent );
}
/**
* Check if device is Android.
*
* @return bool
*/
private function is_android() {
$user_agent = $this->get_server_var( 'HTTP_USER_AGENT' );
return (bool) preg_match( '/Android/i', $user_agent );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'mobile' => __( 'Mobile', 'generateblocks-pro' ),
'tablet' => __( 'Tablet', 'generateblocks-pro' ),
'desktop' => __( 'Desktop', 'generateblocks-pro' ),
'ios' => __( 'iOS', 'generateblocks-pro' ),
'android' => __( 'Android', 'generateblocks-pro' ),
];
return apply_filters( 'generateblocks_device_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
}
@@ -0,0 +1,199 @@
<?php
/**
* Language Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Language condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Language extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$current_locale = $this->get_current_locale();
$is_match = false;
switch ( $rule ) {
case 'locale':
// Full locale match (e.g., en_US).
$is_match = $current_locale === $value;
break;
case 'language':
// Language code match (e.g., en).
$current_lang = substr( $current_locale, 0, 2 );
$is_match = $current_lang === $value;
break;
case 'rtl':
// Check if Right-to-Left language.
$is_match = is_rtl();
break;
case 'custom':
// Custom locale check.
$is_match = $current_locale === $value;
break;
default:
// For predefined locales (en_US, fr_FR, etc.), check directly.
$is_match = $current_locale === $rule;
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Get the current locale.
*
* @return string
*/
private function get_current_locale() {
// Check for WPML.
if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
global $sitepress;
if ( $sitepress && method_exists( $sitepress, 'get_locale' ) ) {
$locale = $sitepress->get_locale( ICL_LANGUAGE_CODE );
if ( $locale ) {
return $locale;
}
}
}
// Check for Polylang.
if ( function_exists( 'pll_current_language' ) ) {
$locale = pll_current_language( 'locale' );
if ( $locale ) {
return $locale;
}
}
// Default WordPress locale.
return get_locale();
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'locale' => __( 'Full Locale (e.g., en_US)', 'generateblocks-pro' ),
'language' => __( 'Language Code (e.g., en)', 'generateblocks-pro' ),
'rtl' => __( 'RTL Language', 'generateblocks-pro' ),
'custom' => __( 'Custom Locale', 'generateblocks-pro' ),
];
// Add common locales.
$common_locales = [
'en_US' => __( 'English (United States)', 'generateblocks-pro' ),
'en_GB' => __( 'English (UK)', 'generateblocks-pro' ),
'en_CA' => __( 'English (Canada)', 'generateblocks-pro' ),
'en_AU' => __( 'English (Australia)', 'generateblocks-pro' ),
'es_ES' => __( 'Spanish (Spain)', 'generateblocks-pro' ),
'es_MX' => __( 'Spanish (Mexico)', 'generateblocks-pro' ),
'fr_FR' => __( 'French (France)', 'generateblocks-pro' ),
'fr_CA' => __( 'French (Canada)', 'generateblocks-pro' ),
'de_DE' => __( 'German', 'generateblocks-pro' ),
'it_IT' => __( 'Italian', 'generateblocks-pro' ),
'pt_BR' => __( 'Portuguese (Brazil)', 'generateblocks-pro' ),
'pt_PT' => __( 'Portuguese (Portugal)', 'generateblocks-pro' ),
'nl_NL' => __( 'Dutch', 'generateblocks-pro' ),
'ru_RU' => __( 'Russian', 'generateblocks-pro' ),
'ja' => __( 'Japanese', 'generateblocks-pro' ),
'zh_CN' => __( 'Chinese (Simplified)', 'generateblocks-pro' ),
'zh_TW' => __( 'Chinese (Traditional)', 'generateblocks-pro' ),
'ko_KR' => __( 'Korean', 'generateblocks-pro' ),
'ar' => __( 'Arabic', 'generateblocks-pro' ),
'he_IL' => __( 'Hebrew', 'generateblocks-pro' ),
];
// Get installed languages if available.
if ( function_exists( 'get_available_languages' ) ) {
$installed = get_available_languages();
foreach ( $installed as $locale ) {
if ( ! isset( $common_locales[ $locale ] ) && 'en_US' !== $locale ) {
$common_locales[ $locale ] = $locale;
}
}
}
$rules = array_merge( $rules, $common_locales );
return apply_filters( 'generateblocks_language_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
// RTL check doesn't need a value.
if ( 'rtl' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
// Language code needs a 2-letter code.
if ( 'language' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'text',
];
}
// Custom and locale need full locale codes.
if ( in_array( $rule, [ 'locale', 'custom' ], true ) ) {
return [
'needs_value' => true,
'value_type' => 'text',
];
}
// Predefined locales don't need values.
return [
'needs_value' => false,
'value_type' => 'none',
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
// For language codes, ensure lowercase and 2 characters.
if ( 'language' === $rule ) {
return strtolower( substr( sanitize_text_field( $value ), 0, 2 ) );
}
// For locales, allow underscores and dashes.
if ( in_array( $rule, [ 'locale', 'custom' ], true ) ) {
return preg_replace( '/[^a-zA-Z0-9_-]/', '', sanitize_text_field( $value ) );
}
return sanitize_text_field( $value );
}
}
@@ -0,0 +1,537 @@
<?php
/**
* Location Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Location condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Location extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$current_location = $this->get_current_location();
// Handle parent/child relationships first (they have their own multi-value handling).
if ( in_array( $rule, [ 'child_of', 'parent_of' ], true ) ) {
return $this->evaluate_hierarchy( $rule, $operator, $value, $context );
}
// Handle post taxonomy terms.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return $this->evaluate_post_terms( $rule, $operator, $value, $context );
}
// Handle multi-value operators for non-hierarchical rules.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $current_location ) {
return $this->check_single_location_match( $rule, $check_value, $current_location );
}
);
}
$is_match = false;
// Check if current location matches the rule.
if ( in_array( $rule, $current_location['location'], true ) ) {
if ( empty( $value ) ) {
$is_match = true;
} else {
// Check specific object (post ID, term ID, etc.).
$is_match = ( $current_location['object'] == $value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
}
}
// Special cases - use true original query to check page context, not loop context.
if ( ! $is_match ) {
global $wp_the_query;
switch ( $rule ) {
case 'general:site':
$is_match = true;
break;
case 'general:singular':
$is_match = $wp_the_query->is_singular ?? is_singular();
break;
case 'general:archive':
$is_match = $wp_the_query->is_archive ?? is_archive();
break;
}
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if a single location value matches the current location.
*
* @param string $rule The condition rule.
* @param mixed $check_value The value to check.
* @param array $current_location Current location data.
* @return bool
*/
private function check_single_location_match( $rule, $check_value, $current_location ) {
// Check if current location rule matches.
if ( in_array( $rule, $current_location['location'], true ) ) {
if ( empty( $check_value ) ) {
return true;
} else {
// Check specific object (post ID, term ID, etc.).
return ( $current_location['object'] == $check_value ); // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
}
}
// Special cases for general rules - use true original query.
global $wp_the_query;
switch ( $rule ) {
case 'general:site':
return true;
case 'general:singular':
return $wp_the_query->is_singular ?? is_singular();
case 'general:archive':
return $wp_the_query->is_archive ?? is_archive();
}
return false;
}
/**
* Evaluate post taxonomy terms condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
private function evaluate_post_terms( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Extract taxonomy from rule.
$taxonomy = str_replace( 'post_terms:', '', $rule );
if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
return false;
}
// Get post terms.
$post_terms = get_the_terms( $post->ID, $taxonomy );
if ( is_wp_error( $post_terms ) ) {
return false;
}
// Convert to array of term IDs.
$post_term_ids = [];
if ( $post_terms && is_array( $post_terms ) ) {
$post_term_ids = wp_list_pluck( $post_terms, 'term_id' );
$post_term_ids = array_map( 'strval', $post_term_ids ); // Convert to strings for comparison.
}
// Handle multi-value operators.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_array( $operator, $post_term_ids, $value );
}
// For single value operators.
if ( empty( $value ) ) {
// No specific term - just check if post has any terms.
return 'is_not' === $operator ? empty( $post_term_ids ) : ! empty( $post_term_ids );
}
$has_term = in_array( strval( $value ), $post_term_ids, true );
return 'is_not' === $operator ? ! $has_term : $has_term;
}
/**
* Evaluate hierarchical relationships.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
private function evaluate_hierarchy( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Check if post type is hierarchical.
$post_type_object = get_post_type_object( $post->post_type );
if ( ! $post_type_object || ! $post_type_object->hierarchical ) {
return false;
}
// Handle empty value - check for any parent/child relationship.
if ( empty( $value ) ) {
return $this->check_any_hierarchy( $rule, $operator, $post );
}
// Handle multi-value for hierarchy.
if ( $this->is_multi_value_operator( $operator ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) use ( $rule, $post ) {
return $this->check_single_hierarchy( $rule, $check_value, $post );
}
);
}
$is_match = $this->check_single_hierarchy( $rule, $value, $post );
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check single hierarchy relationship.
*
* @param string $rule The hierarchy rule.
* @param mixed $value The value to check.
* @param WP_Post $post The post object to check.
* @return bool
*/
private function check_single_hierarchy( $rule, $value, $post ) {
if ( 'child_of' === $rule ) {
// Check if current post is a child of the specified post.
$ancestors = get_post_ancestors( $post->ID );
return in_array( intval( $value ), $ancestors, true );
} elseif ( 'parent_of' === $rule ) {
// Check if current post is a parent of the specified post.
$target_post = get_post( intval( $value ) );
if ( $target_post ) {
$target_ancestors = get_post_ancestors( $target_post->ID );
return in_array( $post->ID, $target_ancestors, true );
}
}
return false;
}
/**
* Check if post has any hierarchical relationship.
*
* @param string $rule The hierarchy rule.
* @param string $operator The condition operator.
* @param WP_Post $post The post object to check.
* @return bool
*/
private function check_any_hierarchy( $rule, $operator, $post ) {
if ( 'child_of' === $rule ) {
// Check if current post has any parent.
$has_parent = 0 < $post->post_parent;
return 'is_not' === $operator ? ! $has_parent : $has_parent;
} elseif ( 'parent_of' === $rule ) {
// Check if current post has any children.
$children = get_children(
array(
'post_parent' => $post->ID,
'post_type' => $post->post_type,
'numberposts' => 1,
'post_status' => array( 'publish', 'private', 'draft' ),
)
);
$has_children = ! empty( $children );
return 'is_not' === $operator ? ! $has_children : $has_children;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'general:site' => __( 'Entire Site', 'generateblocks-pro' ),
'general:front_page' => __( 'Front Page', 'generateblocks-pro' ),
'general:blog' => __( 'Blog', 'generateblocks-pro' ),
'general:singular' => __( 'All Singular', 'generateblocks-pro' ),
'general:archive' => __( 'All Archives', 'generateblocks-pro' ),
'general:author' => __( 'Author Archives', 'generateblocks-pro' ),
'general:date' => __( 'Date Archives', 'generateblocks-pro' ),
'general:search' => __( 'Search Results', 'generateblocks-pro' ),
'general:no_results' => __( 'No Results', 'generateblocks-pro' ),
'general:404' => __( '404 Template', 'generateblocks-pro' ),
];
// Add hierarchical relationships.
$rules['child_of'] = __( 'Child of', 'generateblocks-pro' );
$rules['parent_of'] = __( 'Parent of', 'generateblocks-pro' );
// Add post types.
$post_types = get_post_types( [ 'public' => true ], 'objects' );
foreach ( $post_types as $post_type_slug => $post_type ) {
$rules[ 'post:' . $post_type_slug ] = $post_type->labels->singular_name;
if ( $post_type->has_archive ) {
$rules[ 'archive:' . $post_type_slug ] = sprintf(
/* translators: %s: post type singular name */
__( '%s Archive', 'generateblocks-pro' ),
$post_type->labels->singular_name
);
}
}
// Add taxonomies for archive pages.
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
foreach ( $taxonomies as $taxonomy_slug => $taxonomy ) {
$rules[ 'taxonomy:' . $taxonomy_slug ] = sprintf(
/* translators: %s: taxonomy singular name */
__( '%s Archive', 'generateblocks-pro' ),
$taxonomy->labels->singular_name
);
}
// Add post taxonomy terms - for checking if current post has specific terms.
foreach ( $taxonomies as $taxonomy_slug => $taxonomy ) {
// Use name if available, fallback to singular_name for tests.
$taxonomy_name = isset( $taxonomy->labels->name ) ? $taxonomy->labels->name : $taxonomy->labels->singular_name;
$rules[ 'post_terms:' . $taxonomy_slug ] = sprintf(
/* translators: %s: taxonomy name */
__( 'Post %s', 'generateblocks-pro' ),
$taxonomy_name
);
}
return apply_filters( 'generateblocks_location_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
$general_location_rules = array(
'general:site',
'general:front_page',
'general:blog',
'general:singular',
'general:archive',
'general:author',
'general:date',
'general:search',
'general:no_results',
'general:404',
);
if ( in_array( $rule, $general_location_rules, true ) ) {
return array(
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
);
}
// Parent/child relationships need object selection but allow empty values.
if ( in_array( $rule, array( 'child_of', 'parent_of' ), true ) ) {
return array(
'needs_value' => false,
'value_type' => 'hierarchical_object_selector',
'supports_multi' => true,
);
}
// Archive rules typically don't need values.
if ( 0 === strpos( $rule, 'archive:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
);
}
// Post taxonomy terms need term selection.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'object_selector',
'supports_multi' => true,
);
}
// Post and taxonomy rules can optionally have specific object selection.
if ( 0 === strpos( $rule, 'post:' ) || 0 === strpos( $rule, 'taxonomy:' ) ) {
return array(
'needs_value' => false,
'value_type' => 'object_selector',
'supports_multi' => true,
);
}
return $this->get_default_rule_metadata();
}
/**
* Get current location data.
*
* @return array
*/
private function get_current_location() {
global $wp_the_query;
$location = [];
$object = null;
// Always use the true original query to determine location, not the current loop item.
// This ensures Location conditions check "where you are" not "what you're looking at".
// Using $wp_the_query is bulletproof against query_posts() and other query manipulations.
// Falls back to global functions if $wp_the_query is not available.
if ( $wp_the_query->is_front_page ?? is_front_page() ) {
$location[] = 'general:front_page';
// When "Your homepage displays" is set to "Your latest posts",
// both is_front_page() and is_home() are true.
if ( $wp_the_query->is_home ?? is_home() ) {
$location[] = 'general:blog';
}
if ( $wp_the_query->is_page ?? is_page() ) {
$location[] = 'post:page';
$location[] = 'general:singular';
// Get the front page ID for specific page targeting.
$front_page_id = get_option( 'page_on_front' );
if ( $front_page_id ) {
$object = $front_page_id;
}
}
} elseif ( $wp_the_query->is_home ?? is_home() ) {
$location[] = 'general:blog';
} elseif ( $wp_the_query->is_singular ?? is_singular() ) {
$location[] = 'general:singular';
$queried_object = isset( $wp_the_query ) ? $wp_the_query->get_queried_object() : get_queried_object();
if ( $queried_object && isset( $queried_object->post_type ) ) {
$location[] = 'post:' . $queried_object->post_type;
$object = $queried_object->ID;
}
} elseif ( $wp_the_query->is_archive ?? is_archive() ) {
$location[] = 'general:archive';
if ( ( $wp_the_query->is_category ?? is_category() ) || ( $wp_the_query->is_tag ?? is_tag() ) || ( $wp_the_query->is_tax ?? is_tax() ) ) {
$queried_object = isset( $wp_the_query ) ? $wp_the_query->get_queried_object() : get_queried_object();
if ( $queried_object && isset( $queried_object->taxonomy ) ) {
$location[] = 'taxonomy:' . $queried_object->taxonomy;
$object = $queried_object->term_id;
}
} elseif ( $wp_the_query->is_post_type_archive ?? is_post_type_archive() ) {
$post_type = isset( $wp_the_query ) ? $wp_the_query->get( 'post_type' ) : get_query_var( 'post_type' );
if ( $post_type ) {
$location[] = 'archive:' . $post_type;
}
} elseif ( $wp_the_query->is_author ?? is_author() ) {
$location[] = 'general:author';
} elseif ( $wp_the_query->is_date ?? is_date() ) {
$location[] = 'general:date';
}
} elseif ( $wp_the_query->is_search ?? is_search() ) {
$location[] = 'general:search';
// Check if search has no results.
if ( ( $wp_the_query->found_posts ?? 0 ) === 0 ) {
$location[] = 'general:no_results';
}
} elseif ( $wp_the_query->is_404 ?? is_404() ) {
$location[] = 'general:404';
}
// Always include site.
$location[] = 'general:site';
return [
'location' => $location,
'object' => $object,
];
}
/**
* Get operators available for a specific location rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// General location rules - only basic operators (you're either there or not).
$general_rules = array(
'general:site',
'general:front_page',
'general:blog',
'general:singular',
'general:archive',
'general:author',
'general:date',
'general:search',
'general:no_results',
'general:404',
);
if ( in_array( $rule, $general_rules, true ) ) {
return array( 'is', 'is_not' );
}
// Archive rules - only basic operators.
if ( 0 === strpos( $rule, 'archive:' ) ) {
return array( 'is', 'is_not' );
}
// Hierarchical relationships - simplified operators (no includes_all/excludes_all).
if ( in_array( $rule, array( 'child_of', 'parent_of' ), true ) ) {
return array( 'is', 'is_not', 'includes_any', 'excludes_any' );
}
// Post taxonomy terms - full multi-value support.
if ( 0 === strpos( $rule, 'post_terms:' ) ) {
return array( 'is', 'is_not', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' );
}
// For all other rules, check if multi-select is actually supported.
if ( $this->rule_supports_multi_select( $rule ) ) {
// Multi-select interface available.
if ( 0 === strpos( $rule, 'post:' ) ) {
// Posts: can select multiple but can't be on all simultaneously.
return array( 'is', 'is_not', 'includes_any', 'excludes_any' );
}
// Taxonomies: full support.
return array( 'is', 'is_not', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' );
} else {
// Single input only: no any/all operators.
return array( 'is', 'is_not' );
}
}
}
@@ -0,0 +1,195 @@
<?php
/**
* Options Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Options condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Options extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the option name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$option_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $option_name ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'option', 0, $option_name, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'option', 0, $option_name );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'option', 0, $option_name );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'option', 0, $option_name, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Option', 'generateblocks-pro' ),
];
// Add common WordPress options.
$common_options = [
'blogname' => __( 'Site Title', 'generateblocks-pro' ),
'blogdescription' => __( 'Site Tagline', 'generateblocks-pro' ),
'timezone_string' => __( 'Timezone', 'generateblocks-pro' ),
'date_format' => __( 'Date Format', 'generateblocks-pro' ),
'time_format' => __( 'Time Format', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_options );
// Allow themes/plugins to add their options.
return apply_filters( 'generateblocks_options_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Boolean options need special handling.
$boolean_options = [
'users_can_register',
'comment_registration',
'close_comments_for_old_posts',
'comment_moderation',
'moderation_notify',
'comments_notify',
];
if ( in_array( $rule, $boolean_options, true ) ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
// Numeric options.
$numeric_options = [
'posts_per_page',
'comments_per_page',
'start_of_week',
];
if ( in_array( $rule, $numeric_options, true ) ) {
return [
'needs_value' => true,
'value_type' => 'number',
'supports_multi' => true,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific option rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Boolean options only need existence checks.
$boolean_options = [
'users_can_register',
'comment_registration',
'close_comments_for_old_posts',
'comment_moderation',
'moderation_notify',
'comments_notify',
];
if ( in_array( $rule, $boolean_options, true ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Numeric options support all operators.
$numeric_options = [
'posts_per_page',
'comments_per_page',
'start_of_week',
];
if ( in_array( $rule, $numeric_options, true ) ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'greater_than', 'less_than' ];
}
}
// Text options support text operators.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
}
@@ -0,0 +1,154 @@
<?php
/**
* Post Meta Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Post meta condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Post_Meta extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Use context if provided, otherwise get current post ID.
$post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : get_the_ID();
if ( ! $post_id ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
// Parse the meta key and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'post', $post->ID, $meta_key, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'post', $post->ID, $meta_key );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'post', $post->ID, $meta_key );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'post', $post->ID, $meta_key, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Meta Key', 'generateblocks-pro' ),
];
// Add common meta keys.
$common_keys = [
'_thumbnail_id' => __( 'Has Featured Image', 'generateblocks-pro' ),
'_wp_page_template' => __( 'Page Template', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_keys );
// Allow themes/plugins to add their meta keys.
return apply_filters( 'generateblocks_post_meta_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Special handling for specific meta keys.
if ( '_thumbnail_id' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
// Page template is text-based.
if ( '_wp_page_template' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Get operators available for a specific post meta rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Special case for thumbnail - only existence checks.
if ( '_thumbnail_id' === $rule ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Page template - only text operations, no greater_than/less_than.
if ( '_wp_page_template' === $rule ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
// Custom meta key supports all operators including numeric.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than' ];
}
}
}
@@ -0,0 +1,160 @@
<?php
/**
* Query Argument Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Query argument condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Query_Arg extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Parse the parameter name and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$param_name = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $param_name ) ) {
return false;
}
switch ( $operator ) {
case 'exists':
return $this->query_param_exists( $param_name );
case 'not_exists':
return ! $this->query_param_exists( $param_name );
case 'has_value':
return $this->query_param_has_value( $param_name );
case 'no_value':
return ! $this->query_param_has_value( $param_name );
case 'equals':
$param_value = $this->get_query_param_raw( $param_name );
// For arrays, check if comparison value is in the array.
if ( is_array( $param_value ) ) {
return in_array( $comparison_value, $param_value, true );
}
return null !== $param_value && $param_value === $comparison_value;
case 'contains':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return false !== strpos( $param_value, $comparison_value );
case 'not_contains':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return true for arrays (they don't "contain" the string).
if ( ! is_string( $param_value ) ) {
return true;
}
return false === strpos( $param_value, $comparison_value );
case 'starts_with':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return 0 === strpos( $param_value, $comparison_value );
case 'ends_with':
$param_value = $this->get_query_param_raw( $param_name );
// String operations only work on strings, return false for arrays.
if ( ! is_string( $param_value ) ) {
return false;
}
return substr( $param_value, -strlen( $comparison_value ) ) === $comparison_value;
default:
return false;
}
}
/**
* Get raw query parameter value without sanitization to preserve arrays.
*
* @param string $param_name Parameter name.
* @return mixed|null
*/
private function get_query_param_raw( $param_name ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET[ $param_name ] ) ) {
return null;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$value = wp_unslash( $_GET[ $param_name ] );
// Sanitize based on type.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Parameter', 'generateblocks-pro' ),
];
// Allow filtering to add predefined parameters.
return apply_filters( 'generateblocks_query_arg_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
return [
'needs_value' => true,
'value_type' => 'text',
];
}
/**
* Get operators available for a specific query arg rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// All query args support the same text operators.
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'starts_with', 'ends_with' ];
}
}
@@ -0,0 +1,138 @@
<?php
/**
* Referrer Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Referrer condition evaluator.
*/
class GenerateBlocks_Pro_Condition_Referrer extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
$referrer = $this->get_server_var( 'HTTP_REFERER' );
// Handle direct traffic (no referrer).
if ( 'direct' === $rule ) {
$has_referrer = ! empty( $referrer );
return 'is_not' === $operator ? $has_referrer : ! $has_referrer;
}
// Handle custom referrer.
if ( 'custom' === $rule ) {
// Handle has_value/no_value operators first.
if ( 'has_value' === $operator ) {
return $this->referrer_has_value();
}
if ( 'no_value' === $operator ) {
return ! $this->referrer_has_value();
}
$parsed = $this->parse_custom_value( $value );
$comparison_value = $parsed['comparison_value'] ? $parsed['comparison_value'] : $parsed['field_name'];
if ( empty( $comparison_value ) ) {
return false;
}
$is_match = false;
switch ( $operator ) {
case 'contains':
$is_match = false !== strpos( $referrer, $comparison_value );
break;
case 'not_contains':
$is_match = false === strpos( $referrer, $comparison_value );
break;
case 'equals':
$is_match = $referrer === $comparison_value;
break;
case 'starts_with':
$is_match = 0 === strpos( $referrer, $comparison_value );
break;
case 'ends_with':
$is_match = substr( $referrer, -strlen( $comparison_value ) ) === $comparison_value;
break;
}
return $is_match;
}
return false;
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'direct' => __( 'Direct Traffic (No Referrer)', 'generateblocks-pro' ),
'custom' => __( 'Custom Referrer', 'generateblocks-pro' ),
];
return apply_filters( 'generateblocks_referrer_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
];
}
if ( 'direct' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
];
}
return $this->get_default_rule_metadata();
}
/**
* Get operators available for a specific referrer rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
if ( 'direct' === $rule ) {
// Direct traffic only supports is/is_not.
return [ 'is', 'is_not' ];
}
if ( 'custom' === $rule ) {
// Custom referrer supports all text comparison operators including not_contains.
return [ 'has_value', 'no_value', 'contains', 'not_contains', 'equals', 'starts_with', 'ends_with' ];
}
return [ 'contains', 'not_contains', 'equals', 'starts_with' ];
}
}
@@ -0,0 +1,166 @@
<?php
/**
* User Meta Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* User meta condition evaluator.
*/
class GenerateBlocks_Pro_Condition_User_Meta extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// User must be logged in for user meta conditions.
if ( ! is_user_logged_in() ) {
return false;
}
$user_id = get_current_user_id();
if ( ! $user_id ) {
return false;
}
// Parse the meta key and comparison value using standardized method.
$parsed = $this->parse_meta_field( $rule, $value );
$meta_key = $parsed['field_name'];
$comparison_value = $parsed['comparison_value'];
if ( empty( $meta_key ) ) {
return false;
}
// Handle existence operators using standardized method.
if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
return $this->evaluate_meta_existence( 'user', $user_id, $meta_key, $operator );
}
// Handle has_value/no_value operators.
if ( 'has_value' === $operator ) {
return $this->evaluate_meta_has_value( 'user', $user_id, $meta_key );
}
if ( 'no_value' === $operator ) {
return ! $this->evaluate_meta_has_value( 'user', $user_id, $meta_key );
}
// Handle all other operators using standardized method.
return $this->evaluate_meta_value( 'user', $user_id, $meta_key, $operator, $comparison_value );
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'custom' => __( 'Custom Meta Key', 'generateblocks-pro' ),
];
// Add common user meta keys.
$common_keys = [
'first_name' => __( 'First Name', 'generateblocks-pro' ),
'last_name' => __( 'Last Name', 'generateblocks-pro' ),
'nickname' => __( 'Nickname', 'generateblocks-pro' ),
'description' => __( 'Biographical Info', 'generateblocks-pro' ),
'show_admin_bar_front' => __( 'Show Admin Bar', 'generateblocks-pro' ),
];
$rules = array_merge( $rules, $common_keys );
// Allow themes/plugins to add their meta keys.
return apply_filters( 'generateblocks_user_meta_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
if ( 'custom' === $rule ) {
return [
'needs_value' => true,
'value_type' => 'custom_field',
'supports_multi' => true,
];
}
// Show admin bar is a boolean stored as string.
if ( 'show_admin_bar_front' === $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
return [
'needs_value' => true,
'value_type' => 'text',
'supports_multi' => true,
];
}
/**
* Sanitize the condition value.
*
* @param mixed $value The value to sanitize.
* @param string $rule The rule being used.
* @return mixed
*/
public function sanitize_value( $value, $rule ) {
if ( 'custom' === $rule ) {
return $this->sanitize_custom_value( $value );
}
// Handle array values for multi-select.
if ( is_array( $value ) ) {
return array_map( 'sanitize_text_field', $value );
}
return sanitize_text_field( $value );
}
/**
* Get operators available for a specific user meta rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// Show admin bar only needs existence checks.
if ( 'show_admin_bar_front' === $rule ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value' ];
}
// Text fields - no greater_than/less_than as per feedback.
if ( in_array( $rule, [ 'first_name', 'last_name', 'nickname', 'description' ], true ) ) {
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains' ];
}
}
// Custom meta key supports all operators.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than', 'includes_any', 'includes_all', 'excludes_any', 'excludes_all' ];
} else {
return [ 'exists', 'not_exists', 'has_value', 'no_value', 'equals', 'contains', 'not_contains', 'greater_than', 'less_than' ];
}
}
}
@@ -0,0 +1,179 @@
<?php
/**
* User Role Condition
*
* @package GenerateBlocks
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* User role condition evaluator.
*/
class GenerateBlocks_Pro_Condition_User_Role extends GenerateBlocks_Pro_Condition_Abstract {
/**
* Evaluate the condition.
*
* @param string $rule The condition rule.
* @param string $operator The condition operator.
* @param mixed $value The value to check against.
* @param array $context Additional context data.
* @return bool
*/
public function evaluate( $rule, $operator, $value, $context = [] ) {
// Handle multi-value operators for roles and capabilities only.
if ( $this->is_multi_value_operator( $operator ) && $this->rule_supports_multi_select( $rule ) ) {
return $this->evaluate_multi_value_generic(
$operator,
$value,
function( $check_value ) {
return $this->check_single_user_role( $check_value );
}
);
}
$is_match = false;
switch ( $rule ) {
case 'general:logged_in':
$is_match = is_user_logged_in();
break;
case 'general:logged_out':
$is_match = ! is_user_logged_in();
break;
default:
// Check specific role or capability.
$user = wp_get_current_user();
if ( $user && $user->exists() ) {
// Check if it's a capability (prefixed with 'cap:').
if ( 0 === strpos( $rule, 'cap:' ) ) {
// Remove prefix.
$capability = substr( $rule, 4 );
$is_match = user_can( $user, $capability );
} else {
// It's a role.
$is_match = in_array( $rule, (array) $user->roles, true );
}
}
break;
}
return 'is_not' === $operator ? ! $is_match : $is_match;
}
/**
* Check if current user matches a single role/capability.
*
* @param string $check_value The role or capability to check.
* @return bool
*/
private function check_single_user_role( $check_value ) {
$user = wp_get_current_user();
if ( ! $user || ! $user->exists() ) {
// For logged out users, only check against general rules.
return 'general:logged_out' === $check_value;
}
switch ( $check_value ) {
case 'general:logged_in':
return true; // User is logged in if we reach this point.
case 'general:logged_out':
return false; // User is logged in.
default:
// Check specific role or capability.
if ( 0 === strpos( $check_value, 'cap:' ) ) {
// Remove prefix.
$capability = substr( $check_value, 4 );
return user_can( $user, $capability );
} else {
// It's a role.
$user_roles = (array) $user->roles;
return in_array( $check_value, $user_roles, true );
}
}
}
/**
* Get available rules for this condition type.
*
* @return array
*/
public function get_rules() {
$rules = [
'general:logged_in' => __( 'Logged In', 'generateblocks-pro' ),
'general:logged_out' => __( 'Logged Out', 'generateblocks-pro' ),
];
// Ensure get_editable_roles() is available.
if ( ! function_exists( 'get_editable_roles' ) ) {
require_once ABSPATH . 'wp-admin/includes/user.php';
}
// Add WordPress roles.
if ( function_exists( 'get_editable_roles' ) ) {
$roles = get_editable_roles();
foreach ( $roles as $slug => $data ) {
$rules[ $slug ] = translate_user_role( $data['name'] );
}
}
// Add capabilities - keep the most commonly used ones.
$capabilities = [
'edit_posts' => __( 'Can Edit Posts', 'generateblocks-pro' ),
'edit_pages' => __( 'Can Edit Pages', 'generateblocks-pro' ),
'edit_published_posts' => __( 'Can Edit Published Posts', 'generateblocks-pro' ),
'edit_others_posts' => __( 'Can Edit Others Posts', 'generateblocks-pro' ),
'publish_posts' => __( 'Can Publish Posts', 'generateblocks-pro' ),
'manage_options' => __( 'Can Manage Options', 'generateblocks-pro' ),
'moderate_comments' => __( 'Can Moderate Comments', 'generateblocks-pro' ),
];
// Capabilities are prefixed with 'cap:' to distinguish from roles.
foreach ( $capabilities as $cap => $label ) {
$rules[ 'cap:' . $cap ] = $label;
}
return apply_filters( 'generateblocks_user_role_rules', $rules );
}
/**
* Get metadata for a specific rule.
*
* @param string $rule The rule key.
* @return array
*/
public function get_rule_metadata( $rule ) {
return [
'needs_value' => false,
'value_type' => 'none',
'supports_multi' => false,
];
}
/**
* Get operators available for a specific user role rule.
*
* @param string $rule The rule key.
* @return array Array of operator keys.
*/
public function get_operators_for_rule( $rule ) {
// General status rules - simple is/is_not only.
if ( in_array( $rule, [ 'general:logged_in', 'general:logged_out' ], true ) ) {
return [ 'is', 'is_not' ];
}
// For individual roles and capabilities, support multi-select if UI supports it.
if ( $this->rule_supports_multi_select( $rule ) ) {
return [ 'is', 'is_not', 'includes_any', 'excludes_any' ];
}
// Fallback to simple operators.
return [ 'is', 'is_not' ];
}
}