balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

base.php (24817B)


      1 <?php
      2 namespace Elementor\Core\Files\CSS;
      3 
      4 use Elementor\Base_Data_Control;
      5 use Elementor\Control_Repeater;
      6 use Elementor\Controls_Manager;
      7 use Elementor\Controls_Stack;
      8 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
      9 use Elementor\Core\Files\Base as Base_File;
     10 use Elementor\Core\DynamicTags\Manager;
     11 use Elementor\Core\DynamicTags\Tag;
     12 use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
     13 use Elementor\Element_Base;
     14 use Elementor\Plugin;
     15 use Elementor\Stylesheet;
     16 use Elementor\Icons_Manager;
     17 
     18 if ( ! defined( 'ABSPATH' ) ) {
     19 	exit; // Exit if accessed directly.
     20 }
     21 
     22 /**
     23  * Elementor CSS file.
     24  *
     25  * Elementor CSS file handler class is responsible for generating CSS files.
     26  *
     27  * @since 1.2.0
     28  * @abstract
     29  */
     30 abstract class Base extends Base_File {
     31 
     32 	/**
     33 	 * Elementor CSS file generated status.
     34 	 *
     35 	 * The parsing result after generating CSS file.
     36 	 */
     37 	const CSS_STATUS_FILE = 'file';
     38 
     39 	/**
     40 	 * Elementor inline CSS status.
     41 	 *
     42 	 * The parsing result after generating inline CSS.
     43 	 */
     44 	const CSS_STATUS_INLINE = 'inline';
     45 
     46 	/**
     47 	 * Elementor CSS empty status.
     48 	 *
     49 	 * The parsing result when an empty CSS returned.
     50 	 */
     51 	const CSS_STATUS_EMPTY = 'empty';
     52 
     53 	/**
     54 	 * Fonts.
     55 	 *
     56 	 * Holds the list of fonts.
     57 	 *
     58 	 * @access private
     59 	 *
     60 	 * @var array
     61 	 */
     62 	private $fonts = [];
     63 
     64 	private $icons_fonts = [];
     65 
     66 	private $dynamic_elements_ids = [];
     67 
     68 	/**
     69 	 * Stylesheet object.
     70 	 *
     71 	 * Holds the CSS file stylesheet instance.
     72 	 *
     73 	 * @access protected
     74 	 *
     75 	 * @var Stylesheet
     76 	 */
     77 	protected $stylesheet_obj;
     78 
     79 	/**
     80 	 * Printed.
     81 	 *
     82 	 * Holds the list of printed files.
     83 	 *
     84 	 * @access protected
     85 	 *
     86 	 * @var array
     87 	 */
     88 	private static $printed = [];
     89 
     90 	/**
     91 	 * Get CSS file name.
     92 	 *
     93 	 * Retrieve the CSS file name.
     94 	 *
     95 	 * @since 1.6.0
     96 	 * @access public
     97 	 * @abstract
     98 	 */
     99 	abstract public function get_name();
    100 
    101 	protected function is_global_parsing_supported() {
    102 		return false;
    103 	}
    104 
    105 	/**
    106 	 * Use external file.
    107 	 *
    108 	 * Whether to use external CSS file of not. When there are new schemes or settings
    109 	 * updates.
    110 	 *
    111 	 * @since 1.9.0
    112 	 * @access protected
    113 	 *
    114 	 * @return bool True if the CSS requires an update, False otherwise.
    115 	 */
    116 	protected function use_external_file() {
    117 		return 'internal' !== get_option( 'elementor_css_print_method' );
    118 	}
    119 
    120 	/**
    121 	 * Update the CSS file.
    122 	 *
    123 	 * Delete old CSS, parse the CSS, save the new file and update the database.
    124 	 *
    125 	 * This method also sets the CSS status to be used later on in the render posses.
    126 	 *
    127 	 * @since 1.2.0
    128 	 * @access public
    129 	 */
    130 	public function update() {
    131 		$this->update_file();
    132 
    133 		$meta = $this->get_meta();
    134 
    135 		$meta['time'] = time();
    136 
    137 		$content = $this->get_content();
    138 
    139 		if ( empty( $content ) ) {
    140 			$meta['status'] = self::CSS_STATUS_EMPTY;
    141 			$meta['css'] = '';
    142 		} else {
    143 			$use_external_file = $this->use_external_file();
    144 
    145 			if ( $use_external_file ) {
    146 				$meta['status'] = self::CSS_STATUS_FILE;
    147 			} else {
    148 				$meta['status'] = self::CSS_STATUS_INLINE;
    149 				$meta['css'] = $content;
    150 			}
    151 		}
    152 
    153 		$meta['dynamic_elements_ids'] = $this->dynamic_elements_ids;
    154 
    155 		$this->update_meta( $meta );
    156 	}
    157 
    158 	/**
    159 	 * @since 2.1.0
    160 	 * @access public
    161 	 */
    162 	public function write() {
    163 		if ( $this->use_external_file() ) {
    164 			parent::write();
    165 		}
    166 	}
    167 
    168 	/**
    169 	 * @since 3.0.0
    170 	 * @access public
    171 	 */
    172 	public function delete() {
    173 		if ( $this->use_external_file() ) {
    174 			parent::delete();
    175 		} else {
    176 			$this->delete_meta();
    177 		}
    178 	}
    179 
    180 	/**
    181 	 * Get Responsive Control Duplication Mode
    182 	 *
    183 	 * @since 3.4.0
    184 	 *
    185 	 * @return string
    186 	 */
    187 	protected function get_responsive_control_duplication_mode() {
    188 		return 'on';
    189 	}
    190 
    191 	/**
    192 	 * Enqueue CSS.
    193 	 *
    194 	 * Either enqueue the CSS file in Elementor or add inline style.
    195 	 *
    196 	 * This method is also responsible for loading the fonts.
    197 	 *
    198 	 * @since 1.2.0
    199 	 * @access public
    200 	 */
    201 	public function enqueue() {
    202 		$handle_id = $this->get_file_handle_id();
    203 
    204 		if ( isset( self::$printed[ $handle_id ] ) ) {
    205 			return;
    206 		}
    207 
    208 		self::$printed[ $handle_id ] = true;
    209 
    210 		$meta = $this->get_meta();
    211 
    212 		if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
    213 			return;
    214 		}
    215 
    216 		// First time after clear cache and etc.
    217 		if ( '' === $meta['status'] || $this->is_update_required() ) {
    218 			$this->update();
    219 
    220 			$meta = $this->get_meta();
    221 		}
    222 
    223 		if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
    224 			$dep = $this->get_inline_dependency();
    225 			// If the dependency has already been printed ( like a template in footer )
    226 			if ( wp_styles()->query( $dep, 'done' ) ) {
    227 				printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok.
    228 			} else {
    229 				wp_add_inline_style( $dep, $meta['css'] );
    230 			}
    231 		} elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
    232 			wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
    233 		}
    234 
    235 		// Handle fonts.
    236 		if ( ! empty( $meta['fonts'] ) ) {
    237 			foreach ( $meta['fonts'] as $font ) {
    238 				Plugin::$instance->frontend->enqueue_font( $font );
    239 			}
    240 		}
    241 
    242 		if ( ! empty( $meta['icons'] ) ) {
    243 			$icons_types = Icons_Manager::get_icon_manager_tabs();
    244 			foreach ( $meta['icons'] as $icon_font ) {
    245 				if ( ! isset( $icons_types[ $icon_font ] ) ) {
    246 					continue;
    247 				}
    248 				Plugin::$instance->frontend->enqueue_font( $icon_font );
    249 			}
    250 		}
    251 
    252 		$name = $this->get_name();
    253 
    254 		/**
    255 		 * Enqueue CSS file.
    256 		 *
    257 		 * Fires when CSS file is enqueued on Elementor.
    258 		 *
    259 		 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
    260 		 *
    261 		 * @since 2.0.0
    262 		 *
    263 		 * @param Base $this The current CSS file.
    264 		 */
    265 		do_action( "elementor/css-file/{$name}/enqueue", $this );
    266 	}
    267 
    268 	/**
    269 	 * Print CSS.
    270 	 *
    271 	 * Output the final CSS inside the `<style>` tags and all the frontend fonts in
    272 	 * use.
    273 	 *
    274 	 * @since 1.9.4
    275 	 * @access public
    276 	 */
    277 	public function print_css() {
    278 		echo '<style>' . $this->get_content() . '</style>'; // XSS ok.
    279 		Plugin::$instance->frontend->print_fonts_links();
    280 	}
    281 
    282 	/**
    283 	 * Add control rules.
    284 	 *
    285 	 * Parse the CSS for all the elements inside any given control.
    286 	 *
    287 	 * This method recursively renders the CSS for all the selectors in the control.
    288 	 *
    289 	 * @since 1.2.0
    290 	 * @access public
    291 	 *
    292 	 * @param array    $control        The controls.
    293 	 * @param array    $controls_stack The controls stack.
    294 	 * @param callable $value_callback Callback function for the value.
    295 	 * @param array    $placeholders   Placeholders.
    296 	 * @param array    $replacements   Replacements.
    297 	 * @param array    $values         Global Values.
    298 	 */
    299 	public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
    300 		if ( empty( $control['selectors'] ) ) {
    301 			return;
    302 		}
    303 
    304 		$control_global_key = $control['name'];
    305 
    306 		if ( ! empty( $control['groupType'] ) ) {
    307 			$control_global_key = $control['groupPrefix'] . $control['groupType'];
    308 		}
    309 
    310 		$global_values = [];
    311 		$global_key = '';
    312 
    313 		if ( ! empty( $values['__globals__'] ) ) {
    314 			$global_values = $values['__globals__'];
    315 		}
    316 
    317 		if ( ! empty( $global_values[ $control_global_key ] ) ) {
    318 			$global_key = $global_values[ $control_global_key ];
    319 		}
    320 
    321 		if ( ! $global_key ) {
    322 			$value = call_user_func( $value_callback, $control );
    323 
    324 			if ( null === $value ) {
    325 				return;
    326 			}
    327 		}
    328 
    329 		$stylesheet = $this->get_stylesheet();
    330 
    331 		foreach ( $control['selectors'] as $selector => $css_property ) {
    332 			$output_css_property = '';
    333 
    334 			if ( $global_key ) {
    335 				$selector_global_value = $this->get_selector_global_value( $control, $global_key );
    336 
    337 				if ( $selector_global_value ) {
    338 					$output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
    339 				}
    340 			} else {
    341 				try {
    342 					$output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
    343 						$external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
    344 
    345 						$parsed_value = '';
    346 
    347 						if ( ! $external_control_missing ) {
    348 							$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
    349 						}
    350 
    351 						if ( '' === $parsed_value ) {
    352 							if ( isset( $matches[4] ) ) {
    353 								$parsed_value = $matches[4];
    354 
    355 								$is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
    356 
    357 								if ( $is_string_value ) {
    358 									$parsed_value = $string_matches[2];
    359 								} elseif ( ! is_numeric( $parsed_value ) ) {
    360 									if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
    361 										return '';
    362 									}
    363 
    364 									$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
    365 								}
    366 							}
    367 
    368 							if ( '' === $parsed_value ) {
    369 								if ( $external_control_missing ) {
    370 									return '';
    371 								}
    372 
    373 								throw new \Exception();
    374 							}
    375 						}
    376 
    377 						return $parsed_value;
    378 					}, $css_property );
    379 				} catch ( \Exception $e ) {
    380 					return;
    381 				}
    382 			}
    383 
    384 			if ( ! $output_css_property ) {
    385 				continue;
    386 			}
    387 
    388 			$device_pattern = '/^(?:\([^\)]+\)){1,2}/';
    389 
    390 			preg_match( $device_pattern, $selector, $device_rules );
    391 
    392 			$query = [];
    393 
    394 			if ( $device_rules ) {
    395 				$selector = preg_replace( $device_pattern, '', $selector );
    396 
    397 				preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
    398 
    399 				$pure_device_rules = $pure_device_rules[1];
    400 
    401 				foreach ( $pure_device_rules as $device_rule ) {
    402 					if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_rule ) {
    403 						continue;
    404 					}
    405 
    406 					$device = preg_replace( '/\+$/', '', $device_rule );
    407 
    408 					$endpoint = $device === $device_rule ? 'max' : 'min';
    409 
    410 					$query[ $endpoint ] = $device;
    411 				}
    412 			}
    413 
    414 			$parsed_selector = str_replace( $placeholders, $replacements, $selector );
    415 
    416 			if ( ! $query && ! empty( $control['responsive'] ) ) {
    417 				$query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
    418 
    419 				if ( ! empty( $query['max'] ) && Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $query['max'] ) {
    420 					unset( $query['max'] );
    421 				}
    422 			}
    423 
    424 			$stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
    425 		}
    426 	}
    427 
    428 	/**
    429 	 * @param array    $control
    430 	 * @param mixed    $value
    431 	 * @param array    $controls_stack
    432 	 * @param callable $value_callback
    433 	 * @param string   $placeholder
    434 	 * @param string   $parser_control_name
    435 	 *
    436 	 * @return string
    437 	 */
    438 	public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
    439 		if ( $parser_control_name ) {
    440 			$control = $controls_stack[ $parser_control_name ];
    441 
    442 			$value = call_user_func( $value_callback, $control );
    443 		}
    444 
    445 		if ( Controls_Manager::FONT === $control['type'] ) {
    446 			$this->fonts[] = $value;
    447 		}
    448 
    449 		/** @var Base_Data_Control $control_obj */
    450 		$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
    451 
    452 		return (string) $control_obj->get_style_value( $placeholder, $value, $control );
    453 	}
    454 
    455 	/**
    456 	 * Get the fonts.
    457 	 *
    458 	 * Retrieve the list of fonts.
    459 	 *
    460 	 * @since 1.9.0
    461 	 * @access public
    462 	 *
    463 	 * @return array Fonts.
    464 	 */
    465 	public function get_fonts() {
    466 		return $this->fonts;
    467 	}
    468 
    469 	/**
    470 	 * Get stylesheet.
    471 	 *
    472 	 * Retrieve the CSS file stylesheet instance.
    473 	 *
    474 	 * @since 1.2.0
    475 	 * @access public
    476 	 *
    477 	 * @return Stylesheet The stylesheet object.
    478 	 */
    479 	public function get_stylesheet() {
    480 		if ( ! $this->stylesheet_obj ) {
    481 			$this->init_stylesheet();
    482 		}
    483 
    484 		return $this->stylesheet_obj;
    485 	}
    486 
    487 	/**
    488 	 * Add controls stack style rules.
    489 	 *
    490 	 * Parse the CSS for all the elements inside any given controls stack.
    491 	 *
    492 	 * This method recursively renders the CSS for all the child elements in the stack.
    493 	 *
    494 	 * @since 1.6.0
    495 	 * @access public
    496 	 *
    497 	 * @param Controls_Stack $controls_stack The controls stack.
    498 	 * @param array          $controls       Controls array.
    499 	 * @param array          $values         Values array.
    500 	 * @param array          $placeholders   Placeholders.
    501 	 * @param array          $replacements   Replacements.
    502 	 * @param array          $all_controls   All controls.
    503 	 */
    504 	public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
    505 		if ( ! $all_controls ) {
    506 			$all_controls = $controls_stack->get_controls();
    507 		}
    508 
    509 		$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
    510 
    511 		foreach ( $controls as $control ) {
    512 			if ( ! empty( $control['style_fields'] ) ) {
    513 				$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
    514 			}
    515 
    516 			if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
    517 				$this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
    518 			}
    519 
    520 			if ( Controls_Manager::ICONS === $control['type'] ) {
    521 				$this->icons_fonts[] = $values[ $control['name'] ]['library'];
    522 			}
    523 
    524 			if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
    525 				// Dynamic CSS should not be added to the CSS files.
    526 				// Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
    527 				// and printed in a style tag.
    528 				unset( $parsed_dynamic_settings[ $control['name'] ] );
    529 
    530 				$this->dynamic_elements_ids[] = $controls_stack->get_id();
    531 
    532 				continue;
    533 			}
    534 
    535 			if ( empty( $control['selectors'] ) ) {
    536 				continue;
    537 			}
    538 
    539 			$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
    540 		}
    541 	}
    542 
    543 	/**
    544 	 * Get file handle ID.
    545 	 *
    546 	 * Retrieve the file handle ID.
    547 	 *
    548 	 * @since 1.2.0
    549 	 * @access protected
    550 	 * @abstract
    551 	 *
    552 	 * @return string CSS file handle ID.
    553 	 */
    554 	abstract protected function get_file_handle_id();
    555 
    556 	/**
    557 	 * Render CSS.
    558 	 *
    559 	 * Parse the CSS.
    560 	 *
    561 	 * @since 1.2.0
    562 	 * @access protected
    563 	 * @abstract
    564 	 */
    565 	abstract protected function render_css();
    566 
    567 	protected function get_default_meta() {
    568 		return array_merge( parent::get_default_meta(), [
    569 			'fonts' => array_unique( $this->fonts ),
    570 			'icons' => array_unique( $this->icons_fonts ),
    571 			'dynamic_elements_ids' => [],
    572 			'status' => '',
    573 		] );
    574 	}
    575 
    576 	/**
    577 	 * Get enqueue dependencies.
    578 	 *
    579 	 * Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
    580 	 *
    581 	 * @since 1.2.0
    582 	 * @access protected
    583 	 *
    584 	 * @return array Name of the stylesheet.
    585 	 */
    586 	protected function get_enqueue_dependencies() {
    587 		return [];
    588 	}
    589 
    590 	/**
    591 	 * Get inline dependency.
    592 	 *
    593 	 * Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
    594 	 *
    595 	 * @since 1.2.0
    596 	 * @access protected
    597 	 *
    598 	 * @return string Name of the stylesheet.
    599 	 */
    600 	protected function get_inline_dependency() {
    601 		return '';
    602 	}
    603 
    604 	/**
    605 	 * Is update required.
    606 	 *
    607 	 * Whether the CSS requires an update. When there are new schemes or settings
    608 	 * updates.
    609 	 *
    610 	 * @since 1.2.0
    611 	 * @access protected
    612 	 *
    613 	 * @return bool True if the CSS requires an update, False otherwise.
    614 	 */
    615 	protected function is_update_required() {
    616 		return false;
    617 	}
    618 
    619 	/**
    620 	 * Parse CSS.
    621 	 *
    622 	 * Parsing the CSS file.
    623 	 *
    624 	 * @since 1.2.0
    625 	 * @access protected
    626 	 */
    627 	protected function parse_content() {
    628 		$initial_responsive_controls_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode();
    629 
    630 		Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $this->get_responsive_control_duplication_mode() );
    631 
    632 		$this->render_css();
    633 
    634 		$name = $this->get_name();
    635 
    636 		/**
    637 		 * Parse CSS file.
    638 		 *
    639 		 * Fires when CSS file is parsed on Elementor.
    640 		 *
    641 		 * The dynamic portion of the hook name, `$name`, refers to the CSS file name.
    642 		 *
    643 		 * @since 2.0.0
    644 		 *
    645 		 * @param Base $this The current CSS file.
    646 		 */
    647 		do_action( "elementor/css-file/{$name}/parse", $this );
    648 
    649 		Plugin::$instance->breakpoints->set_responsive_control_duplication_mode( $initial_responsive_controls_duplication_mode );
    650 
    651 		return $this->get_stylesheet()->__toString();
    652 	}
    653 
    654 	/**
    655 	 * Add control style rules.
    656 	 *
    657 	 * Register new style rules for the control.
    658 	 *
    659 	 * @since 1.6.0
    660 	 * @access private
    661 	 *
    662 	 * @param array $control      The control.
    663 	 * @param array $values       Values array.
    664 	 * @param array $controls     The controls stack.
    665 	 * @param array $placeholders Placeholders.
    666 	 * @param array $replacements Replacements.
    667 	 */
    668 	protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
    669 		$this->add_control_rules(
    670 			$control, $controls, function( $control ) use ( $values ) {
    671 				return $this->get_style_control_value( $control, $values );
    672 			}, $placeholders, $replacements, $values
    673 		);
    674 	}
    675 
    676 	/**
    677 	 * Get style control value.
    678 	 *
    679 	 * Retrieve the value of the style control for any give control and values.
    680 	 *
    681 	 * It will retrieve the control name and return the style value.
    682 	 *
    683 	 * @since 1.6.0
    684 	 * @access private
    685 	 *
    686 	 * @param array $control The control.
    687 	 * @param array $values  Values array.
    688 	 *
    689 	 * @return mixed Style control value.
    690 	 */
    691 	private function get_style_control_value( array $control, array $values ) {
    692 		if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
    693 			// When the control itself has no global value, but it refers to another control global value
    694 			return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
    695 		}
    696 
    697 		$value = $values[ $control['name'] ];
    698 
    699 		if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
    700 			$value = $control['selectors_dictionary'][ $value ];
    701 		}
    702 
    703 		if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
    704 			return null;
    705 		}
    706 
    707 		return $value;
    708 	}
    709 
    710 	/**
    711 	 * Init stylesheet.
    712 	 *
    713 	 * Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
    714 	 * breakpoints for the stylesheet.
    715 	 *
    716 	 * @since 1.2.0
    717 	 * @access private
    718 	 */
    719 	private function init_stylesheet() {
    720 		$this->stylesheet_obj = new Stylesheet();
    721 
    722 		$active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
    723 
    724 		foreach ( $active_breakpoints as $breakpoint_name => $breakpoint ) {
    725 			$this->stylesheet_obj->add_device( $breakpoint_name, $breakpoint->get_value() );
    726 		}
    727 	}
    728 
    729 	/**
    730 	 * Add repeater control style rules.
    731 	 *
    732 	 * Register new style rules for the repeater control.
    733 	 *
    734 	 * @since 2.0.0
    735 	 * @access private
    736 	 *
    737 	 * @param Controls_Stack $controls_stack   The control stack.
    738 	 * @param array          $repeater_control The repeater control.
    739 	 * @param array          $repeater_values  Repeater values array.
    740 	 * @param array          $placeholders     Placeholders.
    741 	 * @param array          $replacements     Replacements.
    742 	 */
    743 	protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
    744 		$placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
    745 
    746 		foreach ( $repeater_control['style_fields'] as $index => $item ) {
    747 			$this->add_controls_stack_style_rules(
    748 				$controls_stack,
    749 				$item,
    750 				$repeater_values[ $index ],
    751 				$placeholders,
    752 				array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
    753 				$repeater_control['fields']
    754 			);
    755 		}
    756 	}
    757 
    758 	/**
    759 	 * Add dynamic control style rules.
    760 	 *
    761 	 * Register new style rules for the dynamic control.
    762 	 *
    763 	 * @since 2.0.0
    764 	 * @access private
    765 	 *
    766 	 * @param array  $control The control.
    767 	 * @param string $value   The value.
    768 	 */
    769 	protected function add_dynamic_control_style_rules( array $control, $value ) {
    770 		Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
    771 			$tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
    772 
    773 			if ( ! $tag instanceof Tag ) {
    774 				return;
    775 			}
    776 
    777 			$this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
    778 		} );
    779 	}
    780 
    781 	private function get_selector_global_value( $control, $global_key ) {
    782 		$data = Plugin::$instance->data_manager->run( $global_key );
    783 
    784 		if ( empty( $data['value'] ) ) {
    785 			return null;
    786 		}
    787 
    788 		$global_args = explode( '?id=', $global_key );
    789 
    790 		$id = $global_args[1];
    791 
    792 		if ( ! empty( $control['groupType'] ) ) {
    793 			$strings_to_replace = [ $control['groupPrefix'] ];
    794 
    795 			$active_breakpoint_keys = array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() );
    796 
    797 			foreach ( $active_breakpoint_keys as $breakpoint ) {
    798 				$strings_to_replace[] = '_' . $breakpoint;
    799 			}
    800 
    801 			$property_name = str_replace( $strings_to_replace, '', $control['name'] );
    802 
    803 			// TODO: This check won't retrieve the proper answer for array values (multiple controls).
    804 			if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
    805 				return null;
    806 			}
    807 
    808 			$property_name = str_replace( '_', '-', $property_name );
    809 
    810 			$value = "var( --e-global-$control[groupType]-$id-$property_name )";
    811 
    812 			if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) {
    813 				$default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' );
    814 
    815 				if ( $default_generic_fonts ) {
    816 					$value  .= ", $default_generic_fonts";
    817 				}
    818 			}
    819 		} else {
    820 			$value = "var( --e-global-$control[type]-$id )";
    821 		}
    822 
    823 		return $value;
    824 	}
    825 
    826 	final protected function get_active_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
    827 		if ( ! $controls ) {
    828 			$controls = $controls_stack->get_controls();
    829 		}
    830 
    831 		if ( ! $settings ) {
    832 			$settings = $controls_stack->get_controls_settings();
    833 		}
    834 
    835 		if ( $this->is_global_parsing_supported() ) {
    836 			$settings = $this->parse_global_settings( $settings, $controls );
    837 		}
    838 
    839 		$active_controls = array_reduce(
    840 			array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
    841 				$control = $controls[ $control_key ];
    842 
    843 				if ( $controls_stack->is_control_visible( $control, $settings ) ) {
    844 					$active_controls[ $control_key ] = $control;
    845 				}
    846 
    847 				return $active_controls;
    848 			}, []
    849 		);
    850 
    851 		return $active_controls;
    852 	}
    853 
    854 	final public function get_style_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
    855 		$controls = $this->get_active_controls( $controls_stack, $controls, $settings );
    856 
    857 		$style_controls = [];
    858 
    859 		foreach ( $controls as $control_name => $control ) {
    860 			$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
    861 
    862 			if ( ! $control_obj instanceof Base_Data_Control ) {
    863 				continue;
    864 			}
    865 
    866 			$control = array_merge( $control_obj->get_settings(), $control );
    867 
    868 			if ( $control_obj instanceof Control_Repeater ) {
    869 				$style_fields = [];
    870 
    871 				foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
    872 					$style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
    873 				}
    874 
    875 				$control['style_fields'] = $style_fields;
    876 			}
    877 
    878 			if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
    879 				$style_controls[ $control_name ] = $control;
    880 			}
    881 		}
    882 
    883 		return $style_controls;
    884 	}
    885 
    886 	private function parse_global_settings( array $settings, array $controls ) {
    887 		foreach ( $controls as $control ) {
    888 			$control_name = $control['name'];
    889 			$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
    890 
    891 			if ( ! $control_obj instanceof Base_Data_Control ) {
    892 				continue;
    893 			}
    894 
    895 			if ( $control_obj instanceof Control_Repeater ) {
    896 				foreach ( $settings[ $control_name ] as & $field ) {
    897 					$field = $this->parse_global_settings( $field, $control['fields'] );
    898 				}
    899 
    900 				continue;
    901 			}
    902 
    903 			if ( empty( $control['global']['active'] ) ) {
    904 				continue;
    905 			}
    906 
    907 			if ( empty( $settings['__globals__'][ $control_name ] ) ) {
    908 				continue;
    909 			}
    910 
    911 			$settings[ $control_name ] = 'global';
    912 		}
    913 
    914 		return $settings;
    915 	}
    916 
    917 	private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
    918 		$control = $controls[ $control_name ];
    919 
    920 		$control_global_key = $control_name;
    921 
    922 		if ( ! empty( $control['groupType'] ) ) {
    923 			$control_global_key = $control['groupPrefix'] . $control['groupType'];
    924 		}
    925 
    926 		if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
    927 			return false;
    928 		}
    929 
    930 		$globals = $controls_stack->get_settings( '__globals__' );
    931 
    932 		return ! empty( $globals[ $control_global_key ] );
    933 	}
    934 }