Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-wp-hook.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Plugin API: WP_Hook class
4
 *
5
 * @package WordPress
6
 * @subpackage Plugin
7
 * @since 4.7.0
8
 */
9
10
/**
11
 * Core class used to implement action and filter hook functionality.
12
 *
13
 * @since 4.7.0
14
 *
15
 * @see Iterator
16
 * @see ArrayAccess
17
 */
18
final class WP_Hook implements Iterator, ArrayAccess {
19
20
	/**
21
	 * Hook callbacks.
22
	 *
23
	 * @since 4.7.0
24
	 * @access public
25
	 * @var array
26
	 */
27
	public $callbacks = array();
28
29
	/**
30
	 * The priority keys of actively running iterations of a hook.
31
	 *
32
	 * @since 4.7.0
33
	 * @access private
34
	 * @var array
35
	 */
36
	private $iterations = array();
37
38
	/**
39
	 * The current priority of actively running iterations of a hook.
40
	 *
41
	 * @since 4.7.0
42
	 * @access private
43
	 * @var array
44
	 */
45
	private $current_priority = array();
46
47
	/**
48
	 * Number of levels this hook can be recursively called.
49
	 *
50
	 * @since 4.7.0
51
	 * @access private
52
	 * @var int
53
	 */
54
	private $nesting_level = 0;
55
56
	/**
57
	 * Flag for if we're current doing an action, rather than a filter.
58
	 *
59
	 * @since 4.7.0
60
	 * @access private
61
	 * @var bool
62
	 */
63
	private $doing_action = false;
64
65
	/**
66
	 * Hooks a function or method to a specific filter action.
67
	 *
68
	 * @since 4.7.0
69
	 * @access public
70
	 *
71
	 * @param string   $tag             The name of the filter to hook the $function_to_add callback to.
72
	 * @param callable $function_to_add The callback to be run when the filter is applied.
73
	 * @param int      $priority        The order in which the functions associated with a
74
	 *                                  particular action are executed. Lower numbers correspond with
75
	 *                                  earlier execution, and functions with the same priority are executed
76
	 *                                  in the order in which they were added to the action.
77
	 * @param int      $accepted_args   The number of arguments the function accepts.
78
	 */
79
	public function add_filter( $tag, $function_to_add, $priority, $accepted_args ) {
80
		$idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );
81
		$priority_existed = isset( $this->callbacks[ $priority ] );
82
83
		$this->callbacks[ $priority ][ $idx ] = array(
84
			'function' => $function_to_add,
85
			'accepted_args' => $accepted_args
86
		);
87
88
		// if we're adding a new priority to the list, put them back in sorted order
89
		if ( ! $priority_existed && count( $this->callbacks ) > 1 ) {
90
			ksort( $this->callbacks, SORT_NUMERIC );
91
		}
92
93
		if ( $this->nesting_level > 0 ) {
94
			$this->resort_active_iterations( $priority, $priority_existed );
95
		}
96
	}
97
98
	/**
99
	 * Handles reseting callback priority keys mid-iteration.
100
	 *
101
	 * @since 4.7.0
102
	 * @access private
103
	 *
104
	 * @param bool|int $new_priority     Optional. The priority of the new filter being added. Default false,
105
	 *                                   for no priority being added.
106
	 * @param bool     $priority_existed Optional. Flag for whether the priority already existed before the new
107
	 *                                   filter was added. Default false.
108
	 */
109
	private function resort_active_iterations( $new_priority = false, $priority_existed = false ) {
110
		$new_priorities = array_keys( $this->callbacks );
111
112
		// If there are no remaining hooks, clear out all running iterations.
113
		if ( ! $new_priorities ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_priorities of type array<integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
114
			foreach ( $this->iterations as $index => $iteration ) {
115
				$this->iterations[ $index ] = $new_priorities;
116
			}
117
			return;
118
		}
119
120
		$min = min( $new_priorities );
121
		foreach ( $this->iterations as $index => &$iteration ) {
122
			$current = current( $iteration );
123
			// If we're already at the end of this iteration, just leave the array pointer where it is.
124
			if ( false === $current ) {
125
				continue;
126
			}
127
128
			$iteration = $new_priorities;
129
130
			if ( $current < $min ) {
131
				array_unshift( $iteration, $current );
132
				continue;
133
			}
134
135
			while ( current( $iteration ) < $current ) {
136
				if ( false === next( $iteration ) ) {
137
					break;
138
				}
139
			}
140
141
			// If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority...
142
			if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) {
143
				/*
144
				 * ... and the new priority is the same as what $this->iterations thinks is the previous
145
				 * priority, we need to move back to it.
146
				 */
147
148
				if ( false === current( $iteration ) ) {
149
					// If we've already moved off the end of the array, go back to the last element.
150
					$prev = end( $iteration );
151
				} else {
152
					// Otherwise, just go back to the previous element.
153
					$prev = prev( $iteration );
154
				}
155
				if ( false === $prev ) {
156
					// Start of the array. Reset, and go about our day.
157
					reset( $iteration );
158
				} elseif ( $new_priority !== $prev ) {
159
					// Previous wasn't the same. Move forward again.
160
					next( $iteration );
161
				}
162
			}
163
		}
164
		unset( $iteration );
165
	}
166
167
	/**
168
	 * Unhooks a function or method from a specific filter action.
169
	 *
170
	 * @since 4.7.0
171
	 * @access public
172
	 *
173
	 * @param string   $tag                The filter hook to which the function to be removed is hooked. Used
174
	 *                                     for building the callback ID when SPL is not available.
175
	 * @param callable $function_to_remove The callback to be removed from running when the filter is applied.
176
	 * @param int      $priority           The exact priority used when adding the original filter callback.
177
	 * @return bool Whether the callback existed before it was removed.
178
	 */
179
	public function remove_filter( $tag, $function_to_remove, $priority ) {
180
		$function_key = _wp_filter_build_unique_id( $tag, $function_to_remove, $priority );
181
182
		$exists = isset( $this->callbacks[ $priority ][ $function_key ] );
183
		if ( $exists ) {
184
			unset( $this->callbacks[ $priority ][ $function_key ] );
185
			if ( ! $this->callbacks[ $priority ] ) {
186
				unset( $this->callbacks[ $priority ] );
187
				if ( $this->nesting_level > 0 ) {
188
					$this->resort_active_iterations();
189
				}
190
			}
191
		}
192
		return $exists;
193
	}
194
195
	/**
196
	 * Checks if a specific action has been registered for this hook.
197
	 *
198
	 * @since 4.7.0
199
	 * @access public
200
	 *
201
	 * @param callable|bool $function_to_check Optional. The callback to check for. Default false.
202
	 * @param string        $tag               Optional. The name of the filter hook. Used for building
203
	 *                                         the callback ID when SPL is not available. Default empty.
204
	 * @return bool|int The priority of that hook is returned, or false if the function is not attached.
0 ignored issues
show
Should the return type not be boolean|integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
205
	 */
206
	public function has_filter( $tag = '', $function_to_check = false ) {
207
		if ( false === $function_to_check ) {
208
			return $this->has_filters();
209
		}
210
211
		$function_key = _wp_filter_build_unique_id( $tag, $function_to_check, false );
0 ignored issues
show
It seems like $function_to_check defined by parameter $function_to_check on line 206 can also be of type boolean; however, _wp_filter_build_unique_id() does only seem to accept callable, 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...
212
		if ( ! $function_key ) {
213
			return false;
214
		}
215
216
		foreach ( $this->callbacks as $priority => $callbacks ) {
217
			if ( isset( $callbacks[ $function_key ] ) ) {
218
				return $priority;
219
			}
220
		}
221
222
		return false;
223
	}
224
225
	/**
226
	 * Checks if any callbacks have been registered for this hook.
227
	 *
228
	 * @since 4.7.0
229
	 * @access public
230
	 *
231
	 * @return bool True if callbacks have been registered for the current hook, otherwise false.
232
	 */
233
	public function has_filters() {
234
		foreach ( $this->callbacks as $callbacks ) {
235
			if ( $callbacks ) {
236
				return true;
237
			}
238
		}
239
		return false;
240
	}
241
242
	/**
243
	 * Removes all callbacks from the current filter.
244
	 *
245
	 * @since 4.7.0
246
	 * @access public
247
	 *
248
	 * @param int|bool $priority Optional. The priority number to remove. Default false.
249
	 */
250
	public function remove_all_filters( $priority = false ) {
251
		if ( ! $this->callbacks ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->callbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
252
			return;
253
		}
254
255
		if ( false === $priority ) {
256
			$this->callbacks = array();
257
		} else if ( isset( $this->callbacks[ $priority ] ) ) {
258
			unset( $this->callbacks[ $priority ] );
259
		}
260
261
		if ( $this->nesting_level > 0 ) {
262
			$this->resort_active_iterations();
263
		}
264
	}
265
266
	/**
267
	 * Calls the callback functions added to a filter hook.
268
	 *
269
	 * @since 4.7.0
270
	 * @access public
271
	 *
272
	 * @param mixed $value The value to filter.
273
	 * @param array $args  Arguments to pass to callbacks.
274
	 * @return mixed The filtered value after all hooked functions are applied to it.
275
	 */
276
	public function apply_filters( $value, $args ) {
277
		if ( ! $this->callbacks ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->callbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
278
			return $value;
279
		}
280
281
		$nesting_level = $this->nesting_level++;
282
283
		$this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
284
		$num_args = count( $args );
285
286
		do {
287
			$this->current_priority[ $nesting_level ] = $priority = current( $this->iterations[ $nesting_level ] );
288
289
			foreach ( $this->callbacks[ $priority ] as $the_ ) {
290
				if( ! $this->doing_action ) {
291
					$args[ 0 ] = $value;
292
				}
293
294
				// Avoid the array_slice if possible.
295
				if ( $the_['accepted_args'] == 0 ) {
296
					$value = call_user_func_array( $the_['function'], array() );
297
				} elseif ( $the_['accepted_args'] >= $num_args ) {
298
					$value = call_user_func_array( $the_['function'], $args );
299
				} else {
300
					$value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int)$the_['accepted_args'] ) );
301
				}
302
			}
303
		} while ( false !== next( $this->iterations[ $nesting_level ] ) );
304
305
		unset( $this->iterations[ $nesting_level ] );
306
		unset( $this->current_priority[ $nesting_level ] );
307
308
		$this->nesting_level--;
309
310
		return $value;
311
	}
312
313
	/**
314
	 * Executes the callback functions hooked on a specific action hook.
315
	 *
316
	 * @since 4.7.0
317
	 * @access public
318
	 *
319
	 * @param mixed $args Arguments to pass to the hook callbacks.
320
	 */
321
	public function do_action( $args ) {
322
		$this->doing_action = true;
323
		$this->apply_filters( '', $args );
324
325
		// If there are recursive calls to the current action, we haven't finished it until we get to the last one.
326
		if ( ! $this->nesting_level ) {
327
			$this->doing_action = false;
328
		}
329
	}
330
331
	/**
332
	 * Processes the functions hooked into the 'all' hook.
333
	 *
334
	 * @since 4.7.0
335
	 * @access public
336
	 *
337
	 * @param array $args Arguments to pass to the hook callbacks. Passed by reference.
338
	 */
339
	public function do_all_hook( &$args ) {
340
		$nesting_level = $this->nesting_level++;
341
		$this->iterations[ $nesting_level ] = array_keys( $this->callbacks );
342
343
		do {
344
			$priority = current( $this->iterations[ $nesting_level ] );
345
			foreach ( $this->callbacks[ $priority ] as $the_ ) {
346
				call_user_func_array( $the_['function'], $args );
347
			}
348
		} while ( false !== next( $this->iterations[ $nesting_level ] ) );
349
350
		unset( $this->iterations[ $nesting_level ] );
351
		$this->nesting_level--;
352
	}
353
354
	/**
355
	 * Return the current priority level of the currently running iteration of the hook.
356
	 *
357
	 * @since 4.7.0
358
	 * @access public
359
	 *
360
	 * @return int|false If the hook is running, return the current priority level. If it isn't running, return false.
361
	 */
362
	public function current_priority() {
363
		if ( false === current( $this->iterations ) ) {
364
			return false;
365
		}
366
367
		return current( current( $this->iterations ) );
368
	}
369
370
	/**
371
	 * Normalizes filters set up before WordPress has initialized to WP_Hook objects.
372
	 *
373
	 * @since 4.7.0
374
	 * @access public
375
	 * @static
376
	 *
377
	 * @param array $filters Filters to normalize.
378
	 * @return WP_Hook[] Array of normalized filters.
0 ignored issues
show
Should the return type not be array<*,WP_Hook>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
379
	 */
380
	public static function build_preinitialized_hooks( $filters ) {
381
		/** @var WP_Hook[] $normalized */
382
		$normalized = array();
383
384
		foreach ( $filters as $tag => $callback_groups ) {
385
			if ( is_object( $callback_groups ) && $callback_groups instanceof WP_Hook ) {
386
				$normalized[ $tag ] = $callback_groups;
387
				continue;
388
			}
389
			$hook = new WP_Hook();
390
391
			// Loop through callback groups.
392
			foreach ( $callback_groups as $priority => $callbacks ) {
393
394
				// Loop through callbacks.
395
				foreach ( $callbacks as $cb ) {
396
					$hook->add_filter( $tag, $cb['function'], $priority, $cb['accepted_args'] );
397
				}
398
			}
399
			$normalized[ $tag ] = $hook;
400
		}
401
		return $normalized;
402
	}
403
404
	/**
405
	 * Determines whether an offset value exists.
406
	 *
407
	 * @since 4.7.0
408
	 * @access public
409
	 *
410
	 * @link http://php.net/manual/en/arrayaccess.offsetexists.php
411
	 *
412
	 * @param mixed $offset An offset to check for.
413
	 * @return bool True if the offset exists, false otherwise.
414
	 */
415
	public function offsetExists( $offset ) {
416
		return isset( $this->callbacks[ $offset ] );
417
	}
418
419
	/**
420
	 * Retrieves a value at a specified offset.
421
	 *
422
	 * @since 4.7.0
423
	 * @access public
424
	 *
425
	 * @link http://php.net/manual/en/arrayaccess.offsetget.php
426
	 *
427
	 * @param mixed $offset The offset to retrieve.
428
	 * @return mixed If set, the value at the specified offset, null otherwise.
429
	 */
430
	public function offsetGet( $offset ) {
431
		return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null;
432
	}
433
434
	/**
435
	 * Sets a value at a specified offset.
436
	 *
437
	 * @since 4.7.0
438
	 * @access public
439
	 *
440
	 * @link http://php.net/manual/en/arrayaccess.offsetset.php
441
	 *
442
	 * @param mixed $offset The offset to assign the value to.
443
	 * @param mixed $value The value to set.
444
	 */
445
	public function offsetSet( $offset, $value ) {
446
		if ( is_null( $offset ) ) {
447
			$this->callbacks[] = $value;
448
		} else {
449
			$this->callbacks[ $offset ] = $value;
450
		}
451
	}
452
453
	/**
454
	 * Unsets a specified offset.
455
	 *
456
	 * @since 4.7.0
457
	 * @access public
458
	 *
459
	 * @link http://php.net/manual/en/arrayaccess.offsetunset.php
460
	 *
461
	 * @param mixed $offset The offset to unset.
462
	 */
463
	public function offsetUnset( $offset ) {
464
		unset( $this->callbacks[ $offset ] );
465
	}
466
467
	/**
468
	 * Returns the current element.
469
	 *
470
	 * @since 4.7.0
471
	 * @access public
472
	 *
473
	 * @link http://php.net/manual/en/iterator.current.php
474
	 *
475
	 * @return array Of callbacks at current priority.
476
	 */
477
	public function current() {
478
		return current( $this->callbacks );
479
	}
480
481
	/**
482
	 * Moves forward to the next element.
483
	 *
484
	 * @since 4.7.0
485
	 * @access public
486
	 *
487
	 * @link http://php.net/manual/en/iterator.next.php
488
	 *
489
	 * @return array Of callbacks at next priority.
490
	 */
491
	public function next() {
492
		return next( $this->callbacks );
493
	}
494
495
	/**
496
	 * Returns the key of the current element.
497
	 *
498
	 * @since 4.7.0
499
	 * @access public
500
	 *
501
	 * @link http://php.net/manual/en/iterator.key.php
502
	 *
503
	 * @return mixed Returns current priority on success, or NULL on failure
504
	 */
505
	public function key() {
506
		return key( $this->callbacks );
507
	}
508
509
	/**
510
	 * Checks if current position is valid.
511
	 *
512
	 * @since 4.7.0
513
	 * @access public
514
	 *
515
	 * @link http://php.net/manual/en/iterator.valid.php
516
	 *
517
	 * @return boolean
518
	 */
519
	public function valid() {
520
		return key( $this->callbacks ) !== null;
521
	}
522
523
	/**
524
	 * Rewinds the Iterator to the first element.
525
	 *
526
	 * @since 4.7.0
527
	 * @access public
528
	 *
529
	 * @link http://php.net/manual/en/iterator.rewind.php
530
	 */
531
	public function rewind() {
532
		reset( $this->callbacks );
533
	}
534
535
}
536