Issues (191)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/ILess/FunctionRegistry.php (48 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the ILess
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace ILess;
11
12
use Exception;
13
use ILess\Exception\CompilerException;
14
use ILess\Exception\FunctionException;
15
use ILess\Exception\IOException;
16
use ILess\Node\AnonymousNode;
17
use ILess\Node\ColorNode;
18
use ILess\Node\CommentNode;
19
use ILess\Node\DimensionNode;
20
use ILess\Node\ExpressionNode;
21
use ILess\Node\KeywordNode;
22
use ILess\Node\UrlNode;
23
use ILess\Node\UnitNode;
24
use ILess\Node\ToColorConvertibleInterface;
25
use ILess\Node\QuotedNode;
26
use ILess\Node\OperationNode;
27
use ILess\Util\Mime;
28
use InvalidArgumentException;
29
use RuntimeException;
30
31
/**
32
 * Function registry.
33
 *
34
 * @see http://lesscss.org/#reference
35
 */
36
class FunctionRegistry
37
{
38
    /**
39
     * Maximum allowed size of data uri for IE8.
40
     */
41
    const IE8_DATA_URI_MAX = 32768;
42
43
    /**
44
     * Less environment.
45
     *
46
     * @var Context
47
     */
48
    protected $context;
49
50
    /**
51
     * Array of function aliases.
52
     *
53
     * @var array
54
     */
55
    protected $aliases = [
56
        '%' => 'template',
57
        'data-uri' => 'dataUri',
58
        'get-unit' => 'getunit',
59
        'svg-gradient' => 'svggradient',
60
        'default' => 'defaultFunc',
61
        'image-size' => 'imageSize',
62
        'image-width' => 'imageWidth',
63
        'image-height' => 'imageHeight',
64
    ];
65
66
    /**
67
     * @var FileInfo
68
     */
69
    protected $currentFileInfo;
70
71
    /**
72
     * @var FunctionRegistry
73
     */
74
    private $parent;
75
76
    /**
77
     * Array of callable functions.
78
     *
79
     * @var array
80
     */
81
    private $functions = [
82
        'abs' => true,
83
        'acos' => true,
84
        'alpha' => true,
85
        'argb' => true,
86
        'asin' => true,
87
        'atan' => true,
88
        'average' => true,
89
        'blue' => true,
90
        'call' => true,
91
        'ceil' => true,
92
        'clamp' => true,
93
        'color' => true,
94
        'contrast' => true,
95
        'convert' => true,
96
        'cos' => true,
97
        'darken' => true,
98
        'dataUri' => true,
99
        'desaturate' => true,
100
        'difference' => true,
101
        'e' => true,
102
        'escape' => true,
103
        'exclusion' => true,
104
        'extract' => true,
105
        'fade' => true,
106
        'fadein' => true,
107
        'fadeout' => true,
108
        'floor' => true,
109
        'green' => true,
110
        'greyscale' => true,
111
        'hardlight' => true,
112
        'hsl' => true,
113
        'hsla' => true,
114
        'hsv' => true,
115
        'hsva' => true,
116
        'hsvhue' => true,
117
        'hsvsaturation' => true,
118
        'hsvvalue' => true,
119
        'hue' => true,
120
        'iscolor' => true,
121
        'isem' => true,
122
        'iskeyword' => true,
123
        'isnumber' => true,
124
        'ispercentage' => true,
125
        'ispixel' => true,
126
        'isstring' => true,
127
        'isunit' => true,
128
        'isurl' => true,
129
        'isruleset' => true,
130
        'length' => true,
131
        'lighten' => true,
132
        'lightness' => true,
133
        'luma' => true,
134
        'luminance' => true,
135
        'max' => true,
136
        'min' => true,
137
        'mix' => true,
138
        'mod' => true,
139
        'multiply' => true,
140
        'negation' => true,
141
        'number' => true,
142
        'overlay' => true,
143
        'percentage' => true,
144
        'pi' => true,
145
        'pow' => true,
146
        'red' => true,
147
        'replace' => true,
148
        'rgb' => true,
149
        'rgba' => true,
150
        'round' => true,
151
        'saturate' => true,
152
        'saturation' => true,
153
        'screen' => true,
154
        'shade' => true,
155
        'sin' => true,
156
        'softlight' => true,
157
        'spin' => true,
158
        'sqrt' => true,
159
        'svggradient' => true,
160
        'tan' => true,
161
        'template' => true,
162
        'tint' => true,
163
        'unit' => true,
164
        'getunit' => true,
165
        'defaultFunc' => true,
166
        'imageSize' => true,
167
        'imageWidth' => true,
168
        'imageHeight' => true,
169
    ];
170
171
    /**
172
     * Constructor.
173
     *
174
     * @param array $aliases Array of function aliases in the format array(alias => function)
175
     * @param Context $context The context
176
     */
177
    public function __construct($aliases = [], Context $context = null)
178
    {
179
        $this->context = $context;
180
        $this->addAliases($aliases);
181
    }
182
183
    /**
184
     * @param FileInfo $file
185
     *
186
     * @return $this
187
     */
188
    public function setCurrentFile(FileInfo $file)
189
    {
190
        $this->currentFileInfo = $file;
191
192
        return $this;
193
    }
194
195
    /**
196
     * Adds a function to the functions.
197
     *
198
     * @param string $functionName
199
     * @param callable $callable
200
     * @param string|array $aliases The array of aliases
201
     *
202
     * @return FunctionRegistry
203
     *
204
     * @throws InvalidArgumentException If the callable is not valid
205
     */
206
    public function addFunction($functionName, $callable, $aliases = [])
207
    {
208
        if (!is_callable($callable, null, $callableName)) {
209
            throw new InvalidArgumentException(
210
                sprintf('The callable "%s" for function "%s" is not valid.', $callableName, $functionName)
211
            );
212
        }
213
214
        // all functions are case insensitive
215
        $functionName = strtolower($functionName);
216
217
        $this->functions[$functionName] = $callable;
218
219
        if (!is_array($aliases)) {
220
            $aliases = [$aliases];
221
        }
222
223
        foreach ($aliases as $alias) {
224
            $this->addAlias($alias, $functionName);
225
        }
226
227
        return $this;
228
    }
229
230
    /**
231
     * Adds functions.
232
     *
233
     * <pre>
234
     * $registry->addFunctions(array('name' => function() {}));
235
     * $registry->addFunctions(array(array('name' => 'load', 'callable' => 'load'));
236
     * $registry->addFunctions(array(array('name' => 'load', 'callable' => 'load', 'alias' => 'l'));
237
     * </pre>
238
     *
239
     * @param array $functions Array of functions
240
     *
241
     * @return FunctionRegistry
242
     */
243
    public function addFunctions(array $functions)
244
    {
245
        foreach ($functions as $key => $function) {
246
            if (is_numeric($key)) {
247
                $this->addFunction(
248
                    $function['name'],
249
                    $function['callable'],
250
                    isset($function['alias']) ? $function['alias'] : []
251
                );
252
            } else {
253
                // alternative syntax without aliases
254
                $this->addFunction($key, $function);
255
            }
256
        }
257
258
        return $this;
259
    }
260
261
    /**
262
     * Sets the parent registry.
263
     *
264
     * @param FunctionRegistry $parent
265
     *
266
     * @return $this
267
     */
268
    public function setParent(FunctionRegistry $parent)
269
    {
270
        // verify
271
        if ($parent === $this) {
272
            throw new RuntimeException('Invalid parent registry. The parent is the same object.');
273
        }
274
275
        $this->parent = $parent;
276
277
        return $this;
278
    }
279
280
    /**
281
     * Loads a plugin from given path.
282
     *
283
     * @param string $path
284
     *
285
     * @return $this
286
     *
287
     * @throws RuntimeException
288
     */
289
    public function loadPlugin($path)
290
    {
291
        if (!is_readable($path)) {
292
            throw new RuntimeException(
293
                sprintf('The plugin cannot be loaded. The given file "%s" does not exist or is not readable', $path)
294
            );
295
        }
296
297
        // FIXME: what about security?
298
        // FIXME: php syntax check?
299
        require $path;
300
301
        return $this;
302
    }
303
304
    /**
305
     * Clones the registry and setups the parent.
306
     *
307
     * @return FunctionRegistry
308
     */
309
    public function inherit()
310
    {
311
        $new = clone $this;
312
313
        $new->setParent($this);
314
315
        return $new;
316
    }
317
318
    /**
319
     * Returns names of registered functions.
320
     *
321
     * @return array
322
     */
323
    public function getFunctions()
324
    {
325
        return array_keys($this->functions);
326
    }
327
328
    /**
329
     * Does the function exist?
330
     *
331
     * @param string $name
332
     *
333
     * @return bool
334
     */
335
    public function hasFunction($name)
336
    {
337
        $name = strtolower($name);
338
339
        return isset($this->functions[$name]) || isset($this->aliases[$name]);
340
    }
341
342
    /**
343
     * Returns aliases for registered functions.
344
     *
345
     * @param bool $withFunctions Include function names?
346
     *
347
     * @return array
348
     */
349
    public function getAliases($withFunctions = false)
350
    {
351
        return $withFunctions ? $this->aliases : array_keys($this->aliases);
352
    }
353
354
    /**
355
     * Adds a function alias.
356
     *
357
     * @param string $alias The alias name
358
     * @param string $function The function name
359
     *
360
     * @return FunctionRegistry
361
     */
362
    public function addAlias($alias, $function)
363
    {
364
        $functionLower = strtolower($function);
365
        if (!isset($this->functions[$functionLower])) {
366
            throw new InvalidArgumentException(
367
                sprintf('Invalid alias "%s" for "%s" given. The "%s" does not exist.', $alias, $function)
368
            );
369
        }
370
        $this->aliases[strtolower($alias)] = $functionLower;
371
372
        return $this;
373
    }
374
375
    /**
376
     * Adds aliases.
377
     *
378
     * @param array $aliases
379
     *
380
     * @return FunctionRegistry
381
     */
382
    public function addAliases(array $aliases)
383
    {
384
        foreach ($aliases as $alias => $function) {
385
            $this->addAlias($alias, $function);
386
        }
387
388
        return $this;
389
    }
390
391
    /**
392
     * Sets The context.
393
     *
394
     * @param Context $context
395
     */
396
    public function setEnvironment(Context $context)
397
    {
398
        $this->context = $context;
399
    }
400
401
    /**
402
     * Returns the context instance.
403
     *
404
     * @return mixed
405
     */
406
    public function getContext()
407
    {
408
        return $this->context;
409
    }
410
411
    /**
412
     * Calls a method with given arguments.
413
     *
414
     * @param string $methodName
415
     * @param array $arguments
416
     *
417
     * @return mixed
418
     *
419
     * @throws FunctionException If the method does not exist
420
     */
421
    public function call($methodName, $arguments = [])
422
    {
423
        $methodName = strtolower($methodName);
424
425
        if (isset($this->aliases[$methodName])) {
426
            $methodName = $this->aliases[$methodName];
427
        }
428
429
        if (isset($this->functions[$methodName])) {
430
            $arguments = $this->prepareArguments($arguments);
431
432
            if ($this->functions[$methodName] === true) {
433
                // built in function
434
                return call_user_func_array([$this, $methodName], $arguments);
435
            } else {
436
                // this is a external callable
437
                // provide access to function registry (pass as first parameter)
438
                array_unshift($arguments, $this);
439
440
                return call_user_func_array($this->functions[$methodName], $arguments);
441
            }
442
        } else {
443
            if ($this->parent) {
444
                return $this->parent->call($methodName, $arguments);
445
            }
446
        }
447
    }
448
449
    /**
450
     * Prepares the arguments before function calls.
451
     * This does the same as in less.js's functionCaller object.
452
     *
453
     * @param array $arguments
454
     *
455
     * @return array
456
     */
457
    protected function prepareArguments(array $arguments)
458
    {
459
        $return = array_filter(
460
            $arguments,
461
            function ($item) {
462
                if ($item instanceof CommentNode) {
463
                    return false;
464
                }
465
466
                return true;
467
            }
468
        );
469
470
        $return = array_map(
471
            function (&$item) {
472
                if ($item instanceof ExpressionNode) {
473
                    $subNodes = array_filter(
474
                        $item->value,
475
                        function ($i) {
476
                            if ($i instanceof CommentNode) {
477
                                return false;
478
                            }
479
480
                            return true;
481
                        }
482
                    );
483
                    if (count($subNodes) === 1) {
484
                        return $subNodes[0];
485
                    } else {
486
                        return new ExpressionNode($subNodes);
487
                    }
488
                }
489
490
                return $item;
491
            },
492
            $return
493
        );
494
495
        return $return;
496
    }
497
498
    /**
499
     * Applies URL-encoding to special characters found in the input string.
500
     *
501
     * * Following characters are exceptions and not encoded: `,, /, ?, @, &, +, ', ~, ! $`
502
     * * Most common encoded characters are: `<space>`, #, ^, (, ), {, }, |, :, >, <, ;, ], [ =`
503
     *
504
     * @param Node $string A string to escape
505
     *
506
     * @return AnonymousNode Escaped string content without quotes.
507
     */
508
    public function escape(Node $string)
509
    {
510
        return new AnonymousNode(urlencode((string) $string));
511
    }
512
513
    /**
514
     * CSS escaping similar to ~"value" syntax. It expects string as a parameter and return
515
     * its content as is, but without quotes. It can be used to output CSS value which is either not valid CSS syntax,
516
     * or uses proprietary syntax which LESS doesn't recognize.
517
     *
518
     * @param string $string A string to escape
519
     *
520
     * @return AnonymousNode Content without quotes.
521
     */
522
    public function e(Node $string)
523
    {
524
        return new AnonymousNode(str_replace(['~"', '"'], '', (string) $string));
525
    }
526
527
    /**
528
     * Returns the number of items.
529
     *
530
     * @param Node $values
531
     *
532
     * @return DimensionNode
533
     */
534
    public function length(Node $values)
535
    {
536
        return new DimensionNode(is_array($values->value) ? count($values->value) : 1);
537
    }
538
539
    /**
540
     * Min.
541
     *
542
     * @return DimensionNode
543
     */
544
    public function min()
545
    {
546
        return $this->doMinmax(true, func_get_args());
547
    }
548
549
    /**
550
     * Max.
551
     *
552
     * @return DimensionNode
553
     */
554
    public function max()
555
    {
556
        return $this->doMinmax(false, func_get_args());
557
    }
558
559
    /**
560
     * Extract.
561
     *
562
     * @param Node $node
563
     * @param Node $index
564
     *
565
     * @return null|Node
566
     */
567
    public function extract(Node $node, Node $index)
568
    {
569
        $index = (int) $index->value - 1; // (1-based index)
570
        $values = $this->getItemsFromNode($node);
571
        if (isset($values[$index])) {
572
            return $values[$index];
573
        }
574
575
        return;
576
    }
577
578
    /**
579
     * @param Node $node
580
     *
581
     * @return array
582
     */
583
    private function getItemsFromNode(Node $node)
584
    {
585
        // handle non-array values as an array of length 1
586
        // return 'undefined' if index is invalid
587
        if (is_array($node->value)) {
588
            $items = $node->value;
589
        } else {
590
            $items = [$node];
591
        }
592
593
        // reset array keys!
594
        return array_values($items);
595
    }
596
597
    /**
598
     * Formats a string. Less equivalent is: `%`.
599
     * The first argument is string with placeholders.
600
     * All placeholders start with percentage symbol % followed by letter s,S,d,D,a, or A.
601
     * Remaining arguments contain expressions to replace placeholders.
602
     * If you need to print the percentage symbol, escape it by another percentage %%.*.
603
     *
604
     * @param Node $string
605
     *
606
     * @return QuotedNode
607
     */
608
    public function template(Node $string /* , $value1, $value2, ... */)
609
    {
610
        $args = func_get_args();
611
        array_shift($args);
612
613
        $result = $string->value;
614
        foreach ($args as $arg) {
615
            if (preg_match('/%[sda]/i', $result, $token)) {
616
                $token = $token[0];
617
618
                if ($arg instanceof QuotedNode && stristr($token, 's')) {
619
                    $value = $arg->value;
620
                } else {
621
                    $value = $arg->toCSS($this->context);
622
                }
623
624
                $value = preg_match('/[A-Z]$/', $token) ? Util::encodeURIComponent($value) : $value;
625
                $result = preg_replace('/%[sda]/i', $value, $result, 1);
626
            }
627
        }
628
        $result = str_replace('%%', '%', $result);
629
630
        return new QuotedNode(
631
            isset($string->quote) ? $string->quote : '',
632
            $result,
633
            isset($string->escaped) ? $string->escaped : true
634
        );
635
    }
636
637
    /**
638
     * Replaces a string or regexp pattern within a string.
639
     *
640
     * @param Node $string The string to search and replace in.
641
     * @param Node $pattern A string or regular expression pattern to search for.
642
     * @param Node $replacement The string to replace the matched pattern with.
643
     * @param Node $flags (Optional) regular expression flags.
644
     *
645
     * @return QuotedNode
646
     *
647
     * @see http://lesscss.org/functions/#string-functions-replace
648
     */
649
    public function replace(Node $string, Node $pattern, Node $replacement, Node $flags = null)
650
    {
651
        $result = $string->value;
652
653
        if ($replacement instanceof QuotedNode) {
654
            $replacement = $replacement->value;
655
        } else {
656
            $replacement = $replacement->toCSS($this->context);
657
        }
658
659
        $limit = 1;
660
        // we have some flags
661
        if ($flags) {
662
            $flags = $flags->value;
663
            // global replacement
664
            $global = strpos($flags, 'g') !== false;
665
            // strip js php non compatible flags
666
            $flags = str_replace('g', '', $flags);
667
            if ($global) {
668
                $limit = -1;
669
            }
670
        } else {
671
            $flags = '';
672
        }
673
674
        // we cannot use preg_quote here, since the expression is already quoted in less.js
675
        $regexp = str_replace('/', '\\/', $pattern->value);
676
        $result = preg_replace('/' . $regexp . '/' . $flags, $replacement, $result, $limit);
677
678
        return new QuotedNode(
679
            isset($string->quote) ? $string->quote : '',
680
            $result,
0 ignored issues
show
It seems like $result defined by preg_replace('/' . $rege...ement, $result, $limit) on line 676 can also be of type array<integer,string>; however, ILess\Node\QuotedNode::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
681
            isset($string->escaped) ? $string->escaped : true
682
        );
683
    }
684
685
    /**
686
     * Inlines a resource and falls back to url() if the ieCompat option is on
687
     * and the resource is too large, or if you use the function in the browser.
688
     * If the mime is not given then node uses the mime package to determine the correct mime type.
689
     *
690
     * @param Node $mimeType A mime type string
691
     * @param Node $url The URL of the file to inline.
692
     *
693
     * @return UrlNode
694
     *
695
     * @throws IOException
696
     */
697
    public function dataUri(Node $mimeType, Node $filePath = null)
698
    {
699
        if (func_num_args() < 2) {
700
            $path = $mimeType->value;
701
            $mime = false; // we will detect it later
702
        } else {
703
            $path = $filePath->value;
704
            $mime = $mimeType->value;
705
        }
706
707
        $path = $this->getFilePath($path);
708
        list($fragment, $path) = Util::getFragmentAndPath($path);
709
710
        if ($mime === false) {
711
            $mime = Mime::lookup($path);
712
            if ($mime === 'image/svg+xml') {
713
                $useBase64 = false;
714
            } else {
715
                // use base 64 unless it's an ASCII or UTF-8 format
716
                $charset = Mime::charsetsLookup($mime);
717
                $useBase64 = !in_array($charset, ['US-ASCII', 'UTF-8']);
718
            }
719
            if ($useBase64) {
720
                $mime .= ';base64';
721
            }
722
        } else {
723
            $useBase64 = (bool) preg_match('/;base64$/', $mime);
724
        }
725
726
        // the file was not found
727
        // FIXME: warn
728
        if (!is_readable($path)) {
729
            $url = new UrlNode(
730
                ($filePath ? $filePath : $mimeType),
731
                0, // FIXME: we don't have access to current index here!
732
                $this->context->currentFileInfo
733
            );
734
735
            return $url->compile($this->context);
736
        }
737
738
        $buffer = file_get_contents($path);
739
        $buffer = $useBase64 ? base64_encode($buffer) : Util::encodeURIComponent($buffer);
740
741
        $uri = 'data:' . $mime . ',' . $buffer . $fragment;
742
743
        // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
744
        // and the ieCompat option is enabled, return normal url() instead.
745
        if ($this->context->ieCompat) {
746
            if (strlen($uri) >= self::IE8_DATA_URI_MAX) {
747
                // FIXME: warn that we cannot use data uri here
748
                // FIXME: we don't have access to current index here!
749
                $url = new UrlNode(($filePath ? $filePath : $mimeType));
750
751
                return $url->compile($this->context);
752
            }
753
        }
754
755
        // FIXME: we don't have any information about current index here!
756
        return new UrlNode(new QuotedNode('"' . $uri . '"', $uri, false));
757
    }
758
759
    /**
760
     * Rounds up to an integer.
761
     *
762
     * @param Node $number
763
     *
764
     * @return DimensionNode
765
     */
766
    public function ceil($number)
767
    {
768
        return new DimensionNode(ceil($this->number($number)), $number->unit);
0 ignored issues
show
$number is of type object<ILess\Node>, but the function expects a object<ILess\Node\DimensionNode>|integer|double.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
769
    }
770
771
    /**
772
     * Rounds down to an integer.
773
     *
774
     * @param Node $number
775
     *
776
     * @return Node
777
     */
778
    public function floor($number)
779
    {
780
        return $this->doMath('floor', $number);
781
    }
782
783
    /**
784
     * Converts to a %, e.g. 0.5 -> 50%.
785
     *
786
     * @param Node $number
787
     *
788
     * @return DimensionNode
789
     */
790
    public function percentage(Node $number)
791
    {
792
        return $this->doMath(function ($n) {
793
                return $n * 100;
794
        }, $number, '%');
795
    }
796
797
    /**
798
     * Rounds a number to a number of places.
799
     *
800
     * @param string|Node $number The number to round
801
     * @param int $places The precision
802
     *
803
     * @return DimensionNode
804
     */
805
    public function round($number, DimensionNode $places = null)
806
    {
807
        if ($number instanceof DimensionNode) {
808
            $unit = $number->unit;
809
            $number = $number->value;
810
        } else {
811
            $unit = null;
812
        }
813
814
        $rounded = round(floatval($number), $places ? $places->value : 0);
815
816
        return new DimensionNode($rounded, $unit);
817
    }
818
819
    /**
820
     * Calculates square root of a number.
821
     *
822
     * @param mixed $number
823
     *
824
     * @return mixed
825
     */
826
    public function sqrt($number)
827
    {
828
        return $this->doMath('sqrt', $number);
829
    }
830
831
    /**
832
     * Absolute value of a number.
833
     *
834
     * @param mixed $number The number
835
     *
836
     * @return mixed
837
     */
838
    public function abs($number)
839
    {
840
        return $this->doMath('abs', $number);
841
    }
842
843
    /**
844
     * Sine function.
845
     *
846
     * @param string $number The number
847
     *
848
     * @return mixed
849
     */
850
    public function sin($number)
851
    {
852
        return $this->doMath('sin', $number, '');
853
    }
854
855
    /**
856
     * Arcsine - inverse of sine function.
857
     *
858
     * @param string $number
859
     *
860
     * @return mixed
861
     */
862
    public function asin($number)
863
    {
864
        return $this->doMath('asin', $number, 'rad');
865
    }
866
867
    /**
868
     * Cosine function.
869
     *
870
     * @param string $number
871
     *
872
     * @return mixed
873
     */
874
    public function cos($number)
875
    {
876
        return $this->doMath('cos', $number, '');
877
    }
878
879
    /**
880
     * Arc cosine - inverse of cosine function.
881
     *
882
     * @param string $number
883
     *
884
     * @return mixed
885
     */
886
    public function acos($number)
887
    {
888
        return $this->doMath('acos', $number, 'rad');
889
    }
890
891
    /**
892
     * Tangent function.
893
     *
894
     * @param mixed $number
895
     *
896
     * @return mixed
897
     */
898
    public function tan($number)
899
    {
900
        return $this->doMath('tan', $number, '');
901
    }
902
903
    /**
904
     * Arc tangent - inverse of tangent function.
905
     *
906
     * @param mixed $number
907
     *
908
     * @return mixed
909
     */
910
    public function atan($number)
911
    {
912
        return $this->doMath('atan', $number, 'rad');
913
    }
914
915
    /**
916
     * Does the math using Math.
917
     *
918
     * @param string $func The math function like sqrt, floor...
919
     * @param DimensionNode|int $number The number
920
     * @param UnitNode|string $unit The unit
921
     * @param mixed ...$argument1 Argument for the mathematical function
922
     * @param mixed ...$argument2 Argument for the mathematical function
923
     *
924
     * @return mixed
925
     *
926
     * @throws CompilerException
927
     */
928
    protected function doMath($func, $number, $unit = null /*, $arguments...*/)
929
    {
930
        $arguments = [];
931
        if (func_num_args() > 3) {
932
            foreach (func_get_args() as $i => $arg) {
933
                // skip first 3 arguments
934
                if ($i < 3) {
935
                    continue;
936
                }
937
                $arguments[] = $arg;
938
            }
939
        }
940
941
        if ($number instanceof DimensionNode) {
942
            if ($unit === null) {
943
                $unit = $number->unit;
944
            } else {
945
                $number = $number->unify();
946
            }
947
            $number = floatval($number->value);
948
949
            array_unshift($arguments, $number);
950
951
            return new DimensionNode(call_user_func_array($func, $arguments), $unit);
952
        } elseif (is_numeric($number)) {
953
            array_unshift($arguments, $number);
954
955
            return call_user_func_array($func, $arguments);
956
        }
957
958
        throw new CompilerException('argument must be a number');
959
    }
960
961
    /**
962
     * Returns pi.
963
     *
964
     * @return DimensionNode
965
     */
966
    public function pi()
967
    {
968
        return new DimensionNode(M_PI);
969
    }
970
971
    /**
972
     * First argument raised to the power of the second argument.
973
     *
974
     * @param Node $number
975
     * @param Node $exponent
976
     *
977
     * @throws CompilerException
978
     *
979
     * @return DimensionNode
980
     */
981
    public function pow($number, $exponent)
982
    {
983
        if (is_numeric($number) && is_numeric($exponent)) {
984
            $number = new DimensionNode($number);
985
            $exponent = new DimensionNode($exponent);
986
        } elseif (!($number instanceof DimensionNode)
987
            || !($exponent instanceof DimensionNode)
988
        ) {
989
            throw new CompilerException('Arguments must be numbers.');
990
        }
991
992
        return new DimensionNode(pow($number->value, $exponent->value), $number->unit);
993
    }
994
995
    /**
996
     * First argument modulus second argument.
997
     *
998
     * @param DimensionNode $number1
999
     * @param DimensionNode $number2
1000
     *
1001
     * @return DimensionNode
1002
     */
1003
    public function mod(DimensionNode $number1, DimensionNode $number2)
1004
    {
1005
        return new DimensionNode($number1->value % $number2->value, $number1->unit);
1006
    }
1007
1008
    /**
1009
     * Converts between number types.
1010
     *
1011
     * @param DimensionNode $number
1012
     * @param Node $units
1013
     *
1014
     * @return DimensionNode|null
1015
     */
1016
    public function convert(Node $number, Node $units)
1017
    {
1018
        if (!$number instanceof DimensionNode) {
1019
            return;
1020
        }
1021
1022
        return $number->convertTo($units->value);
0 ignored issues
show
It seems like $units->value can also be of type object<ILess\Node>; however, ILess\Node\DimensionNode::convertTo() does only seem to accept array|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1023
    }
1024
1025
    /**
1026
     * Changes number units without converting it.
1027
     *
1028
     * @param Node $number The dimension
1029
     * @param Node $unit The unit
1030
     *
1031
     * @throws CompilerException
1032
     *
1033
     * @return DimensionNode
1034
     */
1035
    public function unit(Node $number, Node $unit = null)
1036
    {
1037
        if (!$number instanceof DimensionNode) {
1038
            throw new CompilerException(
1039
                sprintf(
1040
                    'The first argument to unit must be a number%s',
1041
                    ($number instanceof OperationNode ? '. Have you forgotten parenthesis?' : '.')
1042
                )
1043
            );
1044
        }
1045
1046
        if ($unit) {
1047
            if ($unit instanceof KeywordNode) {
1048
                $unit = $unit->value;
1049
            } else {
1050
                $unit = $unit->toCSS($this->context);
1051
            }
1052
        } else {
1053
            $unit = '';
1054
        }
1055
1056
        return new DimensionNode($number->value, $unit);
0 ignored issues
show
It seems like $number->value can also be of type object<ILess\Node>; however, ILess\Node\DimensionNode::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
It seems like $unit defined by $unit->value on line 1048 can also be of type object<ILess\Node>; however, ILess\Node\DimensionNode::__construct() does only seem to accept object<ILess\Node\UnitNode>|string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1057
    }
1058
1059
    /**
1060
     * Returns units of a number.
1061
     *
1062
     * @param DimensionNode $node
1063
     *
1064
     * @return AnonymousNode
1065
     *
1066
     * @see http://lesscss.org/functions/#misc-functions-get-unit
1067
     */
1068
    public function getunit(DimensionNode $node)
1069
    {
1070
        return new AnonymousNode($node->unit);
1071
    }
1072
1073
    /**
1074
     * Converts string or escaped value into color.
1075
     *
1076
     * @param Node $string
1077
     *
1078
     * @throws CompilerException
1079
     * @returns ColorNode
1080
     */
1081
    public function color(Node $string)
1082
    {
1083
        if ($string instanceof QuotedNode &&
1084
            preg_match('/^#([a-f0-9]{6}|[a-f0-9]{3})$/i', $string->value)
1085
        ) {
1086
            return new ColorNode(substr($string->value, 1));
1087
        }
1088
1089
        if ($string instanceof ColorNode) {
1090
            // remove keyword, so the color is not output as `plum` but in hex code
1091
            $string->value->keyword = null;
1092
1093
            return $string;
1094
        } else {
1095
            if (Color::isNamedColor($string->value)) {
0 ignored issues
show
It seems like $string->value can also be of type object<ILess\Node>; however, ILess\Color::isNamedColor() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1096
                return new ColorNode(Color::color($string->value));
0 ignored issues
show
It seems like $string->value can also be of type object<ILess\Node>; however, ILess\Color::color() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
It seems like \ILess\Color::color($string->value) targeting ILess\Color::color() can also be of type false; however, ILess\Node\ColorNode::__construct() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
1097
            }
1098
        }
1099
1100
        throw new CompilerException('Argument must be a color keyword or 3/6 digit hex e.g. #FFF');
1101
    }
1102
1103
    /**
1104
     * Converts to a color.
1105
     *
1106
     * @param Node|int $red The red component of a color
1107
     * @param Node|int $green The green component of a color
1108
     * @param Node|int $blue The blue component of a color
1109
     *
1110
     * @return ColorNode
1111
     */
1112
    public function rgb($red, $green, $blue)
1113
    {
1114
        return $this->rgba($red, $green, $blue, 1);
0 ignored issues
show
It seems like $red defined by parameter $red on line 1112 can also be of type integer; however, ILess\FunctionRegistry::rgba() does only seem to accept object<ILess\Node>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $green defined by parameter $green on line 1112 can also be of type integer; however, ILess\FunctionRegistry::rgba() does only seem to accept object<ILess\Node>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $blue defined by parameter $blue on line 1112 can also be of type integer; however, ILess\FunctionRegistry::rgba() does only seem to accept object<ILess\Node>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1115
    }
1116
1117
    /**
1118
     * Converts to a color.
1119
     *
1120
     * @param Node $red The red component of a color
1121
     * @param Node $green The green component of a color
1122
     * @param Node $blue The blue component of a color
1123
     * @param Node|float $alpha The alpha channel
1124
     *
1125
     * @return ColorNode
1126
     */
1127
    public function rgba($red, $green, $blue, $alpha)
1128
    {
1129
        $rgb = array_map([$this, 'scaled'], [$red, $green, $blue]);
1130
1131
        return new ColorNode($rgb, $this->number($alpha));
0 ignored issues
show
It seems like $alpha defined by parameter $alpha on line 1127 can also be of type object<ILess\Node>; however, ILess\FunctionRegistry::number() does only seem to accept object<ILess\Node\DimensionNode>|integer|double, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1132
    }
1133
1134
    /**
1135
     * Scales the number to percentage.
1136
     *
1137
     * @param DimensionNode $n The number
1138
     * @param int $size
1139
     *
1140
     * @return float
1141
     */
1142
    protected function scaled($n, $size = 255)
1143
    {
1144
        if ($n instanceof DimensionNode && $n->unit->is('%')) {
1145
            return $n->value * $size / 100;
1146
        } else {
1147
            return $this->number($n);
1148
        }
1149
    }
1150
1151
    /**
1152
     * Converts the $number to "real" number.
1153
     *
1154
     * @param DimensionNode|int|float $number
1155
     *
1156
     * @return float
1157
     *
1158
     * @throws InvalidArgumentException
1159
     */
1160
    public function number($number)
1161
    {
1162
        if ($number instanceof DimensionNode) {
1163
            return $number->unit->is('%') ? $number->value / 100 : $number->value;
1164
        } elseif (is_numeric($number)) {
1165
            return $number;
1166
        } else {
1167
            throw new InvalidArgumentException(
1168
                sprintf('Color functions take numbers as parameters. "%s" given.', gettype($number))
1169
            );
1170
        }
1171
    }
1172
1173
    /**
1174
     * Creates a `#AARRGGBB`.
1175
     *
1176
     * @param Color $color The color
1177
     *
1178
     * @return AnonymousNode
1179
     */
1180
    public function argb(Node $color)
1181
    {
1182
        if (!$color instanceof ColorNode) {
1183
            return $color;
1184
        }
1185
1186
        return new AnonymousNode($color->toARGB());
1187
    }
1188
1189
    /**
1190
     * Creates a color.
1191
     *
1192
     * @param Node $hue The hue
1193
     * @param Node $saturation The saturation
1194
     * @param Node $lightness The lightness
1195
     *
1196
     * @return ColorNode
1197
     */
1198
    public function hsl($hue, $saturation, $lightness)
1199
    {
1200
        return $this->hsla($hue, $saturation, $lightness, 1);
1201
    }
1202
1203
    /**
1204
     * Creates a color from hsla color namespace.
1205
     *
1206
     * @param mixed $hue
1207
     * @param mixed $saturation
1208
     * @param mixed $lightness
1209
     * @param mixed $alpha
1210
     *
1211
     * @return ColorNode
1212
     */
1213
    public function hsla($hue, $saturation, $lightness, $alpha)
1214
    {
1215
        $hue = fmod($this->number($hue), 360) / 360; // Classic % operator will change float to int
1216
        $saturation = $this->clamp($this->number($saturation));
1217
        $lightness = $this->clamp($this->number($lightness));
1218
        $alpha = $this->clamp($this->number($alpha));
1219
1220
        $m2 = $lightness <= 0.5 ? $lightness * ($saturation + 1) : $lightness + $saturation - $lightness * $saturation;
1221
        $m1 = $lightness * 2 - $m2;
1222
1223
        return $this->rgba(
1224
            $this->hslaHue($hue + 1 / 3, $m1, $m2) * 255,
0 ignored issues
show
$this->hslaHue($hue + 1 / 3, $m1, $m2) * 255 is of type double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1225
            $this->hslaHue($hue, $m1, $m2) * 255,
0 ignored issues
show
$this->hslaHue($hue, $m1, $m2) * 255 is of type double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1226
            $this->hslaHue($hue - 1 / 3, $m1, $m2) * 255,
0 ignored issues
show
$this->hslaHue($hue - 1 / 3, $m1, $m2) * 255 is of type double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1227
            $alpha
1228
        );
1229
    }
1230
1231
    /**
1232
     * Helper for hsla().
1233
     *
1234
     * @param float $h
1235
     * @param float $m1
1236
     * @param float $m2
1237
     *
1238
     * @return float
1239
     */
1240
    protected function hslaHue($h, $m1, $m2)
1241
    {
1242
        $h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
1243
        if ($h * 6 < 1) {
1244
            return $m1 + ($m2 - $m1) * $h * 6;
1245
        } elseif ($h * 2 < 1) {
1246
            return $m2;
1247
        } elseif ($h * 3 < 2) {
1248
            return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
1249
        }
1250
1251
        return $m1;
1252
    }
1253
1254
    /**
1255
     * Creates a color.
1256
     *
1257
     * @param int $hue The hue
1258
     * @param int $saturation The saturation
1259
     * @param int $value The value
1260
     */
1261
    public function hsv($hue, $saturation, $value)
1262
    {
1263
        return $this->hsva($hue, $saturation, $value, 1);
1264
    }
1265
1266
    /**
1267
     * Creates a color.
1268
     *
1269
     * @param int $hue The hue
1270
     * @param int $saturation The saturation
1271
     * @param int $value The value
1272
     * @param int $alpha The alpha channel
1273
     */
1274
    public function hsva($hue, $saturation, $value, $alpha)
1275
    {
1276
        $hue = (($this->number($hue) % 360) / 360) * 360;
1277
        $saturation = $this->number($saturation);
1278
        $value = $this->number($value);
1279
        $alpha = $this->number($alpha);
1280
1281
        $i = floor(($hue / 60) % 6);
1282
        $f = ($hue / 60) - $i;
1283
1284
        $vs = [
1285
            $value,
1286
            $value * (1 - $saturation),
1287
            $value * (1 - $f * $saturation),
1288
            $value * (1 - (1 - $f) * $saturation),
1289
        ];
1290
1291
        $perm = [
1292
            [0, 3, 1],
1293
            [2, 0, 1],
1294
            [1, 0, 3],
1295
            [1, 2, 0],
1296
            [3, 1, 0],
1297
            [0, 1, 2],
1298
        ];
1299
1300
        return $this->rgba(
1301
            $vs[$perm[$i][0]] * 255,
0 ignored issues
show
$vs[$perm[$i][0]] * 255 is of type integer|double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1302
            $vs[$perm[$i][1]] * 255,
0 ignored issues
show
$vs[$perm[$i][1]] * 255 is of type integer|double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1303
            $vs[$perm[$i][2]] * 255,
0 ignored issues
show
$vs[$perm[$i][2]] * 255 is of type integer|double, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1304
            $alpha
1305
        );
1306
    }
1307
1308
    /**
1309
     * Returns the `hue` channel of the $color in the HSL space.
1310
     *
1311
     * @param ColorNode $color
1312
     *
1313
     * @return DimensionNode
1314
     */
1315
    public function hue(ColorNode $color)
1316
    {
1317
        return $color->getHue();
1318
    }
1319
1320
    /**
1321
     * Returns the `saturation` channel of the $color in the HSL space.
1322
     *
1323
     * @param ColorNode $color
1324
     *
1325
     * @return DimensionNode
1326
     */
1327
    public function saturation(ColorNode $color)
1328
    {
1329
        return $color->getSaturation();
1330
    }
1331
1332
    /**
1333
     * Returns the 'lightness' channel of @color in the HSL space.
1334
     *
1335
     * @param ColorNode $color
1336
     *
1337
     * @return DimensionNode
1338
     */
1339
    public function lightness(ColorNode $color)
1340
    {
1341
        return $color->getLightness();
1342
    }
1343
1344
    /**
1345
     * Returns the `hue` channel of @color in the HSV space.
1346
     *
1347
     * @param ColorNode $color
1348
     *
1349
     * @return string
1350
     */
1351
    public function hsvhue(Node $color)
1352
    {
1353
        if (!$color instanceof ColorNode) {
1354
            return $color;
1355
        }
1356
        $hsv = $color->toHSV();
1357
1358
        return new DimensionNode(Math::round($hsv['h']));
1359
    }
1360
1361
    /**
1362
     * Returns the `saturation` channel of @color in the HSV space.
1363
     *
1364
     * @param ColorNode $color
1365
     *
1366
     * @return string
1367
     */
1368
    public function hsvsaturation(Node $color)
1369
    {
1370
        if (!$color instanceof ColorNode) {
1371
            return $color;
1372
        }
1373
        $hsv = $color->toHSV();
1374
1375
        return new DimensionNode(Math::round($hsv['s'] * 100), '%');
1376
    }
1377
1378
    /**
1379
     * Returns the 'value' channel of @color in the HSV space.
1380
     *
1381
     * @param ColorNode $color
1382
     *
1383
     * @return string
1384
     */
1385
    public function hsvvalue(Node $color)
1386
    {
1387
        if (!$color instanceof ColorNode) {
1388
            return $color;
1389
        }
1390
        $hsv = $color->toHSV();
1391
1392
        return new DimensionNode(Math::round($hsv['v'] * 100), '%');
1393
    }
1394
1395
    /**
1396
     * Returns the 'red' channel of @color.
1397
     *
1398
     * @param ColorNode $color
1399
     *
1400
     * @return string
1401
     */
1402
    public function red(ColorNode $color)
1403
    {
1404
        return $color->getRed();
1405
    }
1406
1407
    /**
1408
     * Returns the 'green' channel of @color.
1409
     *
1410
     * @param ColorNode $color
1411
     *
1412
     * @return string
1413
     */
1414
    public function green(ColorNode $color)
1415
    {
1416
        return $color->getGreen();
1417
    }
1418
1419
    /**
1420
     * Returns the 'blue' channel of @color.
1421
     *
1422
     * @param ColorNode $color
1423
     *
1424
     * @return DimensionNode
1425
     */
1426
    public function blue(ColorNode $color)
1427
    {
1428
        return $color->getBlue();
1429
    }
1430
1431
    /**
1432
     * Returns the 'alpha' channel of the $color.
1433
     *
1434
     * @param ColorNode $color The color
1435
     *
1436
     * @return DimensionNode
1437
     */
1438
    public function alpha(Node $color)
1439
    {
1440
        if (!$color instanceof ColorNode) {
1441
            return $color;
1442
        }
1443
1444
        return $color->getAlpha();
1445
    }
1446
1447
    /**
1448
     * Returns the 'luma' value (perceptual brightness) of the $color.
1449
     *
1450
     * @param ColorNode $color
1451
     *
1452
     * @return DimensionNode
1453
     */
1454
    public function luma(ColorNode $color)
1455
    {
1456
        return $color->getLuma();
1457
    }
1458
1459
    /**
1460
     * Returns the luminance of the color.
1461
     *
1462
     * @param ColorNode $color
1463
     *
1464
     * @return DimensionNode
1465
     */
1466
    public function luminance(ColorNode $color)
1467
    {
1468
        return $color->getLuminance();
1469
    }
1470
1471
    /**
1472
     * Return a color 10% points *more* saturated.
1473
     *
1474
     * @param ColorNode $color
1475
     * @param Node $percentage The percentage
1476
     * @param Node $method The color method
1477
     *
1478
     * @throws InvalidArgumentException
1479
     *
1480
     * @return ColorNode*
1481
     */
1482
    public function saturate(Node $color, Node $percentage = null, Node $method = null)
1483
    {
1484
        // filter: saturate(3.2);
1485
        // should be kept as is, so check for color
1486
        if ($color instanceof DimensionNode) {
1487
            return;
1488
        }
1489
1490
        $color = $this->getColorNode($color, 'Cannot saturate the color');
1491
1492
        $hsl = $color->toHSL();
1493
        $percentage = $percentage ? $percentage->value / 100 : 10;
1494
1495
        // relative
1496
        if ($method && $method->value === 'relative') {
1497
            $hsl['s'] += $hsl['s'] * $percentage;
1498
        } else {
1499
            $hsl['s'] += $percentage;
1500
        }
1501
1502
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $color->getAlpha());
1503
    }
1504
1505
    /**
1506
     * Return a color 10% points *less* saturated.
1507
     *
1508
     * @param ColorNode $color
1509
     * @param Node $percentage The percentage
1510
     * @param Node $method The color method
1511
     *
1512
     * @throws InvalidArgumentException
1513
     *
1514
     * @return ColorNode
1515
     */
1516
    public function desaturate(ColorNode $color, Node $percentage = null, Node $method = null)
1517
    {
1518
        $color = $this->getColorNode($color, 'Cannot desaturate the color');
1519
1520
        $hsl = $color->toHSL();
1521
        $percentage = $percentage ? $percentage->value / 100 : 10;
1522
1523
        // relative
1524
        if ($method && $method->value === 'relative') {
1525
            $hsl['s'] -= $hsl['s'] * $percentage;
1526
        } else {
1527
            $hsl['s'] -= $percentage;
1528
        }
1529
1530
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $color->getAlpha());
1531
    }
1532
1533
    /**
1534
     * Return a color 10% points *lighter*.
1535
     *
1536
     * @param Node $color
1537
     * @param Node $percentage The percentage (Default to 10%)
1538
     * @param Node $method The color method
1539
     *
1540
     * @throws InvalidArgumentException
1541
     *
1542
     * @return ColorNode
1543
     */
1544
    public function lighten(Node $color, Node $percentage = null, Node $method = null)
1545
    {
1546
        $color = $this->getColorNode($color, 'Cannot lighten the color');
1547
        $hsl = $color->toHSL();
1548
        $percentage = $percentage ? $percentage->value / 100 : 10;
1549
1550
        // relative
1551
        if ($method && $method->value === 'relative') {
1552
            $hsl['l'] += $hsl['l'] * $percentage;
1553
        } else {
1554
            $hsl['l'] += $percentage;
1555
        }
1556
1557
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $color->getAlpha());
1558
    }
1559
1560
    /**
1561
     * Return a color 10% points *darker*.
1562
     *
1563
     * @param Node $color
1564
     * @param DimensionNode $percentage The percentage (Default to 10%)
1565
     * @param Node $method The method
1566
     *
1567
     * @return ColorNode
1568
     *
1569
     * @throws InvalidArgumentException
1570
     */
1571
    public function darken(Node $color, DimensionNode $percentage = null, Node $method = null)
1572
    {
1573
        $color = $this->getColorNode($color, 'Cannot darken the color');
1574
        $hsl = $color->toHSL();
1575
        $percentage = $percentage ? $percentage->value / 100 : 10;
1576
1577
        // relative
1578
        if ($method && $method->value === 'relative') {
1579
            $hsl['l'] -= $hsl['l'] * $percentage;
1580
        } else {
1581
            $hsl['l'] -= $percentage;
1582
        }
1583
1584
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $color->getAlpha());
1585
    }
1586
1587
    /**
1588
     * Return a color 10% points *less* transparent.
1589
     *
1590
     * @param ColorNode $color
1591
     * @param DimensionNode $percentage The percentage (Default to 10%)
1592
     * @param Node $method The method
1593
     *
1594
     * @return ColorNode
1595
     *
1596
     * @throws InvalidArgumentException
1597
     */
1598
    public function fadein(ColorNode $color, DimensionNode $percentage = null, Node $method = null)
1599
    {
1600
        $color = $this->getColorNode($color, 'Cannot fade in the color');
1601
        $hsl = $color->toHSL();
1602
        $percentage = $percentage ? $percentage->value / 100 : 10;
1603
1604
        // relative
1605
        if ($method && $method->value === 'relative') {
1606
            $hsl['a'] += $hsl['a'] * $percentage;
1607
        } else {
1608
            $hsl['a'] += $percentage;
1609
        }
1610
1611
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
1612
    }
1613
1614
    /**
1615
     * Return a color 10% points *more* transparent.
1616
     *
1617
     * @param ColorNode $color
1618
     * @param DimensionNode $percentage The percentage (Default to 10%)
1619
     * @param Node $method The method
1620
     *
1621
     * @return ColorNode
1622
     *
1623
     * @throws InvalidArgumentException
1624
     */
1625
    public function fadeout(ColorNode $color, DimensionNode $percentage = null, Node $method = null)
1626
    {
1627
        $color = $this->getColorNode($color, 'Cannot fade in the color');
1628
        $hsl = $color->toHSL();
1629
        $percentage = $percentage ? $percentage->value / 100 : 10;
1630
1631
        // relative
1632
        if ($method && $method->value === 'relative') {
1633
            $hsl['a'] -= $hsl['a'] * $percentage;
1634
        } else {
1635
            $hsl['a'] -= $percentage;
1636
        }
1637
1638
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
1639
    }
1640
1641
    /**
1642
     * Return $color with 50% transparency.
1643
     *
1644
     * @param ColorNode $color
1645
     * @param DimensionNode $percentage
1646
     *
1647
     * @return string
1648
     */
1649
    public function fade(ColorNode $color, DimensionNode $percentage = null)
1650
    {
1651
        $hsl = $color->toHSL();
1652
1653
        if ($percentage && $percentage->unit->is('%')) {
1654
            $hsl['a'] = $percentage->value / 100;
1655
        } else {
1656
            $hsl['a'] = $percentage ? $percentage->value : 50;
1657
        }
1658
1659
        $hsl['a'] = $this->clamp($hsl['a']);
1660
1661
        return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
1662
    }
1663
1664
    /**
1665
     * Return a color with a 10 degree larger in hue.
1666
     *
1667
     * @param ColorNode $color
1668
     * @param DimensionNode $degrees
1669
     *
1670
     * @return ColorNode
1671
     */
1672
    public function spin(ColorNode $color, DimensionNode $degrees = null)
1673
    {
1674
        $degrees = $degrees ? $degrees->value : 10;
1675
        $hue = (string) fmod($color->getHue(true) + $degrees, 360);
1676
        $hue = $hue < 0 ? 360 + $hue : $hue;
1677
1678
        return $this->hsla($hue, $color->getSaturation(true), $color->getLightness(true), $color->getAlpha());
1679
    }
1680
1681
    /**
1682
     * Return a mix of $color1 and $color2 with given $weightPercentage (defaults to 50%).
1683
     *
1684
     * @param Node $color1
1685
     * @param Node $color2
1686
     * @param DimensionNode $weightPercentage
1687
     *
1688
     * @return ColorNode
1689
     *
1690
     * @link http://sass-lang.com
1691
     *
1692
     * @copyright 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
1693
     */
1694
    public function mix(Node $color1, Node $color2, DimensionNode $weightPercentage = null)
1695
    {
1696
        if (!$color1 instanceof ColorNode) {
1697
            return $color1;
1698
        } elseif (!$color2 instanceof ColorNode) {
1699
            return $color1;
1700
        }
1701
1702
        if (!$weightPercentage) {
1703
            $weightPercentage = new DimensionNode(50);
1704
        }
1705
1706
        $p = $weightPercentage->value / 100.0;
1707
        $w = $p * 2 - 1;
1708
        $a = $color1->getAlpha(true) - $color2->getAlpha(true);
1709
1710
        $w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
1711
        $w2 = 1 - $w1;
1712
1713
        $color1Rgb = $color1->getRGB();
1714
        $color2Rgb = $color2->getRGB();
1715
1716
        $rgb = [
1717
            $color1Rgb[0] * $w1 + $color2Rgb[0] * $w2,
1718
            $color1Rgb[1] * $w1 + $color2Rgb[1] * $w2,
1719
            $color1Rgb[2] * $w1 + $color2Rgb[2] * $w2,
1720
        ];
1721
1722
        $alpha = $color1->getAlpha(true) * $p + $color2->getAlpha(true) * (1 - $p);
1723
1724
        return new ColorNode($rgb, $alpha);
1725
    }
1726
1727
    /**
1728
     * Return a color mixed 10% with white.
1729
     *
1730
     * @param Node $color
1731
     *
1732
     * @return string
1733
     */
1734
    public function tint(Node $color, Node $percentage = null)
1735
    {
1736
        return $this->mix($this->rgb(255, 255, 255), $color, $percentage);
0 ignored issues
show
It seems like $percentage defined by parameter $percentage on line 1734 can also be of type object<ILess\Node>; however, ILess\FunctionRegistry::mix() does only seem to accept null|object<ILess\Node\DimensionNode>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1737
    }
1738
1739
    /**
1740
     * Return a color mixed 10% with black.
1741
     *
1742
     * @param Node $color
1743
     * @param Node $percentage
1744
     *
1745
     * @return string
1746
     */
1747
    public function shade(Node $color, Node $percentage = null)
1748
    {
1749
        return $this->mix($this->rgb(0, 0, 0), $color, $percentage);
0 ignored issues
show
It seems like $percentage defined by parameter $percentage on line 1747 can also be of type object<ILess\Node>; however, ILess\FunctionRegistry::mix() does only seem to accept null|object<ILess\Node\DimensionNode>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1750
    }
1751
1752
    /**
1753
     * Returns a grey, 100% desaturated color.
1754
     *
1755
     * @param ColorNode $color
1756
     *
1757
     * @return string
1758
     */
1759
    public function greyscale(ColorNode $color)
1760
    {
1761
        return $this->desaturate($color, new DimensionNode(100));
1762
    }
1763
1764
    /**
1765
     * Return @darkColor if @color is > 43% luma otherwise return @lightColor, see notes.
1766
     *
1767
     * @param Node $color
1768
     * @param ColorNode|null $darkColor
1769
     * @param ColorNode|null $lightColor
1770
     * @param DimensionNode|null $thresholdPercentage
1771
     *
1772
     * @return ColorNode
1773
     */
1774
    public function contrast(
1775
        Node $color,
1776
        ColorNode $darkColor = null,
1777
        ColorNode $lightColor = null,
1778
        DimensionNode $thresholdPercentage = null
1779
    ) {
1780
        // ping pong back
1781
        // filter: contrast(3.2);
1782
        // should be kept as is, so check for color
1783
        if (!$color instanceof ColorNode) {
1784
            if ($color instanceof DimensionNode ||
1785
                !$color instanceof ToColorConvertibleInterface
1786
            ) {
1787
                return;
1788
            }
1789
            $color = $color->toColor();
1790
        }
1791
1792
        if (!$lightColor) {
1793
            $lightColor = $this->rgba(255, 255, 255, 1);
0 ignored issues
show
255 is of type integer, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1794
        }
1795
1796
        if (!$darkColor) {
1797
            $darkColor = $this->rgba(0, 0, 0, 1);
0 ignored issues
show
0 is of type integer, but the function expects a object<ILess\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1798
        }
1799
1800
        //Figure out which is actually light and dark!
1801
        if ($darkColor->getLuma(true) > $lightColor->getLuma(true)) {
1802
            $t = $lightColor;
1803
            $lightColor = $darkColor;
1804
            $darkColor = $t;
1805
        }
1806
1807
        if (!$thresholdPercentage) {
1808
            $thresholdPercentage = 0.43;
1809
        } else {
1810
            $thresholdPercentage = $this->number($thresholdPercentage);
1811
        }
1812
1813
        if (($color->getLuma(true)) < $thresholdPercentage) {
1814
            return $lightColor;
1815
        } else {
1816
            return $darkColor;
1817
        }
1818
    }
1819
1820
    /**
1821
     * Multiplies the $color1 with $color2.
1822
     *
1823
     * @param ColorNode $color1 The first color
1824
     * @param ColorNode $color2 The second color
1825
     *
1826
     * @return ColorNode
1827
     */
1828
    public function multiply(ColorNode $color1, ColorNode $color2)
1829
    {
1830
        return $this->colorBlend([$this, 'colorBlendMultiply'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1831
    }
1832
1833
    /**
1834
     * Screen.
1835
     *
1836
     * @param ColorNode $color1 The first color
1837
     * @param ColorNode $color2 The second color
1838
     *
1839
     * @return ColorNode
1840
     */
1841
    public function screen(ColorNode $color1, ColorNode $color2)
1842
    {
1843
        return $this->colorBlend([$this, 'colorBlendScreen'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1844
    }
1845
1846
    /**
1847
     * Combines the effects of both multiply and screen. Conditionally make light channels lighter
1848
     * and dark channels darker. Note: The results of the conditions are determined by the
1849
     * first color parameter.
1850
     *
1851
     * @param ColorNode $color1 A base color object. Also the determinant color to make the result lighter or darker.
1852
     * @param ColorNode $color2 A color object to overlay.
1853
     *
1854
     * @return ColorNode
1855
     *
1856
     * @see http://lesscss.org/functions/#color-blending-overlay
1857
     */
1858
    public function overlay(ColorNode $color1, ColorNode $color2)
1859
    {
1860
        return $this->colorBlend([$this, 'colorBlendOverlay'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1861
    }
1862
1863
    /**
1864
     * Softlight - Similar to overlay but avoids pure black resulting in pure black,
1865
     * and pure white resulting in pure white.
1866
     *
1867
     * @param ColorNode $color1 The first color
1868
     * @param ColorNode $color2 The second color
1869
     *
1870
     * @return ColorNode
1871
     *
1872
     * @see http://lesscss.org/functions/#color-blending-softlight
1873
     */
1874
    public function softlight(ColorNode $color1, ColorNode $color2)
1875
    {
1876
        return $this->colorBlend([$this, 'colorBlendSoftlight'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1877
    }
1878
1879
    /**
1880
     * Hardlight filter.
1881
     *
1882
     * @param ColorNode $color1 The first color
1883
     * @param ColorNode $color2 The second color
1884
     *
1885
     * @return ColorNode
1886
     */
1887
    public function hardlight(ColorNode $color1, ColorNode $color2)
1888
    {
1889
        return $this->colorBlend([$this, 'colorBlendHardlight'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1890
    }
1891
1892
    /**
1893
     * Difference.
1894
     *
1895
     * @param ColorNode $color1 The first color
1896
     * @param ColorNode $color2 The second color
1897
     *
1898
     * @return ColorNode
1899
     */
1900
    public function difference(ColorNode $color1, ColorNode $color2)
1901
    {
1902
        return $this->colorBlend([$this, 'colorBlendDifference'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1903
    }
1904
1905
    /**
1906
     * Exclusion.
1907
     *
1908
     * @param ColorNode $color1 The first color
1909
     * @param ColorNode $color2 The second color
1910
     *
1911
     * @return ColorNode
1912
     */
1913
    public function exclusion(ColorNode $color1, ColorNode $color2)
1914
    {
1915
        return $this->colorBlend([$this, 'colorBlendExclusion'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1916
    }
1917
1918
    /**
1919
     * Average.
1920
     *
1921
     * @param ColorNode $color1 The first color
1922
     * @param ColorNode $color2 The second color
1923
     *
1924
     * @return ColorNode
1925
     */
1926
    public function average(ColorNode $color1, ColorNode $color2)
1927
    {
1928
        return $this->colorBlend([$this, 'colorBlendAverage'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1929
    }
1930
1931
    /**
1932
     * Negation.
1933
     *
1934
     * @param ColorNode $color1 The first color
1935
     * @param ColorNode $color2 The second color
1936
     *
1937
     * @return ColorNode
1938
     */
1939
    public function negation(ColorNode $color1, ColorNode $color2)
1940
    {
1941
        return $this->colorBlend([$this, 'colorBlendNegation'], $color1->getColor(), $color2->getColor());
0 ignored issues
show
$color1->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$color2->getColor() is of type object<ILess\Node>|string, but the function expects a object<ILess\Color>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1942
    }
1943
1944
    /**
1945
     * Returns true if passed a color, including keyword colors.
1946
     *
1947
     * @param Node $colorOrAnything
1948
     *
1949
     * @return KeywordNode
1950
     */
1951
    public function iscolor(Node $colorOrAnything)
1952
    {
1953
        return $this->isA($colorOrAnything, 'ILess\Node\ColorNode');
1954
    }
1955
1956
    /**
1957
     * Returns true if a number of any unit.
1958
     *
1959
     * @param Node $numberOrAnything
1960
     *
1961
     * @return KeywordNode
1962
     */
1963
    public function isnumber(Node $numberOrAnything)
1964
    {
1965
        return $this->isA($numberOrAnything, 'ILess\Node\DimensionNode');
1966
    }
1967
1968
    /**
1969
     * Returns true if it is passed a string.
1970
     *
1971
     * @param Node $stringOrAnything
1972
     *
1973
     * @return KeywordNode
1974
     */
1975
    public function isstring(Node $stringOrAnything)
1976
    {
1977
        return $this->isA($stringOrAnything, 'ILess\Node\QuotedNode');
1978
    }
1979
1980
    /**
1981
     * Returns true if it is passed keyword.
1982
     *
1983
     * @param Node $numberOrAnything
1984
     *
1985
     * @return KeywordNode
1986
     */
1987
    public function iskeyword(Node $keywordOrAnything)
1988
    {
1989
        return $this->isA($keywordOrAnything, 'ILess\Node\KeywordNode');
1990
    }
1991
1992
    /**
1993
     * Returns true if it is a string and a url.
1994
     *
1995
     * @param mixed $urlOrAnything
1996
     *
1997
     * @return bool
1998
     */
1999
    public function isurl(Node $urlOrAnything)
2000
    {
2001
        return $this->isA($urlOrAnything, 'ILess\Node\UrlNode');
2002
    }
2003
2004
    /**
2005
     * Returns true if it is a number and a px.
2006
     *
2007
     * @param Node $urlOrAnything The node to check
2008
     *
2009
     * @return KeywordNode
2010
     */
2011
    public function ispixel(Node $pixelOrAnything)
2012
    {
2013
        if ($this->isA($pixelOrAnything, 'ILess\Node\DimensionNode') && $pixelOrAnything->unit->is('px')) {
2014
            return new KeywordNode('true');
2015
        }
2016
2017
        return new KeywordNode('false');
2018
    }
2019
2020
    /**
2021
     * Returns true if it is a number and a %.
2022
     *
2023
     * @param Node $percentageOrAnything
2024
     *
2025
     * @return KeywordNode
2026
     */
2027
    public function ispercentage(Node $percentageOrAnything)
2028
    {
2029
        if ($this->isA($percentageOrAnything, 'ILess\Node\DimensionNode') && $percentageOrAnything->unit->is('%')) {
2030
            return new KeywordNode('true');
2031
        }
2032
2033
        return new KeywordNode('false');
2034
    }
2035
2036
    /**
2037
     * Returns true if it is a number and an em.
2038
     *
2039
     * @param Node $emOrAnything
2040
     *
2041
     * @return KeywordNode
2042
     */
2043
    public function isem(Node $emOrAnything)
2044
    {
2045
        if ($this->isA($emOrAnything, 'ILess\Node\DimensionNode') && $emOrAnything->unit->is('em')) {
2046
            return new KeywordNode('true');
2047
        }
2048
2049
        return new KeywordNode('false');
2050
    }
2051
2052
    /**
2053
     * returns if a parameter is a number and is in a particular unit.
2054
     *
2055
     * @param Node $node
2056
     * @param Node $unit The unit to check
2057
     *
2058
     * @return bool
2059
     */
2060
    public function isunit(Node $node, Node $unit = null)
2061
    {
2062
        if ($this->isA($node, 'ILess\Node\DimensionNode')
2063
            && $node->unit->is((property_exists($unit, 'value') ? $unit->value : $unit))
2064
        ) {
2065
            return new KeywordNode('true');
2066
        }
2067
2068
        return new KeywordNode('false');
2069
    }
2070
2071
    /**
2072
     * Returns true if the node is detached ruleset.
2073
     *
2074
     * @param Node $node
2075
     *
2076
     * @return bool
2077
     */
2078
    public function isruleset(Node $node)
2079
    {
2080
        return $this->isA($node, 'ILess\Node\DetachedRulesetNode');
2081
    }
2082
2083
    /**
2084
     * Creates a SVG gradient.
2085
     *
2086
     * @param Node $direction
2087
     * @param Node ...$stop1
2088
     *
2089
     * @return UrlNode
2090
     *
2091
     * @throws CompilerException If the arguments are invalid
2092
     */
2093
    public function svggradient(Node $direction /*  $stop1, $stop2, ... */)
2094
    {
2095
        $numArgs = func_num_args();
2096
        $arguments = func_get_args();
2097
2098
        if ($numArgs === 2) {
2099
            // a list of colors
2100
            if (is_array($arguments[1]->value) && count($arguments[1]->value) < 2) {
2101
                throw new CompilerException(
2102
                    'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]'
2103
                );
2104
            }
2105
            $stops = $arguments[1]->value;
2106
        } elseif ($numArgs < 3) {
2107
            throw new CompilerException(
2108
                'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]'
2109
            );
2110
        } else {
2111
            $stops = array_slice($arguments, 1);
2112
        }
2113
2114
        $gradientType = 'linear';
2115
        $rectangleDimension = 'x="0" y="0" width="1" height="1"';
2116
        $renderEnv = new Context([
2117
            'compress' => false,
2118
        ]);
2119
        $directionValue = $direction->toCSS($renderEnv);
2120
2121
        switch ($directionValue) {
2122
            case 'to bottom':
2123
                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
2124
                break;
2125
            case 'to right':
2126
                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
2127
                break;
2128
            case 'to bottom right':
2129
                $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
2130
                break;
2131
            case 'to top right':
2132
                $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
2133
                break;
2134
            case 'ellipse':
2135
            case 'ellipse at center':
2136
                $gradientType = 'radial';
2137
                $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
2138
                $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
2139
                break;
2140
            default:
2141
                throw new CompilerException(
2142
                    "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"
2143
                );
2144
        }
2145
2146
        $returner = '<?xml version="1.0" ?>' .
2147
            '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
2148
            '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
2149
2150
        for ($i = 0; $i < count($stops); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2151
            if ($stops[$i] instanceof ExpressionNode) {
2152
                $color = $stops[$i]->value[0];
2153
                $position = $stops[$i]->value[1];
2154
            } else {
2155
                $color = $stops[$i];
2156
                $position = null;
2157
            }
2158
2159
            if (!($color instanceof ColorNode)
2160
                || (!(($i === 0 || $i + 1 === count($stops)) && $position === null)
2161
                    && !($position instanceof DimensionNode))
2162
            ) {
2163
                throw new CompilerException(
2164
                    'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] or direction, color list'
2165
                );
2166
            }
2167
2168
            if ($position) {
2169
                $positionValue = $position->toCSS($renderEnv);
2170
            } elseif ($i === 0) {
2171
                $positionValue = '0%';
2172
            } else {
2173
                $positionValue = '100%';
2174
            }
2175
2176
            $colorValue = $color->getColor()->toRGB();
0 ignored issues
show
The method toRGB() does not seem to exist on object<ILess\Node>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2177
2178
            $alpha = $color->getAlpha(true);
2179
            $returner .= '<stop offset="' . $positionValue . '" stop-color="' .
2180
                $colorValue . '"' .
2181
                ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
2182
        }
2183
2184
        $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
2185
2186
        $returner = Util::encodeURIComponent($returner);
2187
        $returner = 'data:image/svg+xml,' . $returner;
2188
2189
        return new UrlNode(new QuotedNode("'" . $returner . "'", $returner, false));
2190
    }
2191
2192
    /**
2193
     * Default function.
2194
     *
2195
     * @return KeywordNode|null
2196
     *
2197
     * @throws Exception
2198
     */
2199
    public function defaultFunc()
2200
    {
2201
        return DefaultFunc::compile();
2202
    }
2203
2204
    /**
2205
     * Returns the image size.
2206
     *
2207
     * @param Node $node The path node
2208
     *
2209
     * @return ExpressionNode
2210
     *
2211
     * @throws IOException
2212
     */
2213
    public function imageSize(Node $node)
2214
    {
2215
        $size = $this->getImageSize($node);
2216
2217
        return new ExpressionNode(
2218
            [
2219
                new DimensionNode($size['width'], 'px'),
2220
                new DimensionNode($size['height'], 'px'),
2221
            ]
2222
        );
2223
    }
2224
2225
    /**
2226
     * Returns the image width.
2227
     *
2228
     * @param Node $node The path node
2229
     *
2230
     * @return DimensionNode
2231
     *
2232
     * @throws IOException
2233
     */
2234
    public function imageWidth(Node $node)
2235
    {
2236
        $size = $this->getImageSize($node);
2237
2238
        return new DimensionNode($size['width'], 'px');
2239
    }
2240
2241
    /**
2242
     * Returns the image height.
2243
     *
2244
     * @param Node $node The path node
2245
     *
2246
     * @return DimensionNode
2247
     *
2248
     * @throws IOException
2249
     */
2250
    public function imageHeight(Node $node)
2251
    {
2252
        $size = $this->getImageSize($node);
2253
2254
        return new DimensionNode($size['height'], 'px');
2255
    }
2256
2257
    /**
2258
     * Returns the width and height of the image.
2259
     *
2260
     * @param Node $path
2261
     *
2262
     * @throws IOException
2263
     *
2264
     * @return array
2265
     */
2266
    protected function getImageSize(Node $path)
2267
    {
2268
        $filePath = $path->value;
2269
2270
        $fragmentStart = strpos($filePath, '#');
2271
        // $fragment = '';
2272
        if ($fragmentStart !== false) {
2273
            // $fragment = substr($filePath, $fragmentStart);
2274
            $filePath = substr($filePath, 0, $fragmentStart);
2275
        }
2276
2277
        $filePath = $this->getFilePath($filePath);
0 ignored issues
show
It seems like $filePath can also be of type object<ILess\Node>; however, ILess\FunctionRegistry::getFilePath() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2278
2279
        if (!is_readable($filePath)) {
2280
            throw new IOException(sprintf('The file "%s" is does not exist or is not readable', $filePath));
2281
        }
2282
2283
        $size = @getimagesize($filePath);
2284
2285
        if ($size === false) {
2286
            throw new IOException(
2287
                sprintf('The file "%s" dimension could not be read. It is an image?', $filePath)
2288
            );
2289
        }
2290
2291
        return [
2292
            'width' => $size[0],
2293
            'height' => $size[1],
2294
        ];
2295
    }
2296
2297
    /**
2298
     * Returns the file path, takes care about relative urls.
2299
     *
2300
     * @param string $path
2301
     *
2302
     * @return mixed|string
2303
     */
2304
    protected function getFilePath($path)
2305
    {
2306
        $path = Util::sanitizePath($path);
2307
2308
        if (Util::isPathRelative($path) && $this->currentFileInfo) {
2309
            if ($this->context->relativeUrls) {
2310
                $path = $this->currentFileInfo->currentDirectory . $path;
2311
            } else {
2312
                $path = $this->currentFileInfo->entryPath . $path;
2313
            }
2314
            $path = Util::normalizePath($path);
2315
        }
2316
2317
        return $path;
2318
    }
2319
2320
    protected function doMinmax($isMin, $args)
2321
    {
2322
        switch (count($args)) {
2323
            case 0:
2324
                throw new CompilerException('One or more arguments required.');
2325
        }
2326
2327
        $order = []; // elems only contains original argument values.
2328
        $values = []; // key is the unit.toString() for unified tree.Dimension values,
2329
        $unitClone = $unitStatic = $j = null;
2330
2331
        // value is the index into the order array.
2332
        for ($i = 0; $i < count($args); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2333
            $current = $args[$i];
2334
            if (!($current instanceof DimensionNode)) {
2335
                if (is_array($args[$i])) {
2336
                    $args[] = $args[$i]->value;
2337
                }
2338
                continue;
2339
            }
2340
2341
            if ($current->unit->toString() === '' && $unitClone !== null) {
2342
                $dim = new DimensionNode($current->value, $unitClone);
0 ignored issues
show
It seems like $current->value can also be of type object<ILess\Node>; however, ILess\Node\DimensionNode::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2343
                $currentUnified = $dim->unify();
2344
            } else {
2345
                $currentUnified = $current->unify();
2346
            }
2347
2348
            $unit = $currentUnified->unit->toString() === '' && $unitStatic !== null ? $unitStatic : $currentUnified->unit->toString();
2349
            // $unitStatic = $unit !== '' && $unitStatic === null || $unit !== '' && $order[0]->unify()->unit->toString() === '' ? $unit : $unitStatic;
2350
2351
            if ($unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === '') {
2352
                $unitStatic = $unit;
2353
            }
2354
2355
            $unitClone = $unit !== '' && $unitClone === null ? $current->unit->toString() : $unitClone;
2356
2357
            if (isset($values['']) && $unit !== '' && $unit === $unitStatic) {
2358
                $j = $values[''];
2359
            } elseif (isset($values[$unit])) {
2360
                $j = $values[$unit];
2361
            } else {
2362
                if ($unitStatic !== null && $unit !== $unitStatic) {
2363
                    throw new RuntimeException(sprintf('Incompatible types "%s" and "%s" given', $unitStatic, $unit));
2364
                }
2365
2366
                $values[$unit] = count($order);
2367
                $order[] = $current;
2368
                continue;
2369
            }
2370
2371
            if ($order[$j]->unit->toString() === '' && $unitClone !== null) {
2372
                $dim = new DimensionNode($order[$j]->value, $unitClone);
2373
                $referenceUnified = $dim->unify();
2374
            } else {
2375
                $referenceUnified = $order[$j]->unify();
2376
            }
2377
2378
            if (($isMin && $currentUnified->value < $referenceUnified->value) ||
2379
                (!$isMin && $currentUnified->value > $referenceUnified->value)
2380
            ) {
2381
                $order[$j] = $current;
2382
            }
2383
        }
2384
2385
        if (count($order) === 1) {
2386
            return $order[0];
2387
        }
2388
2389
        foreach ($order as $k => $a) {
2390
            $order[$k] = $a->toCSS($this->context);
2391
        }
2392
2393
        $args = implode(($this->context->compress ? ',' : ', '), $order);
2394
2395
        return new AnonymousNode(($isMin ? 'min' : 'max') . '(' . $args . ')');
2396
    }
2397
2398
    /**
2399
     * Checks if the given object is of this class or has this class as one of its parents.
2400
     *
2401
     * @param Node $node
2402
     * @param string $className The className to check
2403
     *
2404
     * @return KeywordNode
2405
     */
2406
    protected function isA(Node $node, $className)
2407
    {
2408
        if (is_a($node, $className)) {
2409
            return new KeywordNode('true');
2410
        }
2411
2412
        return new KeywordNode('false');
2413
    }
2414
2415
    /**
2416
     * Clamps the value.
2417
     *
2418
     * @param int $value
2419
     *
2420
     * @return int
2421
     */
2422
    protected function clamp($value)
2423
    {
2424
        return min(1, max(0, $value));
2425
    }
2426
2427
    /**
2428
     * Convert the given node to color node.
2429
     *
2430
     * @param Node $node The node
2431
     * @param null $exceptionMessage ILess\Exception\Exception message if the node could not be converted to color node
2432
     *
2433
     * @return ColorNode
2434
     *
2435
     * @throws InvalidArgumentException If the node could not be converted to color
2436
     */
2437
    protected function getColorNode(Node $node, $exceptionMessage = null)
2438
    {
2439
        if ($node instanceof ColorNode) {
2440
            return $node;
2441
        }
2442
2443
        // this is a keyword
2444
        if ($node instanceof KeywordNode && Color::isNamedColor($node->value)) {
0 ignored issues
show
It seems like $node->value can also be of type object<ILess\Node>; however, ILess\Color::isNamedColor() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2445
            $node = new ColorNode(Color::color($node->value));
0 ignored issues
show
It seems like $node->value can also be of type object<ILess\Node>; however, ILess\Color::color() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
It seems like \ILess\Color::color($node->value) targeting ILess\Color::color() can also be of type false; however, ILess\Node\ColorNode::__construct() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
2446
        } elseif ($node instanceof ToColorConvertibleInterface) {
2447
            $node = $node->toColor();
2448
        }
2449
2450
        if (!$node instanceof ColorNode) {
2451
            throw new InvalidArgumentException($exceptionMessage ? $exceptionMessage : 'Cannot convert node to color');
2452
        }
2453
2454
        return $node;
2455
    }
2456
2457
    /**
2458
     * Color blending.
2459
     *
2460
     * @param callable $mode
2461
     * @param Color $color1
2462
     * @param Color $color2
2463
     *
2464
     * @return ColorNode
2465
     */
2466
    protected function colorBlend(callable $mode, Color $color1, Color $color2)
2467
    {
2468
        $ab = $color1->getAlpha();    // backdrop
2469
        $as = $color2->getAlpha();    // source
2470
        $r = [];            // result
2471
2472
        $ar = $as + $ab * (1 - $as);
2473
        $rgb1 = $color1->rgb;
2474
        $rgb2 = $color2->rgb;
2475
        for ($i = 0; $i < 3; ++$i) {
2476
            $cb = $rgb1[$i] / 255;
2477
            $cs = $rgb2[$i] / 255;
2478
            $cr = call_user_func($mode, $cb, $cs);
2479
            if ($ar) {
2480
                $cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
2481
            }
2482
            $r[$i] = $cr * 255;
2483
        }
2484
2485
        return new ColorNode($r, $ar);
2486
    }
2487
2488
    private function colorBlendMultiply($cb, $cs)
2489
    {
2490
        return $cb * $cs;
2491
    }
2492
2493
    private function colorBlendScreen($cb, $cs)
2494
    {
2495
        return $cb + $cs - $cb * $cs;
2496
    }
2497
2498
    private function colorBlendOverlay($cb, $cs)
2499
    {
2500
        $cb *= 2;
2501
2502
        return ($cb <= 1)
2503
            ? $this->colorBlendMultiply($cb, $cs)
2504
            : $this->colorBlendScreen($cb - 1, $cs);
2505
    }
2506
2507
    private function colorBlendSoftlight($cb, $cs)
2508
    {
2509
        $d = 1;
2510
        $e = $cb;
2511
        if ($cs > 0.5) {
2512
            $e = 1;
2513
            $d = ($cb > 0.25) ? sqrt($cb)
2514
                : ((16 * $cb - 12) * $cb + 4) * $cb;
2515
        }
2516
2517
        return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
2518
    }
2519
2520
    private function colorBlendHardlight($cb, $cs)
2521
    {
2522
        return $this->colorBlendOverlay($cs, $cb);
2523
    }
2524
2525
    private function colorBlendDifference($cb, $cs)
2526
    {
2527
        return abs($cb - $cs);
2528
    }
2529
2530
    private function colorBlendExclusion($cb, $cs)
2531
    {
2532
        return $cb + $cs - 2 * $cb * $cs;
2533
    }
2534
2535
    private function colorBlendAverage($cb, $cs)
2536
    {
2537
        return ($cb + $cs) / 2;
2538
    }
2539
2540
    private function colorBlendNegation($cb, $cs)
2541
    {
2542
        return 1 - abs($cb + $cs - 1);
2543
    }
2544
}
2545