File: /var/www/html/triad-infosec/wp-content/themes/Avada/includes/lib/inc/class-fusion-dynamic-css.php
<?php
/**
* Dynamic-CSS handler.
*
* @package Fusion-Library
* @since 1.0
*/
/**
* Handle generating the dynamic CSS.
*
* @since 1.0
*/
class Fusion_Dynamic_CSS {
/**
* The one, true instance of this object.
*
* @access protected
* @since 1.0
* @var null|object
*/
protected static $instance = null;
/**
* The mode we'll be using (file/inline).
*
* @access public
* @since 1.0
* @var string
*/
public $mode;
/**
* An object containing helper methods.
*
* @access protected
* @since 1.0
* @var null|object Fusion_Dynamic_CSS_Helpers
*/
protected static $helpers = null;
/**
* An instance of the Fusion_Dynamic_CSS_Inline class.
* null if we're not using inline mode.
*
* @access public
* @since 1.0
* @var null|object Fusion_Dynamic_CSS_Inline
*/
public $inline = null;
/**
* An instance of the Fusion_Dynamic_CSS_File class.
* null if we're not using file mode.
*
* @access protected
* @since 1.0
* @var null|object Fusion_Dynamic_CSS_File
*/
protected $file = null;
/**
* Needs update?
*
* @static
* @access public
* @since 1.0
* @var bool
*/
public static $needs_update = false;
/**
* Disable cache?
* Used in special cases, for example Avada is active but WPtouch plugin is used.
*
* @static
* @access protected
* @since 1.0
* @var bool
*/
protected static $disable_cache = null;
/**
* An array of extra files that we want to add in our CSS.
*
* @static
* @access private
* @since 1.0
* @var array
*/
private static $extra_files = [];
/**
* An array of CSS variables.
*
* @static
* @access private
* @since 1.0
* @var array
*/
private static $css_vars = [];
/**
* An array of css-variables that should NOT be replaced.
*
* @access protected
* @since 2.2
* @var array
*/
public static $preserve_vars = [
'--minFontSize',
'--minViewportSize',
'--multiplier',
'--viewportDiff',
'--diff',
'--base-font-size',
'--typography_sensitivity',
'--content_break_point',
'--typography_factor',
'--grid_main_break_point',
];
/**
* An array of replace patterns [search=>replace].
*
* @static
* @access private
* @since 2.0
* @var array
*/
private static $replace_patterns = [];
/**
* The final CSS.
*
* Used here as a static property to avoid multiple constly calls
* to the make_css() method.
*
* @static
* @access private
* @since 2.0
* @var string
*/
private static $final_css;
/**
* Constructor.
*
* @access protected
* @since 1.0
*/
protected function __construct() {
self::$helpers = $this->get_helpers();
add_action( 'wp_enqueue_scripts', [ $this, 'init' ], 110 );
add_filter( 'awb_should_generate_dynamic_css', [ $this, 'prevent_dynamic_css_genration' ] );
// When a post is saved, reset its caches to force-regenerate the CSS.
add_action( 'save_post', [ $this, 'reset_post_transient' ] );
add_action( 'save_post', [ $this, 'post_update_option' ] );
add_action( 'customize_save_after', [ $this, 'reset_all_caches' ] );
add_filter( 'fusion_dynamic_css', [ $this, 'add_extra_files' ] );
add_filter( 'fusion_dynamic_css', [ $this, 'icomoon_css' ] );
add_filter( 'fusion_dynamic_css_array', [ $this, 'add_css_vars_to_css' ], PHP_INT_MAX );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_extra_files' ], 11 );
}
/**
* Gets the instance of this object.
*
* @static
* @since 1.0
* @return object
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Add extra actions.
*
* @access public
* @since 1.0
* @return void
*/
public function init() {
if ( ! apply_filters( 'awb_should_generate_dynamic_css', true ) ) {
return;
}
// Add options.
$this->add_options();
// Set the $needs_update property.
$this->needs_update();
// Set the $disable_cache property.
$this->is_cache_disabled();
// Set mode.
$this->set_mode();
if ( 'file' === $this->get_mode() ) {
$this->file = new Fusion_Dynamic_CSS_File( $this );
return;
}
$this->inline = new Fusion_Dynamic_CSS_Inline( $this );
}
/**
* Make sure dynamic CSS is not generated in some scenarios.
*
* @access public
* @since 1.0
* @param bool $should_generate Should generate dynamic CSS or not.
* @return bool
*/
public function prevent_dynamic_css_genration( $should_generate ) {
// If builder frame or AJAX request, no need to run.
if ( function_exists( 'fusion_is_builder_frame' ) && fusion_is_builder_frame() || fusion_doing_ajax() ) {
return false;
}
// Do not generate on generation pages of WooCommerce PDF Product Vouchers plugin.
if ( 'wc_voucher' === get_post_type( get_the_ID() ) && isset( $_GET['voucher_key'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return false;
}
return $should_generate;
}
/**
* Determine if we're using file mode or inline mode.
*
* @access public
* @since 1.0
* @return void
*/
public function set_mode() {
$this->mode = 'inline';
$option = Fusion_Settings::get_option_name();
// Early exit if on the customizer.
// This will force-using inline mode.
global $wp_customize;
if ( $wp_customize || ( isset( $_GET['builder_id'] ) && get_transient( 'fusion_app_emulated-' . sanitize_text_field( wp_unslash( $_GET['builder_id'] ) . '-' . $option ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
// Make sure Avada is active if being used.
if ( $this->is_cache_disabled() ) {
$this->mode = 'inline';
return;
}
// Check if we're using file mode or inline mode.
// This simply checks the css_cache_method options.
if ( 'file' === fusion_library()->get_option( 'css_cache_method' ) ) {
$this->mode = 'file';
}
}
/**
* Gets the mode we're using.
*
* @access public
* @since 1.1.5
* @return string
*/
public function get_mode() {
if ( ! $this->mode || null === $this->mode ) {
$this->needs_update();
$this->set_mode();
}
return ( defined( 'FUSION_DISABLE_COMPILERS' ) && FUSION_DISABLE_COMPILERS ) ? 'inline' : $this->mode;
}
/**
* Creates the final CSS.
*
* @access public
* @since 2.0
* @return string The final CSS.
*/
public function generate_final_css() {
if ( self::$final_css ) {
return self::$final_css;
}
do_action( 'awb_generating_css' );
$helpers = self::$helpers;
self::$final_css = $helpers->get_dynamic_css();
self::$final_css = apply_filters( 'fusion_dynamic_css_cached', self::$final_css );
// Apply replace patterns.
$replace_patterns = self::get_replacement_patterns();
foreach ( $replace_patterns as $search => $replace ) {
self::$final_css = str_replace( $search, $replace, self::$final_css );
}
// When using domain-mapping plugins we have to make sure that any references to the original domain
// are replaced with references to the mapped domain.
// We're also stripping protocols from these domains so that there are no issues with SSL certificates.
if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
// The mapped domain of the site.
$mapped_domain = domain_mapping_siteurl( false );
$mapped_domain = str_replace( [ 'https://', 'http://' ], '//', $mapped_domain );
// The original domain of the site.
$original_domain = get_original_url( 'siteurl' );
$original_domain = str_replace( [ 'https://', 'http://' ], '//', $original_domain );
// Replace original domain with mapped domain.
self::$final_css = str_replace( $original_domain, $mapped_domain, self::$final_css );
}
}
// Strip protocols. This helps avoid any issues with https sites.
self::$final_css = str_replace( [ 'https://', 'http://' ], '//', self::$final_css );
self::$final_css = apply_filters( 'fusion_dynamic_css_final', self::$final_css );
// We're adding a warning at the top of the file to prevent users from editing it.
// The warning is then followed by the actual CSS content.
self::$final_css = '/********* Compiled CSS - Do not edit *********/ ' . self::$final_css;
// Security: strips style and script tags.
self::$final_css = preg_replace( '@<(script|style)[^>]*?>.*?@si', '', self::$final_css );
return self::$final_css;
}
/**
* This function takes care of creating the CSS.
*
* @access public
* @since 1.0
* @return string The final CSS.
*/
public function make_css() {
if ( ! self::$final_css || empty( self::$final_css ) ) {
$helpers = self::$helpers;
self::$final_css = $helpers->dynamic_css_cached();
}
return self::$final_css;
}
/**
* Reset ALL CSS transient caches.
*
* @access public
* @since 1.0
* @return void
*/
public function reset_all_transients() {
global $wpdb;
$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_fusion_dynamic_css_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
}
/**
* Reset the dynamic CSS transient for a post.
*
* @access public
* @since 1.0
* @param int $post_id The ID of the post that's being reset.
*/
public function reset_post_transient( $post_id ) {
delete_transient( 'fusion_dynamic_css_' . $post_id );
}
/**
* Create settings.
*
* @access private
*/
private function add_options() {
// The 'fusion_dynamic_css_posts' option will hold an array of posts that have had their css generated.
// We can use that to keep track of which pages need their CSS to be recreated and which don't.
add_option( 'fusion_dynamic_css_posts', [], '', 'yes' );
}
/**
* Update the fusion_dynamic_css_posts option when a post is saved.
* This adds the current post's ID in the array of IDs that the 'fusion_dynamic_css_posts' option has.
*
* @access public
* @since 1.0
* @param int $post_id The post ID.
* @return void
*/
public function post_update_option( $post_id ) {
$options = [
'fusion_dynamic_css_ids',
'fusion_dynamic_css_posts',
];
foreach ( $options as $option_name ) {
$option = get_option( $option_name, [] );
$option[ $post_id ] = false;
update_option( $option_name, $option );
}
}
/**
* Update the fusion_dynamic_css_posts option when the Global Options are saved.
* This basically empties the array of page IDs from the 'fusion_dynamic_css_posts' option.
*
* @access public
* @since 1.0
*/
public function global_reset_option() {
update_option( 'fusion_dynamic_css_posts', [] );
update_option( 'fusion_dynamic_css_ids', [] );
}
/**
* Do we need to update the CSS file?
*
* @access public
* @since 1.0
* @return bool
*/
public function needs_update() {
// Get the 'fusion_dynamic_css_posts' option from the DB.
$option = get_option( 'fusion_dynamic_css_posts', [] );
// Get the current page ID.
$c_page_id = fusion_library()->get_page_id();
$page_id = ( $c_page_id ) ? $c_page_id : 'global';
// If the current page ID exists in the array of pages defined in the 'fusion_dynamic_css_posts' option
// then the page has already been compiled and we don't need to re-compile it.
// If it's not in the array then it has not been compiled before so we need to update it.
if ( ! isset( $option[ $page_id ] ) || ! $option[ $page_id ] ) {
self::$needs_update = true;
}
return self::$needs_update;
}
/**
* There are special cases when cache should be disabled, for example: Avada is active but WPtouch plugin is used.
* In such case all CSS caching should be disabled (and mode set to "inline") in order not to cache CSS which is missing global styles.
* Here TextDomain is used, instead of theme Name, so cache is disabled even if a user renames theme.
*
* @access public
* @since 1.1
* @return bool
*/
public function is_cache_disabled() {
if ( null === self::$disable_cache ) {
$theme = wp_get_theme();
self::$disable_cache = false;
if ( 'Avada' === $theme->get( 'TextDomain' ) && ! class_exists( 'Avada' ) || ( function_exists( 'fusion_is_preview_frame' ) && fusion_is_preview_frame() ) ) {
self::$disable_cache = true;
}
self::$disable_cache = apply_filters( 'fusion_dynamic_cache_disabled', self::$disable_cache );
}
return self::$disable_cache;
}
/**
* This is just a facilitator that will allow us to reset everything.
* Its only job is calling the other methods from this class and reset parts of our caches.
*
* @access public
* @since 1.0
* @return void
*/
public function reset_all_caches() {
$this->reset_all_transients();
$this->global_reset_option();
}
/**
* Get an instance of the Fusion_Dynamic_CSS_Helpers object.
*
* @access public
* @since 1.0
* @return object Fusion_Dynamic_CSS_Helpers
*/
public function get_helpers() {
// Instantiate the Fusion_Dynamic_CSS_Helpers object.
if ( null === self::$helpers ) {
self::$helpers = new Fusion_Dynamic_CSS_Helpers();
}
return self::$helpers;
}
/**
* Makes adding files to the compiled CSS easier.
*
* @static
* @access public
* @since 5.1.0
* @param string $path The file path.
* @param string $url The file URL.
*/
public static function enqueue_style( $path, $url = '' ) {
if ( '' === $url ) {
self::$extra_files[] = $path;
return;
}
self::$extra_files[ $url ] = $path;
}
/**
* Enqueue extra files.
* This is used as a falback in case we can't get the contents of the CSS file.
*
* @access public
* @since 1.0.6
*/
public function enqueue_extra_files() {
if ( fusion_should_defer_styles_loading() && doing_action( 'wp_enqueue_scripts' ) ) {
add_action( 'wp_body_open', [ $this, 'enqueue_extra_files' ], 11 );
return;
}
global $fusion_library_latest_version;
// Get the extra files we need to enqueue.
$extra_assets = get_transient( 'fusion_dynamic_css_extra_files_to_enqueue' );
$extra_assets = ( ! is_array( $extra_assets ) ) ? [] : $extra_assets;
// No need to proceed if $extra_assets doesn't have anything.
if ( empty( $extra_assets ) ) {
return;
}
// If we got this far there are scripts to enqueue.
foreach ( $extra_assets as $url ) {
// Early exit if not a string.
if ( ! is_string( $url ) || is_numeric( $url ) ) {
continue;
}
// Make sure the URL is properly escaped.
$url = esc_url_raw( $url );
// The only thing we have available is the url,
// so we'll simply use md5() to create a unique handle for the script.
$handle = md5( $url );
// Enqueue the style.
wp_enqueue_style( $handle, $url, [], $fusion_library_latest_version );
}
}
/**
* Adds our extra files to the final CSS.
*
* @access public
* @since 1.0
* @param string $css The final CSS.
* @return string The final CSS after our extra files have been added.
*/
public function add_extra_files( $css ) {
$extra_files = array_unique( self::$extra_files );
$extra_assets = [];
$files_css = '';
foreach ( $extra_files as $url => $path ) {
// Get the file contents.
$file_contents = fusion_file_get_contents( $path );
if ( $file_contents ) {
$files_css .= $file_contents;
} else {
$extra_assets[] = $url;
}
}
if ( ! empty( $extra_assets ) ) {
set_transient( 'fusion_dynamic_css_extra_files_to_enqueue', $extra_assets );
}
return $files_css . $css;
}
/**
* Adds icomoon CSS.
*
* @access public
* @since 1.0.2
* @param string $css The original CSS.
* @return string The original CSS with the webfont @font-face declaration appended.
*/
public function icomoon_css( $css ) {
$font_url = FUSION_LIBRARY_URL . '/assets/fonts/icomoon';
$font_url = set_url_scheme( $font_url );
$font_face_display = fusion_library()->get_option( 'font_face_display' );
$font_face_display = ( 'swap-all' === $font_face_display ) ? 'swap' : 'block';
$css .= '@font-face {';
$css .= 'font-family: "awb-icons";';
$css .= 'src:';
$css .= "url('{$font_url}/awb-icons.woff') format('woff'),";
$css .= "url('{$font_url}/awb-icons.ttf') format('truetype'),";
$css .= "url('{$font_url}/awb-icons.svg#awb-icons') format('svg');";
$css .= 'font-weight: normal;';
$css .= 'font-style: normal;';
$css .= 'font-display: ' . $font_face_display . ';';
$css .= '}';
return $css;
}
/**
* Add a CSS Variable.
*
* @static
* @access public
* @since 2.0
* @param array $args The arguments ['name'=>'','value'=>'','element'=>''].
* @return void
*/
public static function add_css_var( $args ) {
// Don't add var if it's value is empty.
if ( isset( $args['value'] ) && '' === $args['value'] ) {
return;
}
if ( isset( $args['preserve'] ) && $args['preserve'] && isset( $args['name'] ) ) {
self::$preserve_vars[] = $args['name'];
}
self::$css_vars[ $args['name'] ] = $args;
}
/**
* Adds a replace pattern.
*
* @static
* @access public
* @since 2.0
* @param string $search What to search for.
* @param string $replace What to replace the search string with.
* @return void
*/
public static function add_replace_pattern( $search, $replace ) {
self::$replace_patterns[ $search ] = $replace;
}
/**
* Get an array of replace patterns.
*
* @static
* @access public
* @since 2.0
* @return array
*/
public static function get_replacement_patterns() {
uksort(
self::$replace_patterns,
function( $a, $b ) {
return strlen( $b ) - strlen( $a );
}
);
return self::$replace_patterns;
}
/**
* Add css-vars to the final CSS.
*
* @access public
* @since 2.0
* @param string $css The CSS.
* @return string
*/
public function add_css_vars_to_css( $css ) {
$vars_styles = '';
foreach ( self::$css_vars as $key => $args ) {
if ( is_string( $key ) && ! is_array( $args['value'] ) ) {
$css['global'][ $args['element'] ][ $key ] = $args['value'];
}
}
return $css;
}
}
/* Omit closing PHP tag to avoid "Headers already sent" issues. */