Completed
Push — master ( 974531...773161 )
by Asif
02:14
created

ShortcodeCompiler::stripTag()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4285
cc 3
eloc 4
nc 2
nop 1
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
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->registered of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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