1
|
|
|
<?php namespace Webwizo\Shortcodes\Compilers; |
2
|
|
|
|
3
|
|
|
use Illuminate\Support\Str; |
4
|
|
|
|
5
|
|
|
class ShortcodeCompiler |
6
|
|
|
{ |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Enabled state |
10
|
|
|
* |
11
|
|
|
* @var boolean |
12
|
|
|
*/ |
13
|
|
|
protected $enabled = false; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Enable strip state |
17
|
|
|
* |
18
|
|
|
* @var boolean |
19
|
|
|
*/ |
20
|
|
|
protected $strip = false; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @var |
24
|
|
|
*/ |
25
|
|
|
protected $matches; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Registered laravel-shortcodes |
29
|
|
|
* |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
protected $registered = []; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Enable |
36
|
|
|
* |
37
|
|
|
* @return void |
38
|
|
|
*/ |
39
|
|
|
public function enable() |
40
|
|
|
{ |
41
|
|
|
$this->enabled = true; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Disable |
46
|
|
|
* |
47
|
|
|
* @return void |
48
|
|
|
*/ |
49
|
|
|
public function disable() |
50
|
|
|
{ |
51
|
|
|
$this->enabled = false; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Add a new shortcode |
56
|
|
|
* |
57
|
|
|
* @param string $name |
58
|
|
|
* @param callable|string $callback |
59
|
|
|
*/ |
60
|
|
|
public function add($name, $callback) |
61
|
|
|
{ |
62
|
|
|
$this->registered[$name] = $callback; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Compile the contents |
67
|
|
|
* |
68
|
|
|
* @param string $value |
69
|
|
|
* |
70
|
|
|
* @return string |
71
|
|
|
*/ |
72
|
|
|
public function compile($value) |
73
|
|
|
{ |
74
|
|
|
// Only continue is laravel-shortcodes have been registered |
75
|
|
|
if (!$this->enabled || !$this->hasShortcodes()) |
76
|
|
|
return $value; |
77
|
|
|
// Set empty result |
78
|
|
|
$result = ''; |
79
|
|
|
// Here we will loop through all of the tokens returned by the Zend lexer and |
80
|
|
|
// parse each one into the corresponding valid PHP. We will then have this |
81
|
|
|
// template as the correctly rendered PHP that can be rendered natively. |
82
|
|
|
foreach (token_get_all($value) as $token) { |
83
|
|
|
$result .= is_array($token) ? $this->parseToken($token) : $token; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
return $result; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Check if laravel-shortcodes have been registered |
91
|
|
|
* |
92
|
|
|
* @return boolean |
93
|
|
|
*/ |
94
|
|
|
public function hasShortcodes() |
95
|
|
|
{ |
96
|
|
|
return !empty($this->registered); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Parse the tokens from the template. |
101
|
|
|
* |
102
|
|
|
* @param array $token |
103
|
|
|
* |
104
|
|
|
* @return string |
105
|
|
|
*/ |
106
|
|
|
protected function parseToken($token) |
107
|
|
|
{ |
108
|
|
|
list($id, $content) = $token; |
109
|
|
|
if ($id == T_INLINE_HTML) { |
110
|
|
|
$content = $this->renderShortcodes($content); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
return $content; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Render laravel-shortcodes |
118
|
|
|
* |
119
|
|
|
* @param string $value |
120
|
|
|
* |
121
|
|
|
* @return string |
122
|
|
|
*/ |
123
|
|
|
protected function renderShortcodes($value) |
124
|
|
|
{ |
125
|
|
|
return preg_replace_callback($this->getRegex(), [ |
126
|
|
|
&$this, |
127
|
|
|
'render' |
128
|
|
|
], $value); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Render the current calld shortcode. |
133
|
|
|
* |
134
|
|
|
* @param array $matches |
135
|
|
|
* |
136
|
|
|
* @return string |
137
|
|
|
*/ |
138
|
|
|
public function render($matches) |
139
|
|
|
{ |
140
|
|
|
// Compile the shortcode |
141
|
|
|
$compiled = $this->compileShortcode($matches); |
142
|
|
|
$name = $compiled->getName(); |
143
|
|
|
|
144
|
|
|
// Render the shortcode through the callback |
145
|
|
|
return call_user_func_array($this->getCallback($name), [ |
146
|
|
|
$compiled, |
147
|
|
|
$compiled->getContent(), |
148
|
|
|
$this, |
149
|
|
|
$name |
150
|
|
|
]); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Get Compiled Attributes. |
155
|
|
|
* |
156
|
|
|
* @param $matches |
157
|
|
|
* |
158
|
|
|
* @return \Webwizo\Shortcodes\Shortcode |
159
|
|
|
*/ |
160
|
|
|
protected function compileShortcode($matches) |
161
|
|
|
{ |
162
|
|
|
// Set matches |
163
|
|
|
$this->setMatches($matches); |
164
|
|
|
// pars the attributes |
165
|
|
|
$attributes = $this->parseAttributes($this->matches[3]); |
166
|
|
|
|
167
|
|
|
// return shortcode instance |
168
|
|
|
return new Shortcode( |
169
|
|
|
$this->getName(), |
170
|
|
|
$attributes, |
171
|
|
|
$this->getContent() |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Set the matches |
177
|
|
|
* |
178
|
|
|
* @param array $matches |
179
|
|
|
*/ |
180
|
|
|
protected function setMatches($matches = []) |
181
|
|
|
{ |
182
|
|
|
$this->matches = $matches; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Return the shortcode name |
187
|
|
|
* |
188
|
|
|
* @return string |
189
|
|
|
*/ |
190
|
|
|
public function getName() |
191
|
|
|
{ |
192
|
|
|
return $this->matches[2]; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Return the shortcode content |
197
|
|
|
* |
198
|
|
|
* @return string |
199
|
|
|
*/ |
200
|
|
|
public function getContent() |
201
|
|
|
{ |
202
|
|
|
// Compile the content, to support nested laravel-shortcodes |
203
|
|
|
return $this->compile($this->matches[5]); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Get the callback for the current shortcode (class or callback) |
208
|
|
|
* |
209
|
|
|
* @param string $name |
210
|
|
|
* |
211
|
|
|
* @return callable|array |
212
|
|
|
*/ |
213
|
|
|
public function getCallback($name) |
214
|
|
|
{ |
215
|
|
|
// Get the callback from the laravel-shortcodes array |
216
|
|
|
$callback = $this->registered[$name]; |
217
|
|
|
// if is a string |
218
|
|
|
if (is_string($callback)) { |
219
|
|
|
// Parse the callback |
220
|
|
|
list($class, $method) = Str::parseCallback($callback, 'register'); |
221
|
|
|
// If the class exist |
222
|
|
|
if (class_exists($class)) { |
223
|
|
|
// return class and method |
|
|
|
|
224
|
|
|
return [ |
225
|
|
|
app($class), |
226
|
|
|
$method |
227
|
|
|
]; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $callback; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Parse the shortcode attributes |
236
|
|
|
* |
237
|
|
|
* @author Wordpress |
238
|
|
|
* @return array |
239
|
|
|
*/ |
240
|
|
|
protected function parseAttributes($text) |
241
|
|
|
{ |
242
|
|
|
$attributes = []; |
243
|
|
|
// attributes pattern |
244
|
|
|
$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/'; |
245
|
|
|
// Match |
246
|
|
|
if (preg_match_all($pattern, preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text), $match, PREG_SET_ORDER)) { |
247
|
|
|
foreach ($match as $m) { |
248
|
|
|
if (!empty($m[1])) { |
249
|
|
|
$attributes[strtolower($m[1])] = stripcslashes($m[2]); |
250
|
|
|
} elseif (!empty($m[3])) { |
251
|
|
|
$attributes[strtolower($m[3])] = stripcslashes($m[4]); |
252
|
|
|
} elseif (!empty($m[5])) { |
253
|
|
|
$attributes[strtolower($m[5])] = stripcslashes($m[6]); |
254
|
|
|
} elseif (isset($m[7]) and strlen($m[7])) { |
|
|
|
|
255
|
|
|
$attributes[] = stripcslashes($m[7]); |
256
|
|
|
} elseif (isset($m[8])) { |
257
|
|
|
$attributes[] = stripcslashes($m[8]); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} else { |
261
|
|
|
$attributes = ltrim($text); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// return attributes |
265
|
|
|
return is_array($attributes) ? $attributes : [$attributes]; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Get shortcode names |
270
|
|
|
* |
271
|
|
|
* @return string |
272
|
|
|
*/ |
273
|
|
|
protected function getShortcodeNames() |
274
|
|
|
{ |
275
|
|
|
return join('|', array_map('preg_quote', array_keys($this->registered))); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Get shortcode regex. |
280
|
|
|
* |
281
|
|
|
* @author Wordpress |
282
|
|
|
* @return string |
283
|
|
|
*/ |
284
|
|
View Code Duplication |
protected function getRegex() |
|
|
|
|
285
|
|
|
{ |
286
|
|
|
// Get shortcode names |
287
|
|
|
$shortcodeNames = $this->getShortcodeNames(); |
288
|
|
|
|
289
|
|
|
// return regex |
290
|
|
|
return "/" |
291
|
|
|
. '\\[' // Opening bracket |
292
|
|
|
. '(\\[?)' // 1: Optional second opening bracket for escaping laravel-shortcodes: [[tag]] |
293
|
|
|
. "($shortcodeNames)" // 2: Shortcode name |
294
|
|
|
. '(?![\\w-])' // Not followed by word character or hyphen |
295
|
|
|
. '(' // 3: Unroll the loop: Inside the opening shortcode tag |
296
|
|
|
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
297
|
|
|
. '(?:' |
298
|
|
|
. '\\/(?!\\])' // A forward slash not followed by a closing bracket |
299
|
|
|
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
300
|
|
|
. ')*?' |
301
|
|
|
. ')' |
302
|
|
|
. '(?:' |
303
|
|
|
. '(\\/)' // 4: Self closing tag ... |
|
|
|
|
304
|
|
|
. '\\]' // ... and closing bracket |
305
|
|
|
. '|' |
306
|
|
|
. '\\]' // Closing bracket |
307
|
|
|
. '(?:' |
308
|
|
|
. '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags |
309
|
|
|
. '[^\\[]*+' // Not an opening bracket |
310
|
|
|
. '(?:' |
311
|
|
|
. '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag |
312
|
|
|
. '[^\\[]*+' // Not an opening bracket |
313
|
|
|
. ')*+' |
314
|
|
|
. ')' |
315
|
|
|
. '\\[\\/\\2\\]' // Closing shortcode tag |
316
|
|
|
. ')?' |
317
|
|
|
. ')' |
318
|
|
|
. '(\\]?)' // 6: Optional second closing brocket for escaping laravel-shortcodes: [[tag]] |
319
|
|
|
. "/s"; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
View Code Duplication |
private function getStripRegex() |
|
|
|
|
323
|
|
|
{ |
324
|
|
|
// Get shortcode names |
325
|
|
|
$shortcodeNames = $this->getShortcodeNames(); |
326
|
|
|
|
327
|
|
|
return |
328
|
|
|
'\\[' // Opening bracket |
329
|
|
|
. '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] |
330
|
|
|
. "($shortcodeNames)" // 2: Shortcode name |
331
|
|
|
. '(?![\\w-])' // Not followed by word character or hyphen |
332
|
|
|
. '(' // 3: Unroll the loop: Inside the opening shortcode tag |
333
|
|
|
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
334
|
|
|
. '(?:' |
335
|
|
|
. '\\/(?!\\])' // A forward slash not followed by a closing bracket |
336
|
|
|
. '[^\\]\\/]*' // Not a closing bracket or forward slash |
337
|
|
|
. ')*?' |
338
|
|
|
. ')' |
339
|
|
|
. '(?:' |
340
|
|
|
. '(\\/)' // 4: Self closing tag ... |
|
|
|
|
341
|
|
|
. '\\]' // ... and closing bracket |
342
|
|
|
. '|' |
343
|
|
|
. '\\]' // Closing bracket |
344
|
|
|
. '(?:' |
345
|
|
|
. '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags |
346
|
|
|
. '[^\\[]*+' // Not an opening bracket |
347
|
|
|
. '(?:' |
348
|
|
|
. '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag |
349
|
|
|
. '[^\\[]*+' // Not an opening bracket |
350
|
|
|
. ')*+' |
351
|
|
|
. ')' |
352
|
|
|
. '\\[\\/\\2\\]' // Closing shortcode tag |
353
|
|
|
. ')?' |
354
|
|
|
. ')' |
355
|
|
|
. '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]] |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Remove all shortcode tags from the given content. |
360
|
|
|
* |
361
|
|
|
* @param string $content Content to remove shortcode tags. |
362
|
|
|
* |
363
|
|
|
* @return string Content without shortcode tags. |
364
|
|
|
*/ |
365
|
|
|
public function strip($content) |
366
|
|
|
{ |
367
|
|
|
if (!$this->registered) { |
|
|
|
|
368
|
|
|
return $content; |
369
|
|
|
} |
370
|
|
|
$pattern = $this->getStripRegex(); |
371
|
|
|
|
372
|
|
|
return preg_replace_callback("/$pattern/s", [$this, 'stripTag'], $content); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* @return boolean |
377
|
|
|
*/ |
378
|
|
|
public function getStrip() |
379
|
|
|
{ |
380
|
|
|
return $this->strip; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* @param boolean $strip |
385
|
|
|
*/ |
386
|
|
|
public function setStrip($strip) |
387
|
|
|
{ |
388
|
|
|
$this->strip = $strip; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Remove shortcode tag |
393
|
|
|
* |
394
|
|
|
* @param type $m |
395
|
|
|
* |
396
|
|
|
* @return string Content without shortcode tag. |
397
|
|
|
*/ |
398
|
|
|
protected function stripTag($m) |
399
|
|
|
{ |
400
|
|
|
if ($m[1] == '[' && $m[6] == ']') { |
401
|
|
|
return substr($m[0], 1, -1); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
return $m[1] . $m[6]; |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.