Server IP : 172.67.214.6 / Your IP : 216.73.216.73 Web Server : LiteSpeed System : Linux premium900.web-hosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : redwjova ( 1790) PHP Version : 8.1.32 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/redwjova/fyntric.com/wp-content/themes/smart-mag/inc/core/customizer/ |
Upload File : |
<?php /** * Render the CSS customizations using Bunyad Framework options * * @uses Bunyad::options() */ class Bunyad_Customizer_Css_Generator { public $elements; public $css_options; public $css = []; public $google_fonts = []; public $groups = []; public $default_fonts = [ 'primary' => '', 'secondary' => '' ]; /** * Stores options relevant to parsing the current CSS * * @var array */ public $args = []; /** * @var array Internal state of previous selectors */ protected $prev_selectors = []; protected $device_keys = [ 'global', 'main', 'x-large', 'large', 'medium', 'small' ]; /** * Media querys key map */ protected $query_map = [ 'root' => false, 'global' => false, 'main' => '@media (min-width: 1200px)', 'x-large' => '@media (min-width: 1201px) and (max-width: 1439px)', 'large' => '@media (min-width: 941px) and (max-width: 1200px)', 'medium' => '@media (min-width: 768px) and (max-width: 940px)', 'small' => '@media (max-width: 767px)', ]; public function __construct() { $this->css = ['root' => []]; } /** * Initialize and get all the Custom CSS options */ public function init() { // Do a full options load if a short load was done. if (Bunyad::options()->is_short_load()) { Bunyad::options()->reinit($short = false); } $this->elements = Bunyad::options()->defaults; $this->css_options = Bunyad::options()->get_all(); } /** * Process the main CSS changes and construct the basic CSS * for colors, typography etc. */ public function process() { // Render all of the custom CSS for elements. foreach ($this->get_css_elements() as $data) { list($element, $value) = $data; $this->process_element($element, $value); } } /** * Get all valid elements with CSS. * * @return array */ public function get_css_elements() { $valid_elements = []; if (!$this->css_options) { return []; } // Loop through options elements, instead of saved, to keep the order. foreach ($this->elements as $key => $element) { // Skip if doesn't have a saved / changed value. if (!array_key_exists($key, $this->css_options)) { continue; } // Not an element with CSS or missing default value. if (!isset($element['css']) || !isset($element['value'])) { continue; } $value = $this->css_options[$key]; // Note: Converting to string as comparison below of 0 == 'any' (unless start with number). // Can't use strict for legacy reasons. Value can be: array, string, integer, float. if (is_numeric($element['value'])) { $element['value'] = (string) $element['value']; } /** * Skip default values. Some legacy limitations. * * 1. For value 0, skip only if default is 0 too. * 2. Everything else is loose comparison. */ if ($value === '0') { // Already converted to string above. if ($element['value'] === '0') { continue; } } else if (empty($value) || $value == $element['value']) { continue; } $valid_elements[] = [$element, $value]; } return apply_filters('bunyad_css_generator_valid_elements', $valid_elements); } /** * Process an element and add CSS etc. * * @param array $element * @param mixed $value */ public function process_element($element, $value) { $element['type'] = isset($element['type']) ? $element['type'] : null; /** * Font-family */ if ($element['type'] == 'font-family') { $font_data = [ 'family' => $value, 'weights' => [400, 500, 600, 700] ]; $fallbacks = [ 'sans-serif' => 'system-ui, -apple-system, "Segoe UI", Arial, sans-serif', 'serif' => 'Georgia, serif' ]; $system_fonts = [ 'sans-serif' => $fallbacks['sans-serif'], 'serif' => $fallbacks['serif'], 'helvetica' => 'Helvetica, ' . $fallbacks['sans-serif'], 'georgia' => $fallbacks['serif'], 'monospace' => 'Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace', ]; // @todo fix: this filter will not apply on live preview in the customizer. $references = apply_filters('bunyad_customizer_font_aliases', []); $font_family = $value; $fallback = stripos($font_family, 'serif') !== false ? 'serif' : 'sans-serif'; $fallback = !empty($element['fallback_stack']) ? $element['fallback_stack'] : $fallback; $is_system_font = isset($system_fonts[ $font_family ]); $reference = $references[ $font_family ] ?? null; // Fallbacks specified in the value? if (strstr($font_family, ',')) { // First is the font-family $families = explode(',', $font_family); $font_family = array_shift($families); // Remaining are fallback $fallback = implode(',', $families); } else { // Pointing to presets? if (isset($fallbacks[ $fallback ])) { $fallback = $fallbacks[ $fallback ]; } } if ($is_system_font) { $value = $system_fonts[ $font_family ]; } else if ($reference) { $font_family = Bunyad::options()->get($reference['ref']) ?: $this->default_fonts[substr($font_family, 1)]; $value = $reference['css']; } else { // Value with fallbacks $value = sprintf( '"%s", %s', esc_attr($font_family), $fallback ); } /** * A group? Handle weight and style for Google Fonts. */ if (isset($element['group'])) { $typography = $this->get_grouped($element['group']); if (!empty($typography['weight'])) { $font_data['weights'] = [ $typography['weight'] ]; } if (!empty($typography['style']) && $typography['style'] == 'italic') { foreach ($font_data['weights'] as $weight) { array_push($font_data['weights'], $weight . 'i'); } } } /** * Register Google fonts. */ if (!$is_system_font) { $font_data['family'] = $font_family; $weights = $font_data['weights']; if (!isset($this->google_fonts[$font_family])) { $this->google_fonts[$font_family] = []; } $this->google_fonts[$font_family] = array_unique(array_merge( $this->google_fonts[$font_family], $weights )); } } /** * Dimensions */ if ($element['type'] == 'dimensions') { foreach ($element['css'] as $selector => $data) { if (!isset($data['dimensions'])) { continue; } $data['props'] = isset($data['props']) ? $data['props'] : []; // Prefix the dimensions $keys = ['top', 'bottom', 'left', 'right']; foreach ($keys as $key) { $callback = function($value) use ($key) { if (!is_array($value) || !isset($value[$key])) { return false; } $value['unit'] = !empty($value['unit']) ? $value['unit'] : 'px'; return $value[$key] . $value['unit']; }; // Alternatively instead of callback, we could use: "{value:{$key}}{value:unit}" // A closure for validation of unit. $data['props'][ $data['dimensions'] . '-' . $key ] = $callback; } unset($data['dimensions']); // Propagate to original element array $element['css'][$selector] = $data; } } /** * Backgrounds / Image */ if ($element['type'] == 'upload' && !empty($element['bg_type'])) { $bg_type = $element['bg_type']['value']; $props = []; if ($bg_type == 'cover' || $bg_type == 'cover-nonfixed') { $props = [ 'background-repeat' => 'no-repeat', 'background-position' => 'center center', 'background-size' => 'cover', ]; if ($bg_type == 'cover') { $props['background-attachment'] = 'fixed'; } } else { $props = ['background-repeat' => esc_attr($bg_type)]; } foreach ($element['css'] as $selector => $data) { // Original props are not overriden, only missing added. $element['css'][$selector]['props'] = array_replace($props, $data['props']); } } return $this->process_element_css($element, $value); } /** * Process an element to register CSS. * * @param array $element * @param mixed $value */ public function process_element_css($element, $value) { if (!is_array($element['css'])) { return; } $merge_previous = false; // Selectors of current element $cur_selectors = array_diff( array_keys($element['css']), ['vars'] ); // Using same selectors as previous element if ( count($this->prev_selectors) == count($cur_selectors) && !array_diff($this->prev_selectors, $cur_selectors) ) { $merge_previous = true; } $this->prev_selectors = $cur_selectors; /** * Further process values for correct data. */ if (!empty($element['devices'])) { $values = (array) $value; $limit = isset($values['limit']) ? $values['limit'] : ''; // Device keys are expected. If they're missing, assume a different value. if (!array_intersect(array_keys($values), $this->device_keys)) { $values = ['global' => $value]; } // Consider desktop MQ applicable to global unless limit is set. if (!$limit && isset($values['main'])) { // + to add at beginning $values = ['global' => $values['main']] + $values; unset($values['main']); } } else { // Simple values apply to either provided media or globally. Also fix legacy or corrupt value. $values = [ 'global' => $value ]; } $devices = array_intersect( array_keys($values), $this->device_keys ); /** * Add CSS for each registered selector for this element. */ $element_css = []; foreach ($element['css'] as $selector => $data) { $props = []; $raw_props = []; $media_queries = $devices; // Data can be in two formats, either direct props or a combination of nested // props within media keys. if (!isset($data['props'])) { // $first = is_array($data) ? reset($data) : []; // Legacy or missing props. // if (!isset($first['props'])) { // continue; // } $raw_props = $data; // Consider all the media keys as some maybe forced and remove 'all'. $media_queries = array_diff( array_merge($media_queries, array_keys($raw_props)), ['all'] ); $media_queries = array_unique($media_queries); // When 'limit' is applied, 'main' is expected to be set. If missing, // fallback to 'global'. if (in_array('main', $media_queries) && !isset($raw_props['main'])) { if (isset($raw_props['global'])) { $raw_props['main'] = $raw_props['global']; } } } else { $raw_props = ['all' => $data]; } foreach ($media_queries as $media) { // Either all or the specific media props need to be defined. if (!isset($raw_props['all']) && !isset($raw_props[$media])) { continue; } // Use props specific to this media or generic 'all'. $the_props = isset($raw_props[$media]) ? $raw_props[$media] : $raw_props['all']; $value_key = $media; // Empty or missing props imply this media query should be ignored. if (empty($the_props['props'])) { continue; } if (isset($the_props['value_key'])) { $value_key = $the_props['value_key']; // Fallback to global when main is missing. if ($value_key === 'main' && !isset($values['main'])) { $value_key = 'global'; } } $the_value = isset($values[$value_key]) ? $values[$value_key] : null; // If a value doesn't exist, skip processing unless forced. if (is_null($the_value)) { if (empty($the_props['force'])) { continue; } } // Add the processed props with values. $props[$media] = $this->process_props($the_props['props'], $the_value); } $element_css[$selector] = $props; } if ($element_css) { return $this->add_element_css($element_css, $merge_previous); } return false; } /** * Process properties, interopolate and convert to strings. * * @return array Array of property strings */ public function process_props($props, $value) { if (isset($props['condition'])) { foreach ($props['condition'] as $expected => $the_props) { if ($value == $expected) { $props = array_merge($props, $the_props); } } unset($props['condition']); } $prop_strings = []; foreach ($props as $prop => $format) { // Some custom CSS here. if ($prop === 'custom') { $prop_strings[] = $this->do_replacements($format, $value); continue; } // Create property value - skip on empty or when it's just units. $prop_value = $this->create_prop_value($format, $value); if (!$prop_value) { continue; } $prop_strings[] = $prop . ': ' . $prop_value; } return $prop_strings; } /** * Add an elements CSS to processing stack. * * @param array $css An assoc array of CSS selectors and props. * @param boolean $merge_previous Whether to merge with previous element CSS. */ public function add_element_css(array $css, $merge_previous = false) { if (isset($css['vars'])) { // Global values - apply without media query at :root. if (!empty($css['vars']['global'])) { $css_vars = !empty($css['vars']['global']) ? $css['vars']['global'] : $css['vars']['main']; // Add the global CSS vars for :root $this->css['root'] = array_merge( (array) $this->css['root'], $css_vars ); } // Presence of 'main' here instead of global implies a "limit to desktop". Use :root selector. else if (!empty($css['vars']['main'])) { $css[':root'] = ['main' => $css['vars']['main']]; // IE11 hack for css-vars-ponyfills. $css['._ie-mq'] = ['global' => $css['vars']['main']]; } unset($css['vars']); // If no other selectors for this element, return. if (empty($css)) { return; } } if ($merge_previous) { $previous = end($this->css); $css = array_merge_recursive($previous, $css); // Replace previous $this->css[ key($this->css) ] = $css; } else { $this->css[] = $css; } return $css; } /** * Get modified elements of a group (defaults won't be included). * * @param string $group ID of the group. * @return array */ public function get_grouped($group = '') { if (empty($this->groups)) { // Process the groups // @todo Further optimize performance. Benchmark lazy-process. foreach ($this->elements as $option) { if ($option['type'] === 'group') { $this->groups[ $option['name'] ] = []; } if (!empty($option['group']) && isset($this->css_options[ $option['name'] ])) { $current = $this->css_options[ $option['name'] ]; // Perhaps a group itself or other pseudo-element. if (!isset($option['value'])) { continue; } // Add only if current value is not same as default value for this option if ($current !== $option['value'] && isset($this->elements[ $option['group'] ])) { // Remove group prefix from names $group_prefix = $this->elements[ $option['group'] ]['name']; $name = preg_replace('/^' . preg_quote($group_prefix) . '_/', '', $option['name']); $this->groups[ $option['group'] ][ $name ] = $current; } } } } if ($group) { return isset($this->groups[ $group ]) ? $this->groups[ $group ] : []; } return $this->groups; } /** * Process the final CSS */ public function render_css() { // Empty array filled query map to preserve order of MQs. $css = array_map('__return_empty_array', $this->query_map); $css_elements = $this->css; // CSS Variables at root. if (!empty($css_elements['root'])) { $css['root'] = $css_elements['root']; unset($css_elements['root']); } // Create rules and add to appropriate media. foreach ($css_elements as $element) { foreach ($element as $selector => $data) { foreach ($data as $media => $props) { if (!$props) { continue; } $rule = sprintf( "%s { %s }", $selector, join(' ', $props) ); $css[ $media ][] = $rule; } } } $css = array_filter($css); $final_css = join("\n", $this->_render_css_media($css)); return $final_css; } /** * Render rules for each media block */ public function _render_css_media($css) { $final_css = []; foreach ($css as $media => $rules) { if ($media == 'global') { $final_css = array_merge($final_css, $rules); } else if ($media == 'root') { $final_css[] = sprintf(':root { %s }', join("\n", $rules)); } else { // Use from map or a custom defined MQ $media = isset($this->query_map[$media]) ? $this->query_map[$media] : $media; // Final rule $final_css[] = sprintf( "%s { %s }", esc_attr($media), join("\n", $rules) ); } } return $final_css; } /** * Do replacements * * @param string|callable $format * @param mixed $value * @return string */ public function do_replacements($format, $value) { // Format's a callback? Resolve. if (is_callable($format)) { $format = call_user_func($format, $value); } if (!is_string($format)) { return false; } // Some dynamic values to be used in formats below, done early. if ($value === '--c-main') { $value = "var({$value})"; } /** * Get value off another element and replace - Support formats: * * {css_my_key} - Normal option key. * {value:value_key} - A key based off the current value. */ preg_match_all('/{([a-z0-9\_\-\:]+?)}/i', $format, $matches); foreach ($matches[1] as $key => $match) { $replacement = $this->_interpolate($match, $value); // Skip this rule altogether if a replacement is missing. // Disabled: Some replacements are empty values (0, '0') which may still have to be replaced. // if (!$replacement) { // return false; // } // Replace $format = str_replace($matches[0][$key], $replacement, $format); } // Replacements expected but no value? if (preg_match('#%(s|d)#', $format) && (!$value && $value !== '0')) { return false; } // Escape % to allow 100% etc. $format = preg_replace('#%(?!s|d)#', '%%', $format); // The replacements. $format = sprintf($format, $value); /** * RGBA color? Supported formats: * * rgba(%s, 0.6) - indirectly, substituted above * rgba(#000, %s) * rgba({css_key}, %s) */ if (preg_match('/rgba\(([^,]+?),([^,]+?)\)/', $format, $match)) { if (!$match[1]) { $match[1] = $value; } $rgb = $this->hex2rgb($match[1]); $color = $rgb['red'] . ',' . $rgb['green'] . ',' . $rgb['blue']; // add in the rgb color part in rgba $format = str_replace($match[1], $color, $format); } // For format: hexToRgb(#fff) if (preg_match('/hexToRgb\((#[a-z0-9]{3,7})\)/i', $format, $match)) { $rgb = $this->hex2rgb($match[1]); $color = $rgb['red'] . ',' . $rgb['green'] . ',' . $rgb['blue']; // add in the rgb color part in rgba $format = str_replace($match[0], $color, $format); } return $format; } /** * Create inner CSS rules based on provided format and value * * @param string|callable $format * @param mixed $value * @return string */ public function create_prop_value($format, $value) { // Add in the current element value $css = $this->do_replacements($format, $value); if (!$css) { return ''; } return $css . ';'; } /** * Helper Callback: String interopolation * * @param mixed $match Matched interpolationg string. * @param mixed $value Current value to replace references with. * * @return mixed */ private function _interpolate($match, $value = '') { // Use a key from current value //$matched = $match[1]; $matched = $match; if (strpos($matched, 'value:') !== false) { list($_, $key) = explode(':', $matched); return isset($value[$key]) ? $value[$key] : false; } // Default values are not necessarily available in $this->css_options. // return $this->css_options[ $matched ]; return Bunyad::options()->get($matched); } /** * Convet hex to rgb * * @param string $color */ public function hex2rgb($color) { if ($color[0] == '#') { $color = substr($color, 1); } // convert 3 to 6 char hex if (strlen($color) == 3) { $color = str_repeat($color[0], 2) . str_repeat($color[1], 2) . str_repeat($color[2], 2); } return array( 'red' => hexdec($color[0] . $color[1]), 'green' => hexdec($color[2] . $color[3]), 'blue' => hexdec($color[4] . $color[5]) ); } /** * Get output without the cache part * * @see get_transient() * @see set_transient() * @see self::process() * * @param string $key google_fonts or output * @return mixed */ public function get_processed($key = '') { $anchor = (!empty($this->args['anchor_obj']) ? '_' . $this->args['anchor_obj'] : 0); $in_cache = false; // Default $data = array( 'google_fonts' => array(), 'output' => '' ); // Only use cache if not on preview and not disabled if (!is_customize_preview() && apply_filters('bunyad_custom_css_nocache', false) !== true) { // Have data in cache? $cache = get_transient('bunyad_custom_css_cache'); if (is_array($cache) && !empty($cache[$anchor])) { $in_cache = true; $data = $cache[$anchor]; } // Key not in cache, bugged? Invalidate if ($key && !isset($data[$key])) { $in_cache = false; } } // Process if not cached if (!$in_cache) { /** * Process to create CSS and enqueues */ $this->init(); $this->process(); $output = $this->render_css() . "\n\n" . (!empty($this->css_options['css_custom']) ? wp_specialchars_decode($this->css_options['css_custom']) : ''); $output = apply_filters('bunyad_custom_css_processed', $output); // Remove excessive tabs $output = str_replace("\t", '', $output); $data = array( 'google_fonts' => (array) $this->google_fonts, 'output' => $output ); // Cache it only if not in customizer preview/changeset. if (!is_customize_preview()) { set_transient('bunyad_custom_css_cache', array($anchor => $data)); } } return ($key ? $data[$key] : $data); } /** * Add google fonts to the top of CSS */ public function get_google_fonts_url() { $fonts = $this->get_processed('google_fonts'); if (!$fonts) { return false; } $families = []; foreach ($fonts as $font => $weights) { $families[] = $font . ':' . join(',', $weights); } $args = array( 'family' => implode('|', $families) ); if (Bunyad::options()->font_charset) { $args['subset'] = implode(',', array_filter(Bunyad::options()->font_charset)); } if (Bunyad::options()->font_display) { $args['display'] = Bunyad::options()->font_display; } return add_query_arg( urlencode_deep($args), 'https://fonts.googleapis.com/css' ); } /** * Render and return the output * * @return string */ public function render() { return $this->get_processed('output'); } }