Hooks   F
last analyzed

Complexity

Total Complexity 131

Size/Duplication

Total Lines 1099
Duplicated Lines 15.2 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 79.79%

Importance

Changes 0
Metric Value
wmc 131
lcom 2
cbo 0
dl 167
loc 1099
ccs 300
cts 376
cp 0.7979
rs 1.204
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A __clone() 0 3 1
A __wakeup() 0 3 1
A getInstance() 0 10 2
A add_filter() 0 13 2
A remove_filter() 0 17 3
A remove_all_filters() 0 18 5
A has_filter() 0 19 6
C apply_filters() 16 56 11
B apply_filters_ref_array() 29 47 10
A add_action() 0 9 1
A has_action() 0 4 1
A remove_action() 0 4 1
A remove_all_actions() 0 4 1
F do_action() 34 79 17
C do_action_ref_array() 34 57 12
A did_action() 0 8 3
A current_filter() 0 4 1
A _filter_build_unique_id() 0 28 5
A _call_all_hook() 13 18 5
A __call_all_hook() 0 7 1
A add_shortcode() 0 10 2
A remove_shortcode() 0 10 2
A remove_all_shortcodes() 0 6 1
A shortcode_exists() 0 4 1
B has_shortcode() 0 25 8
A do_shortcode() 17 17 3
A get_shortcode_regex() 0 37 1
A _do_shortcode_tag() 3 18 4
B shortcode_parse_atts() 0 26 9
A shortcode_atts() 0 36 4
A strip_shortcodes() 18 18 3
A _strip_shortcode_tag() 3 9 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Hooks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Hooks, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * PHP Hooks Class (Modified)
9
 *
10
 * <p>
11
 * <br />
12
 * The PHP Hooks Class is a fork of the WordPress filters hook system rolled in
13
 * to a class to be ported into any php based system
14
 *
15
 * <br /><br />
16
 * This class is heavily based on the WordPress plugin API and most (if not all)
17
 * of the code comes from there.
18
 * </p>
19
 *
20
 * @copyright   2011 - 2018
21
 *
22
 * @author      Ohad Raz <[email protected]>
23
 * @link        http://en.bainternet.info
24
 * @author      David Miles <[email protected]>
25
 * @link        http://github.com/amereservant/PHP-Hooks
26
 * @author      Lars Moelleken <[email protected]>
27
 * @link        https://github.com/voku/PHP-Hooks/
28
 * @author      Damien "Mistic" Sorel <[email protected]>
29
 * @link        http://www.strangeplanet.fr
30
 *
31
 * @license     GNU General Public License v3.0 - license.txt
32
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
35
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
38
 * THE SOFTWARE.
39
 *
40
 * @package     voku\helper
41
 */
42
class Hooks
43
{
44
  /**
45
   * Filters - holds list of hooks
46
   *
47
   * @var array
48
   */
49
  protected $filters = [];
50
51
  /**
52
   * Merged Filters
53
   *
54
   * @var array
55
   */
56
  protected $merged_filters = [];
57
58
  /**
59
   * Actions
60
   *
61
   * @var array
62
   */
63
  protected $actions = [];
64
65
  /**
66
   * Current Filter - holds the name of the current filter
67
   *
68
   * @var array
69
   */
70
  protected $current_filter = [];
71
72
  /**
73
   * Container for storing shortcode tags and their hook to call for the shortcode
74
   *
75
   * @var array
76
   */
77
  public static $shortcode_tags = [];
78
79
  /**
80
   * Default priority
81
   *
82
   * @const int
83
   */
84
  const PRIORITY_NEUTRAL = 50;
85
86
  /**
87 1
   * This class is not allowed to call from outside: private!
88
   */
89 1
  protected function __construct()
90
  {
91
  }
92
93
  /**
94
   * Prevent the object from being cloned.
95
   */
96
  protected function __clone()
97
  {
98
  }
99
100
  /**
101
   * Avoid serialization.
102
   */
103
  public function __wakeup()
104
  {
105
  }
106
107
  /**
108
   * Returns a Singleton instance of this class.
109
   *
110 10
   * @return Hooks
111
   */
112 10
  public static function getInstance(): self
113
  {
114 10
    static $instance;
115 1
116 1
    if (null === $instance) {
117
      $instance = new self();
118 10
    }
119
120
    return $instance;
121
  }
122
123
  /**
124
   * FILTERS
125
   */
126
127
  /**
128
   * Adds Hooks to a function or method to a specific filter action.
129
   *
130
   * @param    string              $tag             <p>
131
   *                                                The name of the filter to hook the
132
   *                                                {@link $function_to_add} to.
133
   *                                                </p>
134
   * @param    string|array|object $function_to_add <p>
135
   *                                                The name of the function to be called
136
   *                                                when the filter is applied.
137
   *                                                </p>
138
   * @param    int                 $priority        <p>
139
   *                                                [optional] Used to specify the order in
140
   *                                                which the functions associated with a
141
   *                                                particular action are executed (default: 50).
142
   *                                                Lower numbers correspond with earlier execution,
143
   *                                                and functions with the same priority are executed
144
   *                                                in the order in which they were added to the action.
145
   *                                                </p>
146
   * @param string                 $include_path    <p>
147
   *                                                [optional] File to include before executing the callback.
148
   *                                                </p>
149
   *
150 7
   * @return bool
151
   */
152 7
  public function add_filter(string $tag, $function_to_add, int $priority = self::PRIORITY_NEUTRAL, string $include_path = null): bool
153
  {
154 7
    $idx = $this->_filter_build_unique_id($function_to_add);
155 7
156 7
    $this->filters[$tag][$priority][$idx] = [
157
        'function'     => $function_to_add,
158
        'include_path' => \is_string($include_path) ? $include_path : null,
159 7
    ];
160
161 7
    unset($this->merged_filters[$tag]);
162
163
    return true;
164
  }
165
166
  /**
167
   * Removes a function from a specified filter hook.
168
   *
169
   * @param string              $tag                <p>The filter hook to which the function to be removed is
170
   *                                                hooked.</p>
171
   * @param string|array|object $function_to_remove <p>The name of the function which should be removed.</p>
172
   * @param int                 $priority           <p>[optional] The priority of the function (default: 50).</p>
173 1
   *
174
   * @return bool
175 1
   */
176
  public function remove_filter(string $tag, $function_to_remove, int $priority = self::PRIORITY_NEUTRAL): bool
177 1
  {
178 1
    $function_to_remove = $this->_filter_build_unique_id($function_to_remove);
179
180
    if (!isset($this->filters[$tag][$priority][$function_to_remove])) {
181 1
      return false;
182 1
    }
183 1
184 1
    unset($this->filters[$tag][$priority][$function_to_remove]);
185
    if (empty($this->filters[$tag][$priority])) {
186 1
      unset($this->filters[$tag][$priority]);
187
    }
188 1
189
    unset($this->merged_filters[$tag]);
190
191
    return true;
192
  }
193
194
  /**
195
   * Remove all of the hooks from a filter.
196
   *
197
   * @param string    $tag      <p>The filter to remove hooks from.</p>
198
   * @param false|int $priority <p>The priority number to remove.</p>
199 4
   *
200
   * @return bool
201 4
   */
202
  public function remove_all_filters(string $tag, $priority = false): bool
203
  {
204
    if (isset($this->merged_filters[$tag])) {
205 4
      unset($this->merged_filters[$tag]);
206 2
    }
207
208
    if (!isset($this->filters[$tag])) {
209 2
      return true;
210 1
    }
211 1
212 2
    if (false !== $priority && isset($this->filters[$tag][$priority])) {
213
      unset($this->filters[$tag][$priority]);
214
    } else {
215 2
      unset($this->filters[$tag]);
216
    }
217
218
    return true;
219
  }
220
221
  /**
222
   * Check if any filter has been registered for the given hook.
223
   *
224
   * <p>
225
   * <br />
226
   * <strong>INFO:</strong> Use !== false to check if it's true!
227
   * </p>
228
   *
229
   * @param    string       $tag               <p>The name of the filter hook.</p>
230
   * @param    false|string $function_to_check <p>[optional] Callback function name to check for </p>
231
   *
232
   * @return   mixed                       <p>
233
   *                                       If {@link $function_to_check} is omitted,
234
   *                                       returns boolean for whether the hook has
235
   *                                       anything registered.
236
   *                                       When checking a specific function, the priority
237
   *                                       of that hook is returned, or false if the
238
   *                                       function is not attached.
239
   *                                       When using the {@link $function_to_check} argument,
240
   *                                       this function may return a non-boolean value that
241
   *                                       evaluates to false
242 3
   *                                       (e.g.) 0, so use the === operator for testing the return value.
243
   *                                       </p>
244 3
   */
245 3
  public function has_filter(string $tag, $function_to_check = false)
246 3
  {
247
    $has = isset($this->filters[$tag]);
248
    if (false === $function_to_check || !$has) {
249 3
      return $has;
250
    }
251
252
    if (!($idx = $this->_filter_build_unique_id($function_to_check))) {
253 3
      return false;
254 3
    }
255 3
256
    foreach (\array_keys($this->filters[$tag]) as $priority) {
257 2
      if (isset($this->filters[$tag][$priority][$idx])) {
258
        return $priority;
259 2
      }
260
    }
261
262
    return false;
263
  }
264
265
  /**
266
   * Call the functions added to a filter hook.
267
   *
268
   * <p>
269
   * <br />
270
   * <strong>INFO:</strong> Additional variables passed to the functions hooked to <tt>$tag</tt>.
271
   * </p>
272
   *
273
   * @param    string $tag   <p>The name of the filter hook.</p>
274
   * @param    mixed  $value <p>The value on which the filters hooked to <tt>$tag</tt> are applied on.</p>
275 4
   *
276
   * @return   mixed               <p>The filtered value after all hooked functions are applied to it.</p>
277 4
   */
278
  public function apply_filters(string $tag, $value)
279
  {
280 4
    $args = [];
281 1
282 1
    // Do 'all' actions first
283 1 View Code Duplication
    if (isset($this->filters['all'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
284 1
      $this->current_filter[] = $tag;
285
      $args = \func_get_args();
286 4
      $this->_call_all_hook($args);
287 1
    }
288 1
289 1 View Code Duplication
    if (!isset($this->filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
290
      if (isset($this->filters['all'])) {
291 1
        \array_pop($this->current_filter);
292
      }
293
294 4
      return $value;
295 4
    }
296 4
297
    if (!isset($this->filters['all'])) {
298
      $this->current_filter[] = $tag;
299 4
    }
300 3
301 3
    // Sort
302 3 View Code Duplication
    if (!isset($this->merged_filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
303
      \ksort($this->filters[$tag]);
304 4
      $this->merged_filters[$tag] = true;
305
    }
306 4
307 4
    \reset($this->filters[$tag]);
308 4
309
    if (empty($args)) {
310 4
      $args = \func_get_args();
311
    }
312
313 4
    \array_shift($args);
314 4
315
    do {
316 4
      foreach ((array)\current($this->filters[$tag]) as $the_) {
317
        if (null !== $the_['function']) {
318
319
          if (null !== $the_['include_path']) {
320
            /** @noinspection PhpIncludeInspection */
321 4
            include_once $the_['include_path'];
322 4
          }
323 4
324 4
          $args[0] = $value;
325 4
          $value = \call_user_func_array($the_['function'], $args);
326
        }
327 4
      }
328
    } while (\next($this->filters[$tag]) !== false);
329 4
330
    \array_pop($this->current_filter);
331
332
    return $value;
333
  }
334
335
  /**
336
   * Execute functions hooked on a specific filter hook, specifying arguments in an array.
337
   *
338
   * @param    string $tag  <p>The name of the filter hook.</p>
339
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
340 1
   *
341
   * @return   mixed        <p>The filtered value after all hooked functions are applied to it.</p>
342
   */
343 1
  public function apply_filters_ref_array(string $tag, array $args)
344 1
  {
345 1
    // Do 'all' actions first
346 1 View Code Duplication
    if (isset($this->filters['all'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
347 1
      $this->current_filter[] = $tag;
348
      $all_args = \func_get_args();
349 1
      $this->_call_all_hook($all_args);
350 1
    }
351
352 View Code Duplication
    if (!isset($this->filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
353
      if (isset($this->filters['all'])) {
354 1
        \array_pop($this->current_filter);
355
      }
356
357 1
      return $args[0];
358
    }
359
360
    if (!isset($this->filters['all'])) {
361
      $this->current_filter[] = $tag;
362 1
    }
363
364
    // Sort
365 View Code Duplication
    if (!isset($this->merged_filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
366
      \ksort($this->filters[$tag]);
367 1
      $this->merged_filters[$tag] = true;
368
    }
369
370 1
    \reset($this->filters[$tag]);
371 1
372 View Code Duplication
    do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
373 1
      foreach ((array)\current($this->filters[$tag]) as $the_) {
374
        if (null !== $the_['function']) {
375
376
          if (null !== $the_['include_path']) {
377
            /** @noinspection PhpIncludeInspection */
378 1
            include_once $the_['include_path'];
379 1
          }
380 1
381 1
          $args[0] = \call_user_func_array($the_['function'], $args);
382
        }
383 1
      }
384
    } while (\next($this->filters[$tag]) !== false);
385 1
386
    \array_pop($this->current_filter);
387
388
    return $args[0];
389
  }
390
391
  /**
392
   * Hooks a function on to a specific action.
393
   *
394
   * @param    string       $tag              <p>
395
   *                                          The name of the action to which the
396
   *                                          <tt>$function_to_add</tt> is hooked.
397
   *                                          </p>
398
   * @param    string|array $function_to_add  <p>The name of the function you wish to be called.</p>
399
   * @param    int          $priority         <p>
400
   *                                          [optional] Used to specify the order in which
401
   *                                          the functions associated with a particular
402
   *                                          action are executed (default: 50).
403
   *                                          Lower numbers correspond with earlier execution,
404
   *                                          and functions with the same priority are executed
405
   *                                          in the order in which they were added to the action.
406
   *                                          </p>
407
   * @param     string      $include_path     <p>[optional] File to include before executing the callback.</p>
408 5
   *
409
   * @return bool
410 5
   */
411
  public function add_action(
412
      string $tag,
413
      $function_to_add,
414
      int $priority = self::PRIORITY_NEUTRAL,
415
      string $include_path = null
416
  ): bool
417
  {
418
    return $this->add_filter($tag, $function_to_add, $priority, $include_path);
419
  }
420
421
  /**
422
   * Check if any action has been registered for a hook.
423
   *
424
   * <p>
425
   * <br />
426
   * <strong>INFO:</strong> Use !== false to check if it's true!
427
   * </p>
428
   *
429
   * @param    string    $tag               <p>The name of the action hook.</p>
430
   * @param false|string $function_to_check <p>[optional]</p>
431
   *
432
   * @return   mixed                       <p>
433
   *                                       If <tt>$function_to_check</tt> is omitted,
434
   *                                       returns boolean for whether the hook has
435
   *                                       anything registered.
436
   *                                       When checking a specific function,
437 3
   *                                       the priority of that hook is returned,
438
   *                                       or false if the function is not attached.
439 3
   *                                       When using the <tt>$function_to_check</tt>
440
   *                                       argument, this function may return a non-boolean
441
   *                                       value that evaluates to false (e.g.) 0,
442
   *                                       so use the === operator for testing the return value.
443
   *                                       </p>
444
   */
445
  public function has_action(string $tag, $function_to_check = false)
446
  {
447
    return $this->has_filter($tag, $function_to_check);
448
  }
449
450
  /**
451 1
   * Removes a function from a specified action hook.
452
   *
453 1
   * @param string $tag                <p>The action hook to which the function to be removed is hooked.</p>
454
   * @param mixed  $function_to_remove <p>The name of the function which should be removed.</p>
455
   * @param int    $priority           <p>[optional] The priority of the function (default: 50).</p>
456
   *
457
   * @return bool <p>Whether the function is removed.</p>
458
   */
459
  public function remove_action(string $tag, $function_to_remove, int $priority = self::PRIORITY_NEUTRAL): bool
460
  {
461
    return $this->remove_filter($tag, $function_to_remove, $priority);
462
  }
463
464 4
  /**
465
   * Remove all of the hooks from an action.
466 4
   *
467
   * @param string    $tag      <p>The action to remove hooks from.</p>
468
   * @param false|int $priority <p>The priority number to remove them from.</p>
469
   *
470
   * @return bool
471
   */
472
  public function remove_all_actions(string $tag, $priority = false): bool
473
  {
474
    return $this->remove_all_filters($tag, $priority);
475
  }
476
477
  /**
478
   * Execute functions hooked on a specific action hook.
479
   *
480 2
   * @param    string $tag     <p>The name of the action to be executed.</p>
481
   * @param    mixed  $arg     <p>
482 2
   *                           [optional] Additional arguments which are passed on
483
   *                           to the functions hooked to the action.
484
   *                           </p>
485
   *
486 2
   * @return   bool            <p>Will return false if $tag does not exist in $filter array.</p>
487 2
   */
488 2
  public function do_action(string $tag, $arg = ''): bool
489 1
  {
490
    if (!\is_array($this->actions)) {
491
      $this->actions = [];
492
    }
493 2
494 1 View Code Duplication
    if (isset($this->actions[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
495 1
      ++$this->actions[$tag];
496 1
    } else {
497 1
      $this->actions[$tag] = 1;
498
    }
499 2
500 1
    // Do 'all' actions first
501 1 View Code Duplication
    if (isset($this->filters['all'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
502 1
      $this->current_filter[] = $tag;
503
      $all_args = \func_get_args();
504 1
      $this->_call_all_hook($all_args);
505
    }
506
507 2 View Code Duplication
    if (!isset($this->filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
508 2
      if (isset($this->filters['all'])) {
509 2
        \array_pop($this->current_filter);
510
      }
511 2
512
      return false;
513
    }
514 2
515 2
    if (!isset($this->filters['all'])) {
516
      $this->current_filter[] = $tag;
517 2
    }
518
519 2
    $args = [];
520
521 2
    if (
522
        \is_array($arg)
523
        &&
524 2
        isset($arg[0])
525
        &&
526
        \is_object($arg[0])
527 2
        &&
528
        1 == \count($arg)
529 2
    ) {
530 1
      $args[] =& $arg[0];
531 1
    } else {
532
      $args[] = $arg;
533
    }
534 2
535 2
    $numArgs = \func_num_args();
536 2
537 2
    for ($a = 2; $a < $numArgs; $a++) {
538
      $args[] = \func_get_arg($a);
539 2
    }
540
541
    // Sort
542 2 View Code Duplication
    if (!isset($this->merged_filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
543 2
      \ksort($this->filters[$tag]);
544
      $this->merged_filters[$tag] = true;
545 2
    }
546
547
    \reset($this->filters[$tag]);
548
549 View Code Duplication
    do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
550 2
      foreach ((array)\current($this->filters[$tag]) as $the_) {
551 2
        if (null !== $the_['function']) {
552 2
553 2
          if (null !== $the_['include_path']) {
554
            /** @noinspection PhpIncludeInspection */
555 2
            include_once $the_['include_path'];
556
          }
557 2
558
          \call_user_func_array($the_['function'], $args);
559
        }
560
      }
561
    } while (\next($this->filters[$tag]) !== false);
562
563
    \array_pop($this->current_filter);
564
565
    return true;
566
  }
567
568 1
  /**
569
   * Execute functions hooked on a specific action hook, specifying arguments in an array.
570 1
   *
571
   * @param    string $tag  <p>The name of the action to be executed.</p>
572
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
573
   *
574 1
   * @return   bool         <p>Will return false if $tag does not exist in $filter array.</p>
575 1
   */
576 1
  public function do_action_ref_array(string $tag, array $args): bool
577 1
  {
578
    if (!\is_array($this->actions)) {
579
      $this->actions = [];
580
    }
581 1
582 1 View Code Duplication
    if (isset($this->actions[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
583 1
      ++$this->actions[$tag];
584 1
    } else {
585 1
      $this->actions[$tag] = 1;
586
    }
587 1
588 1
    // Do 'all' actions first
589 View Code Duplication
    if (isset($this->filters['all'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
590
      $this->current_filter[] = $tag;
591
      $all_args = \func_get_args();
592 1
      $this->_call_all_hook($all_args);
593
    }
594
595 1 View Code Duplication
    if (!isset($this->filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
596
      if (isset($this->filters['all'])) {
597
        \array_pop($this->current_filter);
598
      }
599
600 1
      return false;
601 1
    }
602 1
603 1
    if (!isset($this->filters['all'])) {
604
      $this->current_filter[] = $tag;
605 1
    }
606
607
    // Sort
608 1 View Code Duplication
    if (!isset($this->merged_filters[$tag])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
609 1
      \ksort($this->filters[$tag]);
610
      $this->merged_filters[$tag] = true;
611 1
    }
612
613
    \reset($this->filters[$tag]);
614
615 View Code Duplication
    do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
616 1
      foreach ((array)\current($this->filters[$tag]) as $the_) {
617 1
        if (null !== $the_['function']) {
618 1
619 1
          if (null !== $the_['include_path']) {
620
            /** @noinspection PhpIncludeInspection */
621 1
            include_once $the_['include_path'];
622
          }
623 1
624
          \call_user_func_array($the_['function'], $args);
625
        }
626
      }
627
    } while (\next($this->filters[$tag]) !== false);
628
629
    \array_pop($this->current_filter);
630
631
    return true;
632
  }
633 1
634
  /**
635 1
   * Retrieve the number of times an action has fired.
636
   *
637
   * @param string $tag <p>The name of the action hook.</p>
638
   *
639 1
   * @return int <p>The number of times action hook <tt>$tag</tt> is fired.</p>
640
   */
641
  public function did_action(string $tag): int
642
  {
643
    if (!\is_array($this->actions) || !isset($this->actions[$tag])) {
644
      return 0;
645
    }
646
647
    return $this->actions[$tag];
648
  }
649
650
  /**
651
   * Retrieve the name of the current filter or action.
652
   *
653
   * @return string <p>Hook name of the current filter or action.</p>
654
   */
655
  public function current_filter(): string
656
  {
657
    return \end($this->current_filter);
658
  }
659
660
  /**
661
   * Build Unique ID for storage and retrieval.
662
   *
663 7
   * @param    string|array|object $function <p>Used for creating unique id.</p>
664
   *
665 7
   * @return   string|false            <p>
666 3
   *                                   Unique ID for usage as array key or false if
667
   *                                   $priority === false and $function is an
668
   *                                   object reference, and it does not already have a unique id.
669 4
   *                                   </p>
670
   */
671
  private function _filter_build_unique_id($function)
672 2
  {
673 2
    if (\is_string($function)) {
674 2
      return $function;
675 2
    }
676 2
677
    if (\is_object($function)) {
678
      // Closures are currently implemented as objects
679 4
      $function = [
680
          $function,
681 4
          '',
682
      ];
683
    } else {
684
      $function = (array)$function;
685
    }
686
687
    if (\is_object($function[0])) {
688
      // Object Class Calling
689
      return \spl_object_hash($function[0]) . $function[1];
690
    }
691
692
    if (\is_string($function[0])) {
693
      // Static Calling
694
      return $function[0] . $function[1];
695
    }
696
697 1
    return false;
698
  }
699 1
700
  /**
701
   * Call "All" Hook
702 1
   *
703 1
   * @param array $args
704
   */
705 1
  public function _call_all_hook(array $args)
706
  {
707
    \reset($this->filters['all']);
708
709 View Code Duplication
    do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
710 1
      foreach ((array)\current($this->filters['all']) as $the_) {
711 1
        if (null !== $the_['function']) {
712 1
713 1
          if (null !== $the_['include_path']) {
714 1
            /** @noinspection PhpIncludeInspection */
715
            include_once $the_['include_path'];
716
          }
717
718
          \call_user_func_array($the_['function'], $args);
719
        }
720
      }
721
    } while (\next($this->filters['all']) !== false);
722
  }
723
724
  /** @noinspection MagicMethodsValidityInspection */
725
  /**
726
   * @param array $args
727
   *
728
   * @deprecated use "this->_call_all_hook()"
729
   */
730
  public function __call_all_hook(array $args)
731
  {
732
    // <-- refactoring "__call_all_hook()" into "_call_all_hook()" is a breaking change (BC),
733
    // so we will only deprecate the usage
734
735
    $this->_call_all_hook($args);
736
  }
737
738
  /**
739
   * Add hook for shortcode tag.
740
   *
741
   * <p>
742
   * <br />
743
   * There can only be one hook for each shortcode. Which means that if another
744
   * plugin has a similar shortcode, it will override yours or yours will override
745
   * theirs depending on which order the plugins are included and/or ran.
746
   * <br />
747
   * <br />
748
   * </p>
749
   *
750
   * Simplest example of a shortcode tag using the API:
751
   *
752
   * <code>
753
   * // [footag foo="bar"]
754
   * function footag_func($atts) {
755
   *  return "foo = {$atts[foo]}";
756
   * }
757
   * add_shortcode('footag', 'footag_func');
758
   * </code>
759
   *
760
   * Example with nice attribute defaults:
761
   *
762
   * <code>
763
   * // [bartag foo="bar"]
764
   * function bartag_func($atts) {
765
   *  $args = shortcode_atts(array(
766
   *    'foo' => 'no foo',
767
   *    'baz' => 'default baz',
768
   *  ), $atts);
769
   *
770
   *  return "foo = {$args['foo']}";
771
   * }
772
   * add_shortcode('bartag', 'bartag_func');
773
   * </code>
774
   *
775
   * Example with enclosed content:
776
   *
777
   * <code>
778
   * // [baztag]content[/baztag]
779
   * function baztag_func($atts, $content='') {
780
   *  return "content = $content";
781
   * }
782 2
   * add_shortcode('baztag', 'baztag_func');
783
   * </code>
784 2
   *
785 2
   * @param string   $tag  <p>Shortcode tag to be searched in post content.</p>
786
   * @param callable $func <p>Hook to run when shortcode is found.</p>
787 2
   *
788
   * @return bool
789
   */
790
  public function add_shortcode(string $tag, $func): bool
791
  {
792
    if (\is_callable($func)) {
793
      self::$shortcode_tags[$tag] = $func;
794
795
      return true;
796
    }
797
798
    return false;
799
  }
800 1
801
  /**
802 1
   * Removes hook for shortcode.
803 1
   *
804
   * @param string $tag <p>shortcode tag to remove hook for.</p>
805 1
   *
806
   * @return bool
807
   */
808
  public function remove_shortcode(string $tag): bool
809
  {
810
    if (isset(self::$shortcode_tags[$tag])) {
811
      unset(self::$shortcode_tags[$tag]);
812
813
      return true;
814
    }
815
816
    return false;
817
  }
818 1
819
  /**
820 1
   * This function is simple, it clears all of the shortcode tags by replacing the
821
   * shortcodes by a empty array. This is actually a very efficient method
822 1
   * for removing all shortcodes.
823
   *
824
   * @return bool
825
   */
826
  public function remove_all_shortcodes(): bool
827
  {
828
    self::$shortcode_tags = [];
829
830
    return true;
831
  }
832 1
833
  /**
834 1
   * Whether a registered shortcode exists named $tag
835
   *
836
   * @param string $tag
837
   *
838
   * @return bool
839
   */
840
  public function shortcode_exists(string $tag): bool
841
  {
842
    return \array_key_exists($tag, self::$shortcode_tags);
843
  }
844
845
  /**
846
   * Whether the passed content contains the specified shortcode.
847
   *
848
   * @param string $content
849
   * @param string $tag
850
   *
851
   * @return bool
852
   */
853
  public function has_shortcode(string $content, string $tag): bool
854
  {
855
    if (false === \strpos($content, '[')) {
856
      return false;
857
    }
858
859
    if ($this->shortcode_exists($tag)) {
860
      \preg_match_all('/' . $this->get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER);
861
      if (empty($matches)) {
862
        return false;
863
      }
864
865
      foreach ($matches as $shortcode) {
866
        if ($tag === $shortcode[2]) {
867
          return true;
868
        }
869
870
        if (!empty($shortcode[5]) && $this->has_shortcode($shortcode[5], $tag)) {
871
          return true;
872
        }
873
      }
874
    }
875
876
    return false;
877
  }
878
879
  /**
880
   * Search content for shortcodes and filter shortcodes through their hooks.
881
   *
882
   * <p>
883
   * <br />
884
   * If there are no shortcode tags defined, then the content will be returned
885 2
   * without any filtering. This might cause issues when plugins are disabled but
886
   * the shortcode will still show up in the post or content.
887 2
   * </p>
888 1
   *
889
   * @param string $content <p>Content to search for shortcodes.</p>
890
   *
891 2
   * @return string <p>Content with shortcodes filtered out.</p>
892
   */
893 2 View Code Duplication
  public function do_shortcode(string $content): string
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...
894 2
  {
895
    if (empty(self::$shortcode_tags) || !\is_array(self::$shortcode_tags)) {
896 2
      return $content;
897 2
    }
898 2
899
    $pattern = $this->get_shortcode_regex();
900 2
901
    return \preg_replace_callback(
902
        "/$pattern/s",
903
        [
904
            $this,
905
            '_do_shortcode_tag',
906
        ],
907
        $content
908
    );
909
  }
910
911
  /**
912
   * Retrieve the shortcode regular expression for searching.
913
   *
914
   * <p>
915
   * <br />
916
   * The regular expression combines the shortcode tags in the regular expression
917
   * in a regex class.
918
   * <br /><br />
919
   *
920
   * The regular expression contains 6 different sub matches to help with parsing.
921
   * <br /><br />
922
   *
923
   * 1 - An extra [ to allow for escaping shortcodes with double [[]]<br />
924
   * 2 - The shortcode name<br />
925 2
   * 3 - The shortcode argument list<br />
926
   * 4 - The self closing /<br />
927 2
   * 5 - The content of a shortcode when it wraps some content.<br />
928 2
   * 6 - An extra ] to allow for escaping shortcodes with double [[]]<br />
929
   * </p>
930
   *
931
   * @return string The shortcode search regular expression
932
   */
933
  public function get_shortcode_regex(): string
934
  {
935 2
    $tagnames = \array_keys(self::$shortcode_tags);
936 2
    $tagregexp = \implode('|', \array_map('preg_quote', $tagnames));
937 2
938 2
    // WARNING! Do not change this regex without changing __do_shortcode_tag() and __strip_shortcode_tag()
939 2
    // Also, see shortcode_unautop() and shortcode.js.
940 2
    return
941 2
        '\\[' // Opening bracket
942 2
        . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
943 2
        . "($tagregexp)" // 2: Shortcode name
944 2
        . '(?![\\w-])' // Not followed by word character or hyphen
945 2
        . '(' // 3: Unroll the loop: Inside the opening shortcode tag
946 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
947 2
        . '(?:'
948 2
        . '\\/(?!\\])' // A forward slash not followed by a closing bracket
949 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
950 2
        . ')*?'
951 2
        . ')'
952 2
        . '(?:'
953 2
        . '(\\/)' // 4: Self closing tag ...
954 2
        . '\\]' // ... and closing bracket
955 2
        . '|'
956 2
        . '\\]' // Closing bracket
957 2
        . '(?:'
958 2
        . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
959 2
        . '[^\\[]*+' // Not an opening bracket
960 2
        . '(?:'
961
        . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
962
        . '[^\\[]*+' // Not an opening bracket
963
        . ')*+'
964
        . ')'
965
        . '\\[\\/\\2\\]' // Closing shortcode tag
966
        . ')?'
967
        . ')'
968
        . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
969
  }
970
971
  /**
972 2
   * Regular Expression callable for do_shortcode() for calling shortcode hook.
973
   *
974
   * @see self::get_shortcode_regex for details of the match array contents.
975 2
   *
976
   * @param array $m <p>regular expression match array</p>
977
   *
978
   * @return mixed <p><strong>false</strong> on failure</p>
979 2
   */
980 2
  private function _do_shortcode_tag(array $m)
981
  {
982
    // allow [[foo]] syntax for escaping a tag
983 2 View Code Duplication
    if ($m[1] == '[' && $m[6] == ']') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
984 2
      return \substr($m[0], 1, -1);
985
    }
986
987
    $tag = $m[2];
988
    $attr = $this->shortcode_parse_atts($m[3]);
989
990
    // enclosing tag - extra parameter
991
    if (isset($m[5])) {
992
      return $m[1] . \call_user_func(self::$shortcode_tags[$tag], $attr, $m[5], $tag) . $m[6];
993
    }
994
995
    // self-closing tag
996
    return $m[1] . \call_user_func(self::$shortcode_tags[$tag], $attr, null, $tag) . $m[6];
997
  }
998
999
  /**
1000
   * Retrieve all attributes from the shortcodes tag.
1001
   *
1002
   * <p>
1003
   * <br />
1004
   * The attributes list has the attribute name as the key and the value of the
1005 2
   * attribute as the value in the key/value pair. This allows for easier
1006
   * retrieval of the attributes, since all attributes have to be known.
1007 2
   * </p>
1008 2
   *
1009 2
   * @param string $text
1010 2
   *
1011 2
   * @return array <p>List of attributes and their value.</p>
1012 2
   */
1013 1
  public function shortcode_parse_atts(string $text): array
1014 2
  {
1015
    $atts = [];
1016 1
    $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
1017 1
    $text = \preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $text);
1018 1
    $matches = [];
1019
    if (\preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
1020
      foreach ($matches as $m) {
1021
        if (!empty($m[1])) {
1022
          $atts[\strtolower($m[1])] = \stripcslashes($m[2]);
1023 2
        } elseif (!empty($m[3])) {
1024 2
          $atts[\strtolower($m[3])] = \stripcslashes($m[4]);
1025
        } elseif (!empty($m[5])) {
1026
          $atts[\strtolower($m[5])] = \stripcslashes($m[6]);
1027
        } elseif (isset($m[7]) && $m[7] !== '') {
1028 2
          $atts[] = \stripcslashes($m[7]);
1029
        } elseif (isset($m[8])) {
1030
          $atts[] = \stripcslashes($m[8]);
1031
        }
1032
      }
1033
    } else {
1034
      $atts = \ltrim($text);
1035
    }
1036
1037
    return $atts;
1038
  }
1039
1040
  /**
1041
   * Combine user attributes with known attributes and fill in defaults when needed.
1042
   *
1043
   * <p>
1044
   * <br />
1045
   * The pairs should be considered to be all of the attributes which are
1046
   * supported by the caller and given as a list. The returned attributes will
1047
   * only contain the attributes in the $pairs list.
1048
   *
1049
   * <br /><br />
1050
   * If the $atts list has unsupported attributes, then they will be ignored and
1051 2
   * removed from the final returned list.
1052
   * </p>
1053 2
   *
1054 2
   * @param array  $pairs     <p>Entire list of supported attributes and their defaults.</p>
1055 2
   * @param array  $atts      <p>User defined attributes in shortcode tag.</p>
1056 2
   * @param string $shortcode <p>[optional] The name of the shortcode, provided for context to enable filtering.</p>
1057 1
   *
1058 1
   * @return array <p>Combined and filtered attribute list.</p>
1059 2
   */
1060
  public function shortcode_atts($pairs, $atts, $shortcode = ''): array
1061 2
  {
1062
    $atts = (array)$atts;
1063
    $out = [];
1064
    foreach ($pairs as $name => $default) {
1065
      if (array_key_exists($name, $atts)) {
1066
        $out[$name] = $atts[$name];
1067
      } else {
1068
        $out[$name] = $default;
1069
      }
1070
    }
1071
1072
    /**
1073
     * Filter a shortcode's default attributes.
1074
     *
1075 2
     * <p>
1076
     * <br />
1077
     * If the third parameter of the shortcode_atts() function is present then this filter is available.
1078
     * The third parameter, $shortcode, is the name of the shortcode.
1079
     * </p>
1080
     *
1081
     * @param array $out   <p>The output array of shortcode attributes.</p>
1082
     * @param array $pairs <p>The supported attributes and their defaults.</p>
1083
     * @param array $atts  <p>The user defined shortcode attributes.</p>
1084 2
     */
1085
    if ($shortcode) {
1086
      $out = $this->apply_filters(
1087
          "shortcode_atts_{$shortcode}",
1088
          $out,
1089
          $pairs,
1090
          $atts
1091
      );
1092
    }
1093
1094 1
    return $out;
1095
  }
1096
1097 1
  /**
1098
   * Remove all shortcode tags from the given content.
1099
   *
1100
   * @param string $content <p>Content to remove shortcode tags.</p>
1101 1
   *
1102
   * @return string <p>Content without shortcode tags.</p>
1103 1
   */
1104 1 View Code Duplication
  public function strip_shortcodes(string $content): string
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...
1105
  {
1106 1
1107 1
    if (empty(self::$shortcode_tags) || !\is_array(self::$shortcode_tags)) {
1108 1
      return $content;
1109
    }
1110 1
1111
    $pattern = $this->get_shortcode_regex();
1112
1113
    return preg_replace_callback(
1114
        "/$pattern/s",
1115
        [
1116
            $this,
1117
            '_strip_shortcode_tag',
1118
        ],
1119
        $content
1120 1
    );
1121
  }
1122
1123 1
  /**
1124
   * Strip shortcode by tag.
1125
   *
1126
   * @param array $m
1127 1
   *
1128
   * @return string
1129
   */
1130
  private function _strip_shortcode_tag(array $m): string
1131
  {
1132
    // allow [[foo]] syntax for escaping a tag
1133 View Code Duplication
    if ($m[1] == '[' && $m[6] == ']') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1134
      return substr($m[0], 1, -1);
1135
    }
1136
1137
    return $m[1] . $m[6];
1138
  }
1139
1140
}
1141