from = $from; $this->to = $to; $this->recurse_objects = $recurse_objects; $this->regex = $regex; $this->regex_flags = $regex_flags; $this->regex_delimiter = $regex_delimiter; $this->regex_limit = $regex_limit; $this->clear_log_data(); // Get the XDebug nesting level. Will be zero (no limit) if no value is set $this->max_recursion = intval( ini_get( 'xdebug.max_nesting_level' ) ); } /** * Take a serialised array and unserialise it replacing elements as needed and * unserialising any subordinate arrays and performing the replace on those too. * Ignores any serialized objects unless $recurse_objects is set to true. * * @param array|string $data The data to operate on. * @param bool $serialised Does the value of $data need to be unserialized? * * @return array The original array with all elements replaced as needed. */ public function run($data, $serialised = false) { return $this->run_recursively($data, $serialised); } /** * @param int $recursion_level Current recursion depth within the original data. * @param array $visited_data Data that has been seen in previous recursion iterations. */ private function run_recursively($data, $serialised, $recursion_level = 0, $visited_data = array()) { // some unseriliased data cannot be re-serialised eg. SimpleXMLElements try { if ($this->recurse_objects) { // If we've reached the maximum recursion level, short circuit if ( 0 !== $this->max_recursion && $recursion_level >= $this->max_recursion ) { return $data; } if ( is_array($data) || is_object($data) ) { // If we've seen this exact object or array before, short circuit if (in_array($data, $visited_data, true)) { return $data; // Avoid infinite loops when there's a cycle } // Add this data to the list of $visited_data[] = $data; } } // The error suppression operator is not enough in some cases, so we disable // reporting of notices and warnings as well. $error_reporting = error_reporting(); error_reporting($error_reporting & ~E_NOTICE & ~E_WARNING); $had_error_before = error_get_last(); $unserialized = is_string($data) ? @unserialize($data) : false; $error_get_last = error_get_last(); error_reporting($error_reporting); if (! $had_error_before && $error_get_last) { error_clear_last(); } if (false !== $unserialized) { $data = $this->run_recursively( $unserialized, true, $recursion_level + 1 ); } elseif (is_array($data)) { $keys = array_keys($data); foreach ($keys as $key) { $data[$key] = $this->run_recursively( $data[$key], false, $recursion_level + 1, $visited_data ); } } elseif ( $this->recurse_objects && ( is_object($data) || $data instanceof \__PHP_Incomplete_Class ) ) { if ($data instanceof \__PHP_Incomplete_Class) { $array = new ArrayObject($data); } else { foreach ($data as $key => $value) { $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } } } elseif (is_string($data)) { $data = str_replace($this->from, $this->to, $data); } if ($serialised) { return serialize($data); } } catch (Exception $error) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Deliberally empty. } return $data; } /** * Gets existing data saved for this run when logging. * @return array Array of data strings, prior to replacements. */ public function get_log_data() { return $this->log_data; } /** * Clears data stored for logging. */ public function clear_log_data() { $this->log_data = array(); } /** * Get the PCRE error constant name from an error value. * * @param integer $error Error code. * @return string Error constant name. */ private function preg_error_message( $error ) { static $error_names = null; if ( null === $error_names ) { $definitions = get_defined_constants( true ); $pcre_constants = array_key_exists( 'pcre', $definitions ) ? $definitions['pcre'] : array(); $error_names = array_flip( $pcre_constants ); } return isset( $error_names[ $error ] ) ? $error_names[ $error ] : ''; } }