CSS_File()
Elementor CSS file class.
Description
Elementor CSS file handler class is responsible for generating CSS files.
Changelog
Version | Description |
---|---|
1.2.0 | Introduced. |
Methods
- __construct — CSS file constructor.
- add_control_rules — Add control rules.
- add_control_style_rules — Add control style rules.
- add_controls_stack_style_rules — Add controls stack style rules.
- add_dynamic_control_style_rules — Add dynamic control style rules.
- add_repeater_control_style_rules — Add repeater control style rules.
- delete — Delete the CSS file.
- enqueue — Enqueue CSS.
- get_css — Get CSS.
- get_enqueue_dependencies — Get enqueue dependencies.
Source
includes/css-file/css-file.php
abstract class CSS_File { /** * Elementor CSS files base folder. * * Relative folder in the WordPress uploads folder. */ const FILE_BASE_DIR = '/elementor/css'; /** * Elementor CSS file name pattern. * * %s: Base folder; %s: file name */ const FILE_NAME_PATTERN = '%s/%s.css'; /** * Elementor CSS file generated status. * * The parsing result after generating CSS file. */ const CSS_STATUS_FILE = 'file'; /** * Elementor inline CSS status. * * The parsing result after generating inline CSS. */ const CSS_STATUS_INLINE = 'inline'; /** * Elementor CSS empty status. * * The parsing result when an empty CSS returned. */ const CSS_STATUS_EMPTY = 'empty'; /** * CSS file path. * * Holds the CSS file path. * * @access private * * @var string */ private $path; /** * CSS file URL. * * Holds the CSS file URL. * * @access private * * @var string */ private $url; /** * CSS. * * Holds the CSS. * * @access private * * @var string */ private $css; /** * Fonts. * * Holds the list of fonts. * * @access private * * @var array */ private $fonts = []; /** * Stylesheet object. * * Holds the CSS file stylesheet instance. * * @access protected * * @var Stylesheet */ protected $stylesheet_obj; /** * Printed. * * Holds the list of printed files. * * @access protected * * @var array */ private static $printed = []; /** * Get CSS file name. * * Retrieve the CSS file name. * * @since 1.6.0 * @access public * @abstract */ abstract public function get_name(); /** * CSS file constructor. * * Initializing Elementor CSS file. If using external files, set path and * URL, otherwise initiate stylesheet. * * @since 1.2.0 * @access public */ public function __construct() { if ( $this->use_external_file() ) { $this->set_path_and_url(); } $this->init_stylesheet(); } /** * Use external file. * * Whether to use external CSS file of not. When there are new schemes or settings * updates. * * @since 1.9.0 * @access protected * * @return bool True if the CSS requires an update, False otherwise. */ protected function use_external_file() { return 'internal' !== get_option( 'elementor_css_print_method' ); } /** * Update the CSS file. * * Delete old CSS, parse the CSS, save the new file and update the database. * * This method also sets the CSS status to be used later on in the render posses. * * @since 1.2.0 * @access public */ public function update() { $this->parse_css(); $meta = [ 'time' => time(), 'fonts' => array_unique( $this->fonts ), ]; if ( empty( $this->css ) ) { $this->delete(); $meta['status'] = self::CSS_STATUS_EMPTY; $meta['css'] = ''; } else { $file_created = false; $use_external_file = $this->use_external_file(); if ( $use_external_file && wp_is_writable( dirname( $this->path ) ) ) { $file_created = file_put_contents( $this->path, $this->css ); } if ( $file_created ) { $meta['status'] = self::CSS_STATUS_FILE; } else { $meta['status'] = self::CSS_STATUS_INLINE; $meta['css'] = $this->css; } } $this->update_meta( $meta ); } /** * Delete the CSS file. * * If the CSS file exist, delete it. * * @since 1.2.0 * @access public */ public function delete() { if ( file_exists( $this->path ) ) { unlink( $this->path ); } } /** * Enqueue CSS. * * Either enqueue the CSS file in Elementor or add inline style. * * This method is also responsible for loading the fonts. * * @since 1.2.0 * @access public */ public function enqueue() { $handle_id = $this->get_file_handle_id(); if ( isset( self::$printed[ $handle_id ] ) ) { return; } self::$printed[ $handle_id ] = true; $meta = $this->get_meta(); if ( self::CSS_STATUS_EMPTY === $meta['status'] ) { return; } // First time after clear cache and etc. if ( '' === $meta['status'] || $this->is_update_required() ) { $this->update(); $meta = $this->get_meta(); } if ( self::CSS_STATUS_INLINE === $meta['status'] ) { $dep = $this->get_inline_dependency(); // If the dependency has already been printed ( like a template in footer ) if ( wp_styles()->query( $dep, 'done' ) ) { printf( '<style id="%s">%s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok. } else { wp_add_inline_style( $dep , $meta['css'] ); } } elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update. wp_enqueue_style( $this->get_file_handle_id(), $this->url, $this->get_enqueue_dependencies(), $meta['time'] ); } // Handle fonts. if ( ! empty( $meta['fonts'] ) ) { foreach ( $meta['fonts'] as $font ) { Plugin::$instance->frontend->enqueue_font( $font ); } } $name = $this->get_name(); /** * Enqueue CSS file. * * Fires when CSS file is enqueued on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @todo Need to be hard deprecated using `do_action_deprecated()`. * * @since 1.9.0 * @deprecated 2.0.0 Use `elementor/css-file/{$name}/enqueue` action. * * @param CSS_File $this The current CSS file. */ do_action( "elementor/{$name}-css-file/enqueue", $this ); /** * Enqueue CSS file. * * Fires when CSS file is enqueued on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @since 2.0.0 * * @param CSS_File $this The current CSS file. */ do_action( "elementor/css-file/{$name}/enqueue", $this ); } /** * Add control rules. * * Parse the CSS for all the elements inside any given control. * * This method recursively renders the CSS for all the selectors in the control. * * @since 1.2.0 * @access public * * @param array $control The controls. * @param array $controls_stack The controls stack. * @param callable $value_callback Callback function for the value. * @param array $placeholders Placeholders. * @param array $replacements Replacements. */ public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements ) { $value = call_user_func( $value_callback, $control ); if ( null === $value || empty( $control['selectors'] ) ) { return; } foreach ( $control['selectors'] as $selector => $css_property ) { try { $output_css_property = preg_replace_callback( '/\{\{(?:([^.}]+)\.)?([^}]*)}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) { $parser_control = $control; $value_to_insert = $value; if ( ! empty( $matches[1] ) ) { if ( ! isset( $controls_stack[ $matches[1] ] ) ) { return ''; } $parser_control = $controls_stack[ $matches[1] ]; $value_to_insert = call_user_func( $value_callback, $parser_control ); } if ( Controls_Manager::FONT === $control['type'] ) { $this->fonts[] = $value_to_insert; } /** @var Base_Data_Control $control_obj */ $control_obj = Plugin::$instance->controls_manager->get_control( $parser_control['type'] ); $parsed_value = $control_obj->get_style_value( strtolower( $matches[2] ), $value_to_insert ); if ( '' === $parsed_value ) { throw new \Exception(); } return $parsed_value; }, $css_property ); } catch ( \Exception $e ) { return; } if ( ! $output_css_property ) { continue; } $device_pattern = '/^(?:\([^\)]+\)){1,2}/'; preg_match( $device_pattern, $selector, $device_rules ); $query = []; if ( $device_rules ) { $selector = preg_replace( $device_pattern, '', $selector ); preg_match_all( '/\(([^\)]+)\)/', $device_rules[0], $pure_device_rules ); $pure_device_rules = $pure_device_rules[1]; foreach ( $pure_device_rules as $device_rule ) { if ( Element_Base::RESPONSIVE_DESKTOP === $device_rule ) { continue; } $device = preg_replace( '/\+$/', '', $device_rule ); $endpoint = $device === $device_rule ? 'max' : 'min'; $query[ $endpoint ] = $device; } } $parsed_selector = str_replace( $placeholders, $replacements, $selector ); if ( ! $query && ! empty( $control['responsive'] ) ) { $query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) ); if ( ! empty( $query['max'] ) && Element_Base::RESPONSIVE_DESKTOP === $query['max'] ) { unset( $query['max'] ); } } $this->stylesheet_obj->add_rules( $parsed_selector, $output_css_property, $query ); } } /** * Get the fonts. * * Retrieve the list of fonts. * * @since 1.9.0 * @access public * * @return array Fonts. */ public function get_fonts() { return $this->fonts; } /** * Get CSS. * * Retrieve the CSS. If the CSS is empty, parse it again. * * @since 1.2.0 * @access public * * @return string The CSS. */ public function get_css() { if ( empty( $this->css ) ) { $this->parse_css(); } return $this->css; } /** * Get stylesheet. * * Retrieve the CSS file stylesheet instance. * * @since 1.2.0 * @access public * * @return Stylesheet The stylesheet object. */ public function get_stylesheet() { return $this->stylesheet_obj; } /** * Get meta data. * * Retrieve the CSS file meta data. Returns an array of all the data, or if * custom property is given it will return the property value, or `null` if * the property does not exist. * * @since 1.2.0 * @access public * * @param string $property Optional. Custom meta data property. Default is * null. * * @return array|null An array of all the data, or if custom property is * given it will return the property value, or `null` if * the property does not exist. */ public function get_meta( $property = null ) { $defaults = [ 'status' => '', 'time' => 0, ]; $meta = array_merge( $defaults, (array) $this->load_meta() ); if ( $property ) { return isset( $meta[ $property ] ) ? $meta[ $property ] : null; } return $meta; } /** * Add controls stack style rules. * * Parse the CSS for all the elements inside any given controls stack. * * This method recursively renders the CSS for all the child elements in the stack. * * @since 1.6.0 * @access public * * @param Controls_Stack $controls_stack The controls stack. * @param array $controls Controls array. * @param array $values Values array. * @param array $placeholders Placeholders. * @param array $replacements Replacements. */ public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements ) { foreach ( $controls as $control ) { if ( ! empty( $control['style_fields'] ) ) { foreach ( $values[ $control['name'] ] as $field_value ) { $this->add_controls_stack_style_rules( $controls_stack, $control['style_fields'], $field_value, array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] ), array_merge( $replacements, [ '.elementor-repeater-item-' . $field_value['_id'] ] ) ); } } if ( empty( $control['selectors'] ) ) { continue; } $this->add_control_style_rules( $control, $values, $controls_stack->get_controls(), $placeholders, $replacements ); } } /** * Load meta data. * * Retrieve the CSS file meta data. * * @since 1.2.0 * @access protected * @abstract */ abstract protected function load_meta(); /** * Update meta data. * * Update the global CSS file meta data. * * @since 1.2.0 * @access protected * @abstract * * @param array $meta New meta data. */ abstract protected function update_meta( $meta ); /** * Get file handle ID. * * Retrieve the file handle ID. * * @since 1.2.0 * @access protected * @abstract * * @return string CSS file handle ID. */ abstract protected function get_file_handle_id(); /** * Render CSS. * * Parse the CSS. * * @since 1.2.0 * @access protected * @abstract */ abstract protected function render_css(); /** * Get file name. * * Retrieve the name of the CSS file. * * @since 1.2.0 * @access protected * @abstract * * @return string File name. */ abstract protected function get_file_name(); /** * Get enqueue dependencies. * * Retrieve the name of the stylesheet used by `wp_enqueue_style()`. * * @since 1.2.0 * @access protected * * @return array Name of the stylesheet. */ protected function get_enqueue_dependencies() { return []; } /** * Get inline dependency. * * Retrieve the name of the stylesheet used by `wp_add_inline_style()`. * * @since 1.2.0 * @access protected * * @return string Name of the stylesheet. */ protected function get_inline_dependency() { return ''; } /** * Is update required. * * Whether the CSS requires an update. When there are new schemes or settings * updates. * * @since 1.2.0 * @access protected * * @return bool True if the CSS requires an update, False otherwise. */ protected function is_update_required() { return false; } /** * Parse CSS. * * Parsing the CSS file. * * @since 1.2.0 * @access protected */ protected function parse_css() { $this->render_css(); $name = $this->get_name(); /** * Parse CSS file. * * Fires when CSS file is parsed on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @todo Need to be hard deprecated using `do_action_deprecated()`. * * @since 1.2.0 * @deprecated 2.0.0 Use `elementor/css-file/{$name}/parse` action. * * @param CSS_File $this The current CSS file. */ do_action( "elementor/{$name}-css-file/parse", $this ); /** * Parse CSS file. * * Fires when CSS file is parsed on Elementor. * * The dynamic portion of the hook name, `$name`, refers to the CSS file name. * * @since 2.0.0 * * @param CSS_File $this The current CSS file. */ do_action( "elementor/css-file/{$name}/parse", $this ); $this->css = $this->stylesheet_obj->__toString(); } /** * Add control style rules. * * Register new style rules for the control. * * @since 1.6.0 * @access private * * @param array $control The control. * @param array $values Values array. * @param array $controls_stack The controls stack. * @param array $placeholders Placeholders. * @param array $replacements Replacements. */ private function add_control_style_rules( array $control, array $values, array $controls_stack, array $placeholders, array $replacements ) { $this->add_control_rules( $control, $controls_stack, function( $control ) use ( $values ) { return $this->get_style_control_value( $control, $values ); }, $placeholders, $replacements ); } /** * Get style control value. * * Retrieve the value of the style control for any give control and values. * * It will retrieve the control name and return the style value. * * @since 1.6.0 * @access private * * @param array $control The control. * @param array $values Values array. * * @return mixed Style control value. */ private function get_style_control_value( array $control, array $values ) { $value = $values[ $control['name'] ]; if ( isset( $control['selectors_dictionary'][ $value ] ) ) { $value = $control['selectors_dictionary'][ $value ]; } if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) { return null; } return $value; } /** * Init stylesheet. * * Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new * breakpoints for the stylesheet. * * @since 1.2.0 * @access private */ private function init_stylesheet() { $this->stylesheet_obj = new Stylesheet(); $breakpoints = Responsive::get_breakpoints(); $this->stylesheet_obj ->add_device( 'mobile', 0 ) ->add_device( 'tablet', $breakpoints['md'] ) ->add_device( 'desktop', $breakpoints['lg'] ); } /** * Set path and URL. * * Define the CSS file path and file URL. * * @since 1.2.0 * @access protected */ protected function set_path_and_url() { $wp_upload_dir = wp_upload_dir( null, false ); $relative_path = sprintf( self::FILE_NAME_PATTERN, self::FILE_BASE_DIR, $this->get_file_name() ); $this->path = $wp_upload_dir['basedir'] . $relative_path; $this->url = set_url_scheme( $wp_upload_dir['baseurl'] . $relative_path ); } }