Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Mvc/Entity/ArrayValidator.php
<?php

namespace XF\Mvc\Entity;

use function
array_key_exists, count, is_array, is_object, is_string;

class
ArrayValidator implements \ArrayAccess
{
   
/**
     * @var array
     */
   
protected $columns;

   
/**
     * @var ValueFormatter
     */
   
protected $valueFormatter;

   
/**
     * @var array
     */
   
protected $existingValues;

   
/**
     * @var array
     */
   
protected $newValues = [];

   
/**
     * True if this is considered an update. In this case, requirements will only be checked for columns
     * that have changed.
     *
     * @var bool
     */
   
protected $isUpdating;

   
/**
     * @var array
     */
   
protected $errors = [];

   
/**
     * @var bool
     */
   
protected $requirementsChecked = false;

    public function
__construct(
        array
$columns,
       
ValueFormatter $valueFormatter,
        array
$existingValues = [],
       
bool $isUpdating = false
   
)
    {
       
$this->columns = $columns;
       
$this->valueFormatter = $valueFormatter;
       
$this->existingValues = $existingValues;
       
$this->isUpdating = $isUpdating;
    }

    public function
__isset($key): bool
   
{
        return isset(
$this->columns[$key]);
    }

   
#[\ReturnTypeWillChange]
   
public function offsetExists($offset): bool
   
{
        return
$this->__isset($offset);
    }

    public function
get($key)
    {
        if (!isset(
$this->columns[$key]))
        {
            throw new \
InvalidArgumentException("Unknown column $key");
        }

        if (
array_key_exists($key, $this->newValues))
        {
            return
$this->newValues[$key];
        }
        else if (
array_key_exists($key, $this->existingValues))
        {
            return
$this->existingValues[$key];
        }
        else
        {
            return
null;
        }
    }

    public function
__get($key)
    {
        return
$this->get($key);
    }

   
#[\ReturnTypeWillChange]
   
public function offsetGet($offset)
    {
        return
$this->get($offset);
    }

    public function
set($key, $value, array $options = []): bool
   
{
        if (!isset(
$this->columns[$key]))
        {
            throw new \
InvalidArgumentException("Unknown column $key");
        }

       
$column = $this->columns[$key];
       
$type = $column['type'];

        if (!
$this->verifyValueCustom($value, $key, $type, $column))
        {
            return
false;
        }

        try
        {
           
$value = $this->valueFormatter->castValueToType($value, $type, $column);
        }
        catch (\
Exception $e)
        {
            throw new \
InvalidArgumentException($e->getMessage() . " [$key]", $e->getCode(), $e);
        }

        if (!
$this->valueFormatter->applyValueConstraints(
           
$value, $type, $column, $constraintError, !empty($options['forceConstraint'])
        ))
        {
            if (
$constraintError)
            {
               
$this->errors[$key] = $constraintError;
            }

            return
false;
        }

        if (
array_key_exists($key, $this->newValues))
        {
           
$isDifferent = ($value !== $this->newValues[$key]);
        }
        else if (
array_key_exists($key, $this->existingValues))
        {
           
$isDifferent = ($value !== $this->existingValues[$key]);
        }
        else
        {
           
$isDifferent = true;
        }

        if (
$isDifferent)
        {
           
$this->newValues[$key] = $value;
           
$this->requirementsChecked = false;
        }

        return
true;
    }

    protected function
verifyValueCustom(&$value, $key, $type, array $columnOptions)
    {
       
$success = true;

        if (!empty(
$columnOptions['verify']))
        {
           
$verifier = $columnOptions['verify'];
            if (!(
$verifier instanceof \Closure))
            {
                throw new \
LogicException("Verifier for $key must be closure");
            }

           
$success = $verifier($value, $key, $type, $columnOptions, $this);
            if (
$success !== true && $success !== false)
            {
                throw new \
LogicException("Verification method of $key did not return a valid indicator (true/false)");
            }
        }

        return
$success;
    }

    public function
bulkSet(array $values, array $options = []): array
    {
       
$results = [];
        foreach (
$values AS $key => $value)
        {
           
$results[$key] = $this->set($key, $value, $options);
        }

        return
$results;
    }

    public function
__set($key, $value)
    {
       
$this->set($key, $value);
    }

   
#[\ReturnTypeWillChange]
   
public function offsetSet($offset, $value)
    {
       
$this->set($offset, $value);
    }

   
#[\ReturnTypeWillChange]
   
public function offsetUnset($offset)
    {
        throw new \
LogicException('Entity offsets may not be unset');
    }

    public function
getValues(): array
    {
        if (
$this->hasErrors())
        {
            throw new \
LogicException("Can't get values while having errors; call getValuesForced if you want to ignore errors");
        }

        return
array_replace($this->existingValues, $this->newValues);
    }

    public function
getValuesForced(): array
    {
        return
array_replace($this->existingValues, $this->newValues);
    }

    public function
getExistingValues(): array
    {
        return
$this->existingValues;
    }

    public function
getNewValues(): array
    {
        return
$this->newValues;
    }

    public function
hasChanges(): bool
   
{
        return
count($this->newValues) > 0;
    }

    public function
error($message, $key = null)
    {
        if (
$key !== null)
        {
           
$this->errors[$key] = $message;
        }
        else
        {
           
$this->errors[] = $message;
        }
    }

    public function
hasErrors($withRequirementsCheck = true): bool
   
{
        if (!
$this->requirementsChecked && $withRequirementsCheck)
        {
           
$this->checkRequirements();
           
$this->requirementsChecked = true;
        }

        return
count($this->errors) > 0;
    }

    public function
getErrors(): array
    {
       
$this->hasErrors(); // to run the requirements check

       
return $this->errors;
    }

    protected function
checkRequirements()
    {
        foreach (
$this->columns AS $key => $column)
        {
            if (empty(
$column['required']))
            {
                continue;
            }

            if (isset(
$this->errors[$key]))
            {
               
// already have a more specific error
               
continue;
            }

            if (
$this->isUpdating && !array_key_exists($key, $this->newValues))
            {
               
// for updates, ignore required fields that haven't been changed
               
continue;
            }

           
$value = $this->get($key);
           
$exists = array_key_exists($key, $this->newValues) || array_key_exists($key, $this->existingValues);

            if (!
$exists || $value === '' || $value === [] || $value === null)
            {
                if (
is_string($column['required']))
                {
                   
$this->error(\XF::phrase($column['required']), $key);
                }
                else
                {
                   
$this->error(\XF::phrase('please_enter_value_for_required_field_x', ['field' => $key]), $key);
                }
            }
        }
    }

    public function
appendErrors(&$target)
    {
       
$errors = $this->getErrors();

        if (!
is_array($target))
        {
            if (
is_string($target) || is_object($target))
            {
               
$target = [$target];
            }
            else
            {
               
$target = [];
            }
        }

        if (
$errors)
        {
           
$target += $errors;
        }
    }
}