spatie /
laravel-blade-x
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
Loading history...
|
|||
| 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 |