Completed
Push — master ( 72a175...8fe13c )
by Kevin
12:10
created

RelevantSelectorParser::isAllowedMediaRule()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 3
eloc 4
nc 3
nop 1
1
<?php namespace Luminaire\Premailer\Parser;
2
3
/**
4
 * Created by Sublime Text 3
5
 *
6
 * @user     Kevin Tanjung
7
 * @website  http://kevintanjung.github.io
8
 * @email    [email protected]
9
 * @date     04/08/2016
10
 * @time     09:09
11
 */
12
13
use Crossjoin\Css\Reader\CssString as StylesheetReader;
14
use Crossjoin\Css\Format\Rule\AtMedia\MediaQuery;
15
use Crossjoin\Css\Format\Rule\AtMedia\MediaRule;
16
use Crossjoin\Css\Format\Rule\Style\StyleRuleSet;
17
use Crossjoin\Css\Format\Rule\Style\StyleSelector;
18
use Illuminate\Support\Arr;
19
use InvalidArgumentException;
20
21
/**
22
 * Retrieve relevant CSS selector from a given CSS rules
23
 *
24
 * @package  \Luminaire\Poseidon\Parser
25
 */
26
class RelevantSelectorParser
27
{
28
29
    /**
30
     * The pseudo classes that can be set in a style attribute and that are
31
     * supported by the Symfony CssSelector (doesn't support CSS4 yet).
32
     *
33
     * @var array
34
     */
35
    protected $allowed_pseudo_classes = [
36
        StyleSelector::PSEUDO_CLASS_FIRST_CHILD,
37
        StyleSelector::PSEUDO_CLASS_ROOT,
38
        StyleSelector::PSEUDO_CLASS_NTH_CHILD,
39
        StyleSelector::PSEUDO_CLASS_NTH_LAST_CHILD,
40
        StyleSelector::PSEUDO_CLASS_NTH_OF_TYPE,
41
        StyleSelector::PSEUDO_CLASS_NTH_LAST_OF_TYPE,
42
        StyleSelector::PSEUDO_CLASS_LAST_CHILD,
43
        StyleSelector::PSEUDO_CLASS_FIRST_OF_TYPE,
44
        StyleSelector::PSEUDO_CLASS_LAST_OF_TYPE,
45
        StyleSelector::PSEUDO_CLASS_ONLY_CHILD,
46
        StyleSelector::PSEUDO_CLASS_ONLY_OF_TYPE,
47
        StyleSelector::PSEUDO_CLASS_EMPTY,
48
        StyleSelector::PSEUDO_CLASS_NOT,
49
    ];
50
51
    /**
52
     * The stylesheet reader instance
53
     *
54
     * @var \Crossjoin\Css\Reader\CssString
55
     */
56
    protected $stylesheet;
57
58
    /**
59
     * The charset of the stylesheet
60
     *
61
     * @var string
62
     */
63
    protected $charset;
64
65
    /**
66
     * Create a new instance of "Relevant Selector Parser"
67
     *
68
     * @param  \Crossjoin\Css\Reader\CssString|string|null  $stylesheet
69
     * @param  string                                       $charset
70
     */
71
    public function __construct($stylesheet = null, $charset = 'UTF-8')
72
    {
73
        $this->charset = $charset;
74
75
        if ( ! is_null($stylesheet))
76
        {
77
            $this->setStylesheetReader($stylesheet);
78
        }
79
    }
80
81
    /**
82
     * Get the relevant selectors
83
     *
84
     * @param  \Crossjoin\Css\Reader\CssString  $reader
0 ignored issues
show
Bug introduced by
There is no parameter named $reader. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
85
     * @return array
86
     */
87
    public function extract()
88
    {
89
        $selectors = [];
90
        $rules     = $this->reader->getStyleSheet()->getRules();
0 ignored issues
show
Bug introduced by
The property reader does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
91
        $relevant  = $this->getRelevantStyleRules($rules);
92
93
        foreach ($relevant as $rule)
94
        {
95
            $this->extractSelector($selectors, $rule);
96
        }
97
98
        return $selectors;
99
    }
100
101
    protected function extractSelector(array &$selectors, $rule)
102
    {
103
        foreach ($rule->getSelectors() as $selector)
104
        {
105
            if ( ! $this->isPseudoClassAllowed($selector))
106
            {
107
                continue;
108
            }
109
110
            $key = $selector->getSpecificity() . '.' . $selector->getValue();
111
112
            if (is_null(Arr::get($selectors, $key)))
113
            {
114
                Arr::set($selectors, $key, []);
115
            }
116
117
            foreach ($rule->getDeclarations() as $declaration)
118
            {
119
                $count = count(Arr::get($selectors, $key)) - 1;
120
121
                Arr::set($selectors, "{$key}.{$count}", $declaration);
122
            }
123
        }
124
    }
125
126
    /**
127
     * Set the stylesheet reader instance
128
     *
129
     * @param  \Crossjoin\Css\Reader\CssString|string  $stylesheet
130
     * @return $this
131
     *
132
     * @throws \InvalidArgumentException
133
     */
134
    public function setStylesheetReader($stylesheet)
135
    {
136
        if (is_string($stylesheet))
137
        {
138
            $stylesheet = new StylesheetReader($stylesheet);
139
        }
140
141
        if ( ! $stylesheet instanceof StylesheetReader)
142
        {
143
            throw new InvalidArgumentException('The argument 0 of the [setStylesheetReader] method expects to be a string of CSS or a [Crossjoin\Css\Reader\CssString]');
144
        }
145
146
        $this->reader = $stylesheet;
147
        $this->reader->setEnvironmentEncoding($this->getCharset());
148
149
        return $this;
150
    }
151
152
    /**
153
     * Get the stylesheet reader instance
154
     *
155
     * @return \Crossjoin\Css\Reader\CssString|null
156
     */
157
    public function getStylesheetReader()
158
    {
159
        return $this->reader;
160
    }
161
162
    /**
163
     * Set the charset of the stylesheet
164
     *
165
     * @param  string  $charset
166
     * @return $this
167
     */
168
    public function setCharset($charset)
169
    {
170
        $this->charset = $charset;
171
172
        return $this;
173
    }
174
175
    /**
176
     * Get the charset of the stylesheet
177
     *
178
     * @return string
179
     */
180
    public function getCharset()
181
    {
182
        return $this->charset;
183
    }
184
185
    /**
186
     * Check if a Selector has a valid Pseudo Class
187
     *
188
     * @param  \Crossjoin\Css\Format\Rule\Style\StyleSelector  $selector
189
     * @return bool
190
     */
191
    public function isPseudoClassAllowed(StyleSelector $selector)
192
    {
193
        foreach ($selector->getPseudoClasses() as $pseudo_class)
194
        {
195
            if ( ! in_array($pseudo_class, $this->allowed_pseudo_classes))
196
            {
197
                return false;
198
            }
199
        }
200
201
        return true;
202
    }
203
204
    /**
205
     * Gets all generally relevant style rules
206
     *
207
     * @param  RuleAbstract[]  $rules
208
     * @return StyleRuleSet[]
209
     */
210
    protected function getRelevantStyleRules(array $rules)
211
    {
212
        $style_rules = [];
213
214
        foreach ($rules as $rule)
215
        {
216
            if ($rule instanceof StyleRuleSet)
217
            {
218
                $style_rules[] = $rule;
219
            }
220
221
            if ($rule instanceof MediaRule)
222
            {
223
                $this->getRelevantMediaRule($style_rules, $rule);
0 ignored issues
show
Documentation introduced by
$style_rules is of type array, but the function expects a object<Crossjoin\Css\For...Rule\AtMedia\MediaRule>.

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...
224
            }
225
        }
226
227
        return $style_rules;
228
    }
229
230
    /**
231
     * Gets the relevant style rules from a media rule
232
     *
233
     * @param  \Crossjoin\Css\Format\Rule\AtMedia\MediaRule  $rule
234
     * @param  array                                         &$collection
235
     * @return void
236
     */
237
    protected function getRelevantMediaRule(MediaRule $rule, &$collection)
238
    {
239
        foreach ($rule->getQueries() as $media_query)
240
        {
241
            if ( ! $this->isAllowedMediaRule($media_query))
242
            {
243
                continue;
244
            }
245
246
            foreach ($this->getRelevantStyleRules($rule->getRules()) as $style_rule)
0 ignored issues
show
Documentation introduced by
$rule->getRules() is of type array<integer,object<Cro...mat\Rule\RuleAbstract>>, but the function expects a array<integer,object<Lum...r\Parser\RuleAbstract>>.

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...
247
            {
248
                $collection[] = $style_rule;
249
            }
250
251
            break;
252
        }
253
    }
254
255
    protected function isAllowedMediaRule($media_query)
256
    {
257
        $type      = $media_query->getType();
258
        $condition = count($media_query->getConditions());
259
260
        return ($type === MediaQuery::TYPE_ALL || $type === MediaQuery::TYPE_SCREEN) && $condition === 0;
261
    }
262
263
}
264