Completed
Push — master ( d0de3f...e5362d )
by Lars
02:48
created

Hooks::remove_action()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 3
crap 1
1
<?php
2
3
namespace voku\helper;
4
5
/**
6
 * PHP Hooks Class (Modified)
7
 *
8
 * The PHP Hooks Class is a fork of the WordPress filters hook system rolled in
9
 * to a class to be ported into any php based system
10
 *
11
 * This class is heavily based on the WordPress plugin API and most (if not all)
12
 * of the code comes from there.
13
 *
14
 * @copyright   2011 - 2016
15
 * @author      Ohad Raz <[email protected]>
16
 * @link        http://en.bainternet.info
17
 * @author      David Miles <[email protected]>
18
 * @link        http://github.com/amereservant/PHP-Hooks
19
 * @author      Lars Moelleken <[email protected]>
20
 * @link        https://github.com/voku/PHP-Hooks/
21
 * @author      Damien "Mistic" Sorel <[email protected]>
22
 * @link        http://www.strangeplanet.fr
23
 *
24
 * @license     GNU General Public License v3.0 - license.txt
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
 * THE SOFTWARE.
32
 *
33
 * @package     voku\helper
34
 */
35
class Hooks
36
{
37
  /**
38
   * Filters - holds list of hooks
39
   *
40
   * @var      array
41
   * @access   protected
42
   * @since    1.0.0
43
   */
44
  protected $filters = array();
45
46
  /**
47
   * Merged Filters
48
   *
49
   * @var      array
50
   * @access   protected
51
   * @since    1.0.0
52
   */
53
  protected $merged_filters = array();
54
55
  /**
56
   * Actions
57
   *
58
   * @var      array
59
   * @access   protected
60
   * @since    1.0.0
61
   */
62
  protected $actions = array();
63
64
  /**
65
   * Current Filter - holds the name of the current filter
66
   *
67
   * @var      array
68
   * @access   protected
69
   * @since    1.0.0
70
   */
71
  protected $current_filter = array();
72
73
  /**
74
   * Container for storing shortcode tags and their hook to call for the shortcode
75
   *
76
   * @since 1.0.0
77
   * @name $shortcode_tags
78
   * @var array
79
   */
80
  public static $shortcode_tags = array();
81
82
  /**
83
   * Default priority
84
   *
85
   * @since 0.2
86
   * @const int
87
   */
88
  const PRIORITY_NEUTRAL = 50;
89
90
  /**
91
   * is not allowed to call from outside: private!
92
   *
93
   * @access private
94
   */
95 1
  protected function __construct()
96
  {
97 1
  }
98
99
  /**
100
   * prevent the instance from being cloned
101
   *
102
   * @access private
103
   *
104
   * @return void
105
   */
106
  protected function __clone()
107
  {
108
  }
109
110
  /**
111
   * Singleton Instance
112
   *
113
   * Returns a Singleton instance of this class.
114
   *
115
   * @param    void
116
   *
117
   * @return   Hooks
118
   * @access   public
119
   * @static
120
   * @since    1.0.0
121
   */
122 10
  public static function getInstance()
123
  {
124 10
    static $instance;
125
126 10
    if (null === $instance) {
127 1
      $instance = new self();
128 1
    }
129
130 10
    return $instance;
131
  }
132
133
  /**
134
   * FILTERS
135
   */
136
137
  /**
138
   * Adds Hooks to a function or method to a specific filter action.
139
   *
140
   * @access   public
141
   * @since    1.0.0
142
   *
143
   * @param    string  $tag                The name of the filter to hook the
144
   *                                       {@link $function_to_add} to.
145
   * @param    string  $function_to_add    The name of the function to be called
146
   *                                       when the filter is applied.
147
   * @param    integer $priority           (optional) Used to specify the order in
148
   *                                       which the functions associated with a
149
   *                                       particular action are executed (default: 50).
150
   *                                       Lower numbers correspond with earlier execution,
151
   *                                       and functions with the same priority are executed
152
   *                                       in the order in which they were added to the action.
153
   * @param string     $include_path       optional. File to include before executing the callback.
154
   *
155
   * @return   boolean true
156
   */
157 7
  public function add_filter($tag, $function_to_add, $priority = self::PRIORITY_NEUTRAL, $include_path = null)
158
  {
159 7
    $idx = $this->__filter_build_unique_id($function_to_add);
160
161 7
    $this->filters[$tag][$priority][$idx] = array(
162 7
        'function'     => $function_to_add,
163 7
        'include_path' => is_string($include_path) ? $include_path : null,
164
    );
165
166 7
    unset($this->merged_filters[$tag]);
167
168 7
    return true;
169
  }
170
171
  /**
172
   * Removes a function from a specified filter hook.
173
   *
174
   * @param string $tag                the filter hook to which the function to be removed is hooked.
175
   * @param mixed  $function_to_remove the name of the function which should be removed.
176
   * @param int    $priority           (optional) The priority of the function (default: 50).
177
   *
178
   * @return bool
179
   */
180 1
  public function remove_filter($tag, $function_to_remove, $priority = self::PRIORITY_NEUTRAL)
181
  {
182 1
    $function_to_remove = $this->__filter_build_unique_id($function_to_remove);
183
184 1
    if (!isset($this->filters[$tag][$priority][$function_to_remove])) {
185 1
      return false;
186
    }
187
188 1
    unset($this->filters[$tag][$priority][$function_to_remove]);
189 1
    if (empty($this->filters[$tag][$priority])) {
190 1
      unset($this->filters[$tag][$priority]);
191 1
    }
192
193 1
    unset($this->merged_filters[$tag]);
194
195 1
    return true;
196
  }
197
198
  /**
199
   * Remove all of the hooks from a filter.
200
   *
201
   * @param string $tag      the filter to remove hooks from.
202
   * @param bool   $priority the priority number to remove.
203
   *
204
   * @return bool   True when finished.
205
   */
206 4
  public function remove_all_filters($tag, $priority = false)
207
  {
208 4
    if (isset($this->merged_filters[$tag])) {
209
      unset($this->merged_filters[$tag]);
210
    }
211
212 4
    if (!isset($this->filters[$tag])) {
213 2
      return true;
214
    }
215
216 2
    if (false !== $priority && isset($this->filters[$tag][$priority])) {
217 1
      unset($this->filters[$tag][$priority]);
218 1
    } else {
219 2
      unset($this->filters[$tag]);
220
    }
221
222 2
    return true;
223
  }
224
225
  /**
226
   * Check if any filter has been registered for the given hook.
227
   *
228
   * Info: Use !== false to check if it's true!
229
   *
230
   * @param    string $tag               the name of the filter hook.
231
   * @param    bool   $function_to_check callback function name to check for. [optional]
232
   *
233
   * @return   mixed                       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
   *                                       (e.g.) 0, so use the === operator for testing the return value.
243
   * @access   public
244
   * @since    1.0.0
245
   */
246 3
  public function has_filter($tag, $function_to_check = false)
247
  {
248 3
    $has = isset($this->filters[$tag]);
249 3
    if (false === $function_to_check || !$has) {
250 3
      return $has;
251
    }
252
253 3
    if (!($idx = $this->__filter_build_unique_id($function_to_check))) {
0 ignored issues
show
Documentation introduced by
$function_to_check is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
254
      return false;
255
    }
256
257 3
    foreach ((array)array_keys($this->filters[$tag]) as $priority) {
258 3
      if (isset($this->filters[$tag][$priority][$idx])) {
259 3
        return $priority;
260
      }
261 2
    }
262
263 2
    return false;
264
  }
265
266
  /**
267
   * Call the functions added to a filter hook.
268
   *
269
   * Info:  Additional variables passed to the functions hooked to <tt>$tag</tt>.
270
   *
271
   * @param    string $tag   The name of the filter hook.
272
   * @param    mixed  $value The value on which the filters hooked to <tt>$tag</tt> are applied on.
273
   *
274
   * @return   mixed              The filtered value after all hooked functions are applied to it.
275
   * @access   public
276
   * @since    1.0.0
277
   */
278 4
  public function apply_filters($tag, $value)
279
  {
280 4
    $args = array();
281
282
    // Do 'all' actions first
283 4 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 1
      $args = func_get_args();
286 1
      $this->__call_all_hook($args);
287 1
    }
288
289 4 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 1
      if (isset($this->filters['all'])) {
291 1
        array_pop($this->current_filter);
292 1
      }
293
294 1
      return $value;
295
    }
296
297 4
    if (!isset($this->filters['all'])) {
298 4
      $this->current_filter[] = $tag;
299 4
    }
300
301
    // Sort
302 4 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 3
      ksort($this->filters[$tag]);
304 3
      $this->merged_filters[$tag] = true;
305 3
    }
306
307 4
    reset($this->filters[$tag]);
308
309 4
    if (empty($args)) {
310 4
      $args = func_get_args();
311 4
    }
312
313 4
    array_shift($args);
314
315
    do {
316 4
      foreach ((array)current($this->filters[$tag]) as $the_) {
317 4
        if (null !== $the_['function']) {
318
319 4
          if (null !== $the_['include_path']) {
320
            /** @noinspection PhpIncludeInspection */
321
            include_once $the_['include_path'];
322
          }
323
324 4
          $args[0] = $value;
325 4
          $value = call_user_func_array($the_['function'], $args);
326 4
        }
327 4
      }
328 4
    } while (next($this->filters[$tag]) !== false);
329
330 4
    array_pop($this->current_filter);
331
332 4
    return $value;
333
  }
334
335
  /**
336
   * Execute functions hooked on a specific filter hook, specifying arguments in an array.
337
   *
338
   * @param    string $tag  The name of the filter hook.
339
   * @param    array  $args The arguments supplied to the functions hooked to <tt>$tag</tt>
340
   *
341
   * @return   mixed           The filtered value after all hooked functions are applied to it.
342
   *
343
   * @access   public
344
   * @since    1.0.0
345
   */
346 1
  public function apply_filters_ref_array($tag, $args)
347
  {
348
    // Do 'all' actions first
349 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...
350 1
      $this->current_filter[] = $tag;
351 1
      $all_args = func_get_args();
352 1
      $this->__call_all_hook($all_args);
353 1
    }
354
355 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...
356 1
      if (isset($this->filters['all'])) {
357
        array_pop($this->current_filter);
358
      }
359
360 1
      return $args[0];
361
    }
362
363 1
    if (!isset($this->filters['all'])) {
364
      $this->current_filter[] = $tag;
365
    }
366
367
    // Sort
368 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...
369
      ksort($this->filters[$tag]);
370
      $this->merged_filters[$tag] = true;
371
    }
372
373 1
    reset($this->filters[$tag]);
374
375 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...
376 1
      foreach ((array)current($this->filters[$tag]) as $the_) {
377 1
        if (null !== $the_['function']) {
378
379 1
          if (null !== $the_['include_path']) {
380
            /** @noinspection PhpIncludeInspection */
381
            include_once $the_['include_path'];
382
          }
383
384 1
          $args[0] = call_user_func_array($the_['function'], $args);
385 1
        }
386 1
      }
387 1
    } while (next($this->filters[$tag]) !== false);
388
389 1
    array_pop($this->current_filter);
390
391 1
    return $args[0];
392
  }
393
394
  /**
395
   * ACTIONS
396
   */
397
398
  /**
399
   * Hooks a function on to a specific action.
400
   *
401
   * @param    string  $tag             The name of the action to which the
402
   *                                    <tt>$function_to_add</tt> is hooked.
403
   * @param    string  $function_to_add The name of the function you wish to be called.
404
   * @param    integer $priority        (optional) Used to specify the order in which
405
   *                                    the functions associated with a particular
406
   *                                    action are executed (default: 50).
407
   *                                    Lower numbers correspond with earlier execution,
408
   *                                    and functions with the same priority are executed
409
   *                                    in the order in which they were added to the action.
410
   * @param     string $include_path    optional. File to include before executing the callback.
411
   *
412
   * @access   public
413
   * @since    1.0.0
414
   * @return bool
415
   */
416 5
  public function add_action($tag, $function_to_add, $priority = self::PRIORITY_NEUTRAL, $include_path = null)
417
  {
418 5
    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
   * Info: Use !== false to check if it's true!
425
   *
426
   * @param    string   $tag               The name of the action hook.
427
   * @param bool|string $function_to_check (optional)
428
   *
429
   * @return   mixed                       If <tt>$function_to_check</tt> is omitted,
430
   *                                       returns boolean for whether the hook has
431
   *                                       anything registered.
432
   *                                       When checking a specific function,
433
   *                                       the priority of that hook is returned,
434
   *                                       or false if the function is not attached.
435
   *                                       When using the <tt>$function_to_check</tt>
436
   *                                       argument, this function may return a non-boolean
437
   *                                       value that evaluates to false (e.g.) 0,
438
   *                                       so use the === operator for testing the return value.
439
   * @access   public
440
   * @since    1.0.0
441
   */
442 3
  public function has_action($tag, $function_to_check = false)
443
  {
444 3
    return $this->has_filter($tag, $function_to_check);
0 ignored issues
show
Bug introduced by
It seems like $function_to_check defined by parameter $function_to_check on line 442 can also be of type string; however, voku\helper\Hooks::has_filter() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
445
  }
446
447
  /**
448
   * Removes a function from a specified action hook.
449
   *
450
   * @param string $tag                the action hook to which the function to be removed is hooked.
451
   * @param mixed  $function_to_remove the name of the function which should be removed.
452
   * @param int    $priority           [optional] The priority of the function (default: 50).
453
   *
454
   * @return bool   Whether the function is removed.
455
   */
456 1
  public function remove_action($tag, $function_to_remove, $priority = self::PRIORITY_NEUTRAL)
457
  {
458 1
    return $this->remove_filter($tag, $function_to_remove, $priority);
459
  }
460
461
  /**
462
   * Remove all of the hooks from an action.
463
   *
464
   * @param string $tag      the action to remove hooks from.
465
   * @param bool   $priority the priority number to remove them from.
466
   *
467
   * @return bool True when finished.
468
   */
469 4
  public function remove_all_actions($tag, $priority = false)
470
  {
471 4
    return $this->remove_all_filters($tag, $priority);
472
  }
473
474
  /**
475
   * Execute functions hooked on a specific action hook.
476
   *
477
   * @param    string $tag     The name of the action to be executed.
478
   * @param    mixed  $arg     ,.. Optional additional arguments which are passed on
479
   *                           to the functions hooked to the action.
480
   *
481
   * @return   bool            Will return false if $tag does not exist in $filter array
482
   * @access   public
483
   * @since    1.0.0
484
   */
485 2
  public function do_action($tag, $arg = '')
486
  {
487 2
    if (!is_array($this->actions)) {
488
      $this->actions = array();
489
    }
490
491 2 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...
492 2
      $this->actions[$tag] = 1;
493 2
    } else {
494 1
      ++$this->actions[$tag];
495
    }
496
497
    // Do 'all' actions first
498 2 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...
499 1
      $this->current_filter[] = $tag;
500 1
      $all_args = func_get_args();
501 1
      $this->__call_all_hook($all_args);
502 1
    }
503
504 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...
505 1
      if (isset($this->filters['all'])) {
506 1
        array_pop($this->current_filter);
507 1
      }
508
509 1
      return false;
510
    }
511
512 2
    if (!isset($this->filters['all'])) {
513 2
      $this->current_filter[] = $tag;
514 2
    }
515
516 2
    $args = array();
517
518
    if (
519 2
        is_array($arg)
520 2
        &&
521
        isset($arg[0])
522 2
        &&
523
        is_object($arg[0])
524 2
        &&
525
        1 == count($arg)
526 2
    ) {
527
      $args[] =& $arg[0];
528
    } else {
529 2
      $args[] = $arg;
530
    }
531
532 2
    $numArgs = func_num_args();
533
534 2
    for ($a = 2; $a < $numArgs; $a++) {
535 1
      $args[] = func_get_arg($a);
536 1
    }
537
538
    // Sort
539 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...
540 2
      ksort($this->filters[$tag]);
541 2
      $this->merged_filters[$tag] = true;
542 2
    }
543
544 2
    reset($this->filters[$tag]);
545
546 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...
547 2
      foreach ((array)current($this->filters[$tag]) as $the_) {
548 2
        if (null !== $the_['function']) {
549
550 2
          if (null !== $the_['include_path']) {
551
            /** @noinspection PhpIncludeInspection */
552
            include_once $the_['include_path'];
553
          }
554
555 2
          call_user_func_array($the_['function'], $args);
556 2
        }
557 2
      }
558 2
    } while (next($this->filters[$tag]) !== false);
559
560 2
    array_pop($this->current_filter);
561
562 2
    return true;
563
  }
564
565
  /**
566
   * Execute functions hooked on a specific action hook, specifying arguments in an array.
567
   *
568
   * @param    string $tag  The name of the action to be executed.
569
   * @param    array  $args The arguments supplied to the functions hooked to <tt>$tag</tt>
570
   *
571
   * @return   bool            Will return false if $tag does not exist in $filter array
572
   * @access   public
573
   * @since    1.0.0
574
   */
575 1
  public function do_action_ref_array($tag, $args)
576
  {
577 1
    if (!is_array($this->actions)) {
578
      $this->actions = array();
579
    }
580
581 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...
582 1
      $this->actions[$tag] = 1;
583 1
    } else {
584 1
      ++$this->actions[$tag];
585
    }
586
587
    // Do 'all' actions first
588 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...
589 1
      $this->current_filter[] = $tag;
590 1
      $all_args = func_get_args();
591 1
      $this->__call_all_hook($all_args);
592 1
    }
593
594 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...
595 1
      if (isset($this->filters['all'])) {
596
        array_pop($this->current_filter);
597
      }
598
599 1
      return false;
600
    }
601
602 1
    if (!isset($this->filters['all'])) {
603
      $this->current_filter[] = $tag;
604
    }
605
606
    // Sort
607 1
    if (!isset($merged_filters[$tag])) {
0 ignored issues
show
Bug introduced by
The variable $merged_filters seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
608 1
      ksort($this->filters[$tag]);
609 1
      $merged_filters[$tag] = true;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$merged_filters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $merged_filters = 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...
610 1
    }
611
612 1
    reset($this->filters[$tag]);
613
614 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...
615 1
      foreach ((array)current($this->filters[$tag]) as $the_) {
616 1
        if (null !== $the_['function']) {
617
618 1
          if (null !== $the_['include_path']) {
619
            /** @noinspection PhpIncludeInspection */
620
            include_once $the_['include_path'];
621
          }
622
623 1
          call_user_func_array($the_['function'], $args);
624 1
        }
625 1
      }
626 1
    } while (next($this->filters[$tag]) !== false);
627
628 1
    array_pop($this->current_filter);
629
630 1
    return true;
631
  }
632
633
  /**
634
   * Retrieve the number of times an action has fired.
635
   *
636
   * @param    string $tag The name of the action hook.
637
   *
638
   * @return   integer         The number of times action hook <tt>$tag</tt> is fired
639
   * @access   public
640
   * @since    1.0.0
641
   */
642 1
  public function did_action($tag)
643
  {
644 1
    if (!is_array($this->actions) || !isset($this->actions[$tag])) {
645
      return 0;
646
    }
647
648 1
    return $this->actions[$tag];
649
  }
650
651
  /**
652
   * HELPERS
653
   */
654
655
  /**
656
   * Retrieve the name of the current filter or action.
657
   *
658
   * @param    void
659
   *
660
   * @return   string  Hook name of the current filter or action.
661
   * @access   public
662
   * @since    1.0.0
663
   */
664
  public function current_filter()
665
  {
666
    return end($this->current_filter);
667
  }
668
669
  /**
670
   * Build Unique ID for storage and retrieval.
671
   *
672
   * @param    string $function Used for creating unique id
673
   *
674
   * @return   string|bool             Unique ID for usage as array key or false if
675
   *                                   $priority === false and $function is an
676
   *                                   object reference, and it does not already have a unique id.
677
   * @access   private
678
   * @since    1.0.0
679
   */
680 7
  private function __filter_build_unique_id($function)
681
  {
682 7
    if (is_string($function)) {
683 3
      return $function;
684
    }
685
686 4
    if (is_object($function)) {
687
      // Closures are currently implemented as objects
688
      $function = array(
689 2
          $function,
690 2
          '',
691 2
      );
692 2
    } else {
693 2
      $function = (array)$function;
694
    }
695
696 4
    if (is_object($function[0])) {
697
      // Object Class Calling
698 4
      return spl_object_hash($function[0]) . $function[1];
699
    } elseif (is_string($function[0])) {
700
      // Static Calling
701
      return $function[0] . $function[1];
702
    }
703
704
    return false;
705
  }
706
707
  /**
708
   * Call "All" Hook
709
   *
710
   * @param    array $args
711
   *
712
   * @access   public
713
   * @since    1.0.0
714
   */
715 1
  public function __call_all_hook($args)
716
  {
717 1
    reset($this->filters['all']);
718
719 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...
720 1
      foreach ((array)current($this->filters['all']) as $the_) {
721 1
        if (null !== $the_['function']) {
722
723 1
          if (null !== $the_['include_path']) {
724
            /** @noinspection PhpIncludeInspection */
725
            include_once $the_['include_path'];
726
          }
727
728 1
          call_user_func_array($the_['function'], $args);
729 1
        }
730 1
      }
731 1
    } while (next($this->filters['all']) !== false);
732 1
  }
733
734
  /**
735
   * Add hook for shortcode tag.
736
   *
737
   * There can only be one hook for each shortcode. Which means that if another
738
   * plugin has a similar shortcode, it will override yours or yours will override
739
   * theirs depending on which order the plugins are included and/or ran.
740
   *
741
   * Simplest example of a shortcode tag using the API:
742
   *
743
   * <code>
744
   * // [footag foo="bar"]
745
   * function footag_func($atts) {
746
   *  return "foo = {$atts[foo]}";
747
   * }
748
   * add_shortcode('footag', 'footag_func');
749
   * </code>
750
   *
751
   * Example with nice attribute defaults:
752
   *
753
   * <code>
754
   * // [bartag foo="bar"]
755
   * function bartag_func($atts) {
756
   *  $args = shortcode_atts(array(
757
   *    'foo' => 'no foo',
758
   *    'baz' => 'default baz',
759
   *  ), $atts);
760
   *
761
   *  return "foo = {$args['foo']}";
762
   * }
763
   * add_shortcode('bartag', 'bartag_func');
764
   * </code>
765
   *
766
   * Example with enclosed content:
767
   *
768
   * <code>
769
   * // [baztag]content[/baztag]
770
   * function baztag_func($atts, $content='') {
771
   *  return "content = $content";
772
   * }
773
   * add_shortcode('baztag', 'baztag_func');
774
   * </code>
775
   *
776
   * @since 1.0.0
777
   *
778
   * @param string   $tag  Shortcode tag to be searched in post content.
779
   * @param callable $func Hook to run when shortcode is found.
780
   *
781
   * @return bool
782
   */
783 2
  public function add_shortcode($tag, $func)
784
  {
785 2
    if (is_callable($func)) {
786 2
      self::$shortcode_tags[$tag] = $func;
787
788 2
      return true;
789
    }
790
791
    return false;
792
  }
793
794
  /**
795
   * Removes hook for shortcode.
796
   *
797
   * @since 1.0.0
798
   *
799
   * @param string $tag shortcode tag to remove hook for.
800
   *
801
   * @return bool
802
   */
803 1
  public function remove_shortcode($tag)
804
  {
805 1
    if (isset(self::$shortcode_tags[$tag])) {
806 1
      unset(self::$shortcode_tags[$tag]);
807
808 1
      return true;
809
    } else {
810
      return false;
811
    }
812
  }
813
814
  /**
815
   * This function is simple, it clears all of the shortcode tags by replacing the
816
   * shortcodes by a empty array. This is actually a very efficient method
817
   * for removing all shortcodes.
818
   *
819
   * @since 1.0.0
820
   *
821
   * @return bool
822
   */
823 1
  public function remove_all_shortcodes()
824
  {
825 1
    self::$shortcode_tags = array();
826
827 1
    return true;
828
  }
829
830
  /**
831
   * Whether a registered shortcode exists named $tag
832
   *
833
   * @since 1.0.0
834
   *
835
   * @param string $tag
836
   *
837
   * @return boolean
838
   */
839 1
  public function shortcode_exists($tag)
840
  {
841 1
    return array_key_exists($tag, self::$shortcode_tags);
842
  }
843
844
  /**
845
   * Whether the passed content contains the specified shortcode
846
   *
847
   * @since 1.0.0
848
   *
849
   * @param $content
850
   * @param $tag
851
   *
852
   * @return bool
853
   */
854
  public function has_shortcode($content, $tag)
855
  {
856
    if (false === strpos($content, '[')) {
857
      return false;
858
    }
859
860
    if ($this->shortcode_exists($tag)) {
861
      preg_match_all('/' . $this->get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER);
862
      if (empty($matches)) {
863
        return false;
864
      }
865
866
      foreach ($matches as $shortcode) {
867
        if ($tag === $shortcode[2]) {
868
          return true;
869
        } elseif (!empty($shortcode[5]) && $this->has_shortcode($shortcode[5], $tag)) {
870
          return true;
871
        }
872
      }
873
    }
874
875
    return false;
876
  }
877
878
  /**
879
   * Search content for shortcodes and filter shortcodes through their hooks.
880
   *
881
   * If there are no shortcode tags defined, then the content will be returned
882
   * without any filtering. This might cause issues when plugins are disabled but
883
   * the shortcode will still show up in the post or content.
884
   *
885
   * @since 1.0.0
886
   *
887
   * @param string $content Content to search for shortcodes
888
   *
889
   * @return string Content with shortcodes filtered out.
890
   */
891 2 View Code Duplication
  public function do_shortcode($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...
892
  {
893 2
    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
894 1
      return $content;
895
    }
896
897 2
    $pattern = $this->get_shortcode_regex();
898
899 2
    return preg_replace_callback(
900 2
        "/$pattern/s",
901
        array(
902 2
            $this,
903 2
            '__do_shortcode_tag',
904 2
        ),
905
        $content
906 2
    );
907
  }
908
909
  /**
910
   * Retrieve the shortcode regular expression for searching.
911
   *
912
   * The regular expression combines the shortcode tags in the regular expression
913
   * in a regex class.
914
   *
915
   * The regular expression contains 6 different sub matches to help with parsing.
916
   *
917
   * 1 - An extra [ to allow for escaping shortcodes with double [[]]
918
   * 2 - The shortcode name
919
   * 3 - The shortcode argument list
920
   * 4 - The self closing /
921
   * 5 - The content of a shortcode when it wraps some content.
922
   * 6 - An extra ] to allow for escaping shortcodes with double [[]]
923
   *
924
   * @since 1.0.0
925
   *
926
   * @return string The shortcode search regular expression
927
   */
928 2
  public function get_shortcode_regex()
929
  {
930 2
    $tagnames = array_keys(self::$shortcode_tags);
931 2
    $tagregexp = implode('|', array_map('preg_quote', $tagnames));
932
933
    // WARNING! Do not change this regex without changing __do_shortcode_tag() and __strip_shortcode_tag()
934
    // Also, see shortcode_unautop() and shortcode.js.
935
    return
936
        '\\[' // Opening bracket
937
        . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
938 2
        . "($tagregexp)" // 2: Shortcode name
939 2
        . '(?![\\w-])' // Not followed by word character or hyphen
940 2
        . '(' // 3: Unroll the loop: Inside the opening shortcode tag
941 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
942 2
        . '(?:'
943 2
        . '\\/(?!\\])' // A forward slash not followed by a closing bracket
944 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
945 2
        . ')*?'
946 2
        . ')'
947 2
        . '(?:'
948 2
        . '(\\/)' // 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...
949 2
        . '\\]' // ... and closing bracket
950 2
        . '|'
951 2
        . '\\]' // Closing bracket
952 2
        . '(?:'
953 2
        . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
954 2
        . '[^\\[]*+' // Not an opening bracket
955 2
        . '(?:'
956 2
        . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
957 2
        . '[^\\[]*+' // Not an opening bracket
958 2
        . ')*+'
959 2
        . ')'
960 2
        . '\\[\\/\\2\\]' // Closing shortcode tag
961 2
        . ')?'
962 2
        . ')'
963 2
        . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
964
  }
965
966
  /**
967
   * Regular Expression callable for do_shortcode() for calling shortcode hook.
968
   *
969
   * @see    get_shortcode_regex for details of the match array contents.
970
   *
971
   * @since  1.0.0
972
   * @access private
973
   *
974
   * @param array $m Regular expression match array
975
   *
976
   * @return mixed False on failure.
977
   */
978 2
  private function __do_shortcode_tag($m)
979
  {
980
    // allow [[foo]] syntax for escaping a tag
981 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...
982
      return substr($m[0], 1, -1);
983
    }
984
985 2
    $tag = $m[2];
986 2
    $attr = $this->shortcode_parse_atts($m[3]);
987
988 2
    if (isset($m[5])) {
989
      // enclosing tag - extra parameter
990 2
      return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, $m[5], $tag) . $m[6];
991
    } else {
992
      // self-closing tag
993
      return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, null, $tag) . $m[6];
994
    }
995
  }
996
997
  /**
998
   * Retrieve all attributes from the shortcodes tag.
999
   *
1000
   * The attributes list has the attribute name as the key and the value of the
1001
   * attribute as the value in the key/value pair. This allows for easier
1002
   * retrieval of the attributes, since all attributes have to be known.
1003
   *
1004
   * @since 1.0.0
1005
   *
1006
   * @param string $text
1007
   *
1008
   * @return array List of attributes and their value.
1009
   */
1010 2
  public function shortcode_parse_atts($text)
1011
  {
1012 2
    $atts = array();
1013 2
    $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
1014 2
    $text = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $text);
1015 2
    if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
1016 2
      foreach ($match as $m) {
1017 2
        if (!empty($m[1])) {
1018 1
          $atts[strtolower($m[1])] = stripcslashes($m[2]);
1019 2
        } elseif (!empty($m[3])) {
1020
          $atts[strtolower($m[3])] = stripcslashes($m[4]);
1021 1
        } elseif (!empty($m[5])) {
1022 1
          $atts[strtolower($m[5])] = stripcslashes($m[6]);
1023 1
        } elseif (isset($m[7]) && $m[7] !== '') {
1024
          $atts[] = stripcslashes($m[7]);
1025
        } elseif (isset($m[8])) {
1026
          $atts[] = stripcslashes($m[8]);
1027
        }
1028 2
      }
1029 2
    } else {
1030
      $atts = ltrim($text);
1031
    }
1032
1033 2
    return $atts;
1034
  }
1035
1036
  /**
1037
   * Combine user attributes with known attributes and fill in defaults when needed.
1038
   *
1039
   * The pairs should be considered to be all of the attributes which are
1040
   * supported by the caller and given as a list. The returned attributes will
1041
   * only contain the attributes in the $pairs list.
1042
   *
1043
   * If the $atts list has unsupported attributes, then they will be ignored and
1044
   * removed from the final returned list.
1045
   *
1046
   * @since 1.0.0
1047
   *
1048
   * @param array  $pairs     Entire list of supported attributes and their defaults.
1049
   * @param array  $atts      User defined attributes in shortcode tag.
1050
   * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering
1051
   *
1052
   * @return array Combined and filtered attribute list.
1053
   */
1054 2
  public function shortcode_atts($pairs, $atts, $shortcode = '')
1055
  {
1056 2
    $atts = (array)$atts;
1057 2
    $out = array();
1058 2
    foreach ($pairs as $name => $default) {
1059 2
      if (array_key_exists($name, $atts)) {
1060 1
        $out[$name] = $atts[$name];
1061 1
      } else {
1062 2
        $out[$name] = $default;
1063
      }
1064 2
    }
1065
    /**
1066
     * Filter a shortcode's default attributes.
1067
     *
1068
     * If the third parameter of the shortcode_atts() function is present then this filter is available.
1069
     * The third parameter, $shortcode, is the name of the shortcode.
1070
     *
1071
     * @since 1.0.0
1072
     *
1073
     * @param array $out   The output array of shortcode attributes.
1074
     * @param array $pairs The supported attributes and their defaults.
1075
     * @param array $atts  The user defined shortcode attributes.
1076
     */
1077 2
    if ($shortcode) {
1078
      $out = $this->apply_filters(
1079
          array(
1080
              $this,
1081
              "shortcode_atts_{$shortcode}",
1082
          ), $out, $pairs, $atts
1083
      );
1084
    }
1085
1086 2
    return $out;
1087
  }
1088
1089
  /**
1090
   * Remove all shortcode tags from the given content.
1091
   *
1092
   * @since 1.0.0
1093
   *
1094
   * @param string $content Content to remove shortcode tags.
1095
   *
1096
   * @return string Content without shortcode tags.
1097
   */
1098 1 View Code Duplication
  public function strip_shortcodes($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...
1099
  {
1100
1101 1
    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
1102
      return $content;
1103
    }
1104
1105 1
    $pattern = $this->get_shortcode_regex();
1106
1107 1
    return preg_replace_callback(
1108 1
        "/$pattern/s",
1109
        array(
1110 1
            $this,
1111 1
            '__strip_shortcode_tag',
1112 1
        ),
1113
        $content
1114 1
    );
1115
  }
1116
1117
  /**
1118
   * Strip shortcode by tag.
1119
   *
1120
   * @access private
1121
   *
1122
   * @param $m
1123
   *
1124
   * @return string
1125
   */
1126 1
  private function __strip_shortcode_tag($m)
1127
  {
1128
    // allow [[foo]] syntax for escaping a tag
1129 1 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...
1130
      return substr($m[0], 1, -1);
1131
    }
1132
1133 1
    return $m[1] . $m[6];
1134
  }
1135
1136
}
1137