1 | <?php |
||||
2 | |||||
3 | |||||
4 | namespace ThinkOne\NovaFlexibleContentFieldShortcode; |
||||
5 | |||||
6 | use Illuminate\Support\Str; |
||||
7 | |||||
8 | class ShortcodeCompiler |
||||
9 | { |
||||
10 | protected string $keyName; |
||||
11 | |||||
12 | protected array $shortcodeData; |
||||
13 | |||||
14 | protected array $presentersMap; |
||||
15 | |||||
16 | /** |
||||
17 | * ShortcodeCompiler constructor. |
||||
18 | * |
||||
19 | * @param array $shortcodeData |
||||
20 | * @param array $shortcodeMap |
||||
21 | * @param string $keyName |
||||
22 | */ |
||||
23 | 6 | public function __construct(array $shortcodeData = [], array $presentersMap = [], ?string $keyName = null) |
|||
24 | { |
||||
25 | 6 | $this->setKeyName($keyName ?? config('nfc-shortcode.key')); |
|||
26 | 6 | $this->shortcodeData = $shortcodeData; |
|||
27 | |||||
28 | 6 | $this->presentersMap = array_merge( |
|||
29 | 6 | config('nfc-shortcode.presenters'), |
|||
30 | 6 | $presentersMap |
|||
31 | 6 | ); |
|||
32 | } |
||||
33 | |||||
34 | 6 | public static function make(...$arguments) |
|||
35 | { |
||||
36 | 6 | return new static(...$arguments); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
37 | } |
||||
38 | |||||
39 | /** |
||||
40 | * @param string $keyName |
||||
41 | * |
||||
42 | * @return ShortcodeCompiler |
||||
43 | */ |
||||
44 | 6 | public function setKeyName(string $keyName): self |
|||
45 | { |
||||
46 | 6 | $this->keyName = $keyName; |
|||
47 | |||||
48 | 6 | return $this; |
|||
49 | } |
||||
50 | |||||
51 | 6 | protected function regex() |
|||
52 | { |
||||
53 | 6 | return '\[\s*' . $this->keyName . '\s*=\s*[\'"]([a-zA-z0-9]*)[\'"].*\]'; |
|||
54 | } |
||||
55 | |||||
56 | 6 | public function renderShortcodes($value) |
|||
57 | { |
||||
58 | 6 | $pattern = $this->regex(); |
|||
59 | 6 | preg_match_all("/{$pattern}/mU", $value, $matches); |
|||
60 | 6 | if ($matches && !empty($matches[0])) { |
|||
61 | 6 | foreach ($matches[0] as $i => $shortcode) { |
|||
62 | 6 | $value = Str::replaceFirst($shortcode, $this->render($matches[0][ $i ], $matches[1][ $i ]), $value); |
|||
63 | } |
||||
64 | } |
||||
65 | |||||
66 | 3 | return $value; |
|||
67 | } |
||||
68 | |||||
69 | /** |
||||
70 | * @throws ShortcodeException |
||||
71 | */ |
||||
72 | 6 | protected function render($shortcode, $key): string |
|||
73 | { |
||||
74 | 6 | $data = collect($this->shortcodeData); |
|||
0 ignored issues
–
show
$this->shortcodeData of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
75 | 6 | $shortcodeData = $data->firstWhere('key', $key); |
|||
76 | 6 | if (!$shortcodeData) { |
|||
77 | 1 | return ''; |
|||
78 | } |
||||
79 | |||||
80 | 5 | $presenter = $this->findPresenter($shortcodeData['layout'], $key); |
|||
81 | 5 | if (!$presenter) { |
|||
82 | 1 | return ''; |
|||
83 | } |
||||
84 | |||||
85 | /** @var ShortcodePresenter $presenter */ |
||||
86 | 4 | $presenter = new $presenter(); |
|||
87 | 4 | if (!($presenter instanceof ShortcodePresenter)) { |
|||
0 ignored issues
–
show
|
|||||
88 | 2 | throw new ShortcodeException('presenter should implement ShortcodePresenter'); |
|||
89 | } |
||||
90 | |||||
91 | 2 | return $presenter->render($key, $shortcodeData['attributes'], $this->parseAttributes($shortcode)); |
|||
92 | } |
||||
93 | |||||
94 | /** |
||||
95 | * Find Presenter |
||||
96 | * |
||||
97 | * @param string $slug |
||||
98 | * @param string $key |
||||
99 | * |
||||
100 | * @return string|null |
||||
101 | */ |
||||
102 | 5 | protected function findPresenter(string $slug, string $key): ?string |
|||
103 | { |
||||
104 | 5 | if (!empty($this->presentersMap[ $key ])) { |
|||
105 | 1 | return $this->presentersMap[ $key ]; |
|||
106 | } |
||||
107 | 4 | if (!empty($this->presentersMap[ $slug ])) { |
|||
108 | 3 | return $this->presentersMap[ $slug ]; |
|||
109 | } |
||||
110 | |||||
111 | 1 | return null; |
|||
112 | } |
||||
113 | |||||
114 | /** |
||||
115 | * Parse the shortcode attributes |
||||
116 | * |
||||
117 | * @return array |
||||
118 | */ |
||||
119 | 2 | protected function parseAttributes($text) |
|||
120 | { |
||||
121 | 2 | $text = trim($text, '[]'); |
|||
122 | // decode attribute values |
||||
123 | 2 | $text = htmlspecialchars_decode($text, ENT_QUOTES); |
|||
124 | |||||
125 | 2 | $attributes = []; |
|||
126 | // attributes pattern |
||||
127 | 2 | $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/'; |
|||
128 | // Match |
||||
129 | 2 | if (preg_match_all($pattern, preg_replace('/[\x{00a0}\x{200b}]+/u', ' ', $text), $match, PREG_SET_ORDER)) { |
|||
130 | 2 | foreach ($match as $m) { |
|||
131 | 2 | if (!empty($m[1])) { |
|||
132 | 2 | $attributes[ strtolower($m[1]) ] = stripcslashes($m[2]); |
|||
133 | } elseif (!empty($m[3])) { |
||||
134 | $attributes[ strtolower($m[3]) ] = stripcslashes($m[4]); |
||||
135 | } elseif (!empty($m[5])) { |
||||
136 | $attributes[ strtolower($m[5]) ] = stripcslashes($m[6]); |
||||
137 | } elseif (isset($m[7]) && strlen($m[7])) { |
||||
138 | $attributes[] = stripcslashes($m[7]); |
||||
139 | } elseif (isset($m[8])) { |
||||
140 | $attributes[] = stripcslashes($m[8]); |
||||
141 | } |
||||
142 | } |
||||
143 | } else { |
||||
144 | $attributes = ltrim($text); |
||||
145 | } |
||||
146 | |||||
147 | 2 | $attributes = is_array($attributes) ? $attributes : [ $attributes ]; |
|||
0 ignored issues
–
show
|
|||||
148 | 2 | if (isset($attributes[ $this->keyName ])) { |
|||
149 | 2 | unset($attributes[ $this->keyName ]); |
|||
150 | } |
||||
151 | |||||
152 | 2 | return $attributes; |
|||
153 | } |
||||
154 | } |
||||
155 |