Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
94 / 94
100.00% covered (success)
100.00%
30 / 30
CRAP
100.00% covered (success)
100.00%
1 / 1
Selector
100.00% covered (success)
100.00%
94 / 94
100.00% covered (success)
100.00%
30 / 30
57
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isElementSelector
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isIdSelector
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isClassSelector
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMultipleSelector
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDescendant
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTabSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTabSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setProperty
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setProperties
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProperties
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeProperty
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 minify
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isMinified
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
16
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Css;
15
16use ArrayIterator;
17
18/**
19 * Pop CSS selector class
20 *
21 * @category   Pop
22 * @package    Pop\Css
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    2.0.0
27 */
28class Selector implements \ArrayAccess, \Countable, \IteratorAggregate
29{
30
31    /**
32     * Trait declaration
33     */
34    use CommentTrait;
35
36    /**
37     * Selector name
38     * @var ?string
39     */
40    protected ?string $name = null;
41
42    /**
43     * Properties
44     * @var array
45     */
46    protected array $properties = [];
47
48    /**
49     * Tab size
50     * @var int
51     */
52    protected int $tabSize = 4;
53
54    /**
55     * Is ID selector flag
56     * @var bool
57     */
58    protected bool $isId = false;
59
60    /**
61     * Is class selector flag
62     * @var bool
63     */
64    protected bool $isClass = false;
65
66    /**
67     * Minify flag
68     * @var bool
69     */
70    protected bool $minify = false;
71
72    /**
73     * Constructor
74     *
75     * Instantiate the CSS selector object
76     *
77     * @param ?string $name
78     * @param int    $tabSize
79     */
80    public function __construct(?string $name = null, int $tabSize = 4)
81    {
82        if ($name !== null) {
83            $this->setName($name);
84        }
85        $this->setTabSize($tabSize);
86    }
87
88    /**
89     * Set name
90     *
91     * @param  string $name
92     * @return Selector
93     */
94    public function setName(string $name): Selector
95    {
96        if (str_contains($name, '.')) {
97            $this->isClass = true;
98        }
99        if (str_contains($name, '#')) {
100            $this->isId = true;
101        }
102        $this->name = $name;
103        return $this;
104    }
105
106    /**
107     * Get name
108     *
109     * @return string|null
110     */
111    public function getName(): string|null
112    {
113        return $this->name;
114    }
115
116    /**
117     * Check if is element selector
118     *
119     * @return bool
120     */
121    public function isElementSelector(): bool
122    {
123        return (!($this->isId) && !($this->isClass));
124    }
125
126    /**
127     * Check if is ID selector
128     *
129     * @return bool
130     */
131    public function isIdSelector(): bool
132    {
133        return $this->isId;
134    }
135
136    /**
137     * Check if is class selector
138     *
139     * @return bool
140     */
141    public function isClassSelector(): bool
142    {
143        return $this->isClass;
144    }
145
146    /**
147     * Check if is multiple selector
148     *
149     * @return bool
150     */
151    public function isMultipleSelector(): bool
152    {
153        return (str_contains($this->name, ','));
154    }
155
156    /**
157     * Check if selector has a descendant
158     *
159     * @return bool
160     */
161    public function hasDescendant(): bool
162    {
163        return (str_contains($this->name, '>'));
164    }
165
166    /**
167     * Set tab size
168     *
169     * @param  int $tabSize
170     * @return Selector
171     */
172    public function setTabSize(int $tabSize): Selector
173    {
174        $this->tabSize = $tabSize;
175        return $this;
176    }
177
178    /**
179     * Get tab size
180     *
181     * @return int
182     */
183    public function getTabSize(): int
184    {
185        return $this->tabSize;
186    }
187
188    /**
189     * Set property
190     *
191     * @param  string $property
192     * @param  string $value
193     * @return Selector
194     */
195    public function setProperty(string $property, string $value): Selector
196    {
197        $this->properties[$property] = $value;
198        return $this;
199    }
200
201    /**
202     * Set properties
203     *
204     * @param  array $properties
205     * @return Selector
206     */
207    public function setProperties(array $properties): Selector
208    {
209        foreach ($properties as $property => $value) {
210            $this->setProperty($property, $value);
211        }
212        return $this;
213    }
214
215    /**
216     * Check if selector has property
217     *
218     * @param  string $property
219     * @return bool
220     */
221    public function hasProperty(string $property): bool
222    {
223        return isset($this->properties[$property]);
224    }
225
226    /**
227     * Get properties
228     *
229     * @return array
230     */
231    public function getProperties(): array
232    {
233        return $this->properties;
234    }
235
236    /**
237     * Get property
238     *
239     * @param  string $property
240     * @return string|null
241     */
242    public function getProperty(string $property): string|null
243    {
244        return $this->properties[$property] ?? null;
245    }
246
247    /**
248     * Remove property
249     *
250     * @param  string $property
251     * @return Selector
252     */
253    public function removeProperty(string $property): Selector
254    {
255        if (isset($this->properties[$property])) {
256            unset($this->properties[$property]);
257        }
258        return $this;
259    }
260
261    /**
262     * Set minify flag
263     *
264     * @param  bool $minify
265     * @return Selector
266     */
267    public function minify(bool $minify = true): Selector
268    {
269        $this->minify = $minify;
270        return $this;
271    }
272
273    /**
274     * Check if minify flag is set
275     *
276     * @return bool
277     */
278    public function isMinified(): bool
279    {
280        return $this->minify;
281    }
282
283    /**
284     * Method to iterate over the properties
285     *
286     * @return ArrayIterator
287     */
288    public function getIterator(): ArrayIterator
289    {
290        return new ArrayIterator($this->properties);
291    }
292
293    /**
294     * Method to get the count of properties
295     *
296     * @return int
297     */
298    public function count(): int
299    {
300        return count($this->properties);
301    }
302
303    /**
304     * Method to render the selector CSS
305     *
306     * @return string
307     */
308    public function render(): string
309    {
310        $css = '';
311
312        if (!$this->minify) {
313            foreach ($this->comments as $comment) {
314                $css .= (string)$comment . PHP_EOL;
315            }
316        }
317
318        if (!$this->minify) {
319            $css .= $this->name . ' {' . PHP_EOL;
320
321            foreach ($this->properties as $property => $value) {
322                $css .= str_repeat(' ', $this->tabSize) . $property . ': ' . $value . ';' . PHP_EOL;
323            }
324
325            $css .= '}' . PHP_EOL;
326        } else {
327            $css .= $this->name . '{';
328            foreach ($this->properties as $property => $value) {
329                $css .= $property . ':' . $value . ';';
330            }
331
332            $css .= '}';
333        }
334
335        return $css;
336    }
337
338    /**
339     * To string method
340     *
341     * @return string
342     */
343    public function __toString(): string
344    {
345        return $this->render();
346    }
347
348    /**
349     * Magic method to set the property to the value of $this->properties[$name]
350     *
351     * @param  string $name
352     * @param  mixed $value
353     * @return void
354     */
355    public function __set(string $name, mixed $value): void
356    {
357        $this->properties[$name] = $value;
358    }
359
360    /**
361     * Magic method to return the value of $this->properties[$name]
362     *
363     * @param  string $name
364     * @return mixed
365     */
366    public function __get(string $name): mixed
367    {
368        if ((str_contains($name, 'margin-')) && !isset($this->properties[$name]) && isset($this->properties['margin'])) {
369            $values         = explode(' ', $this->properties['margin']);
370            $position       = substr($name, strpos($name, '-') + 1);
371            $positionValues = ['top' => null, 'right' => null, 'bottom' => null, 'left' => null];
372
373            switch (count($values)) {
374                case 4:
375                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[2], 'left' => $values[3]];
376                    break;
377                case 3:
378                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[2], 'left' => $values[1]];
379                    break;
380                case 2:
381                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[0], 'left' => $values[1]];
382                    break;
383                case 1:
384                    $positionValues = ['top' => $values[0], 'right' => $values[0], 'bottom' => $values[0], 'left' => $values[0]];
385                    break;
386            }
387
388            return $positionValues[$position];
389        } else if ((str_contains($name, 'padding-')) && !isset($this->properties[$name]) && isset($this->properties['padding'])) {
390            $values         = explode(' ', $this->properties['padding']);
391            $position       = substr($name, strpos($name, '-') + 1);
392            $positionValues = ['top' => null, 'right' => null, 'bottom' => null, 'left' => null];
393
394            switch (count($values)) {
395                case 4:
396                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[2], 'left' => $values[3]];
397                    break;
398                case 3:
399                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[2], 'left' => $values[1]];
400                    break;
401                case 2:
402                    $positionValues = ['top' => $values[0], 'right' => $values[1], 'bottom' => $values[0], 'left' => $values[1]];
403                    break;
404                case 1:
405                    $positionValues = ['top' => $values[0], 'right' => $values[0], 'bottom' => $values[0], 'left' => $values[0]];
406                    break;
407            }
408
409            return $positionValues[$position];
410        } else {
411            return (isset($this->properties[$name])) ? $this->properties[$name] : null;
412        }
413    }
414
415    /**
416     * Magic method to return the isset value of $this->properties[$name]
417     *
418     * @param  string $name
419     * @return bool
420     */
421    public function __isset(string $name): bool
422    {
423        return isset($this->properties[$name]);
424    }
425
426    /**
427     * Magic method to unset $this->properties[$name]
428     *
429     * @param  string $name
430     * @return void
431     */
432    public function __unset(string $name): void
433    {
434        if (isset($this->properties[$name])) {
435            unset($this->properties[$name]);
436        }
437    }
438
439    /**
440     * ArrayAccess offsetExists
441     *
442     * @param  mixed $offset
443     * @return bool
444     */
445    public function offsetExists(mixed $offset): bool
446    {
447        return $this->__isset($offset);
448    }
449
450    /**
451     * ArrayAccess offsetGet
452     *
453     * @param  mixed $offset
454     * @return mixed
455     */
456    public function offsetGet(mixed $offset): mixed
457    {
458        return $this->__get($offset);
459    }
460
461    /**
462     * ArrayAccess offsetSet
463     *
464     * @param  mixed $offset
465     * @param  mixed $value
466     * @return void
467     */
468    public function offsetSet(mixed $offset, mixed $value): void
469    {
470        $this->__set($offset, $value);
471    }
472
473    /**
474     * ArrayAccess offsetUnset
475     *
476     * @param  mixed $offset
477     * @return void
478     */
479    public function offsetUnset(mixed $offset): void
480    {
481        $this->__unset($offset);
482    }
483
484}