Files
WP-Content-Sync/src/Url/MetadataUrlTransformer.php
T
2026-04-26 14:57:48 -05:00

143 lines
3.5 KiB
PHP

<?php
namespace WPContentSync\Url;
final class MetadataUrlTransformer {
private UrlTransformer $url_transformer;
public function __construct( UrlTransformer $url_transformer ) {
$this->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<mixed> $value Metadata array.
* @param UrlMappingCollection $mappings URL mappings.
* @return array<mixed>
*/
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;
}
}