<?php
/**
 * Akeeba Backup Restoration Script
 *
 * @package   brs
 * @copyright Copyright (c)2025 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\BRS\Platform\Engine\RowFilter;

use Akeeba\BRS\Framework\Container\ContainerAwareInterface;
use Akeeba\BRS\Framework\Container\ContainerAwareTrait;
use Akeeba\BRS\Framework\Database\AbstractDriver;
use Akeeba\BRS\Framework\Database\DatabaseAwareInterface;
use Akeeba\BRS\Framework\Database\DatabaseAwareTrait;
use Psr\Container\ContainerInterface;

defined('_AKEEBA') or die();

class WordPressOptions implements ContainerAwareInterface, DatabaseAwareInterface
{
	use ContainerAwareTrait;
	use DatabaseAwareTrait;

	private $whiteListOptionNames = [
		// WordPress 6.3: Core blocks CSS files
		'_transient_wp_core_block_css_files',
	];

	public function __construct(ContainerInterface $container, AbstractDriver $db)
	{
		$this->setContainer($container);
		$this->setDriver($db);
	}

	/**
	 * Tell the engine if a table row should have its data replaced.
	 *
	 * @param   string  $tableName  The name of the table, e.g. wp_foobar
	 * @param   array   $row        The row contents as an associative array
	 *
	 * @return  bool  True to allow replacements.
	 * @since   10.0
	 */
	public function __invoke(string $tableName, array $row): bool
	{
		// This filter only applies to the options table
		if (!$this->isAnOptionsTable($tableName))
		{
			return true;
		}

		$name = $row['option_name'];

		// Explicitly allow replacements on some transient options
		if (in_array($name, $this->whiteListOptionNames))
		{
			return true;
		}

		// Explicitly allow replacements on block patterns' transients
		if (strpos($name, '_site_transient_wp_remote_block_patterns_') !== false)
		{
			return true;
		}

		// Do not replace data in site transients
		if (strpos($name, '_site_transient_') === 0)
		{
			return false;
		}

		// Do not replace data in transients
		if (strpos($name, '_transient') === 0)
		{
			return false;
		}

		return true;
	}

	private function isAnOptionsTable(string $tableName): bool
	{
		$db     = $this->getDbo();
		$prefix = $db->getPrefix();

		// Single-site and multisite default (e.g. wp_options)
		if ($tableName === $prefix . 'options')
		{
			return true;
		}

		/**
		 * I will be looking for <prefix>_<integer>_options, e.g. wp_2_options.
		 *
		 * So, first check, does the table name start with the prefix?
		 */
		if (strpos($tableName, $prefix) === 0)
		{
			return false;
		}

		// Remove the prefix.
		$tableName = substr($tableName, strlen($prefix));

		// Does the table name end with _options?
		if (!$this->str_ends_with($tableName, '_options'))
		{
			return false;
		}

		$tableName = substr($tableName, 0, -8);

		// Is what left an integer?
		return @is_numeric($tableName) && (@intval($tableName) == $tableName);
	}

	private function str_ends_with($haystack, $needle)
	{
		if (function_exists('str_ends_with'))
		{
			return str_ends_with($haystack, $needle);
		}

		return strlen($needle) === 0 || substr($haystack, -strlen($needle)) === $needle;
	}
}