Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions src/wp-includes/class-wp-superglobals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* WP_Superglobals class.
*
* Provides an abstraction layer over PHP superglobals ($_GET, $_POST,
* $_REQUEST, $_COOKIE, $_SERVER) to return unslashed values.
*
* WordPress adds slashes to superglobals via wp_magic_quotes() for
* historical reasons. This class wraps the live superglobal by reference
* and transparently strips slashes on read access, eliminating the need
* for manual wp_unslash() calls throughout the codebase.
*
* @package WordPress
* @since x.x.x
*
* @see wp_magic_quotes()
* @link https://core.trac.wordpress.org/ticket/22325
*/

/**
* Core class used to provide unslashed access to PHP superglobals.
*
* Implements ArrayAccess, Countable, and IteratorAggregate so instances
* can be used as drop-in replacements for superglobal arrays while
* always returning unslashed values.
*
* This class is read-only. Attempts to set or unset values via array
* access will be silently ignored, following the same pattern used by
* WP_Theme.
*
* @since x.x.x
*
* @see ArrayAccess
* @see Countable
* @see IteratorAggregate
*/
class WP_Superglobals implements ArrayAccess, Countable, IteratorAggregate {

/**
* Reference to the underlying PHP superglobal array.
*
* @since x.x.x
* @var array
*/
private $data;

/**
* Human-readable name of the superglobal, e.g. '$_GET'.
*
* Used in _doing_it_wrong() messages.
*
* @since x.x.x
* @var string
*/
private $name;

/**
* Constructor.
*
* @since x.x.x
*
* @param array $superglobal Reference to a PHP superglobal array.
* @param string $name Human-readable name for debug messages, e.g. '$_GET'.
*/
public function __construct( &$superglobal, $name = '' ) {
$this->data = &$superglobal;
$this->name = $name;
}

/**
* Retrieves an unslashed value from the superglobal.
*
* @since x.x.x
*
* @param string $key The key to retrieve.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
public function get( $key, $default_value = null ) {
if ( ! isset( $this->data[ $key ] ) ) {
return $default_value;
}

return wp_unslash( $this->data[ $key ] );
}

/**
* Checks whether a key exists in the superglobal.
*
* @since x.x.x
*
* @param string $key The key to check.
* @return bool True if the key exists, false otherwise.
*/
public function has( $key ) {
return isset( $this->data[ $key ] );
}

/**
* Retrieves all values from the superglobal, unslashed.
*
* @since x.x.x
*
* @return array All unslashed values.
*/
public function all() {
return wp_unslash( $this->data );
}

/**
* Checks if a parameter is set.
*
* @since x.x.x
*
* @param string $offset Key to check.
* @return bool Whether the key is set.
*/
#[ReturnTypeWillChange]
public function offsetExists( $offset ) {
return isset( $this->data[ $offset ] );
}

/**
* Retrieves an unslashed value by key.
*
* @since x.x.x
*
* @param string $offset Key to retrieve.
* @return mixed|null Unslashed value if set, null otherwise.
*/
#[ReturnTypeWillChange]
public function offsetGet( $offset ) {
return $this->get( $offset );
}

/**
* Not implemented. Superglobal wrappers are read-only.
*
* This method is a no-op to maintain read-only behavior.
*
* @since x.x.x
*
* @param string $offset Key to set.
* @param mixed $value Value to set.
*/
#[ReturnTypeWillChange]
public function offsetSet( $offset, $value ) {}

/**
* Not implemented. Superglobal wrappers are read-only.
*
* This method is a no-op to maintain read-only behavior.
*
* @since x.x.x
*
* @param string $offset Key to unset.
*/
#[ReturnTypeWillChange]
public function offsetUnset( $offset ) {}

/**
* Returns the number of entries in the superglobal.
*
* @since x.x.x
*
* @return int Number of entries.
*/
#[ReturnTypeWillChange]
public function count() {
return count( $this->data );
}

/**
* Returns an iterator over all unslashed values.
*
* @since x.x.x
*
* @return ArrayIterator Iterator over unslashed key-value pairs.
*/
#[ReturnTypeWillChange]
public function getIterator() {
return new ArrayIterator( $this->all() );
}
}
155 changes: 155 additions & 0 deletions src/wp-includes/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -1279,10 +1279,21 @@ function wp_set_internal_encoding() {
* Also forces `$_REQUEST` to be `$_GET + $_POST`. If `$_SERVER`,
* `$_COOKIE`, or `$_ENV` are needed, use those superglobals directly.
*
* Initializes the global WP_Superglobals instances that provide unslashed
* access to the superglobal values.
*
* @since 3.0.0
* @access private
*
* @global WP_Superglobals $wp_get Unslashed access to $_GET.
* @global WP_Superglobals $wp_post Unslashed access to $_POST.
* @global WP_Superglobals $wp_request Unslashed access to $_REQUEST.
* @global WP_Superglobals $wp_cookie Unslashed access to $_COOKIE.
* @global WP_Superglobals $wp_server Unslashed access to $_SERVER.
*/
function wp_magic_quotes() {
global $wp_get, $wp_post, $wp_request, $wp_cookie, $wp_server;

// Escape with wpdb.
$_GET = add_magic_quotes( $_GET );
$_POST = add_magic_quotes( $_POST );
Expand All @@ -1291,6 +1302,150 @@ function wp_magic_quotes() {

// Force REQUEST to be GET + POST.
$_REQUEST = array_merge( $_GET, $_POST );

/*
* Initialize global WP_Superglobals instances.
*
* These wrap the live superglobals by reference and transparently
* strip slashes on read, so callers never need to call wp_unslash()
* manually. Initialized after add_magic_quotes() so the wrappers
* always reference the slashed superglobals (and unslash on access).
*/
$wp_get = new WP_Superglobals( $_GET, '$_GET' );
$wp_post = new WP_Superglobals( $_POST, '$_POST' );
$wp_request = new WP_Superglobals( $_REQUEST, '$_REQUEST' );
$wp_cookie = new WP_Superglobals( $_COOKIE, '$_COOKIE' );
$wp_server = new WP_Superglobals( $_SERVER, '$_SERVER' );
}

/**
* Retrieves an unslashed value from the $_GET superglobal.
*
* Returns the value without the slashes added by wp_magic_quotes(),
* removing the need for manual wp_unslash() calls.
*
* This is analogous to PHP's filter_input( INPUT_GET, ... ) but returns
* the raw unslashed value rather than a filtered one.
*
* @since x.x.x
*
* @param string $key The key to retrieve from $_GET.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
function wp_input_get( $key, $default_value = null ) {
global $wp_get;

if ( ! $wp_get instanceof WP_Superglobals ) {
return isset( $_GET[ $key ] ) ? wp_unslash( $_GET[ $key ] ) : $default_value;
}

return $wp_get->get( $key, $default_value );
}

/**
* Retrieves an unslashed value from the $_POST superglobal.
*
* Returns the value without the slashes added by wp_magic_quotes(),
* removing the need for manual wp_unslash() calls.
*
* This is analogous to PHP's filter_input( INPUT_POST, ... ) but returns
* the raw unslashed value rather than a filtered one.
*
* @since x.x.x
*
* @param string $key The key to retrieve from $_POST.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
function wp_input_post( $key, $default_value = null ) {
global $wp_post;

if ( ! $wp_post instanceof WP_Superglobals ) {
return isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : $default_value;
}

return $wp_post->get( $key, $default_value );
}

/**
* Retrieves an unslashed value from the $_REQUEST superglobal.
*
* Returns the value without the slashes added by wp_magic_quotes(),
* removing the need for manual wp_unslash() calls.
*
* This is analogous to PHP's filter_input( INPUT_REQUEST, ... ) but returns
* the raw unslashed value rather than a filtered one.
*
* @since x.x.x
*
* @param string $key The key to retrieve from $_REQUEST.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
function wp_input_request( $key, $default_value = null ) {
global $wp_request;

if ( ! $wp_request instanceof WP_Superglobals ) {
return isset( $_REQUEST[ $key ] ) ? wp_unslash( $_REQUEST[ $key ] ) : $default_value;
}

return $wp_request->get( $key, $default_value );
}

/**
* Retrieves an unslashed value from the $_COOKIE superglobal.
*
* Returns the value without the slashes added by wp_magic_quotes(),
* removing the need for manual wp_unslash() calls.
*
* This is analogous to PHP's filter_input( INPUT_COOKIE, ... ) but returns
* the raw unslashed value rather than a filtered one.
*
* @since x.x.x
*
* @param string $key The key to retrieve from $_COOKIE.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
function wp_input_cookie( $key, $default_value = null ) {
global $wp_cookie;

if ( ! $wp_cookie instanceof WP_Superglobals ) {
return isset( $_COOKIE[ $key ] ) ? wp_unslash( $_COOKIE[ $key ] ) : $default_value;
}

return $wp_cookie->get( $key, $default_value );
}

/**
* Retrieves an unslashed value from the $_SERVER superglobal.
*
* Returns the value without the slashes added by wp_magic_quotes(),
* removing the need for manual wp_unslash() calls.
*
* This is analogous to PHP's filter_input( INPUT_SERVER, ... ) but returns
* the raw unslashed value rather than a filtered one.
*
* @since x.x.x
*
* @param string $key The key to retrieve from $_SERVER.
* @param mixed $default_value Optional. Default value to return if the key does not exist.
* Default null.
* @return mixed The unslashed value, or `$default_value` if the key is not set.
*/
function wp_input_server( $key, $default_value = null ) {
global $wp_server;

if ( ! $wp_server instanceof WP_Superglobals ) {
return isset( $_SERVER[ $key ] ) ? wp_unslash( $_SERVER[ $key ] ) : $default_value;
}

return $wp_server->get( $key, $default_value );
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
require ABSPATH . WPINC . '/class-wp-meta-query.php';
require ABSPATH . WPINC . '/class-wp-matchesmapregex.php';
require ABSPATH . WPINC . '/class-wp.php';
require ABSPATH . WPINC . '/class-wp-superglobals.php';
require ABSPATH . WPINC . '/class-wp-error.php';
require ABSPATH . WPINC . '/pomo/mo.php';
require ABSPATH . WPINC . '/l10n/class-wp-translation-controller.php';
Expand Down
Loading
Loading