mirror of
				https://github.com/Icinga/icingaweb2.git
				synced 2025-10-24 17:04:04 +02:00 
			
		
		
		
	The method `Visitor::visitColor` seems like a dead code and contains dynamic property allocation. Hence, this method has been removed.
		
			
				
	
	
		
			234 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
 | |
| 
 | |
| namespace Icinga\Less;
 | |
| 
 | |
| use Less_Parser;
 | |
| use Less_Tree_Expression;
 | |
| use Less_Tree_Rule;
 | |
| use Less_Tree_Value;
 | |
| use Less_Tree_Variable;
 | |
| use Less_VisitorReplacing;
 | |
| use LogicException;
 | |
| use ReflectionProperty;
 | |
| 
 | |
| /**
 | |
|  * Replace compiled Less colors with CSS var() function calls and inject light mode calls
 | |
|  *
 | |
|  * Color replacing basically works by replacing every visited Less variable with {@link ColorPropOrVariable},
 | |
|  * which is later compiled to {@link ColorProp} if it is a color.
 | |
|  *
 | |
|  * Light mode calls are generated from light mode definitions.
 | |
|  */
 | |
| class Visitor extends Less_VisitorReplacing
 | |
| {
 | |
|     const LIGHT_MODE_CSS = <<<'CSS'
 | |
| @media (min-height: @prefer-light-color-scheme), print,
 | |
| (prefers-color-scheme: light) and (min-height: @enable-color-preference) {
 | |
|     %s
 | |
| }
 | |
| CSS;
 | |
| 
 | |
|     const LIGHT_MODE_NAME = 'light-mode';
 | |
| 
 | |
|     public $isPreEvalVisitor = true;
 | |
| 
 | |
|     /**
 | |
|      * Whether calling var() CSS function
 | |
|      *
 | |
|      * If that's the case, don't try to replace compiled Less colors with CSS var() function calls.
 | |
|      *
 | |
|      * @var bool|string
 | |
|      */
 | |
|     protected $callingVar = false;
 | |
| 
 | |
|     /**
 | |
|      * Whether defining a variable
 | |
|      *
 | |
|      * If that's the case, don't try to replace compiled Less colors with CSS var() function calls.
 | |
|      *
 | |
|      * @var false|string
 | |
|      */
 | |
|     protected $definingVariable = false;
 | |
| 
 | |
|     /** @var Less_Tree_Rule If defining a variable, determines the origin rule of the variable */
 | |
|     protected $variableOrigin;
 | |
| 
 | |
|     /** @var LightMode Light mode registry */
 | |
|     protected $lightMode;
 | |
| 
 | |
|     /** @var false|string Whether parsing module Less */
 | |
|     protected $moduleScope = false;
 | |
| 
 | |
|     /** @var null|string CSS module selector if any */
 | |
|     protected $moduleSelector;
 | |
| 
 | |
|     public function visitCall($c)
 | |
|     {
 | |
|         if ($c->name !== 'var') {
 | |
|             // We need to use our own tree call class , so that we can precompile the arguments before making
 | |
|             // the actual LESS function calls. Otherwise, it will produce lots of invalid argument exceptions!
 | |
|             $c = Call::fromCall($c);
 | |
|         }
 | |
| 
 | |
|         return $c;
 | |
|     }
 | |
| 
 | |
|     public function visitDetachedRuleset($drs)
 | |
|     {
 | |
|         if ($this->variableOrigin->name === '@' . static::LIGHT_MODE_NAME) {
 | |
|             $this->variableOrigin->name .= '-' . substr(sha1(uniqid(mt_rand(), true)), 0, 7);
 | |
| 
 | |
|             $this->lightMode->add($this->variableOrigin->name);
 | |
| 
 | |
|             if ($this->moduleSelector !== false) {
 | |
|                 $this->lightMode->setSelector($this->variableOrigin->name, $this->moduleSelector);
 | |
|             }
 | |
| 
 | |
|             $drs = LightModeDefinition::fromDetachedRuleset($drs)
 | |
|                 ->setLightMode($this->lightMode)
 | |
|                 ->setName($this->variableOrigin->name);
 | |
|         }
 | |
| 
 | |
|         // Since a detached ruleset is a variable definition in the first place,
 | |
|         // just reset that we define a variable.
 | |
|         $this->definingVariable = false;
 | |
| 
 | |
|         return $drs;
 | |
|     }
 | |
| 
 | |
|     public function visitMixinCall($c)
 | |
|     {
 | |
|         // Less_Tree_Mixin_Call::accept() does not visit arguments, but we have to replace them if necessary.
 | |
|         foreach ($c->arguments as $a) {
 | |
|             $a['value'] = $this->visitObj($a['value']);
 | |
|         }
 | |
| 
 | |
|         return $c;
 | |
|     }
 | |
| 
 | |
|     public function visitMixinDefinition($m)
 | |
|     {
 | |
|         // Less_Tree_Mixin_Definition::accept() does not visit params, but we have to replace them if necessary.
 | |
|         foreach ($m->params as $p) {
 | |
|             if (! isset($p['value'])) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             $p['value'] = $this->visitObj($p['value']);
 | |
|         }
 | |
| 
 | |
|         return $m;
 | |
|     }
 | |
| 
 | |
|     public function visitRule($r)
 | |
|     {
 | |
|         if ($r->name[0] === '@' && $r->variable) {
 | |
|             if ($this->definingVariable !== false) {
 | |
|                 throw new LogicException('Already defining a variable');
 | |
|             }
 | |
| 
 | |
|             $this->definingVariable = spl_object_hash($r);
 | |
|             $this->variableOrigin = $r;
 | |
| 
 | |
|             if ($r->value instanceof Less_Tree_Value) {
 | |
|                 if ($r->value->value[0] instanceof Less_Tree_Expression) {
 | |
|                     if ($r->value->value[0]->value[0] instanceof Less_Tree_Variable) {
 | |
|                         // Transform the variable definition rule into our own class
 | |
|                         $r->value->value[0]->value[0] = new DeferredColorProp($r->name, $r->value->value[0]->value[0]);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $r;
 | |
|     }
 | |
| 
 | |
|     public function visitRuleOut($r)
 | |
|     {
 | |
|         if ($this->definingVariable !== false && $this->definingVariable === spl_object_hash($r)) {
 | |
|             $this->definingVariable = false;
 | |
|             $this->variableOrigin = null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function visitRuleset($rs)
 | |
|     {
 | |
|         // Method is required, otherwise visitRulesetOut will not be called.
 | |
|         return $rs;
 | |
|     }
 | |
| 
 | |
|     public function visitRulesetOut($rs)
 | |
|     {
 | |
|         if ($this->moduleScope !== false
 | |
|             && isset($rs->selectors)
 | |
|             && spl_object_hash($rs->selectors[0]) === $this->moduleScope
 | |
|         ) {
 | |
|             $this->moduleSelector = null;
 | |
|             $this->moduleScope = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function visitSelector($s)
 | |
|     {
 | |
|         if ($s->_oelements_len === 2 && $s->_oelements[0] === '.icinga-module') {
 | |
|             $this->moduleSelector = implode('', $s->_oelements);
 | |
|             $this->moduleScope = spl_object_hash($s);
 | |
|         }
 | |
| 
 | |
|         return $s;
 | |
|     }
 | |
| 
 | |
|     public function visitVariable($v)
 | |
|     {
 | |
|         if ($this->definingVariable !== false) {
 | |
|             return $v;
 | |
|         }
 | |
| 
 | |
|         return (new ColorPropOrVariable())
 | |
|             ->setVariable($v);
 | |
|     }
 | |
| 
 | |
|     public function run($node)
 | |
|     {
 | |
|         $this->lightMode = new LightMode();
 | |
| 
 | |
|         $evald = $this->visitObj($node);
 | |
| 
 | |
|         // The visitor has registered all light modes in visitDetachedRuleset, but has not called them yet.
 | |
|         // Now the light mode calls are prepared with the appropriate CSS selectors.
 | |
|         $calls = [];
 | |
|         foreach ($this->lightMode as $mode) {
 | |
|             if ($this->lightMode->hasSelector($mode)) {
 | |
|                 $calls[] = "{$this->lightMode->getSelector($mode)} {\n$mode();\n}";
 | |
|             } else {
 | |
|                 $calls[] = "$mode();";
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (! empty($calls)) {
 | |
|             // Place and parse light mode calls into a new anonymous file,
 | |
|             // leaving the original Less in which the light modes were defined untouched.
 | |
|             $parser = (new Less_Parser())
 | |
|                 ->parse(sprintf(static::LIGHT_MODE_CSS, implode("\n", $calls)));
 | |
| 
 | |
|             // Because Less variables are block scoped,
 | |
|             // we can't just access the light mode definitions in the calls above.
 | |
|             // The LightModeVisitor ensures that all calls have access to the environment in which the mode was defined.
 | |
|             // Finally, the rules are merged so that the light mode calls are also rendered to CSS.
 | |
|             $rules = new ReflectionProperty(get_class($parser), 'rules');
 | |
|             $rules->setAccessible(true);
 | |
|             $evald->rules = array_merge(
 | |
|                 $evald->rules,
 | |
|                 (new LightModeVisitor())
 | |
|                     ->setLightMode($this->lightMode)
 | |
|                     ->visitArray($rules->getValue($parser))
 | |
|             );
 | |
|             // The LightModeVisitor is used explicitly here instead of using it as a plugin
 | |
|             // since we only need to process the newly created rules for the light mode calls.
 | |
|         }
 | |
| 
 | |
|         return $evald;
 | |
|     }
 | |
| }
 |