{ => 450 }`, so the id that has to be * remapped is the array key and nothing in the value is an id at all. */ class Hint_Path { /** * Path segment naming the keys of a map instead of its values. */ const KEYS = '{}'; /** * Path segment naming only the integer-keyed values of an array. * * For a list that shares its array with named flags, such as a gallery * stored as `{ enable: '1', 0: 12, 1: 15 }`: `*` would read the flag's '1' * as attachment id 1. */ const INDEXED = '#'; /** * Split a hint key into its meta key and the path inside the value. * * @param string $hint_key For example 'wp_travel_engine_setting.map.image_url'. * @return array{key:string,segments:string[]} */ public static function split( string $hint_key ): array { $parts = explode( '.', $hint_key ); $key = (string) array_shift( $parts ); return array( 'key' => $key, 'segments' => $parts, ); } /** * Rewrite the keys of a map, preserving order and each key's value. * * A key is a scalar, so the callback receives it alone. Two source keys can * map onto one local id — the same term already existing under two slugs — * and the last one wins, which is what merging two rows onto one term means. * * @param mixed $value The map whose keys are ids. * @param string[] $segments Remaining path, applied to each value. * @param callable $apply Callback receiving each key. * @return mixed The map with its keys rewritten. */ private static function rewrite_keys( $value, array $segments, callable $apply ) { if ( ! is_array( $value ) ) { return $value; } $rewritten = array(); foreach ( $value as $key => $item ) { $new_key = $apply( $key ); if ( is_array( $new_key ) || is_object( $new_key ) || null === $new_key ) { $new_key = $key; } $rewritten[ is_int( $key ) ? (int) $new_key : (string) $new_key ] = array() === $segments ? $item : self::rewrite_at( $item, $segments, $apply ); } return $rewritten; } /** * Apply a callback to the value at a path, leaving everything else alone. * * @param mixed $value The whole meta value. * @param string[] $segments Path segments; empty means the whole value. * @param callable $apply Receives the leaf value, returns its replacement. * @return mixed The value with the leaf replaced. */ public static function rewrite_at( $value, array $segments, callable $apply ) { if ( array() === $segments ) { return $apply( $value ); } if ( ! is_array( $value ) ) { return $value; } $segment = array_shift( $segments ); if ( self::KEYS === $segment ) { return self::rewrite_keys( $value, $segments, $apply ); } if ( '*' === $segment || self::INDEXED === $segment ) { foreach ( $value as $key => $item ) { if ( self::INDEXED === $segment && ! is_int( $key ) ) { continue; } $value[ $key ] = self::rewrite_at( $item, $segments, $apply ); } return $value; } if ( ! array_key_exists( $segment, $value ) ) { return $value; } $value[ $segment ] = self::rewrite_at( $value[ $segment ], $segments, $apply ); return $value; } /** * Every integer id found at a path. * * Used by the exporter to pull in the media a post references. * * @param mixed $value The whole meta value. * @param string[] $segments Path segments; empty means the whole value. * @return int[] */ public static function collect_at( $value, array $segments ): array { $found = array(); self::rewrite_at( $value, $segments, static function ( $leaf ) use ( &$found ) { $found = array_merge( $found, self::ids_in( $leaf ) ); return $leaf; } ); return array_values( array_unique( $found ) ); } /** * Every integer id inside a value, whatever shape it is in. * * @param mixed $value Any value. * @return int[] */ public static function ids_in( $value ): array { if ( is_string( $value ) && is_serialized( $value ) ) { $value = unserialize( $value, array( 'allowed_classes' => false ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- allowed_classes is false; serialized values are rewritten in place. } if ( is_array( $value ) ) { $ids = array(); foreach ( $value as $item ) { $ids = array_merge( $ids, self::ids_in( $item ) ); } return $ids; } if ( is_int( $value ) && $value > 0 ) { return array( $value ); } if ( ! is_string( $value ) || '' === $value ) { return array(); } if ( ctype_digit( $value ) ) { return array( (int) $value ); } if ( 1 === preg_match( '/^\d+(,\d+)+$/', $value ) ) { return array_map( 'intval', explode( ',', $value ) ); } return array(); } }