'value1', * 'key2' => 'value2', * ), 'http://example.com' ); * * Omitting the URL from either use results in the current URL being used * (the value of `$_SERVER['REQUEST_URI']`). * * Values are expected to be encoded appropriately with urlencode() or rawurlencode(). * * Setting any query variable's value to boolean false removes the key (see remove_query_arg()). * * Important: The return value of add_query_arg() is not escaped by default. Output should be * late-escaped with esc_url() or similar to help prevent vulnerability to cross-site scripting * (XSS) attacks. * * @param string|array $key Either a query variable key, or an associative array of query variables. * @param string $value Optional. Either a query variable value, or a URL to act upon. * @param string $url Optional. A URL to act upon. * * @return string New URL query string (unescaped). * */ public static function add_query_arg() { $args = func_get_args(); if (is_array($args[0])) { if (count($args) < 2 || false === $args[1]) { $uri = Request::$SERVER->getVar('REQUEST_URI'); } else { $uri = $args[1]; } } else { if (count($args) < 3 || false === $args[2]) { $uri = Request::$SERVER->getVar('REQUEST_URI'); } else { $uri = $args[2]; } } if ($frag = strstr($uri, '#')) { $uri = substr($uri, 0, -strlen($frag)); } else { $frag = ''; } if (0 === stripos($uri, 'http://')) { $protocol = 'http://'; $uri = substr($uri, 7); } elseif (0 === stripos($uri, 'https://')) { $protocol = 'https://'; $uri = substr($uri, 8); } else { $protocol = ''; } if (strpos($uri, '?') !== false) { list($base, $query) = explode('?', $uri, 2); $base .= '?'; } elseif ($protocol || strpos($uri, '=') === false) { $base = $uri . '?'; $query = ''; } else { $base = ''; $query = $uri; } self::wp_parse_str($query, $qs); $qs = self::urlencode_deep($qs); // this re-URL-encodes things that were already in the query string if (is_array($args[0])) { foreach ($args[0] as $k => $v) { $qs[$k] = $v; } } else { $qs[$args[0]] = $args[1]; } foreach ($qs as $k => $v) { if ($v === false) { unset($qs[$k]); } } $ret = self::build_query($qs); $ret = trim($ret, '?'); $ret = preg_replace('#=(&|$)#', '$1', $ret); $ret = $protocol . $base . $ret . $frag; $ret = rtrim($ret, '?'); return $ret; } private static function wp_parse_str($string, &$array) { parse_str($string, $array); if (version_compare(PHP_VERSION, '7.4.0', '<')) { if (get_magic_quotes_gpc()) { $array = self::stripslashes_deep($array); } } } static function urlencode_deep($value) { return self::map_deep($value, 'urlencode'); } /** * Build URL query based on an associative and, or indexed array. * * This is a convenient function for easily building url queries. It sets the * separator to '&' and uses _http_build_query() function. * * @param array $data URL-encode key/value pairs. * * @return string URL-encoded string. * @link https://secure.php.net/manual/en/function.http-build-query.php for more on what * http_build_query() does. * * @since 2.3.0 * * @see _http_build_query() Used to build the query */ private static function build_query($data) { return self::_http_build_query($data, null, '&', '', false); } /** * From php.net (modified by Mark Jaquith to behave like the native PHP5 function). * * @param array|object $data An array or object of data. Converted to array. * @param string $prefix Optional. Numeric index. If set, start parameter numbering with it. * Default null. * @param string $sep Optional. Argument separator; defaults to 'arg_separator.output'. * Default null. * @param string $key Optional. Used to prefix key name. Default empty. * @param bool $urlencode Optional. Whether to use urlencode() in the result. Default true. * * @return string The query string. * @since 3.2.0 * @access private * * @see https://secure.php.net/manual/en/function.http-build-query.php * */ private static function _http_build_query($data, $prefix = null, $sep = null, $key = '', $urlencode = true) { $ret = array(); foreach ((array)$data as $k => $v) { if ($urlencode) { $k = urlencode($k); } if (is_int($k) && $prefix != null) { $k = $prefix . $k; } if (!empty($key)) { $k = $key . '%5B' . $k . '%5D'; } if ($v === null) { continue; } elseif ($v === false) { $v = '0'; } if (is_array($v) || is_object($v)) { array_push($ret, self::_http_build_query($v, '', $sep, $k, $urlencode)); } elseif ($urlencode) { array_push($ret, $k . '=' . urlencode($v)); } else { array_push($ret, $k . '=' . $v); } } if (null === $sep) { $sep = ini_get('arg_separator.output'); } return implode($sep, $ret); } /** * Parses a string into variables to be stored in an array. * * Uses {@link https://secure.php.net/parse_str parse_str()} and stripslashes if * {@link https://secure.php.net/magic_quotes magic_quotes_gpc} is on. * * @param string $string The string to be parsed. * @param array $array Variables will be stored in this array. * * @since 2.2.1 * */ private static function parse_str($string, &$array) { parse_str($string, $array); if (version_compare(PHP_VERSION, '7.4.0', '<')) { if (get_magic_quotes_gpc()) { $array = self::stripslashes_deep($array); } } } /** * Navigates through an array, object, or scalar, and removes slashes from the values. * * @param mixed $value The value to be stripped. * * @return mixed Stripped value. * @since 2.0.0 * */ private static function stripslashes_deep($value) { return self::map_deep($value, array( self::class, 'stripslashes_from_strings_only' )); } /** * Callback function for `stripslashes_deep()` which strips slashes from strings. * * @param mixed $value The array or string to be stripped. * * @return mixed $value The stripped value. * @since 4.4.0 * */ public static function stripslashes_from_strings_only($value) { return is_string($value) ? stripslashes($value) : $value; } /** * Maps a function to all non-iterable elements of an array or an object. * * This is similar to `array_walk_recursive()` but acts upon objects too. * * @param mixed $value The array, object, or scalar. * @param callable $callback The function to map onto $value. * * @return mixed The value with the callback applied to all non-arrays and non-objects inside it. * @since 4.4.0 * */ private static function map_deep($value, $callback) { if (is_array($value)) { foreach ($value as $index => $item) { $value[$index] = self::map_deep($item, $callback); } } elseif (is_object($value)) { $object_vars = get_object_vars($value); foreach ($object_vars as $property_name => $property_value) { $value->$property_name = self::map_deep($property_value, $callback); } } else { $value = call_user_func($callback, $value); } return $value; } }