Completed
Branch BUG-10532-replace-hooks-for-ts (65130e)
by
unknown
28:48 queued 15:20
created

EE_Shortcodes   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 432
rs 9
c 0
b 0
f 0
wmc 35
lcom 1
cbo 4

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A _set_defaults() 0 6 1
A _set_shortcode_helper() 0 5 1
A get_shortcode_helper() 0 7 2
B parser() 0 24 3
A get_shortcodes() 0 9 1
_init_props() 0 1 ?
_parser() 0 1 ?
C _validate_list_requirements() 0 29 7
A _get_shortcode_attrs() 0 12 2
B _mutate_conditional_block_in_template() 0 20 6
B _get_conditional_block_regex() 0 42 2
B _set_messages_properties() 0 11 5
A get_set_message_type() 0 4 1
A get_set_messenger() 0 4 1
A get_set_context() 0 4 1
A get_set_message() 0 4 1
1
<?php
2
3
if (! defined('EVENT_ESPRESSO_VERSION')) {
4
    exit('NO direct script access allowed');
5
}
6
7
/**
8
 * Event Espresso
9
 * Event Registration and Management Plugin for WordPress
10
 * @ package            Event Espresso
11
 * @ author                Seth Shoultes
12
 * @ copyright        (c) 2008-2011 Event Espresso  All Rights Reserved.
13
 * @ license            http://eventespresso.com/support/terms-conditions/   * see Plugin Licensing *
14
 * @ link                http://www.eventespresso.com
15
 * @ version            4.0
16
 * ------------------------------------------------------------------------
17
 * EE_Shortcodes
18
 * This is the parent class for the shortcodes libraries.  All common methods, properties are defined in here.
19
 * The way this library works is a child class would be for defining a logical "grouping" of shortcodes (i.e.
20
 * 'payment', 'address', 'attendee', 'event' etc.).  The child class extends this parent and then that grouping of
21
 * shortcodes can be retrieved wherever they are needed. This library takes care of defining shortcodes and their
22
 * descriptions and also the parsers for those shortcodes.
23
 *
24
 * @abstract
25
 * @package        Event Espresso
26
 * @subpackage     libraries/shortcodes/EE_Shortcodes.lib.php
27
 * @author         Darren Ethier
28
 *                 ------------------------------------------------------------------------
29
 */
30
abstract class EE_Shortcodes extends EE_Base
31
{
32
33
    /**
34
     * holds label for library
35
     * This is used for referencing the library label
36
     *
37
     * @access public
38
     * @var string
39
     */
40
    public $label;
41
42
43
    /**
44
     * This property is used for referencing a short description of the library
45
     *
46
     * @access public
47
     * @var string
48
     */
49
    public $description;
50
51
52
    /**
53
     * This will hold an array of shortcodes with the key as the shortcode ([shortcode]) and the value as a
54
     * label/description for the shortcode.
55
     *
56
     * @access protected
57
     * @var array
58
     */
59
    protected $_shortcodes;
60
61
62
    /**
63
     * This will hold the incoming data item sent to the parser method
64
     *
65
     * @access protected
66
     * @var mixed (array|object)
67
     */
68
    protected $_data;
69
70
71
    /**
72
     * some shortcodes may require extra data to parse.  This property is provided for that.
73
     *
74
     * @var array
75
     */
76
    protected $_extra_data;
77
78
79
    /**
80
     * EE_messenger used to generate the template being parsed.
81
     *
82
     * @since 4.5.0
83
     * @var EE_messenger
84
     */
85
    protected $_messenger;
86
87
88
    /**
89
     * message type used to generate the template being parsed.
90
     *
91
     * @since 4.5.0
92
     * @var EE_message_type
93
     */
94
    protected $_message_type;
95
96
97
    /**
98
     * context used for the template being parsed
99
     *
100
     * @since 4.5.0
101
     * @var string
102
     */
103
    protected $_context;
104
105
106
    /**
107
     * Specific Message Template Group ID
108
     *
109
     * @since 4.5.0
110
     * @var int
111
     */
112
    protected $_GRP_ID;
113
114
115
    /**
116
     * @since 4.9.0
117
     * @type EE_Message
118
     */
119
    protected $_message;
120
121
122
    /**
123
     * This will hold an instance of the EEH_Parse_Shortcodes helper that will be used when handling list type
124
     * shortcodes
125
     *
126
     * @var EEH_Parse_Shortcodes
127
     */
128
    protected $_shortcode_helper;
129
130
131
    public function __construct()
132
    {
133
        $this->_set_defaults();
134
        $this->_init_props();
135
    }
136
137
138
    /**
139
     * This sets the defaults for the properties.  Child classes will override these properties in their _init_props
140
     * method
141
     */
142
    private function _set_defaults()
143
    {
144
        $this->name        = $this->description = '';
0 ignored issues
show
Documentation introduced by
The property name does not exist on object<EE_Shortcodes>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
145
        $this->_shortcodes = array();
146
        $this->_set_shortcode_helper();
147
    }
148
149
150
    /**
151
     * loads an instance of the EE_Shortcode_Parser helper when requested
152
     */
153
    protected function _set_shortcode_helper()
154
    {
155
        //get shortcode_replace instance- set when _get_messages is called in child...
156
        $this->_shortcode_helper = new EEH_Parse_Shortcodes();
157
    }
158
159
160
    public function get_shortcode_helper()
161
    {
162
        if (! $this->_shortcode_helper instanceof EEH_Parse_Shortcodes) {
163
            $this->_set_shortcode_helper();
164
        }
165
        return $this->_shortcode_helper;
166
    }
167
168
169
    /**
170
     * This is the public method for kicking of the parser included with each child.  It can be overridden by child
171
     * classes if necessary (see EE_Questions_Answers for example)
172
     *
173
     * @param  string               $shortcode  incoming shortcode to be parsed
174
     * @param  mixed (object|array) $data       incoming data to be be used for parsing
175
     * @param  mixed (object|array) $extra_data extra incoming data (usually EE_Messages_Addressee)
176
     * @return string            parsed shortcode.
177
     */
178
    public function parser($shortcode, $data, $extra_data = array())
179
    {
180
181
        //filter setup shortcodes
182
        $this->_shortcodes = $this->get_shortcodes();
183
184
        //we need to setup any dynamic shortcodes so that they work with the array_key_exists
185
        $sc           = preg_match_all('/(\[[A-Za-z0-9\_]+_\*)/', $shortcode, $matches);
0 ignored issues
show
Unused Code introduced by
$sc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
186
        $sc_to_verify = ! empty($matches[0]) ? $matches[0][0] . ']' : $shortcode;
187
188
        //first we want to make sure this is a valid shortcode
189
        if (! array_key_exists($sc_to_verify, $this->_shortcodes)) {
190
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by EE_Shortcodes::parser of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
191
        } //get out, this parser doesn't handle the incoming shortcode.
192
        $this->_data       = $data;
193
        $this->_extra_data = $extra_data;
194
        $this->_set_messages_properties();
195
        $parsed = apply_filters('FHEE__' . get_class($this) . '__parser_after', $this->_parser($shortcode), $shortcode,
196
            $data, $extra_data, $this);
197
198
        //note the below filter applies to ALL shortcode parsers... be careful!
199
        $parsed = apply_filters('FHEE__EE_Shortcodes__parser_after', $parsed, $shortcode, $data, $extra_data, $this);
200
        return $parsed;
201
    }
202
203
204
    /**
205
     * This method just returns the shortcodes in the $_shortcodes array property.
206
     *
207
     * @access public
208
     * @return array array of shortcodes => description pairs
209
     */
210
    public function get_shortcodes()
211
    {
212
        $this->_shortcodes = apply_filters('FHEE__' . get_class($this) . '__shortcodes', $this->_shortcodes, $this);
213
214
        //note the below filter applies to ALL shortcode parsers... be careful!
215
        $this->_shortcodes = apply_filters('FHEE__EE_Shortcodes__shortcodes', $this->_shortcodes, $this);
216
217
        return $this->_shortcodes;
218
    }
219
220
221
    /**
222
     * Child classes use this method to set the $name, $description, and $_shortcodes properties.
223
     *
224
     * @abstract
225
     * @access protected
226
     * @return void
227
     */
228
    abstract protected function _init_props();
229
230
231
    /**
232
     * This method will give parsing instructions for each shortcode defined in the _shortcodes array.  Child methods
233
     * will have to take care of handling.
234
     *
235
     * @abstract
236
     * @access protected
237
     * @param string               $shortcode the shortcode to be parsed.
238
     * @param mixed (object|array) $data      incoming data for the parser.  The data could be either an object or
239
     *                             array because there are some shortcodes that might be replaced by prepared data that
240
     *                             has multiple items in a list (i.e. list of attendees in an event and we're showing
241
     *                             fname/lname for each attendee).  In this case data will be in an array.  Otherwise
242
     *                             the data shoudl be in a properly formatted object.  The
243
     *                             EEH_Parse_Shortcodes.helper.php describes the data object we're expecting.
244
     * @return string parsed shortcode
245
     */
246
    abstract protected function _parser($shortcode);
247
248
249
    /**
250
     * This just validates incoming data for list type shortcode parsers (and they call this method) to make sure it
251
     * meets their requirements
252
     *
253
     * @return mixed (void|exception) If validation fails we'll throw an exception.
254
     */
255
    protected function _validate_list_requirements()
256
    {
257
258
        //first test to make sure we've got an array!
259
        if (! is_array($this->_data)) {
260
            throw new EE_Error(sprintf(__('Expecting an array for the data sent to %s. Instead it was %s',
261
                'event_espresso'), get_class($this), gettype($this->_data)));
262
        }
263
264
        //next test to make sure we've got the required template in the index!
265
        if (! isset($this->_data['template'])) {
266
            throw new EE_Error(sprintf(__('The incoming data does not have the required template index in its array',
267
                'event_espresso')));
268
        }
269
270
        //next test to make sure we've got got a data index in the incoming data array
271
        if (! isset($this->_data['data'])) {
272
            throw new EE_Error(__('The incoming data does not have the required data index in its array',
273
                'event_espresso'));
274
        }
275
276
        //all is well let's make sure _extra_data always has the values needed.
277
        //let's make sure that extra_data includes all templates (for later parsing if necessary)
278
        if (empty($this->_extra_data) || (empty($this->_extra_data['data']) && empty($this->_extra_data['template']))) {
279
            $this->_extra_data['data']     = $this->_data['data'];
280
            $this->_extra_data['template'] = $this->_data['template'];
281
        }
282
283
    }
284
285
286
    /**
287
     * This returns any attributes that may be existing on an EE_Shortcode
288
     *
289
     * @since 4.5.0
290
     * @param string $shortcode incoming shortcode
291
     * @return array An array with the attributes
292
     */
293
    protected function _get_shortcode_attrs($shortcode)
294
    {
295
        //make sure the required wp helper function is present
296
        //require the shortcode file if necessary
297
        if (! function_exists('shortcode_parse_atts')) {
298
            require_once(ABSPATH . WPINC . '/shortcodes.php');
299
        }
300
301
        //let's get any attributes that may be present and set the defaults.
302
        $shortcode_to_parse = str_replace('[', '', str_replace(']', '', $shortcode));
303
        return shortcode_parse_atts($shortcode_to_parse);
304
    }
305
306
307
    /**
308
     * Conditional blocks are shortcode patterns with an opening conditional tag `[IF_*]` and a corresponding
309
     * closing tag (eg `[/IF_*]`).  The content within the tags will be displayed/hidden depending on whatever conditions
310
     * existed in the opening tag.  This method handles parsing the actual template to show/hide this conditional content.
311
     *
312
     * @since 4.9.32
313
     *
314
     * @param string $shortcode  This should be original shortcode as used in the template and passed to the parser.
315
     * @param bool $show  true means the opening and closing tags are removed and the content is left showing, false
316
     *                    means the opening and closing tags and the contained content are removed.
317
     * @return string     The template for the shortcode is returned.
318
     */
319
    protected function _mutate_conditional_block_in_template($shortcode, $show = true)
320
    {
321
        //first let's get all the matches in the template for this particular shortcode.
322
        preg_match_all('~' . $this->_get_conditional_block_regex($shortcode) . '~', $this->_data['template'], $matches);
323
324
        if ($matches && is_array($matches[0]) && !empty($matches[0])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[][] 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...
325
            //we need to hide all instances of the matches
326
            foreach ($matches[0] as $index => $content_to_show_or_hide) {
327
                $content_to_show_or_hide = preg_quote($content_to_show_or_hide);
328
                $replacement = $show ? $matches[4][$index] : '';
329
                $this->_data['template'] = preg_replace(
330
                    '~' . $content_to_show_or_hide . '~',
331
                    $replacement,
332
                    $this->_data['template']
333
                );
334
            }
335
        }
336
        //return $template
337
        return $this->_data['template'];
338
    }
339
340
341
    /**
342
     * This returns the regex pattern to use for conditional shortcodes parsing.
343
     *
344
     * Note: regex comes in part from the WP `get_shortcode_regex` expression in \wp-includes\shortcodes.php
345
     *
346
     * @param $shortcode
347
     * @since 4.9.32
348
     * @return string
349
     */
350
    private function _get_conditional_block_regex($shortcode)
351
    {
352
        //get just the shortcode tag for the match
353
        preg_match('@\[([^<>&/\[\]\x00-\x20=]++)@', $shortcode, $shortcode_tag_matches);
354
        if (empty($shortcode_tag_matches[1])) {
355
            return $this->_data['template'];
356
        }
357
358
        $shortcode_tag = $shortcode_tag_matches[1];
359
        //get attributes_part_of_tag
360
        $attributes_part = preg_quote(str_replace(array($shortcode_tag,'[',']'), '', $shortcode));
361
        //escape
362
        $shortcode_tag = preg_quote($shortcode_tag);
363
364
        return
365
              '\['                                  //Opening Bracket
366
            . "($shortcode_tag)$attributes_part"    //1: Shortcode Name
367
            . '(?![\w-])'                           //Not followed by word character or hyphen
368
            . '('                                   //2: Unroll the loop: Inside the opening shortcode tag
369
            .   '[^\]\/]*'                          //Not a closing bracket or forward slash
370
            .   '(?:'
371
            .       '\/(?!\])'                      //A forward slash not followed by a closing bracket
372
            .       '[^\]\/]*'                      //Not a closing bracket or forward slash.
373
            .   ')*?'
374
            . ')'
375
            . '(?:'
376
            .   '(\/)'                              //3. Self closing tag ...
377
            .   '\]'                                // ... and closing bracket
378
            . '|'
379
            .   '\]'                                //Closing bracket
380
            .   '(?:'
381
            .       '('                             //4: Unroll the loop: Optionally, anything between the opening and closing brackets
382
            .           '[^\[]*+'                   //Not an opening bracket
383
            .           '(?:'
384
            .               '\[(?!\/\1\])'          //An opening bracket not followed by the closing shortcode tag.
385
            .               '[^\[]*+'               //Not an opening bracket
386
            .           ')*+'
387
            .       ')'
388
            .       '\[\/\1\]'                      //Closing shortcode tag
389
            .   ')?'
390
            . ')';
391
    }
392
393
394
    /**
395
     * This sets the properties related to the messages system
396
     *
397
     * @since 4.5.0
398
     * @return void
399
     */
400
    protected function _set_messages_properties()
401
    {
402
        //should be in _extra_data
403
        if (isset($this->_extra_data['messenger'])) {
404
            $this->_messenger    = $this->_extra_data['messenger'];
405
            $this->_message_type = $this->_extra_data['message_type'];
406
            $this->_context      = $this->_extra_data['message'] instanceof EE_Message ? $this->_extra_data['message']->context() : '';
407
            $this->_GRP_ID       = $this->_extra_data['message'] instanceof EE_Message ? $this->_extra_data['message']->GRP_ID() : 0;
408
            $this->_message      = $this->_extra_data['message'] instanceof EE_Message ? $this->_extra_data['message'] : null;
409
        }
410
    }
411
412
413
    /**
414
     * This returns whatever the set message type object is that was set on this shortcode parser.
415
     *
416
     * @since 4.5.0
417
     * @return EE_message_type
418
     */
419
    public function get_set_message_type()
420
    {
421
        return $this->_message_type;
422
    }
423
424
425
    /**
426
     * This returns whatever the set messenger object is that was set on this shortcode parser
427
     *
428
     * @since 4.5.0
429
     * @return EE_messenger
430
     */
431
    public function get_set_messenger()
432
    {
433
        return $this->_messenger;
434
    }
435
436
437
    /**
438
     * This returns whatever the set context string is on this shortcode parser.
439
     *
440
     * @since 4.5.0
441
     * @return string
442
     */
443
    public function get_set_context()
444
    {
445
        return $this->_context;
446
    }
447
448
449
    /**
450
     * This returns whatever the set EE_Message object is on this shortcode.
451
     *
452
     * @since 4.9.0
453
     * @return EE_Message
454
     */
455
    public function get_set_message()
456
    {
457
        return $this->_message;
458
    }
459
460
461
} //end EE_Shortcodes
462