rule_type = 'role'; // Load role-specific data. if ( isset( $rule_data['user_roles'] ) ) { $this->user_roles = is_array( $rule_data['user_roles'] ) ? $rule_data['user_roles'] : explode( ',', $rule_data['user_roles'] ); $this->user_roles = array_map( 'sanitize_text_field', $this->user_roles ); } // Check for guest setting. if ( isset( $this->adjustments['apply_to_guests'] ) ) { $this->apply_to_guests = (bool) $this->adjustments['apply_to_guests']; } // Add 'guest' to roles if applicable. if ( $this->apply_to_guests && ! in_array( 'guest', $this->user_roles, true ) ) { $this->user_roles[] = 'guest'; } } /** * Check if rule is applicable. * * @param array $context Context data for evaluation. * @return bool */ public function is_applicable( $context ) { // Check if rule is active. if ( 'active' !== $this->status ) { return false; } // Check date range. if ( ! $this->is_within_date_range() ) { return false; } // Check user role. // If no roles are specified, treat it as "apply to all users". if ( ! empty( $this->user_roles ) ) { // Roles are specified - check if user matches. $user_role = isset( $context['user_role'] ) ? $context['user_role'] : 'guest'; // First check if the current user role is in the allowed roles. if ( ! in_array( $user_role, $this->user_roles, true ) ) { // Not a direct match, check if logged in user has any of the specified roles. if ( isset( $context['user_id'] ) && $context['user_id'] > 0 ) { $user = get_user_by( 'id', $context['user_id'] ); if ( $user ) { $user_has_role = false; foreach ( $this->user_roles as $role ) { // Skip 'guest' role for logged-in users. if ( 'guest' === $role ) { continue; } if ( in_array( $role, $user->roles, true ) ) { $user_has_role = true; break; } } if ( ! $user_has_role ) { return false; } } else { return false; } } elseif ( ! in_array( 'guest', $this->user_roles, true ) ) { // Guest user - check if guest role is allowed. return false; } } } // No roles specified - apply to all users (all roles and guests). // Just check conditions and let it pass through. // Check additional conditions. if ( ! $this->check_conditions( $context ) ) { return false; } return true; } /** * Calculate price based on rule. * * @param float $original_price Original price. * @param array $context Context data. * @return float Modified price. */ public function calculate_price( $original_price, $context ) { // Get role-specific adjustment. $adjustment = $this->get_role_adjustment( $context ); if ( ! $adjustment ) { return $original_price; } // Apply the adjustment. return $this->apply_adjustment( $original_price, $adjustment ); } /** * Get role-specific adjustment. * * @param array $context Context data. * @return array|null Adjustment data or null. */ private function get_role_adjustment( $context ) { $user_role = isset( $context['user_role'] ) ? $context['user_role'] : 'guest'; // Check for role-specific adjustments. if ( isset( $this->adjustments['role_adjustments'][ $user_role ] ) ) { return $this->adjustments['role_adjustments'][ $user_role ]; } // Check for new array format first [{'type': 'percentage', 'value': 40}]. if ( is_array( $this->adjustments ) && isset( $this->adjustments[0] ) && is_array( $this->adjustments[0] ) ) { $first_adjustment = $this->adjustments[0]; if ( isset( $first_adjustment['type'] ) && isset( $first_adjustment['value'] ) ) { return array( 'type' => $first_adjustment['type'], 'value' => floatval( $first_adjustment['value'] ), ); } } // Return default adjustment for all specified roles. if ( isset( $this->adjustments['default'] ) ) { return $this->adjustments['default']; } // Fallback to old simple adjustment format (backward compatibility). if ( isset( $this->adjustments['discount_type'] ) && isset( $this->adjustments['discount_value'] ) ) { return array( 'type' => $this->adjustments['discount_type'], 'value' => floatval( $this->adjustments['discount_value'] ), ); } // No adjustment found. return null; } /** * Get display price for frontend. * * @param WC_Product $product Product object. * @param string $user_role User role. * @return float|string */ public function get_display_price( $product, $user_role = null ) { if ( ! $product ) { return ''; } // Get current user role if not provided. if ( ! $user_role ) { $user = wp_get_current_user(); $user_role = $user->ID > 0 ? ( ! empty( $user->roles ) ? $user->roles[0] : 'customer' ) : 'guest'; } // Build context. $context = array( 'product_id' => $product->get_id(), 'quantity' => 1, 'user_role' => $user_role, 'user_id' => get_current_user_id(), ); // Check if rule applies. if ( ! $this->is_applicable( $context ) ) { return $product->get_price(); } // Calculate and return price. return $this->calculate_price( $product->get_price(), $context ); } /** * Set user roles for this rule. * * @param array $roles Array of role slugs. */ public function set_user_roles( $roles ) { $this->user_roles = array_map( 'sanitize_text_field', (array) $roles ); } /** * Get user roles. * * @return array */ public function get_user_roles() { return $this->user_roles; } /** * Set whether to apply to guests. * * @param bool $apply Whether to apply to guest users. */ public function set_apply_to_guests( $apply ) { $this->apply_to_guests = (bool) $apply; $this->adjustments['apply_to_guests'] = $this->apply_to_guests; if ( $this->apply_to_guests && ! in_array( 'guest', $this->user_roles, true ) ) { $this->user_roles[] = 'guest'; } elseif ( ! $this->apply_to_guests ) { $this->user_roles = array_diff( $this->user_roles, array( 'guest' ) ); } } /** * Add role-specific adjustment. * * @param string $role User role. * @param string $discount_type Type of discount. * @param float $discount_value Discount value. */ public function add_role_adjustment( $role, $discount_type, $discount_value ) { if ( ! isset( $this->adjustments['role_adjustments'] ) ) { $this->adjustments['role_adjustments'] = array(); } $this->adjustments['role_adjustments'][ $role ] = array( 'type' => sanitize_text_field( $discount_type ), 'value' => floatval( $discount_value ), ); } /** * Get available WordPress user roles. * * @return array Array of role names keyed by role slug. */ public static function get_available_roles() { $wp_roles = wp_roles(); $roles = array( 'guest' => __( 'Guest (Not logged in)', 'aicoso-dynamic-pricing-for-woocommerce' ), ); foreach ( $wp_roles->roles as $role_slug => $role_data ) { $roles[ $role_slug ] = $role_data['name']; } return $roles; } /** * Check if current user has discount. * * @return bool */ public function current_user_has_discount() { $user = wp_get_current_user(); if ( $user->ID > 0 ) { // Logged in user. foreach ( $this->user_roles as $role ) { if ( in_array( $role, $user->roles, true ) ) { return true; } } } else { // Guest user. return in_array( 'guest', $this->user_roles, true ); } return false; } /** * Get discount badge text. * * @return string */ public function get_discount_badge_text() { $user = wp_get_current_user(); $user_role = $user->ID > 0 && ! empty( $user->roles ) ? $user->roles[0] : 'guest'; $adjustment = $this->get_role_adjustment( array( 'user_role' => $user_role ) ); if ( ! $adjustment || empty( $adjustment['value'] ) ) { return ''; } if ( 'percentage' === $adjustment['type'] ) { /* translators: %s: discount percentage */ return sprintf( __( '%s%% Off', 'aicoso-dynamic-pricing-for-woocommerce' ), $adjustment['value'] ); } elseif ( 'fixed' === $adjustment['type'] ) { /* translators: %s: discount amount */ return sprintf( __( '%s Off', 'aicoso-dynamic-pricing-for-woocommerce' ), wc_price( $adjustment['value'] ) ); } return __( 'Special Price', 'aicoso-dynamic-pricing-for-woocommerce' ); } /** * Save rule with role relationships. * * @return int|WP_Error Rule ID on success, WP_Error on failure. */ public function save() { // Save the rule. $result = parent::save(); if ( is_wp_error( $result ) ) { return $result; } // Save role relationships. if ( ! empty( $this->user_roles ) ) { global $wpdb; $relationships_table = $wpdb->prefix . 'dp_aicoso_rule_relationships'; // Delete existing relationships. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->delete( $relationships_table, array( 'rule_id' => $this->rule_id ), array( '%d' ) ); // Clear role cache for this rule. wp_cache_delete( "dp_aicoso_rule_roles_{$this->rule_id}", 'dp_aicoso_rules' ); // Insert new relationships. foreach ( $this->user_roles as $role ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert( $relationships_table, array( 'rule_id' => $this->rule_id, 'object_id' => 0, // Roles don't have IDs, use 0. 'object_type' => 'role:' . $role, ), array( '%d', '%d', '%s' ) ); } // Clear role-specific caches. foreach ( $this->user_roles as $role ) { wp_cache_delete( "dp_aicoso_role_rules_{$role}", 'dp_aicoso_rules' ); } } return $this->rule_id; } /** * Load role relationships from database. */ public function load_relationships() { if ( ! $this->rule_id ) { return; } // Check cache first. $cache_key = "dp_aicoso_rule_roles_{$this->rule_id}"; $cached_roles = wp_cache_get( $cache_key, 'dp_aicoso_rules' ); if ( false !== $cached_roles ) { $this->user_roles = $cached_roles; return; } global $wpdb; $relationships_table = $wpdb->prefix . 'dp_aicoso_rule_relationships'; // Check if table exists before querying. $table_exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( 'SHOW TABLES LIKE %s', $relationships_table ) ) === $relationships_table; if ( ! $table_exists ) { return; } $role_prefix = 'role:%'; $role_types = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( "SELECT object_type FROM {$wpdb->prefix}dp_aicoso_rule_relationships WHERE rule_id = %d AND object_type LIKE %s", $this->rule_id, $role_prefix ) ); if ( $role_types && ! is_wp_error( $role_types ) ) { $this->user_roles = array(); foreach ( $role_types as $role_type ) { // Extract role from 'role:rolename' format. $role = str_replace( 'role:', '', $role_type ); $this->user_roles[] = $role; } } // Cache the result for 5 minutes. wp_cache_set( $cache_key, $this->user_roles, 'dp_aicoso_rules', 300 ); } }