<?php
/**
 * Safe unserialize helper
 *
 * Deserializes third-party SEO plugin blobs without instantiating arbitrary classes.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Hardened alternative to maybe_unserialize() for untrusted serialized data.
 */
class SafeUnserialize {

	/**
	 * Unserialize a value without allowing class instantiation.
	 *
	 * Mirrors maybe_unserialize() for arrays, non-strings, and non-serialized
	 * strings. Serialized payloads use allowed_classes => false. Incomplete
	 * object payloads are converted to arrays so field extractors can still read
	 * public properties without running constructors or __wakeup.
	 *
	 * @param mixed $data Raw value (string|array|object|false|…).
	 * @return mixed
	 */
	public static function maybe( $data ) {
		if ( is_array( $data ) ) {
			return $data;
		}

		if ( ! is_string( $data ) ) {
			return $data;
		}

		if ( ! is_serialized( $data ) ) {
			return $data;
		}

		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize, WordPress.PHP.NoSilencedErrors.Discouraged -- Hardened gate: is_serialized() first, then allowed_classes => false to block object injection from third-party DB blobs.
		$result = @unserialize( trim( $data ), array( 'allowed_classes' => false ) );

		if ( $result instanceof \__PHP_Incomplete_Class ) {
			return self::incomplete_object_to_array( $result );
		}

		return $result;
	}

	/**
	 * Convert an incomplete-class payload into a plain associative array.
	 *
	 * @param \__PHP_Incomplete_Class $object Incomplete object from unserialize.
	 * @return array
	 */
	private static function incomplete_object_to_array( $object ) {
		$raw = (array) $object;
		$out = array();

		foreach ( $raw as $key => $value ) {
			if ( is_string( $key ) && false !== strpos( $key, "\0" ) ) {
				$parts = explode( "\0", $key );
				$key   = end( $parts );
			}

			if ( '__PHP_Incomplete_Class_Name' === $key ) {
				continue;
			}

			$out[ $key ] = $value;
		}

		return $out;
	}
}
