| 
<?phpdeclare(strict_types=1);
 namespace ParagonIE\Ionizer;
 
 use ParagonIE\Ionizer\Contract\{
 FilterInterface,
 FilterContainerInterface
 };
 
 /**
 * Class InputFilterContainer
 *
 * Contains a set of filter rules, useful for enforcing a strict type on
 * unstructured data (e.g. HTTP POST parameters).
 *
 * @package ParagonIE\Ionizer
 */
 abstract class InputFilterContainer implements FilterContainerInterface
 {
 /** @const string SEPARATOR */
 const SEPARATOR = '.';
 
 /**
 * @var array<string, array<mixed, FilterInterface>>
 */
 protected $filterMap = [];
 
 /**
 * InputFilterContainer constructor.
 */
 abstract public function __construct();
 
 /**
 * Add a new filter to this input value
 *
 * @param string $path
 * @param FilterInterface $filter
 * @return FilterContainerInterface
 */
 public function addFilter(
 string $path,
 FilterInterface $filter
 ): FilterContainerInterface {
 if (!isset($this->filterMap[$path])) {
 $this->filterMap[$path] = [];
 }
 /** @psalm-suppress MixedArrayAssignment */
 $this->filterMap[$path][] = $filter->setIndex($path);
 return $this;
 }
 
 /**
 * @param string $path
 * @return array<array-key, FilterInterface>
 */
 public function getFiltersForPath(string $path)
 {
 if (!isset($this->filterMap[$path])) {
 $this->filterMap[$path] = [];
 }
 return $this->filterMap[$path];
 }
 
 /**
 * Use firstlevel.second_level.thirdLevel to find indices in an array
 *
 * @param string $key
 * @param mixed $multiDimensional
 * @return mixed
 * @throws \Error
 * @throws InvalidDataException
 *
 * @psalm-suppress UnusedVariable
 */
 public function filterValue(string $key, $multiDimensional)
 {
 /** @var array<int, string> $pieces */
 $pieces = Util::chunk($key, (string) static::SEPARATOR);
 /** @var array|string $filtered */
 $filtered =& $multiDimensional;
 
 /**
 * @security This shouldn't be escapable. We know eval is evil, but
 *           there's not a more elegant way to process this in PHP.
 */
 if (\is_array($multiDimensional)) {
 $var = '$multiDimensional';
 foreach ($pieces as $piece) {
 $append = '[' . self::sanitize($piece) . ']';
 
 // Alphabetize the parent array
 eval(
 'if (!isset(' . $var . $append . ')) {' . "\n" .
 '    ' . $var . $append . ' = null;' . "\n" .
 '}' . "\n" .
 '\ksort(' . $var . ');' . "\n"
 );
 $var .= $append;
 }
 eval('$filtered =& ' . $var. ';');
 }
 
 // If we have filters, let's apply them:
 if (isset($this->filterMap[$key])) {
 /** @var object|null $filter */
 foreach ($this->filterMap[$key] as $filter) {
 if ($filter instanceof FilterInterface) {
 /** @var string|int|bool|float|array $filtered */
 $filtered = $filter->process($filtered);
 }
 }
 }
 return $multiDimensional;
 }
 
 /**
 * Use firstlevel.second_level.thirdLevel to find indices in an array
 *
 * Doesn't apply filters
 *
 * @param string $key
 * @param array<string, string|array> $multiDimensional
 * @return mixed
 * @psalm-suppress PossiblyInvalidArrayOffset
 */
 public function getUnfilteredValue(string $key, array $multiDimensional = [])
 {
 /** @var array<string, string> $pieces */
 $pieces = Util::chunk($key, (string) static::SEPARATOR);
 
 /** @var string|array<string, string|array> $value */
 $value = $multiDimensional;
 
 /**
 * @var array<string, string> $pieces
 * @var string $piece
 */
 foreach ($pieces as $piece) {
 if (!isset($value[$piece])) {
 return null;
 }
 /** @var string|array<string, string|array> $next */
 $next = $value[$piece];
 
 /** @var string|array<string, string|array> $value */
 $value = $next;
 }
 return $value;
 }
 
 /**
 * Only allow allow printable ASCII characters:
 *
 * @param string $input
 * @return string
 * @throws \Error
 */
 protected static function sanitize(string $input): string
 {
 /** @var string|bool $sanitized */
 $sanitized = \json_encode(
 \preg_replace('#[^\x20-\x7e]#', '', $input)
 );
 if (!\is_string($sanitized)) {
 throw new \Error('Could not sanitize string');
 }
 return $sanitized;
 }
 
 /**
 * Process the input array.
 *
 * @param array $dataInput
 * @return array
 * @throws \Error
 * @throws InvalidDataException
 */
 public function __invoke(array $dataInput = []): array
 {
 /** @var string $key */
 foreach (\array_keys($this->filterMap) as $key) {
 /** @var array $dataInput */
 $dataInput = $this->filterValue($key, $dataInput);
 }
 return $dataInput;
 }
 }
 
 |