url_transformer = $url_transformer; } /** * Transform URLs within a metadata value. * * @param mixed $value Metadata value. * @param UrlMappingCollection $mappings URL mappings. * @return mixed */ public function transformValue( $value, UrlMappingCollection $mappings ) { if ( is_array( $value ) ) { return $this->transformArray( $value, $mappings ); } if ( ! is_string( $value ) ) { return $value; } if ( $this->looksSerialized( trim( $value ) ) && trim( $value ) !== $value ) { return $value; } if ( $this->looksSerialized( $value ) ) { $unserialized = $this->unserializeValue( $value ); if ( ! $unserialized['valid'] || $this->containsObject( $unserialized['value'] ) ) { return $value; } // phpcs:ignore -- Required to reserialize metadata with updated string lengths. return serialize( $this->transformValue( $unserialized['value'], $mappings ) ); } if ( $this->isJsonObjectOrArray( $value ) ) { $decoded = json_decode( $value, true ); if ( is_array( $decoded ) && JSON_ERROR_NONE === json_last_error() ) { $encoded = wp_json_encode( $this->transformArray( $decoded, $mappings ) ); if ( is_string( $encoded ) ) { return $encoded; } } return $value; } return $this->url_transformer->transformString( $value, $mappings ); } /** * Transform URLs within a metadata array. * * @param array $value Metadata array. * @param UrlMappingCollection $mappings URL mappings. * @return array */ private function transformArray( array $value, UrlMappingCollection $mappings ): array { $transformed = array(); foreach ( $value as $key => $item ) { $transformed[ $key ] = $this->transformValue( $item, $mappings ); } return $transformed; } private function looksSerialized( string $value ): bool { return 'N;' === $value || 'b:0;' === $value || 1 === preg_match( '/^(?:a|O|s|i|d|b):/', $value ); } /** * @return array{valid: bool, value: mixed} */ private function unserializeValue( string $value ): array { if ( 'N;' === $value ) { return array( 'valid' => true, 'value' => null, ); } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Metadata may be stored in PHP serialized format. $unserialized = @unserialize( $value, array( 'allowed_classes' => false ) ); if ( false === $unserialized && 'b:0;' !== $value ) { return array( 'valid' => false, 'value' => null, ); } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Strict validation avoids transforming malformed serialized-looking strings. if ( serialize( $unserialized ) !== $value ) { return array( 'valid' => false, 'value' => null, ); } return array( 'valid' => true, 'value' => $unserialized, ); } private function isJsonObjectOrArray( string $value ): bool { $trimmed = trim( $value ); return '' !== $trimmed && in_array( $trimmed[0], array( '{', '[' ), true ); } /** * @param mixed $value Value to inspect. */ private function containsObject( $value ): bool { if ( is_object( $value ) ) { return true; } if ( ! is_array( $value ) ) { return false; } foreach ( $value as $item ) { if ( $this->containsObject( $item ) ) { return true; } } return false; } }