/
home
/
assocoweys
/
khata
/
components
/
com_sppagebuilder
/
parser
/
Upload File
HOME
<?php /** * @package SP Page Builder * @author JoomShaper http://www.joomshaper.com * @copyright Copyright (c) 2010 - 2023 JoomShaper * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later */ use Joomla\CMS\Factory; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Uri\Uri; /** No direct access. */ defined('_JEXEC') or die('Restricted access'); /** * Migrate the CSS method used in the site.php file. * * @since 4.0.0 */ class CSSHelper extends HelperBase { /** * The default device * * @var string * @since 4.0.0 */ private static $device; /** * The font registry array. Registering all the fonts here. * * @var array * @since 4.0.0 */ private $fontRegistry = []; /** * The constructor function of the CSS Migrator. * * @param string $id The addon ID selector. * @param bool $force Flag to set the ID as whatever it is provided, no sanitization is required. * * @since 4.0.0 */ public function __construct(string $id, bool $force = false) { parent::__construct($id, $force); self::$device = SpPgaeBuilderBase::$defaultDevice; } private function getFallbackValue($settings, $prop) { $keys = explode('.', $prop, 2); if (empty($keys)) return null; if (count($keys) > 1) { list($key1, $key2) = $keys; $originalKey1 = $key1 . '_original'; $originalKey2 = $key2 . '_original'; if (isset($settings->$originalKey1)) { $key1 = $originalKey1; } if (isset($settings->$originalKey2)) { $key2 = $originalKey2; } return isset($settings->$key1->$key2) ? $settings->$key1->$key2 : null; } $key1 = $keys[0]; $originalKey = $key1 . '_original'; if (isset($settings->$originalKey)) { $key1 = $originalKey; } return isset($settings->$key1) ? $settings->$key1 : null; } /** * Register a font name to the font registry. * * @param string $fontName The font name which will be stored to the registry. * * @return void * @since 4.0.0 */ private function registerFont(string $fontName) { if (!\in_array($fontName, $this->fontRegistry)) { $this->fontRegistry[] = $fontName; } } /** * Get the font registry array. * * @return array * @since 4.0.0 */ private function getFontRegistry(): array { return $this->fontRegistry; } /** * Load the google font for the typography fields. * * @return void * @since 4.0.0 */ public function loadGoogleFont(string $fontName) { $systemFonts = array( 'Arial', 'Tahoma', 'Verdana', 'Helvetica', 'Times New Roman', 'Trebuchet MS', 'Georgia' ); if (!empty($fontName)) { if (!\in_array($fontName, $systemFonts)) { $fontPath = '/media/com_sppagebuilder/assets/google-fonts/' . $fontName . '/stylesheet.css'; $params = ComponentHelper::getParams('com_sppagebuilder'); $disableGoogleFonts = (bool) $params->get('disable_google_fonts', 0); if ($disableGoogleFonts) { return; } if (file_exists(JPATH_ROOT . $fontPath)) { Factory::getDocument()->addStylesheet(Uri::root(true) . $fontPath); } else { $linkTagUrl = "https://fonts.googleapis.com/css?family=" . $fontName . ":100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic&display=swap"; Factory::getDocument()->addStylesheet($linkTagUrl); } } } } public function loadLocalFont(string $fontName) { $fontPath = '/media/com_sppagebuilder/assets/custom-fonts/' . $fontName . '/stylesheet.css'; if (file_exists(JPATH_ROOT . $fontPath)) { Factory::getDocument()->addStylesheet(Uri::root(true) . $fontPath); } } private function purifyTypographyObject($obj) { $device = SpPgaeBuilderBase::$defaultDevice; $obj = $obj ?? ''; if (!\is_object($obj)) { if (preg_match("@[^-|\d+]@", $obj)) { $obj = \preg_replace("@\D+$@", '', $obj); } $newObject = AddonHelper::initDeviceObject(); foreach ($newObject as $k => &$v) { // Todo: need to replace unit value "px" with dynamic $obj unit value. $v = (object) ['value' => $k === $device ? $obj : '', 'unit' => 'px']; } unset($v); return $newObject; } else { foreach ($obj as $k => &$v) { if (!\is_object($v)) { $v = (object) ['value' => $v, 'unit' => 'px']; } } unset($v); } return $obj; } /** * Typography style generation by using the settings and fallback array. * * @param string $selector The CSS selector. * @param object $settings The settings object. * @param string $prop The settings prop or the typography field name. * @param array $fallback The fallback array. * * @return string * @since 4.0.0 */ public function typography($selector, $settings, $prop, $fallback = []) { $hasFallback = !empty($fallback) && !isset($settings->$prop); if ($hasFallback) { $font = array_key_exists('font', $fallback) ? $fallback['font'] : ""; $size = array_key_exists('size', $fallback) ? $fallback['size'] : ""; $lineHeight = array_key_exists('line_height', $fallback) ? $fallback['line_height'] : ""; $letterSpacing = array_key_exists('letter_spacing', $fallback) ? $fallback['letter_spacing'] : ""; $uppercase = array_key_exists('uppercase', $fallback) ? $fallback['uppercase'] : ""; $italic = array_key_exists('italic', $fallback) ? $fallback['italic'] : ""; $underline = array_key_exists('underline', $fallback) ? $fallback['underline'] : ""; $weight = array_key_exists('weight', $fallback) ? $fallback['weight'] : ""; $settings->$prop = new \stdClass; $settings->$prop->font = $this->getFallbackValue($settings, $font); $settings->$prop->weight = $this->getFallbackValue($settings, $weight); $settings->$prop->uppercase = $this->getFallbackValue($settings, $uppercase); $settings->$prop->italic = $this->getFallbackValue($settings, $italic); $settings->$prop->underline = $this->getFallbackValue($settings, $underline); $settings->$prop->size = $this->getFallbackValue($settings, $size); $settings->$prop->line_height = $this->getFallbackValue($settings, $lineHeight); $settings->$prop->letter_spacing = $this->getFallbackValue($settings, $letterSpacing); } if (!isset($settings->$prop)) return ''; $typography = $settings->$prop; $objectKeys = ["letter_spacing", "line_height", "size"]; foreach ($objectKeys as $key) { if (isset($typography->$key)) { $typography->$key = $this->purifyTypographyObject($typography->$key); } } if (!empty($typography->weight) && \preg_match("@[^\d]+@", $typography->weight)) { $typography->weight = (int) $typography->weight; } /** If font exists to the typography field then register the font and load it. */ if (!empty($typography->font)) { $this->registerFont($typography->font); if (isset($typography->type) && $typography->type === 'local') { $this->loadLocalFont($typography->font); } else { $this->loadGoogleFont($typography->font); } } $props = [ 'font' => isset($typography->font) && !empty($typography->font) ? 'font-family' : null, 'weight' => isset($typography->weight) && !empty($typography->weight) ? 'font-weight' : null, 'uppercase' => isset($typography->uppercase) && !empty($typography->uppercase) ? 'text-transform' : null, 'italic' => isset($typography->italic) && !empty($typography->italic) ? 'font-style' : null, 'underline' => isset($typography->underline) && !empty($typography->underline) ? 'text-decoration' : null, 'size' => isset($typography->size) && !empty($typography->size) ? 'font-size' : null, 'line_height' => isset($typography->line_height) && !empty($typography->line_height) ? 'line-height' : null, 'letter_spacing' => isset($typography->letter_spacing) && !empty($typography->letter_spacing) ? 'letter-spacing' : null ]; $units = ['font' => false, 'weight' => false, 'size' => false, 'uppercase' => false, 'italic' => false, 'underline' => false]; $modifiers = []; $defaults = ['uppercase' => 'uppercase', 'italic' => 'italic', 'underline' => 'underline']; $typographyStyle = $this->generateStyle($selector, $typography, $props, $units, $modifiers, $defaults); return $typographyStyle; } /** * Parse the color and generate a valid color property value. * * @param mixed $color Object or string value of the color. * * @return string Returns the color value string. * @since 4.0.0 */ public static function parseColor($settings, $prop): string { $originalProp = $prop . '_original'; if (isset($settings->$originalProp)) { $prop = $originalProp; } $color = isset($settings->$prop) ? $settings->$prop : ''; if (\is_string($color)) { return $color; } if (\is_object($color)) { if (isset($color->type)) { $color1 = "#398AF1"; $color2 = "#5EDCED"; if (!empty($color->color)) { $color1 = !empty(preg_replace("@\s+@", "", $color->color)) ? $color->color : '#398AF1'; } if (!empty($color->color2)) { $color2 = !empty(preg_replace("@\s+@", "", $color->color2)) ? $color->color2 : '#5EDCED'; } switch ($color->type) { case 'solid': return preg_replace("@\s+@", "", $color->color); case 'linear': $start = $color->pos ?? 0; $end = $color->pos2 ?? 100; return 'linear-gradient(' . ($color->deg ?? 0) . 'deg, ' . $color1 . ' ' . $start . '%, ' . $color2 . ' ' . $end . '%)'; case 'radial': $start = $color->pos ?? 0; $end = $color->pos2 ?? 100; $position = $color->radialPos ?? 'center center'; return 'radial-gradient(at ' . $position . ', ' . $color1 . ' ' . $start . '%, ' . $color2 . ' ' . $end . '%)'; default: return $color->color ?? ''; } } } return ''; } /** * Parse the alignment prop and generate the alignment value. * * @param object $settings The settings object. * @param string $prop The alignment prop name. * @param bool $flex If the alignment for the flex or not. * * @return string The alignment value. * @since 4.0.0 */ public static function parseAlignment($settings, $prop, $flex = false) { $primitiveProp = $prop; $originalProp = $prop . '_original'; $prop = isset($settings->$originalProp) ? $originalProp : $prop; if (!isset($settings->$prop)) { return ''; } $alignmentMap = [ 'sppb-text-center' => 'center', 'sppb-text-left' => 'left', 'sppb-text-right' => 'right', ]; $flexMap = [ 'center' => 'center', 'left' => 'flex-start', 'right' => 'flex-end' ]; $alignment = $settings->$prop; if (self::hasMultiDeviceSettings($alignment)) { if ($flex) { foreach ($alignment as &$value) { $value = isset($flexMap[$value]) ? $flexMap[$value] : $value; } unset($value); } else { foreach ($alignment as &$value) { $value = isset($alignmentMap[$value]) ? $alignmentMap[$value] : $value; } unset($value); } return $alignment; } $align = isset($alignmentMap[$alignment]) ? $alignmentMap[$alignment] : $alignment; return $flex && isset($flexMap[$align]) ? $flexMap[$align] : $align; } private static function isEnabledBoxShadow($shadow) { if (empty($shadow)) { return false; } if (\is_string($shadow)) { $shadow = preg_replace("@\s+@", ' ', $shadow); $shadow = explode(' ', $shadow); $shadow = (object) $shadow; } $numberKeys = ['ho', 'vo', 'blur', 'spread']; if (\is_object($shadow)) { if (isset($shadow->enabled)) { return $shadow->enabled; } foreach ($shadow as $key => &$value) { if (\in_array($key, $numberKeys)) { $value = (float) $value; if (!empty($value)) { return true; } } if ($key === 'color') { $value = strtolower($value); return !($value === '#fff' || $value === '#ffffff'); } } unset($value); } return false; } /** * Parse the box shadow values and generate the box-shadow CSS property. * * @param object $settings The addon settings. * @param string $prop The box-shadow settings key inside the $settings object. * @param boolean $isTextShadow Data comes from text-shadow or box shadow. * * @return string The generated box-shadow. * @since 4.0.0 */ public static function parseBoxShadow($settings, string $prop, $isTextShadow = false): string { if (!isset($settings->$prop)) { return ''; } $box = $settings->$prop; if (\is_object($box)) { $horizontalOffset = !empty($box->ho) ? $box->ho . 'px' : '0px'; $verticalOffset = !empty($box->vo) ? $box->vo . 'px' : '0px'; $blur = !empty($box->blur) ? $box->blur . 'px' : '0px'; $spread = (!$isTextShadow) ? (!empty($box->spread) ? $box->spread . 'px' : '0px') : ' '; $color = !empty($box->color) ? $box->color : ''; return self::isEnabledBoxShadow($box) ? $horizontalOffset . " " . $verticalOffset . " " . $blur . " " . $spread . " " . $color : ''; } if ($box === '0 0 0 0 #fff' || $box === '0 0 0 #ffffff' || $box === '0 0 0 transparent' || $box === '0 0 0 0 transparent') { return ''; } return $box; } /** * Check if the value has a multi-device settings. * * @param mixed $value The settings value. * * @return boolean * @since 4.0.0 */ private static function hasMultiDeviceSettings($value): bool { return isset($value->xl) || isset($value->lg) || isset($value->md) || isset($value->sm) || isset($value->xs); } /** * Extract the value field, if the field has a unit property then add the unit with it. * * @param object $value The value object. * * @return string The extracted string value. * @since 4.0.0 */ private function extractValue($value): string { if (\is_object($value)) { $_value = ''; if (isset($value->value)) { $_value = is_object($value->value) ? $value->value->value : $value->value; } if (!empty($_value) && isset($value->unit)) { $_value .= is_object($value->unit) ? $value->unit->unit : $value->unit; } return $_value; } if (\is_array($value) && empty($value)) { return $value = ''; } return (string) $value; } /** * Generate style for a specific selector with help of settings object, prop map, units etc. * * @param string $selector The selector string where to apply the styles. * @param \stdClass $settings The settings object. * @param array $propMap The property map. The structure of the array is [prop => cssProp | [cssProps]] * @param mixed $unit The unit array. The structure of the array is [prop => unitValue | false] * @param array $modifier The modifier array. This array says if any modifier is needed to apply * to this property. Currently we are covering the spacing property. * if [prop => 'spacing'] is provided then handle the value as spacing value. * @param mixed $default The default values. This would be useful if we don't want to * use the $settings->$prop value as a result but want * user specific value as a value. * @param boolean $important The important specifier says if the property need * to append !important; at the end or not. * @param string $static Provided any static style value. If provided that would be contacted. * * @return string The CSS style with media query. * @since 4.0.0 */ public function generateStyle(string $selector, $settings, $propMap, $unit = 'px', $modifier = [], $default = null, $important = false, $static = ''): string { $device = self::$device; $selector = $this->generateSelector($selector); $css = []; $media = []; $_unit = 'px'; $css[] = $selector . "{"; if (!empty($static)) { $css[] = $static; } foreach ($propMap as $prop => $cssProp) { if (\is_null($cssProp)) { continue; } $primitiveProp = $prop; $originalProp = $prop . '_original'; if (isset($settings->$originalProp)) { $prop = $originalProp; } $_unit = \is_array($unit) ? ($unit[$primitiveProp] ?? 'px') : $unit; $_important = \is_array($important) ? ($important[$primitiveProp] ?? false) : $important; $_default = \is_array($default) ? ($default[$primitiveProp] ?? null) : $default; if (!isset($settings->$prop) && \is_null($_default)) { continue; } if (isset($settings->$prop) && \is_string($settings->$prop) && preg_replace("@\s+@", '', $settings->$prop) === '' && \is_null($_default)) { continue; } if (\is_string($cssProp)) { $cssProp = [$cssProp]; } if (!\is_null($_default)) { $str = ''; foreach ($cssProp as $attr) { $str .= $attr . ': ' . $_default . ';'; } $css[] = $str; continue; } if (self::hasMultiDeviceSettings($settings->$prop)) { $multiDeviceData = null; if (isset($modifier[$primitiveProp]) && $modifier[$primitiveProp] === 'spacing') { $multiDeviceData = AddonHelper::generateSpacingObject($settings, $prop, $cssProp, $device); } else { $multiDeviceData = AddonHelper::generateMultiDeviceObject($settings, $prop, $cssProp, $device, $_important, $_unit); } $media[] = $multiDeviceData; $css[] = $multiDeviceData->$device; unset($multiDeviceData); } else { if (isset($settings->$prop)) { if (isset($modifier[$primitiveProp]) && $modifier[$primitiveProp] === 'spacing') { $_object = AddonHelper::generateSpacingObject($settings, $prop, $cssProp, $device); $media[] = $_object; } $val = $this->extractValue($settings->$prop); if (isset($val) && $val !== '') { foreach ($cssProp as $attr) { $str = ""; if (!empty($_object) && !empty($_object->$device)) { $str .= $_object->$device; unset($_object); } else { $str .= (strpos($attr, '%1$s') || strpos($attr, '%s')) !== false ? \sprintf($attr, $this->extractValue($settings->$prop) ?? '') : $attr . ': ' . $this->extractValue($settings->$prop) ?? ''; } if (!empty($_unit)) { $str .= $_unit; } $str .= $_important ? ' !important' : ''; if (substr($str, -1) !== ';') { $str .= ';'; } $css[] = $str; } } } } } $css[] = "}"; if (!empty($media)) { $mediaStyle = array_map(function ($key) use ($selector, $media) { $str = AddonHelper::mediaQuery($key); $str .= $selector . "{"; foreach ($media as $object) { $str .= $object->$key; } $str .= "}"; $str .= "}"; return $str; }, $this->getDeviceListExcludeDefault()); $css[] = implode("\r\n", $mediaStyle); } $css = array_filter($css, function ($style) { return !empty($style); }); return implode("\r\n", $css); } function getFormattedValue($transformFunctionCssValue, $value) { if (empty($value)) { return ''; } $axises = ['x', 'y', 'z', 'top', 'left']; $values = []; $valueOfAxis = null; foreach($axises as $axis) { if(empty($value->$axis)) { continue; } if(is_object($value->$axis)) { $valueOfAxis = isset($value->$axis->value) ? $value->$axis->value : null; $unitOfAxis = isset($value->$axis->unit) ? $value->$axis->unit : null; } else if (is_string($value->$axis)) { $valueOfAxis = isset($value->$axis) ? $value->$axis : null; $unitOfAxis = ''; } if(empty($valueOfAxis) && $valueOfAxis !== 0 && $valueOfAxis !== '0') { continue; } if($transformFunctionCssValue === 'transform_origin') { $values[] = $valueOfAxis . $unitOfAxis; } else { $values[] = $transformFunctionCssValue . strtoupper($axis) . '(' . $valueOfAxis . $unitOfAxis . ')'; } } return implode(' ', $values); } /** * Parse style for transform property * @since 5.2.10 * * @param string $selector The selector string where to apply the styles. * @param \stdClass $settings The settings object * @param string $prop The filter settings key inside the $settings object * * @return string */ public function generateTransformStyle(string $selector, $settings, $prop): string { $selector = $this->generateSelector($selector); $transform = isset($settings->$prop) ? $settings->$prop : null; if (!isset($transform)) { return ''; } // Define an array to store transform functions $transformFunctions = []; $transformFunctionMap = [ 'move' => 'translate', 'rotate' => 'rotate', 'scale' => 'scale', 'skew' => 'skew', ]; foreach ($transformFunctionMap as $transformFunction => $transformFunctionCssValue) { if (!empty($transform->$transformFunction)) { $unitWithValue = $this->getFormattedValue($transformFunctionCssValue, $transform->$transformFunction); if(!empty($unitWithValue)) { $transformFunctions[] = $unitWithValue; } } } $transformOriginValue = ''; if(!empty($transform->transform_origin)) { $value = $this->getFormattedValue('transform_origin', $transform->transform_origin); if(!empty($value)) { $transformOriginValue = 'transform-origin: ' . $value . ';'; } } $finalCssStyle = ''; if (!empty($transformFunctions) || !empty($transformOriginValue)) { $finalCssStyle .= $selector . '{ '; if(!empty($transformFunctions)) { $finalCssStyle .= 'transform: ' . implode(' ', $transformFunctions) . '; '; } if(!empty($transformOriginValue)) { $finalCssStyle .= $transformOriginValue; } $finalCssStyle .= ' }'; } return $finalCssStyle; } /** * Parse style for css filter property * @since 5.2.8 * * @param \stdClass $settings The settings object * @param string $prop The filter settings key inside the $settings object * * @return string */ public static function parseCssEffects($settings, $prop): string { $effects = $settings->$prop; if (!isset($effects)) { return ''; } // Define an array to store filter functions $filterFunctions = []; // CSS unit definitions $effectsKeyUnit = [ 'opacity' => '%', 'blur' => 'px', 'brightness' => '%', 'contrast' => '%', 'saturate' => '%', 'grayscale' => '%', 'invert' => '%', 'sepia' => '%', 'hue_rotate' => 'deg', ]; foreach ($effectsKeyUnit as $filterFunction => $unit) { // Ensure property is valid and exists in $effectsKeyUnit if (!empty($effects->$filterFunction)) { $updatedFunction = $filterFunction === 'hue_rotate' ? 'hue-rotate' : $filterFunction; $filterFunctions[] = $updatedFunction . '(' . $effects->$filterFunction . $unit . ')'; } } $parsedFilterCssValue = ''; // If there are filter functions, construct the CSS rule if (!empty($filterFunctions)) { $parsedFilterCssValue = implode(' ', $filterFunctions); } return $parsedFilterCssValue; } /** * Parse the backdrop filter values and generate the backdrop filter CSS property. * * @param object $settings The Add-On settings. * @param string $property The backdrop-filter settings key inside the $settings object. * @param string $propertyValue The backdrop settings value. * @since 4.0.8 * @return string */ public static function parseBackDropFilter($settings, string $property, string $propertyValue) { if (!isset($settings->$property)) { return ''; } $unit = ($settings->$property == 'blur') ? 'px' : '%'; $parsedValue = $settings->$property . '(' . $settings->$propertyValue . $unit . ')'; return $parsedValue; } /** * Generate missing break points of field width for old layouts. * * @param object $fieldWidth given breakpoints of field width * * @return object * * @since 4.0.9 */ public static function generateMissingBreakPoints($fieldWidth) { $missing_breakpoints = ['xl', 'lg']; foreach ($missing_breakpoints as $key) { if (!property_exists($fieldWidth, $key)) { $fieldWidth->$key = isset($fieldWidth->md) && $fieldWidth->md ? $fieldWidth->md : ""; } } return $fieldWidth; } public static function generateBorderWidth(array $width, $style, $color) { $positions = ['top', 'right', 'bottom', 'left']; $widthStyles = []; $slicedWidth = array_slice($width, 0, 4); foreach ($slicedWidth as $index => $value) { if (trim($value) !== '') { $widthStyles[] = 'border-' . $positions[$index] . ': ' . $value . ' ' . $style . ' ' . $color . ';'; } } return implode("\n\r", $widthStyles); } public function border($selector, $settings, $prop) { $value = $settings->{$prop . '_original'} ?? $settings->$prop ?? null; $output = ''; if (is_null($value)) { return ''; } $deviceKeys = ['xl', 'lg', 'md', 'sm', 'xs']; $isResponsive = false; foreach ($value as $key => $_) { if (array_key_exists($key, array_flip($deviceKeys))) { $isResponsive = true; break; } } if ($isResponsive) { $defaultDevice = SpPgaeBuilderBase::$defaultDevice; foreach ($deviceKeys as $key) { if (isset($value->$key)) { $option = $value->$key; $width = isset($option->border_width) ? $option->border_width : ''; $style = isset($option->border_style) ? $option->border_style : ''; $color = isset($option->border_color) ? $option->border_color : ''; $widthParts = explode(' ', $width); if (empty($widthParts)) { continue; } $borderStyle = ''; if (count($widthParts) === 1) { $borderStyle = 'border: ' . $width . ' ' . $style . ' ' . $color . ';'; } else { $borderStyle = static::generateBorderWidth($widthParts, $style, $color); } $generatedSelector = $this->generateSelector($selector); $str = ''; if ($defaultDevice === $key) { $str .= $generatedSelector . '{'; $str .= $borderStyle; $str .= '}'; } else { $str .= AddonHelper::mediaQuery($key); $str .= $generatedSelector . '{'; $str .= $borderStyle; $str .= '}'; $str .= '}'; } $output .= $str; } } } else { $width = isset($value->border_width) ? $value->border_width : ''; $style = isset($value->border_style) ? $value->border_style : ''; $color = isset($value->border_color) ? $value->border_color : ''; $widthParts = explode(' ', $width); if (empty($widthParts)) { return ''; } $css = ''; if (count($widthParts) === 1) { $css = 'border: ' . $width . ' ' . $style . ' ' . $color . ';'; } else { $css = static::generateBorderWidth($widthParts, $style, $color); } $generatedSelector = $this->generateSelector($selector); $output = $generatedSelector . ' {'; $output .= $css; $output .= '}'; } return $output; } }