Completed
Push — master ( a46806...24bca3 )
by Lars
02:26
created

Hooks::__wakeup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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...
250
      return false;
251
    }
252
253 3
    foreach ((array)array_keys($this->filters[$tag]) as $priority) {
254 3
      if (isset($this->filters[$tag][$priority][$idx])) {
255 3
        return $priority;
256
      }
257 2
    }
258
259 2
    return false;
260
  }
261
262
  /**
263
   * Call the functions added to a filter hook.
264
   *
265
   * <p>
266
   * <br />
267
   * <strong>INFO:</strong> Additional variables passed to the functions hooked to <tt>$tag</tt>.
268
   * </p>
269
   *
270
   * @param    string|array $tag   <p>The name of the filter hook.</p>
271
   * @param    mixed        $value <p>The value on which the filters hooked to <tt>$tag</tt> are applied on.</p>
272
   *
273
   * @return   mixed               <p>The filtered value after all hooked functions are applied to it.</p>
274
   */
275 4
  public function apply_filters($tag, $value)
276
  {
277 4
    $args = array();
278
279
    // Do 'all' actions first
280 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...
281 1
      $this->current_filter[] = $tag;
282 1
      $args = func_get_args();
283 1
      $this->_call_all_hook($args);
284 1
    }
285
286 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...
287 1
      if (isset($this->filters['all'])) {
288 1
        array_pop($this->current_filter);
289 1
      }
290
291 1
      return $value;
292
    }
293
294 4
    if (!isset($this->filters['all'])) {
295 4
      $this->current_filter[] = $tag;
296 4
    }
297
298
    // Sort
299 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...
300 3
      ksort($this->filters[$tag]);
301 3
      $this->merged_filters[$tag] = true;
302 3
    }
303
304 4
    reset($this->filters[$tag]);
305
306 4
    if (empty($args)) {
307 4
      $args = func_get_args();
308 4
    }
309
310 4
    array_shift($args);
311
312
    do {
313 4
      foreach ((array)current($this->filters[$tag]) as $the_) {
314 4
        if (null !== $the_['function']) {
315
316 4
          if (null !== $the_['include_path']) {
317
            /** @noinspection PhpIncludeInspection */
318
            include_once $the_['include_path'];
319
          }
320
321 4
          $args[0] = $value;
322 4
          $value = call_user_func_array($the_['function'], $args);
323 4
        }
324 4
      }
325 4
    } while (next($this->filters[$tag]) !== false);
326
327 4
    array_pop($this->current_filter);
328
329 4
    return $value;
330
  }
331
332
  /**
333
   * Execute functions hooked on a specific filter hook, specifying arguments in an array.
334
   *
335
   * @param    string $tag  <p>The name of the filter hook.</p>
336
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
337
   *
338
   * @return   mixed        <p>The filtered value after all hooked functions are applied to it.</p>
339
   */
340 1
  public function apply_filters_ref_array($tag, $args)
341
  {
342
    // Do 'all' actions first
343 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...
344 1
      $this->current_filter[] = $tag;
345 1
      $all_args = func_get_args();
346 1
      $this->_call_all_hook($all_args);
347 1
    }
348
349 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...
350 1
      if (isset($this->filters['all'])) {
351
        array_pop($this->current_filter);
352
      }
353
354 1
      return $args[0];
355
    }
356
357 1
    if (!isset($this->filters['all'])) {
358
      $this->current_filter[] = $tag;
359
    }
360
361
    // Sort
362 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...
363
      ksort($this->filters[$tag]);
364
      $this->merged_filters[$tag] = true;
365
    }
366
367 1
    reset($this->filters[$tag]);
368
369 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...
370 1
      foreach ((array)current($this->filters[$tag]) as $the_) {
371 1
        if (null !== $the_['function']) {
372
373 1
          if (null !== $the_['include_path']) {
374
            /** @noinspection PhpIncludeInspection */
375
            include_once $the_['include_path'];
376
          }
377
378 1
          $args[0] = call_user_func_array($the_['function'], $args);
379 1
        }
380 1
      }
381 1
    } while (next($this->filters[$tag]) !== false);
382
383 1
    array_pop($this->current_filter);
384
385 1
    return $args[0];
386
  }
387
388
  /**
389
   * Hooks a function on to a specific action.
390
   *
391
   * @param    string  $tag             <p>
392
   *                                    The name of the action to which the
393
   *                                    <tt>$function_to_add</tt> is hooked.
394
   *                                    </p>
395
   * @param    string  $function_to_add <p>The name of the function you wish to be called.</p>
396
   * @param    integer $priority        <p>
397
   *                                    [optional] Used to specify the order in which
398
   *                                    the functions associated with a particular
399
   *                                    action are executed (default: 50).
400
   *                                    Lower numbers correspond with earlier execution,
401
   *                                    and functions with the same priority are executed
402
   *                                    in the order in which they were added to the action.
403
   *                                    </p>
404
   * @param     string $include_path    <p>[optional] File to include before executing the callback.</p>
405
   *
406
   * @return bool
407
   */
408 5
  public function add_action($tag, $function_to_add, $priority = self::PRIORITY_NEUTRAL, $include_path = null)
409
  {
410 5
    return $this->add_filter($tag, $function_to_add, $priority, $include_path);
411
  }
412
413
  /**
414
   * Check if any action has been registered for a hook.
415
   *
416
   * <p>
417
   * <br />
418
   * <strong>INFO:</strong> Use !== false to check if it's true!
419
   * </p>
420
   *
421
   * @param    string   $tag               <p>The name of the action hook.</p>
422
   * @param bool|string $function_to_check <p>[optional]</p>
423
   *
424
   * @return   mixed                       <p>
425
   *                                       If <tt>$function_to_check</tt> is omitted,
426
   *                                       returns boolean for whether the hook has
427
   *                                       anything registered.
428
   *                                       When checking a specific function,
429
   *                                       the priority of that hook is returned,
430
   *                                       or false if the function is not attached.
431
   *                                       When using the <tt>$function_to_check</tt>
432
   *                                       argument, this function may return a non-boolean
433
   *                                       value that evaluates to false (e.g.) 0,
434
   *                                       so use the === operator for testing the return value.
435
   *                                       </p>
436
   */
437 3
  public function has_action($tag, $function_to_check = false)
438
  {
439 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 437 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...
440
  }
441
442
  /**
443
   * Removes a function from a specified action hook.
444
   *
445
   * @param string $tag                <p>The action hook to which the function to be removed is hooked.</p>
446
   * @param mixed  $function_to_remove <p>The name of the function which should be removed.</p>
447
   * @param int    $priority           <p>[optional] The priority of the function (default: 50).</p>
448
   *
449
   * @return bool <p>Whether the function is removed.</p>
450
   */
451 1
  public function remove_action($tag, $function_to_remove, $priority = self::PRIORITY_NEUTRAL)
452
  {
453 1
    return $this->remove_filter($tag, $function_to_remove, $priority);
454
  }
455
456
  /**
457
   * Remove all of the hooks from an action.
458
   *
459
   * @param string $tag      <p>The action to remove hooks from.</p>
460
   * @param bool   $priority <p>The priority number to remove them from.</p>
461
   *
462
   * @return bool
463
   */
464 4
  public function remove_all_actions($tag, $priority = false)
465
  {
466 4
    return $this->remove_all_filters($tag, $priority);
467
  }
468
469
  /**
470
   * Execute functions hooked on a specific action hook.
471
   *
472
   * @param    string $tag     <p>The name of the action to be executed.</p>
473
   * @param    mixed  $arg     <p>
474
   *                           [optional] Additional arguments which are passed on
475
   *                           to the functions hooked to the action.
476
   *                           </p>
477
   *
478
   * @return   bool            <p>Will return false if $tag does not exist in $filter array.</p>
479
   */
480 2
  public function do_action($tag, $arg = '')
481
  {
482 2
    if (!is_array($this->actions)) {
483
      $this->actions = array();
484
    }
485
486 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...
487 2
      $this->actions[$tag] = 1;
488 2
    } else {
489 1
      ++$this->actions[$tag];
490
    }
491
492
    // Do 'all' actions first
493 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...
494 1
      $this->current_filter[] = $tag;
495 1
      $all_args = func_get_args();
496 1
      $this->_call_all_hook($all_args);
497 1
    }
498
499 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...
500 1
      if (isset($this->filters['all'])) {
501 1
        array_pop($this->current_filter);
502 1
      }
503
504 1
      return false;
505
    }
506
507 2
    if (!isset($this->filters['all'])) {
508 2
      $this->current_filter[] = $tag;
509 2
    }
510
511 2
    $args = array();
512
513
    if (
514 2
        is_array($arg)
515 2
        &&
516
        isset($arg[0])
517 2
        &&
518
        is_object($arg[0])
519 2
        &&
520
        1 == count($arg)
521 2
    ) {
522
      $args[] =& $arg[0];
523
    } else {
524 2
      $args[] = $arg;
525
    }
526
527 2
    $numArgs = func_num_args();
528
529 2
    for ($a = 2; $a < $numArgs; $a++) {
530 1
      $args[] = func_get_arg($a);
531 1
    }
532
533
    // Sort
534 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...
535 2
      ksort($this->filters[$tag]);
536 2
      $this->merged_filters[$tag] = true;
537 2
    }
538
539 2
    reset($this->filters[$tag]);
540
541 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...
542 2
      foreach ((array)current($this->filters[$tag]) as $the_) {
543 2
        if (null !== $the_['function']) {
544
545 2
          if (null !== $the_['include_path']) {
546
            /** @noinspection PhpIncludeInspection */
547
            include_once $the_['include_path'];
548
          }
549
550 2
          call_user_func_array($the_['function'], $args);
551 2
        }
552 2
      }
553 2
    } while (next($this->filters[$tag]) !== false);
554
555 2
    array_pop($this->current_filter);
556
557 2
    return true;
558
  }
559
560
  /**
561
   * Execute functions hooked on a specific action hook, specifying arguments in an array.
562
   *
563
   * @param    string $tag  <p>The name of the action to be executed.</p>
564
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
565
   *
566
   * @return   bool         <p>Will return false if $tag does not exist in $filter array.</p>
567
   */
568 1
  public function do_action_ref_array($tag, $args)
569
  {
570 1
    if (!is_array($this->actions)) {
571
      $this->actions = array();
572
    }
573
574 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...
575 1
      $this->actions[$tag] = 1;
576 1
    } else {
577 1
      ++$this->actions[$tag];
578
    }
579
580
    // Do 'all' actions first
581 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...
582 1
      $this->current_filter[] = $tag;
583 1
      $all_args = func_get_args();
584 1
      $this->_call_all_hook($all_args);
585 1
    }
586
587 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...
588 1
      if (isset($this->filters['all'])) {
589
        array_pop($this->current_filter);
590
      }
591
592 1
      return false;
593
    }
594
595 1
    if (!isset($this->filters['all'])) {
596
      $this->current_filter[] = $tag;
597
    }
598
599
    // Sort
600 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...
601 1
      ksort($this->filters[$tag]);
602 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...
603 1
    }
604
605 1
    reset($this->filters[$tag]);
606
607 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...
608 1
      foreach ((array)current($this->filters[$tag]) as $the_) {
609 1
        if (null !== $the_['function']) {
610
611 1
          if (null !== $the_['include_path']) {
612
            /** @noinspection PhpIncludeInspection */
613
            include_once $the_['include_path'];
614
          }
615
616 1
          call_user_func_array($the_['function'], $args);
617 1
        }
618 1
      }
619 1
    } while (next($this->filters[$tag]) !== false);
620
621 1
    array_pop($this->current_filter);
622
623 1
    return true;
624
  }
625
626
  /**
627
   * Retrieve the number of times an action has fired.
628
   *
629
   * @param string $tag <p>The name of the action hook.</p>
630
   *
631
   * @return integer <p>The number of times action hook <tt>$tag</tt> is fired.</p>
632
   */
633 1
  public function did_action($tag)
634
  {
635 1
    if (!is_array($this->actions) || !isset($this->actions[$tag])) {
636
      return 0;
637
    }
638
639 1
    return $this->actions[$tag];
640
  }
641
642
  /**
643
   * Retrieve the name of the current filter or action.
644
   *
645
   * @return string <p>Hook name of the current filter or action.</p>
646
   */
647
  public function current_filter()
648
  {
649
    return end($this->current_filter);
650
  }
651
652
  /**
653
   * Build Unique ID for storage and retrieval.
654
   *
655
   * @param    string|array $function <p>Used for creating unique id.</p>
656
   *
657
   * @return   string|bool             <p>
658
   *                                   Unique ID for usage as array key or false if
659
   *                                   $priority === false and $function is an
660
   *                                   object reference, and it does not already have a unique id.
661
   *                                   </p>
662
   */
663 7
  private function _filter_build_unique_id($function)
664
  {
665 7
    if (is_string($function)) {
666 3
      return $function;
667
    }
668
669 4
    if (is_object($function)) {
670
      // Closures are currently implemented as objects
671
      $function = array(
672 2
          $function,
673 2
          '',
674 2
      );
675 2
    } else {
676 2
      $function = (array)$function;
677
    }
678
679 4
    if (is_object($function[0])) {
680
      // Object Class Calling
681 4
      return spl_object_hash($function[0]) . $function[1];
682
    }
683
684
    if (is_string($function[0])) {
685
      // Static Calling
686
      return $function[0] . $function[1];
687
    }
688
689
    return false;
690
  }
691
692
  /**
693
   * Call "All" Hook
694
   *
695
   * @param array $args
696
   */
697 1
  public function _call_all_hook($args)
698
  {
699 1
    reset($this->filters['all']);
700
701 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...
702 1
      foreach ((array)current($this->filters['all']) as $the_) {
703 1
        if (null !== $the_['function']) {
704
705 1
          if (null !== $the_['include_path']) {
706
            /** @noinspection PhpIncludeInspection */
707
            include_once $the_['include_path'];
708
          }
709
710 1
          call_user_func_array($the_['function'], $args);
711 1
        }
712 1
      }
713 1
    } while (next($this->filters['all']) !== false);
714 1
  }
715
716
  /** @noinspection MagicMethodsValidityInspection */
717
  /**
718
   * @param array $args
719
   *
720
   * @deprecated use "this->_call_all_hook()"
721
   */
722
  public function __call_all_hook($args)
723
  {
724
    // <-- refactoring "__call_all_hook()" into "_call_all_hook()" is a breaking change (BC),
725
    // so we will only deprecate the usage
726
727
    $this->_call_all_hook($args);
728
  }
729
730
  /**
731
   * Add hook for shortcode tag.
732
   *
733
   * <p>
734
   * <br />
735
   * There can only be one hook for each shortcode. Which means that if another
736
   * plugin has a similar shortcode, it will override yours or yours will override
737
   * theirs depending on which order the plugins are included and/or ran.
738
   * <br />
739
   * <br />
740
   * </p>
741
   *
742
   * Simplest example of a shortcode tag using the API:
743
   *
744
   * <code>
745
   * // [footag foo="bar"]
746
   * function footag_func($atts) {
747
   *  return "foo = {$atts[foo]}";
748
   * }
749
   * add_shortcode('footag', 'footag_func');
750
   * </code>
751
   *
752
   * Example with nice attribute defaults:
753
   *
754
   * <code>
755
   * // [bartag foo="bar"]
756
   * function bartag_func($atts) {
757
   *  $args = shortcode_atts(array(
758
   *    'foo' => 'no foo',
759
   *    'baz' => 'default baz',
760
   *  ), $atts);
761
   *
762
   *  return "foo = {$args['foo']}";
763
   * }
764
   * add_shortcode('bartag', 'bartag_func');
765
   * </code>
766
   *
767
   * Example with enclosed content:
768
   *
769
   * <code>
770
   * // [baztag]content[/baztag]
771
   * function baztag_func($atts, $content='') {
772
   *  return "content = $content";
773
   * }
774
   * add_shortcode('baztag', 'baztag_func');
775
   * </code>
776
   *
777
   * @param string   $tag  <p>Shortcode tag to be searched in post content.</p>
778
   * @param callable $func <p>Hook to run when shortcode is found.</p>
779
   *
780
   * @return bool
781
   */
782 2
  public function add_shortcode($tag, $func)
783
  {
784 2
    if (is_callable($func)) {
785 2
      self::$shortcode_tags[$tag] = $func;
786
787 2
      return true;
788
    }
789
790
    return false;
791
  }
792
793
  /**
794
   * Removes hook for shortcode.
795
   *
796
   * @param string $tag <p>shortcode tag to remove hook for.</p>
797
   *
798
   * @return bool
799
   */
800 1
  public function remove_shortcode($tag)
801
  {
802 1
    if (isset(self::$shortcode_tags[$tag])) {
803 1
      unset(self::$shortcode_tags[$tag]);
804
805 1
      return true;
806
    }
807
808
    return false;
809
  }
810
811
  /**
812
   * This function is simple, it clears all of the shortcode tags by replacing the
813
   * shortcodes by a empty array. This is actually a very efficient method
814
   * for removing all shortcodes.
815
   *
816
   * @return bool
817
   */
818 1
  public function remove_all_shortcodes()
819
  {
820 1
    self::$shortcode_tags = array();
821
822 1
    return true;
823
  }
824
825
  /**
826
   * Whether a registered shortcode exists named $tag
827
   *
828
   * @param string $tag
829
   *
830
   * @return boolean
831
   */
832 1
  public function shortcode_exists($tag)
833
  {
834 1
    return array_key_exists($tag, self::$shortcode_tags);
835
  }
836
837
  /**
838
   * Whether the passed content contains the specified shortcode.
839
   *
840
   * @param string $content
841
   * @param string $tag
842
   *
843
   * @return bool
844
   */
845
  public function has_shortcode($content, $tag)
846
  {
847
    if (false === strpos($content, '[')) {
848
      return false;
849
    }
850
851
    if ($this->shortcode_exists($tag)) {
852
      preg_match_all('/' . $this->get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER);
853
      if (empty($matches)) {
854
        return false;
855
      }
856
857
      foreach ($matches as $shortcode) {
858
        if ($tag === $shortcode[2]) {
859
          return true;
860
        }
861
862
        if (!empty($shortcode[5]) && $this->has_shortcode($shortcode[5], $tag)) {
863
          return true;
864
        }
865
      }
866
    }
867
868
    return false;
869
  }
870
871
  /**
872
   * Search content for shortcodes and filter shortcodes through their hooks.
873
   *
874
   * <p>
875
   * <br />
876
   * If there are no shortcode tags defined, then the content will be returned
877
   * without any filtering. This might cause issues when plugins are disabled but
878
   * the shortcode will still show up in the post or content.
879
   * </p>
880
   *
881
   * @param string $content <p>Content to search for shortcodes.</p>
882
   *
883
   * @return string <p>Content with shortcodes filtered out.</p>
884
   */
885 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...
886
  {
887 2
    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
888 1
      return $content;
889
    }
890
891 2
    $pattern = $this->get_shortcode_regex();
892
893 2
    return preg_replace_callback(
894 2
        "/$pattern/s",
895
        array(
896 2
            $this,
897 2
            '_do_shortcode_tag',
898 2
        ),
899
        $content
900 2
    );
901
  }
902
903
  /**
904
   * Retrieve the shortcode regular expression for searching.
905
   *
906
   * <p>
907
   * <br />
908
   * The regular expression combines the shortcode tags in the regular expression
909
   * in a regex class.
910
   * <br /><br />
911
   *
912
   * The regular expression contains 6 different sub matches to help with parsing.
913
   * <br /><br />
914
   *
915
   * 1 - An extra [ to allow for escaping shortcodes with double [[]]<br />
916
   * 2 - The shortcode name<br />
917
   * 3 - The shortcode argument list<br />
918
   * 4 - The self closing /<br />
919
   * 5 - The content of a shortcode when it wraps some content.<br />
920
   * 6 - An extra ] to allow for escaping shortcodes with double [[]]<br />
921
   * </p>
922
   *
923
   * @return string The shortcode search regular expression
924
   */
925 2
  public function get_shortcode_regex()
926
  {
927 2
    $tagnames = array_keys(self::$shortcode_tags);
928 2
    $tagregexp = implode('|', array_map('preg_quote', $tagnames));
929
930
    // WARNING! Do not change this regex without changing __do_shortcode_tag() and __strip_shortcode_tag()
931
    // Also, see shortcode_unautop() and shortcode.js.
932
    return
933
        '\\[' // Opening bracket
934
        . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
935 2
        . "($tagregexp)" // 2: Shortcode name
936 2
        . '(?![\\w-])' // Not followed by word character or hyphen
937 2
        . '(' // 3: Unroll the loop: Inside the opening shortcode tag
938 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
939 2
        . '(?:'
940 2
        . '\\/(?!\\])' // A forward slash not followed by a closing bracket
941 2
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
942 2
        . ')*?'
943 2
        . ')'
944 2
        . '(?:'
945 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...
946 2
        . '\\]' // ... and closing bracket
947 2
        . '|'
948 2
        . '\\]' // Closing bracket
949 2
        . '(?:'
950 2
        . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
951 2
        . '[^\\[]*+' // Not an opening bracket
952 2
        . '(?:'
953 2
        . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
954 2
        . '[^\\[]*+' // Not an opening bracket
955 2
        . ')*+'
956 2
        . ')'
957 2
        . '\\[\\/\\2\\]' // Closing shortcode tag
958 2
        . ')?'
959 2
        . ')'
960 2
        . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
961
  }
962
963
  /**
964
   * Regular Expression callable for do_shortcode() for calling shortcode hook.
965
   *
966
   * @see self::get_shortcode_regex for details of the match array contents.
967
   *
968
   * @param array $m <p>regular expression match array</p>
969
   *
970
   * @return mixed <p><strong>false</strong> on failure</p>
971
   */
972 2
  private function _do_shortcode_tag($m)
973
  {
974
    // allow [[foo]] syntax for escaping a tag
975 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...
976
      return substr($m[0], 1, -1);
977
    }
978
979 2
    $tag = $m[2];
980 2
    $attr = $this->shortcode_parse_atts($m[3]);
981
982
    // enclosing tag - extra parameter
983 2
    if (isset($m[5])) {
984 2
      return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, $m[5], $tag) . $m[6];
985
    }
986
987
    // self-closing tag
988
    return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, null, $tag) . $m[6];
989
  }
990
991
  /**
992
   * Retrieve all attributes from the shortcodes tag.
993
   *
994
   * <p>
995
   * <br />
996
   * The attributes list has the attribute name as the key and the value of the
997
   * attribute as the value in the key/value pair. This allows for easier
998
   * retrieval of the attributes, since all attributes have to be known.
999
   * </p>
1000
   *
1001
   * @param string $text
1002
   *
1003
   * @return array <p>List of attributes and their value.</p>
1004
   */
1005 2
  public function shortcode_parse_atts($text)
1006
  {
1007 2
    $atts = array();
1008 2
    $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
1009 2
    $text = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $text);
1010 2
    if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
1011 2
      foreach ($match as $m) {
1012 2
        if (!empty($m[1])) {
1013 1
          $atts[strtolower($m[1])] = stripcslashes($m[2]);
1014 2
        } elseif (!empty($m[3])) {
1015
          $atts[strtolower($m[3])] = stripcslashes($m[4]);
1016 1
        } elseif (!empty($m[5])) {
1017 1
          $atts[strtolower($m[5])] = stripcslashes($m[6]);
1018 1
        } elseif (isset($m[7]) && $m[7] !== '') {
1019
          $atts[] = stripcslashes($m[7]);
1020
        } elseif (isset($m[8])) {
1021
          $atts[] = stripcslashes($m[8]);
1022
        }
1023 2
      }
1024 2
    } else {
1025
      $atts = ltrim($text);
1026
    }
1027
1028 2
    return $atts;
1029
  }
1030
1031
  /**
1032
   * Combine user attributes with known attributes and fill in defaults when needed.
1033
   *
1034
   * <p>
1035
   * <br />
1036
   * The pairs should be considered to be all of the attributes which are
1037
   * supported by the caller and given as a list. The returned attributes will
1038
   * only contain the attributes in the $pairs list.
1039
   *
1040
   * <br /><br />
1041
   * If the $atts list has unsupported attributes, then they will be ignored and
1042
   * removed from the final returned list.
1043
   * </p>
1044
   *
1045
   * @param array  $pairs     <p>Entire list of supported attributes and their defaults.</p>
1046
   * @param array  $atts      <p>User defined attributes in shortcode tag.</p>
1047
   * @param string $shortcode <p>[optional] The name of the shortcode, provided for context to enable filtering.</p>
1048
   *
1049
   * @return array <p>Combined and filtered attribute list.</p>
1050
   */
1051 2
  public function shortcode_atts($pairs, $atts, $shortcode = '')
1052
  {
1053 2
    $atts = (array)$atts;
1054 2
    $out = array();
1055 2
    foreach ($pairs as $name => $default) {
1056 2
      if (array_key_exists($name, $atts)) {
1057 1
        $out[$name] = $atts[$name];
1058 1
      } else {
1059 2
        $out[$name] = $default;
1060
      }
1061 2
    }
1062
    /**
1063
     * Filter a shortcode's default attributes.
1064
     *
1065
     * <p>
1066
     * <br />
1067
     * If the third parameter of the shortcode_atts() function is present then this filter is available.
1068
     * The third parameter, $shortcode, is the name of the shortcode.
1069
     * </p>
1070
     *
1071
     * @param array $out   <p>The output array of shortcode attributes.</p>
1072
     * @param array $pairs <p>The supported attributes and their defaults.</p>
1073
     * @param array $atts  <p>The user defined shortcode attributes.</p>
1074
     */
1075 2
    if ($shortcode) {
1076
      $out = $this->apply_filters(
1077
          array(
1078
              $this,
1079
              "shortcode_atts_{$shortcode}",
1080
          ), $out, $pairs, $atts
1081
      );
1082
    }
1083
1084 2
    return $out;
1085
  }
1086
1087
  /**
1088
   * Remove all shortcode tags from the given content.
1089
   *
1090
   * @param string $content <p>Content to remove shortcode tags.</p>
1091
   *
1092
   * @return string <p>Content without shortcode tags.</p>
1093
   */
1094 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...
1095
  {
1096
1097 1
    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
1098
      return $content;
1099
    }
1100
1101 1
    $pattern = $this->get_shortcode_regex();
1102
1103 1
    return preg_replace_callback(
1104 1
        "/$pattern/s",
1105
        array(
1106 1
            $this,
1107 1
            '_strip_shortcode_tag',
1108 1
        ),
1109
        $content
1110 1
    );
1111
  }
1112
1113
  /**
1114
   * Strip shortcode by tag.
1115
   *
1116
   * @param array $m
1117
   *
1118
   * @return string
1119
   */
1120 1
  private function _strip_shortcode_tag($m)
1121
  {
1122
    // allow [[foo]] syntax for escaping a tag
1123 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...
1124
      return substr($m[0], 1, -1);
1125
    }
1126
1127 1
    return $m[1] . $m[6];
1128
  }
1129
1130
}
1131