This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Spatie\BladeX; |
||
4 | |||
5 | use Illuminate\Support\Str; |
||
6 | |||
7 | class Compiler |
||
8 | { |
||
9 | /** @var \Spatie\BladeX\BladeX */ |
||
10 | protected $bladeX; |
||
11 | |||
12 | public function __construct(BladeX $bladeX) |
||
13 | { |
||
14 | return $this->bladeX = $bladeX; |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
15 | } |
||
16 | |||
17 | public function compile(string $viewContents): string |
||
18 | { |
||
19 | return array_reduce( |
||
20 | $this->bladeX->registeredComponents(), |
||
21 | [$this, 'parseComponentHtml'], |
||
22 | $viewContents |
||
23 | ); |
||
24 | } |
||
25 | |||
26 | protected function parseComponentHtml(string $viewContents, Component $component) |
||
27 | { |
||
28 | $viewContents = $this->parseSlots($viewContents); |
||
29 | |||
30 | $viewContents = $this->parseSelfClosingTags($viewContents, $component); |
||
31 | |||
32 | $viewContents = $this->parseOpeningTags($viewContents, $component); |
||
33 | |||
34 | $viewContents = $this->parseClosingTags($viewContents, $component); |
||
35 | |||
36 | return $viewContents; |
||
37 | } |
||
38 | |||
39 | View Code Duplication | protected function parseSelfClosingTags(string $viewContents, Component $component): string |
|
40 | { |
||
41 | $pattern = "/ |
||
42 | < |
||
43 | \s* |
||
44 | {$component->getTag()} |
||
45 | \s* |
||
46 | (?<attributes> |
||
47 | (?: |
||
48 | \s+ |
||
49 | [\w\-:.\$]+ |
||
50 | ( |
||
51 | = |
||
52 | (?: |
||
53 | \\\"[^\\\"]+\\\" |
||
54 | | |
||
55 | \'[^\']+\' |
||
56 | | |
||
57 | [^\'\\\"=<>]+ |
||
58 | ) |
||
59 | )? |
||
60 | )* |
||
61 | \s* |
||
62 | ) |
||
63 | \/> |
||
64 | /x"; |
||
65 | |||
66 | return preg_replace_callback($pattern, function (array $matches) use ($component) { |
||
67 | $attributes = $this->getAttributesFromAttributeString($matches['attributes']); |
||
68 | |||
69 | return $this->componentString($component, $attributes); |
||
70 | }, $viewContents); |
||
71 | } |
||
72 | |||
73 | View Code Duplication | protected function parseOpeningTags(string $viewContents, Component $component): string |
|
74 | { |
||
75 | $pattern = "/ |
||
76 | < |
||
77 | \s* |
||
78 | {$component->getTag()} |
||
79 | (?<attributes> |
||
80 | (?: |
||
81 | \s+ |
||
82 | [\w\-:.\$]+ |
||
83 | ( |
||
84 | = |
||
85 | (?: |
||
86 | \\\"[^\\\"]*\\\" |
||
87 | | |
||
88 | \'[^\']*\' |
||
89 | | |
||
90 | [^\'\\\"=<>]+ |
||
91 | ) |
||
92 | ) |
||
93 | ?)* |
||
94 | \s* |
||
95 | ) |
||
96 | (?<![\/=\-]) |
||
97 | > |
||
98 | /x"; |
||
99 | |||
100 | return preg_replace_callback($pattern, function (array $matches) use ($component) { |
||
101 | $attributes = $this->getAttributesFromAttributeString($matches['attributes']); |
||
102 | |||
103 | return $this->componentStartString($component, $attributes); |
||
104 | }, $viewContents); |
||
105 | } |
||
106 | |||
107 | protected function parseClosingTags(string $viewContents, Component $component): string |
||
108 | { |
||
109 | $pattern = "/<\/\s*{$component->getTag()}\s*>/"; |
||
110 | |||
111 | return preg_replace($pattern, $this->componentEndString($component), $viewContents); |
||
112 | } |
||
113 | |||
114 | protected function componentString(Component $component, array $attributes = []): string |
||
115 | { |
||
116 | return $this->componentStartString($component, $attributes).$this->componentEndString($component); |
||
117 | } |
||
118 | |||
119 | protected function componentStartString(Component $component, array $attributes = []): string |
||
120 | { |
||
121 | $attributesString = $this->attributesToString($attributes); |
||
122 | |||
123 | if ($component->view === 'bladex::context') { |
||
124 | return " @php(app(Spatie\BladeX\ContextStack::class)->push({$attributesString})) "; |
||
125 | } |
||
126 | |||
127 | if ($component->viewModel) { |
||
128 | $attributesString = " |
||
129 | array_merge( |
||
130 | app(Spatie\BladeX\ContextStack::class)->read(), |
||
131 | {$attributesString}, |
||
132 | app( |
||
133 | '{$component->viewModel}', |
||
134 | array_merge( |
||
135 | app(Spatie\BladeX\ContextStack::class)->read(), |
||
136 | {$attributesString} |
||
137 | ) |
||
138 | )->toArray() |
||
139 | )"; |
||
140 | } |
||
141 | |||
142 | return " @component( |
||
143 | '{$component->view}', |
||
144 | array_merge(app(Spatie\BladeX\ContextStack::class)->read(), |
||
145 | {$attributesString}) |
||
146 | ) "; |
||
147 | } |
||
148 | |||
149 | protected function componentEndString(Component $component): string |
||
150 | { |
||
151 | if ($component->view === 'bladex::context') { |
||
152 | return "@php(app(Spatie\BladeX\ContextStack::class)->pop())"; |
||
153 | } |
||
154 | |||
155 | return ' @endcomponent '; |
||
156 | } |
||
157 | |||
158 | protected function getAttributesFromAttributeString(string $attributeString): array |
||
159 | { |
||
160 | $attributeString = $this->parseBindAttributes($attributeString); |
||
161 | |||
162 | $pattern = '/ |
||
163 | (?<attribute>[\w\-:.\$]+) |
||
164 | ( |
||
165 | = |
||
166 | (?<value> |
||
167 | ( |
||
168 | \"[^\"]+\" |
||
169 | | |
||
170 | \\\'[^\\\']+\\\' |
||
171 | | |
||
172 | [^\s>]+ |
||
173 | ) |
||
174 | ) |
||
175 | )? |
||
176 | /x'; |
||
177 | |||
178 | if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) { |
||
179 | return []; |
||
180 | } |
||
181 | |||
182 | $namespaces = []; |
||
183 | $attributes = collect($matches)->mapWithKeys(function ($match) use (&$namespaces) { |
||
184 | $attribute = Str::camel($match['attribute']); |
||
185 | $value = $match['value'] ?? null; |
||
186 | |||
187 | if (is_null($value)) { |
||
188 | $value = 'true'; |
||
189 | $attribute = Str::start($attribute, 'bind:'); |
||
190 | } |
||
191 | |||
192 | $value = $this->stripQuotes($value); |
||
193 | |||
194 | if (Str::startsWith($attribute, 'bind:')) { |
||
195 | $attribute = Str::after($attribute, 'bind:'); |
||
196 | } else { |
||
197 | $value = str_replace("'", "\\'", $value); |
||
198 | $value = "'{$value}'"; |
||
199 | |||
200 | if (Str::contains($attribute, ':')) { |
||
201 | $namespace = Str::before($attribute, ':'); |
||
202 | $attribute = Str::after($attribute, ':'); |
||
203 | |||
204 | data_set($namespaces, "{$namespace}.{$attribute}", $value); |
||
205 | |||
206 | return []; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | return [$attribute => $value]; |
||
211 | }); |
||
212 | |||
213 | return $attributes->merge($namespaces)->toArray(); |
||
214 | } |
||
215 | |||
216 | protected function parseSlots(string $viewContents): string |
||
217 | { |
||
218 | $openingPattern = '/<\s*slot\s+name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/'; |
||
219 | $viewContents = preg_replace_callback($openingPattern, function ($matches) { |
||
220 | $name = $this->stripQuotes($matches['name']); |
||
221 | |||
222 | return " @slot('{$name}') "; |
||
223 | }, $viewContents); |
||
224 | |||
225 | $closingPattern = '/<\/\s*slot[^>]*>/'; |
||
226 | $viewContents = preg_replace($closingPattern, ' @endslot', $viewContents); |
||
227 | |||
228 | return $viewContents; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Adds the `bind:` prefix for all bound data attributes. |
||
233 | * E.g. `foo=bar :name=alex` becomes `foo=bar bind:name=alex`. |
||
234 | * |
||
235 | * @param string $attributeString |
||
236 | * |
||
237 | * @return string |
||
238 | */ |
||
239 | protected function parseBindAttributes(string $attributeString): string |
||
240 | { |
||
241 | $pattern = "/ |
||
242 | (?:^|\s+) # start of the string or whitespace between attributes |
||
243 | : # attribute needs to start with a semicolon |
||
244 | ([\w-]+) # match the actual attribute name |
||
245 | = # only match attributes that have a value |
||
246 | /xm"; |
||
247 | |||
248 | return preg_replace($pattern, ' bind:$1=', $attributeString); |
||
249 | } |
||
250 | |||
251 | protected function attributesToString(array $attributes): string |
||
252 | { |
||
253 | $attributes = collect($attributes); |
||
254 | |||
255 | $string = []; |
||
256 | |||
257 | $plainAttributes = $attributes |
||
258 | ->reject(function ($value, string $attribute) { |
||
259 | return Str::startsWith($attribute, '...$'); |
||
260 | }) |
||
261 | ->map(function ($value, string $attribute) { |
||
262 | if (is_array($value)) { |
||
263 | $value = $this->attributesToString($value); |
||
264 | } |
||
265 | |||
266 | return "'{$attribute}' => {$value}"; |
||
267 | }); |
||
268 | |||
269 | $string['plain'] = '['.$plainAttributes->implode(',').']'; |
||
270 | |||
271 | $string['spread'] = $attributes |
||
272 | ->filter(function ($value, string $attribute) { |
||
273 | return Str::startsWith($attribute, '...$'); |
||
274 | }) |
||
275 | ->map(function ($value, string $attribute) { |
||
276 | $attribute = Str::after($attribute, '...'); |
||
277 | |||
278 | return "{$attribute}"; |
||
279 | }) |
||
280 | ->implode(','); |
||
281 | |||
282 | if (empty($string['spread'])) { |
||
283 | return $string['plain']; |
||
284 | } |
||
285 | |||
286 | return 'array_merge('.$string['spread'].','.$string['plain'].')'; |
||
287 | } |
||
288 | |||
289 | protected function stripQuotes(string $string): string |
||
290 | { |
||
291 | if (Str::startsWith($string, ['"', '\''])) { |
||
292 | return substr($string, 1, -1); |
||
293 | } |
||
294 | |||
295 | return $string; |
||
296 | } |
||
297 | } |
||
298 |