• File: class-conditions-manager.php
  • Full Path: /home/bravrvjk/hpgt.org/wp-content/plugins/royal-elementor-addons/includes/display-conditions/class-conditions-manager.php
  • Date Modified: 04/10/2026 2:58 PM
  • File size: 7.61 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Central manager for the Visibility tab.
 * Loads all sections, registers controls on all Elementor elements, evaluates conditions on render.
 */
class WPR_DC_Conditions_Manager {

	/**
	 * Registered section instances, sorted by order.
	 *
	 * @var WPR_DC_Section_Base[]
	 */
	private $sections = [];

	/**
	 * Cached user-agent parsing result.
	 */
	private $ua_parsed = null;

	public function __construct() {
		$this->load_sections();
		$this->setup_hooks();
	}

	/**
	 * Load and register all section classes from the sections/ directory.
	 */
	private function load_sections() {
		$sections_dir = WPR_ADDONS_PATH . 'includes/display-conditions/sections/';
		$files = glob( $sections_dir . 'class-*.php' );

		if ( ! $files ) {
			return;
		}

		foreach ( $files as $file ) {
			require_once $file;
		}

		// All section classes follow naming: WPR_DC_Section_{PascalName}
		// We collect them by checking declared classes that extend WPR_DC_Section_Base
		$section_classes = [
			'WPR_DC_Section_General_Settings',
			'WPR_DC_Section_Visitor_Roles',
			'WPR_DC_Section_User_Profile',
			'WPR_DC_Section_Page_Content',
			'WPR_DC_Section_Archive',
			'WPR_DC_Section_Date_Time',
			'WPR_DC_Section_Device_Browser',
			'WPR_DC_Section_Visitor_Location',
			'WPR_DC_Section_Url_Parameters',
			'WPR_DC_Section_Woocommerce',
			'WPR_DC_Section_Language',
			'WPR_DC_Section_Custom_Fields',
			'WPR_DC_Section_Dynamic_Tags',
			'WPR_DC_Section_Interaction',
			'WPR_DC_Section_Random_Limits',
			'WPR_DC_Section_Fallback_Content',
			'WPR_DC_Section_Pro_Features',
		];

		foreach ( $section_classes as $class_name ) {
			// Use pro override class if available
			$pro_class = $class_name . '_Pro';
			$use_class = class_exists( $pro_class ) ? $pro_class : $class_name;

			if ( class_exists( $use_class ) ) {
				$section = new $use_class();
				if ( $section->is_available() ) {
					$this->sections[] = $section;
				}
			}
		}

		// Sort by order
		usort( $this->sections, function ( $a, $b ) {
			return $a->get_order() - $b->get_order();
		} );
	}

	/**
	 * Hook into Elementor to register controls and intercept rendering.
	 */
	private function setup_hooks() {
		// Register controls on all element types
		$element_types = [ 'common', 'section', 'column', 'container' ];

		foreach ( $element_types as $type ) {
			add_action(
				"elementor/element/{$type}/section_effects/after_section_end",
				[ $this, 'register_tab_sections' ],
				10,
				2
			);
		}

		// Frontend: intercept rendering to show/hide elements
		$render_types = [ 'widget', 'section', 'column', 'container' ];

		foreach ( $render_types as $type ) {
			add_action( "elementor/frontend/{$type}/before_render", [ $this, 'before_render' ] );
			add_action( "elementor/frontend/{$type}/after_render", [ $this, 'after_render' ] );
		}

		// Prevent Elementor from caching elements that have visibility conditions.
		// Without this, the first render result gets cached and served to all users,
		// breaking role-based and other dynamic visibility logic.
		add_filter( 'elementor/element/is_dynamic_content', [ $this, 'disable_element_caching' ], 10, 2 );
	}

	/**
	 * Tell Elementor that elements with visibility conditions are dynamic
	 * so they are never served from the element cache.
	 *
	 * @param bool  $is_dynamic_content Current dynamic-content flag.
	 * @param array $raw_data           Raw element data including settings.
	 * @return bool
	 */
	public function disable_element_caching( $is_dynamic_content, $raw_data ) {
		if ( ! empty( $raw_data['settings']['wpr_dc_enabled'] ) ) {
			return true;
		}
		return $is_dynamic_content;
	}

	/**
	 * Register all visibility sections as collapsible panels in our custom tab.
	 */
	public function register_tab_sections( $element, $args ) {
		foreach ( $this->sections as $section ) {
			$section_args = [
				'tab'   => 'wpr_display_conditions',
				'label' => $section->get_label(),
			];

			// Hide all sections except General when visibility is not enabled
			if ( 'wpr_dc_general' !== $section->get_id() ) {
				$section_args['condition'] = [
					'wpr_dc_enabled' => 'yes',
				];
			}

			$element->start_controls_section(
				$section->get_id(),
				$section_args
			);

			$section->register_controls( $element );

			$element->end_controls_section();
		}
	}

	/**
	 * Before an element renders on the frontend — evaluate conditions and hide if needed.
	 */
	public function before_render( $element ) {
		// Never hide in editor
		if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
			return;
		}

		// Also skip preview mode so Elementor preview works
		if ( \Elementor\Plugin::$instance->preview->is_preview_mode() ) {
			return;
		}

		$settings = $element->get_settings_for_display();

		// Check master toggle
		if ( empty( $settings['wpr_dc_enabled'] ) || 'yes' !== $settings['wpr_dc_enabled'] ) {
			return;
		}

		$mode     = ! empty( $settings['wpr_dc_mode'] ) ? $settings['wpr_dc_mode'] : 'show';
		$logic    = ! empty( $settings['wpr_dc_logic'] ) ? $settings['wpr_dc_logic'] : 'any';
		$css_only = ! empty( $settings['wpr_dc_css_only'] ) && 'yes' === $settings['wpr_dc_css_only'];

		// Collect results from evaluable sections (skip general, fallback, interaction)
		$skip_ids = [ 'wpr_dc_general', 'wpr_dc_fallback', 'wpr_dc_interaction' ];
		$results  = [];

		foreach ( $this->sections as $section ) {
			if ( in_array( $section->get_id(), $skip_ids, true ) ) {
				continue;
			}

			$result = $section->evaluate( $settings );

			if ( null !== $result ) {
				$results[] = $result;
			}
		}

		// No conditions configured — always show
		if ( empty( $results ) ) {
			return;
		}

		// Apply logic
		if ( 'all' === $logic ) {
			$conditions_met = ! in_array( false, $results, true );
		} else {
			$conditions_met = in_array( true, $results, true );
		}

		// Determine if element should be shown
		$should_show = ( 'show' === $mode ) ? $conditions_met : ! $conditions_met;

		if ( ! $should_show ) {
			WPR_DC_Render_Handler::instance()->start_hiding( $element, $settings, $css_only );
		}
	}

	/**
	 * After an element renders — close output buffer if hiding.
	 */
	public function after_render( $element ) {
		if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
			return;
		}

		WPR_DC_Render_Handler::instance()->finish_hiding( $element );
	}

	/**
	 * Get parsed user-agent info (cached per request).
	 */
	public function get_user_agent_info() {
		if ( null !== $this->ua_parsed ) {
			return $this->ua_parsed;
		}

		$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';

		$this->ua_parsed = [
			'raw'     => $ua,
			'device'  => $this->detect_device( $ua ),
			'browser' => $this->detect_browser( $ua ),
		];

		return $this->ua_parsed;
	}

	/**
	 * Detect device type from user-agent string.
	 */
	private function detect_device( $ua ) {
		if ( preg_match( '/iPad|Android(?!.*Mobile)|Tablet/i', $ua ) ) {
			return 'tablet';
		}
		if ( preg_match( '/Mobile|iPhone|iPod|Android.*Mobile|webOS|BlackBerry|Opera Mini|IEMobile/i', $ua ) ) {
			return 'mobile';
		}
		return 'desktop';
	}

	/**
	 * Detect browser from user-agent string.
	 */
	private function detect_browser( $ua ) {
		if ( preg_match( '/Edg\//i', $ua ) ) {
			return 'edge';
		}
		if ( preg_match( '/OPR|Opera/i', $ua ) ) {
			return 'opera';
		}
		if ( preg_match( '/Chrome/i', $ua ) && ! preg_match( '/Edg|OPR/i', $ua ) ) {
			return 'chrome';
		}
		if ( preg_match( '/Safari/i', $ua ) && ! preg_match( '/Chrome|Edg|OPR/i', $ua ) ) {
			return 'safari';
		}
		if ( preg_match( '/Firefox/i', $ua ) ) {
			return 'firefox';
		}
		if ( preg_match( '/MSIE|Trident/i', $ua ) ) {
			return 'ie';
		}
		return 'other';
	}
}