* @link http://www.tassos.gr * @copyright Copyright © 2022 Tassos Marinos All Rights Reserved * @license GNU GPLv3 or later */ namespace NRFramework\Controls; defined('_JEXEC') or die; class Control { /** * The CSS selector related to this control. * * @var string|array */ protected $selector; /** * The CSS property related to this control. * * @var mixed */ protected $property; /** * The CSS property used when there are conditions and we fail to use the property, so we override it using this property. * * @var mixed */ protected $fallback_property; /** * The CSS property used when there are no conditions and we fail to use the property, so we override it using this value. * * @var mixed */ protected $fallback_value; /** * Some controls may render CSS conditionally, based on the given value. * * @var array */ protected $values; /** * The control value. * * @var mixed */ protected $value; /** * The raw control value. * * @var mixed */ protected $value_raw; /** * The control value unit. * * @var string */ protected $unit; /** * Exclude specific breakpoints from the control's CSS. * * @var array */ protected $exclude_breakpoints = []; /** * The existing controls we have parsed so far. * * @var array */ protected $parsedControls = []; /** * A control may require some conditions to be set. * * @var array */ protected $conditions = []; /** * Whether to ignore "inherit" values. * * @var bool */ protected $skip_inherit_value = false; /** * The current breakpoint we are checking against to set CSS. * * @var string */ protected $current_breakpoint = 'desktop'; public function __construct($payload = []) { $this->parsedControls = isset($payload['parsedControls']) ? $payload['parsedControls'] : null; $this->selector = isset($payload['selector']) ? $payload['selector'] : null; $this->conditions = isset($payload['conditions']) ? $payload['conditions'] : []; $this->property = isset($payload['property']) ? $payload['property'] : null; $this->skip_inherit_value = isset($payload['skip_inherit_value']) ? $payload['skip_inherit_value'] : $this->skip_inherit_value; // The fallback_property is used when we have conditions and we don't have a value, so we override it using this property. $this->fallback_property = isset($payload['fallback_property']) ? $payload['fallback_property'] : null; // The fallback_value is used when we don't have conditions and we don't have a value, so we override it using this value. $this->fallback_value = isset($payload['fallback_value']) ? $payload['fallback_value'] : null; $this->values = isset($payload['values']) ? $payload['values'] : []; $this->exclude_breakpoints = isset($payload['exclude_breakpoints']) ? $payload['exclude_breakpoints'] : null; $this->value = isset($payload['value']['value']) ? $payload['value']['value'] : (isset($payload['value']) ? $payload['value'] : null); $this->value_raw = isset($payload['value']['value']) ? $payload['value']['value'] : (isset($payload['value']) ? $payload['value'] : null); $this->unit = isset($payload['value']['unit']) ? $payload['value']['unit'] : (isset($payload['unit']) ? $payload['unit'] : null); if (isset($this->value['unit'])) { unset($this->value['unit']); } } public function getCSS() { if (!$this->isResponsive()) { $this->value = $this->generateCSSProperty($this->value, $this->unit); return $this->value; } // Prepare value for arrays foreach ($this->value as $breakpoint => &$value) { $this->current_breakpoint = $breakpoint; // If this breakpoint is excluded, skip if (is_array($this->exclude_breakpoints) && count($this->exclude_breakpoints) && in_array($breakpoint, $this->exclude_breakpoints)) { unset($this->value[$breakpoint]); continue; } if (is_scalar($value)) { $value = $this->generateCSSProperty($value, $this->unit); } else { $unit = isset($value['unit']) ? $value['unit'] : $this->unit; // Remove "unit" property if ($unit) { unset($value['unit']); } // Remove "linked" property if (isset($value['linked'])) { unset($value['linked']); } $value = isset($value['value']) ? $value['value'] : $value; // Remove responsive value if no actual value is set if (!$value && $value != '0' && $unit != 'auto') { unset($this->value[$breakpoint]); continue; } $value = $this->generateCSSProperty($value, $unit); } if (is_null($value)) { continue; } } return $this->value; } protected function generateCSSProperty($value, $unit) { if ($this->shouldSkipPropertyGeneration($value, $unit)) { return; } $conditions_pass = $this->conditionsPass($value); $conditionsNotMet = (!$conditions_pass && !$this->fallback_property); $emptyValueWithUnitNotAuto = ($value === '' || is_null($value)) && !$this->fallback_property && $unit !== 'auto'; if ($conditionsNotMet || $emptyValueWithUnitNotAuto) { return; } if ($this->values) { return $this->generateValueCSS($value); } $properties = $this->conditions && !$conditions_pass && $this->fallback_property ? $this->fallback_property : $this->property; // If we have no conditions and no value, but we have a fallback value, use it. if (!$this->conditions && !$value && $this->fallback_value) { $properties = $this->fallback_value; } if (is_array($properties)) { return $this->generateArrayPropertyCSS($properties, $conditions_pass, $value, $unit); } if ($value === 'inherit' && $this->skip_inherit_value) { return; } return $this->generateSinglePropertyCSS($value, $unit); } private function shouldSkipPropertyGeneration($value, $unit) { return !$value && $value != '0' && $unit != 'auto' && !$this->fallback_property; } private function generateValueCSS($value) { $css = ''; $value = explode(' ', $value); foreach ($this->values as $key => $css_value) { if (!in_array($key, $value)) { continue; } $css .= implode('', $css_value); } return $css; } private function generateArrayPropertyCSS($properties, $conditions_pass, $value, $unit) { $css = ''; // If the conditions did pass and we do not have a value, do not set any CSS. if ($conditions_pass && !$value && $unit !== 'auto') { return $css; } foreach ($properties as $prop_key => $prop_value) { $css_line = $prop_key . ':' . str_replace('%value%', $value . $unit, $prop_value) . ';'; $css_line = str_replace('%value_raw%', $value , $css_line); $css .= $css_line; } return $css; } private function generateSinglePropertyCSS($value, $unit) { $value = \NRFramework\Helpers\Controls\Control::findUnitInValue($value); $val = isset($value['value']) ? $value['value'] : $value; $unit = isset($value['unit']) && $value['unit'] ? $value['unit'] : $unit; return $this->getProperty() . ':' . $val . $unit . ';'; } private function conditionsPass(&$value) { if (!$this->conditions) { return true; } foreach ($this->conditions as $conditionItem) { if (!$this->checkCondition($conditionItem, $value)) { return false; } } return true; } private function checkCondition($conditionItem, &$value) { $foundCondition = array_search($conditionItem['property'], array_column($this->parsedControls, 'property')); if ($foundCondition === false) { return false; } $foundConditionControl = isset($this->parsedControls[$foundCondition]) ? $this->parsedControls[$foundCondition] : false; if (!$foundConditionControl) { return false; } $conditionValue = $conditionItem['property'] . ':' . $conditionItem['value'] . ';'; $foundConditionControlValues = $foundConditionControl['control']->getValue(); $breakpointValue = is_array($foundConditionControlValues) ? ($foundConditionControlValues[$this->current_breakpoint] ?? null) : $foundConditionControlValues; if (\NRFramework\Functions::endsWith($breakpointValue, ':inherit;')) { $prevBreakpoint = $this->current_breakpoint === 'tablet' ? 'desktop' : 'tablet'; $breakpointValue = is_array($foundConditionControlValues) ? ($foundConditionControlValues[$prevBreakpoint] ?? null) : $foundConditionControlValues; // Also update the value $value = isset($this->value_raw[$prevBreakpoint]['value']) || isset($this->value_raw[$prevBreakpoint]) ? '' : $value; if (\NRFramework\Functions::endsWith($breakpointValue, ':inherit;') && $this->current_breakpoint === 'mobile' && $prevBreakpoint === 'tablet') { $breakpointValue = is_array($foundConditionControlValues) ? ($foundConditionControlValues['desktop'] ?? null) : $foundConditionControlValues; // Also update the value $value = isset($this->value_raw['desktop']['value']) || isset($this->value_raw['desktop']) ? '' : $value; } } return $conditionValue === $breakpointValue; } public function getValue() { return $this->value; } public function getSelector() { return $this->selector; } public function getProperty() { return $this->property; } public function isResponsive() { $keys = ['desktop', 'tablet', 'mobile']; return is_array($this->value) && !empty(array_intersect_key(array_flip($keys), $this->value)); } }