Completed
Push — master ( 554f93...95ca05 )
by Mark
04:48
created

Shortcodes::stripShortcode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace Jaybizzle\Shortcodes;
4
5
class Shortcodes
6
{
7
    /**
8
     * Container for storing shortcode tags and their hook to call for the shortcode.
9
     *
10
     * @var array
11
     */
12
    public $shortcodeTags = [];
13
14
    /**
15
     * Add shortcode hooks.
16
     *
17
     * @param string $tag
18
     * @param string $class
19
     */
20
    public function add($tag, $class)
21
    {
22
        $this->shortcodeTags[$tag] = $class;
23
    }
24
25
    /**
26
     * Remove shortcode tag from shortcode container.
27
     *
28
     * @param string $tag
29
     */
30
    public function remove($tag)
31
    {
32
        unset($this->shortcodeTags[$tag]);
33
    }
34
35
    /**
36
     * Remove all shortcodes tags from the shortcode container.
37
     */
38
    public function removeAll()
39
    {
40
        $this->shortcodeTags = [];
41
    }
42
43
    /**
44
     * Search content for shortcodes and filter shortcodes through their hooks.
45
     *
46
     * @param  string $content
47
     * @return string
48
     */
49 View Code Duplication
    public function parse($content)
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...
50
    {
51
        if (empty($this->shortcodeTags) || ! is_array($this->shortcodeTags)) {
52
            return $content;
53
        }
54
55
        $pattern = $this->getShortcodeRegex();
56
57
        return preg_replace_callback('/'.$pattern.'/s', [$this, 'doShortcodeTag'], $content);
58
    }
59
60
    /**
61
     * Retrieve the shortcode regular expression for searching.
62
     *
63
     * @return string
64
     */
65
    protected function getShortcodeRegex()
66
    {
67
        $tagNames = array_keys($this->shortcodeTags);
68
        $tagRegexp = implode('|', array_map('preg_quote', $tagNames));
69
70
        return $this->buildShortcodeRegex($tagRegexp);
71
    }
72
73
    /**
74
     * Build the shortcode regex for the specified tags.
75
     *
76
     * @param string $tags
77
     * @return string
78
     */
79
    protected function buildShortcodeRegex($tags)
80
    {
81
        return '(.?)\[('.$tags.')\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?(.?)';
82
    }
83
84
    /**
85
     * Regular Expression callable for doShortcode() for calling shortcode hook.
86
     *
87
     * @param  array $matches
88
     * @return string
89
     */
90
    protected function doShortcodeTag($matches)
91
    {
92
        // allow [[foo]] syntax for escaping a tag
93
        if ($matches[1] == '[' && $matches[6] == ']') {
94
            return substr($matches[0], 1, -1);
95
        }
96
97
        $tag = $matches[2];
98
        $attr = $this->shortcodeParseAtts($matches[3]);
99
100
        $className = $this->shortcodeTags[$tag];
101
102
        if (isset($matches[5])) {
103
            // enclosing tag - extra parameter
104
            $parsed = (new $className($attr, $matches[5], $tag))->parse();
105
106
            return $matches[1].$parsed.$matches[6];
107
        } else {
108
            // self-closing tag
109
            $parsed = (new $className($attr, null, $tag))->parse();
110
111
            return $matches[1].$parsed.$matches[6];
112
        }
113
    }
114
115
    /**
116
     * Retrieve all attributes from the shortcode tag.
117
     *
118
     * @param  string $text
119
     * @return array
120
     */
121
    protected function shortcodeParseAtts($text)
122
    {
123
        $atts = [];
124
        $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
125
        $text = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $text);
126
127
        if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
128
            foreach ($match as $m) {
129
                if (! empty($m[1])) {
130
                    $atts[strtolower($m[1])] = stripcslashes($m[2]);
131
                } elseif (! empty($m[3])) {
132
                    $atts[strtolower($m[3])] = stripcslashes($m[4]);
133
                } elseif (! empty($m[5])) {
134
                    $atts[strtolower($m[5])] = stripcslashes($m[6]);
135
                } 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...
136
                    $atts[] = stripcslashes($m[7]);
137
                } elseif (isset($m[8])) {
138
                    $atts[] = stripcslashes($m[8]);
139
                }
140
            }
141
        } else {
142
            $atts = ltrim($text);
143
        }
144
145
        return $atts;
146
    }
147
148
    /**
149
     * Remove all shortcode tags from the given content.
150
     *
151
     * @param  string $content
152
     * @return string
153
     */
154 View Code Duplication
    public function stripShortcodes($content)
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...
155
    {
156
        if (empty($this->shortcodeTags) || ! is_array($this->shortcodeTags)) {
157
            return $content;
158
        }
159
160
        $pattern = $this->getShortcodeRegex();
161
162
        return preg_replace('/'.$pattern.'/s', ' ', $content);
163
    }
164
165
    /**
166
     * Remove specified shortcode tag from the given content.
167
     *
168
     * @param  string $content
169
     * @return string
170
     */
171
    public function stripShortcode($shortcode, $content)
172
    {
173
        $pattern = $this->buildShortcodeRegex(preg_quote($shortcode));
174
175
        return preg_replace('/'.$pattern.'/s', ' ', $content);
176
    }
177
178
    public function getShortcodes($content)
179
    {
180
        foreach (array_keys($this->shortcodeTags) as $shortcode) {
181
            $tags[$shortcode] = $this->getShortcode($shortcode, $content);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tags was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tags = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
182
        }
183
184
        return $tags;
0 ignored issues
show
Bug introduced by
The variable $tags does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
185
    }
186
187
    /**
188
     * Get attributes for the specified shortcodes.
189
     *
190
     * @param string $shortcode
191
     * @param string $content
192
     * @return array
193
     */
194
    public function getShortcode($shortcode, $content)
195
    {
196
        $pattern = $this->buildShortcodeRegex($shortcode);
197
198
        preg_match_all('/'.$pattern.'/s', $content, $matches);
199
200
        if (empty($matches[3])) {
201
            return [];
202
        }
203
204
        foreach ($matches[3] as $m) {
205
            $data[] = $this->shortcodeParseAtts($m);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
206
        }
207
208
        return $data;
0 ignored issues
show
Bug introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
209
    }
210
}
211