Seditio Source
Root |
./othercms/xenForo 2.2.8/src/vendor/laminas/laminas-validator/src/DateStep.php
<?php

/**
 * @see       https://github.com/laminas/laminas-validator for the canonical source repository
 * @copyright https://github.com/laminas/laminas-validator/blob/master/COPYRIGHT.md
 * @license   https://github.com/laminas/laminas-validator/blob/master/LICENSE.md New BSD License
 */

namespace Laminas\Validator;

use
DateInterval;
use
DateTime;
use
DateTimeZone;
use
Laminas\Stdlib\ArrayUtils;
use
Traversable;

class
DateStep extends Date
{
    const
NOT_STEP       = 'dateStepNotStep';

    const
FORMAT_DEFAULT = DateTime::ISO8601;

   
/**
     * @var array
     */
   
protected $messageTemplates = [
       
self::INVALID      => "Invalid type given. String, integer, array or DateTime expected",
       
self::INVALID_DATE => "The input does not appear to be a valid date",
       
self::FALSEFORMAT  => "The input does not fit the date format '%format%'",
       
self::NOT_STEP     => "The input is not a valid step",
    ];

   
/**
     * Optional base date value
     *
     * @var string|int|\DateTime
     */
   
protected $baseValue = '1970-01-01T00:00:00Z';

   
/**
     * Date step interval (defaults to 1 day).
     * Uses the DateInterval specification.
     *
     * @var DateInterval
     */
   
protected $step;

   
/**
     * Optional timezone to be used when the baseValue
     * and validation values do not contain timezone info
     *
     * @var DateTimeZone
     */
   
protected $timezone;

   
/**
     * Set default options for this instance
     *
     * @param array $options
     */
   
public function __construct($options = [])
    {
        if (
$options instanceof Traversable) {
           
$options = ArrayUtils::iteratorToArray($options);
        } elseif (!
is_array($options)) {
           
$options = func_get_args();
           
$temp['baseValue'] = array_shift($options);
            if (! empty(
$options)) {
               
$temp['step'] = array_shift($options);
            }
            if (! empty(
$options)) {
               
$temp['format'] = array_shift($options);
            }
            if (! empty(
$options)) {
               
$temp['timezone'] = array_shift($options);
            }

           
$options = $temp;
        }

        if (! isset(
$options['step'])) {
           
$options['step'] = new DateInterval('P1D');
        }
        if (! isset(
$options['timezone'])) {
           
$options['timezone'] = new DateTimeZone(date_default_timezone_get());
        }

       
parent::__construct($options);
    }

   
/**
     * Sets the base value from which the step should be computed
     *
     * @param  string|int|\DateTime $baseValue
     * @return DateStep
     */
   
public function setBaseValue($baseValue)
    {
       
$this->baseValue = $baseValue;
        return
$this;
    }

   
/**
     * Returns the base value from which the step should be computed
     *
     * @return string|int|\DateTime
     */
   
public function getBaseValue()
    {
        return
$this->baseValue;
    }

   
/**
     * Sets the step date interval
     *
     * @param  DateInterval $step
     * @return DateStep
     */
   
public function setStep(DateInterval $step)
    {
       
$this->step = $step;
        return
$this;
    }

   
/**
     * Returns the step date interval
     *
     * @return DateInterval
     */
   
public function getStep()
    {
        return
$this->step;
    }

   
/**
     * Returns the timezone option
     *
     * @return DateTimeZone
     */
   
public function getTimezone()
    {
        return
$this->timezone;
    }

   
/**
     * Sets the timezone option
     *
     * @param  DateTimeZone $timezone
     * @return DateStep
     */
   
public function setTimezone(DateTimeZone $timezone)
    {
       
$this->timezone = $timezone;
        return
$this;
    }

   
/**
     * Supports formats with ISO week (W) definitions
     *
     * @see Date::convertString()
     */
   
protected function convertString($value, $addErrors = true)
    {
       
// Custom week format support
       
if (strpos($this->format, 'Y-\WW') === 0
           
&& preg_match('/^([0-9]{4})\-W([0-9]{2})/', $value, $matches)
        ) {
           
$date = new DateTime();
           
$date->setISODate($matches[1], $matches[2]);
        } else {
           
$date = DateTime::createFromFormat($this->format, $value, new DateTimeZone('UTC'));
        }

       
// Invalid dates can show up as warnings (ie. "2007-02-99")
        // and still return a DateTime object.
       
$errors = DateTime::getLastErrors();
        if (
$errors['warning_count'] > 0) {
            if (
$addErrors) {
               
$this->error(self::FALSEFORMAT);
            }
            return
false;
        }

        return
$date;
    }

   
/**
     * Returns true if a date is within a valid step
     *
     * @param  string|int|\DateTime $value
     * @return bool
     * @throws Exception\InvalidArgumentException
     */
   
public function isValid($value)
    {
        if (!
parent::isValid($value)) {
            return
false;
        }

       
$valueDate = $this->convertToDateTime($value, false); // avoid duplicate errors
       
$baseDate  = $this->convertToDateTime($this->baseValue, false);
       
$step      = $this->getStep();

       
// Same date?
       
if ($valueDate == $baseDate) {
            return
true;
        }

       
// Optimization for simple intervals.
        // Handle intervals of just one date or time unit.
       
$intervalParts = explode('|', $step->format('%y|%m|%d|%h|%i|%s'));
       
$partCounts    = array_count_values($intervalParts);

       
$unitKeys = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
       
$intervalParts = array_combine($unitKeys, $intervalParts);

       
// Get absolute time difference to avoid special cases of missing/added time
       
$absoluteValueDate = new DateTime($valueDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC'));
       
$absoluteBaseDate = new DateTime($baseDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC'));

       
$timeDiff  = $absoluteValueDate->diff($absoluteBaseDate, 1);
       
$diffParts = array_combine($unitKeys, explode('|', $timeDiff->format('%y|%m|%d|%h|%i|%s')));

        if (
5 === $partCounts["0"]) {
           
// Find the unit with the non-zero interval
           
$intervalUnit = null;
           
$stepValue    = null;
            foreach (
$intervalParts as $key => $value) {
                if (
0 != $value) {
                   
$intervalUnit = $key;
                   
$stepValue    = (int) $value;
                    break;
                }
            }

           
// Check date units
           
if (in_array($intervalUnit, ['years', 'months', 'days'])) {
                switch (
$intervalUnit) {
                    case
'years':
                        if (
0 == $diffParts['months'] && 0 == $diffParts['days']
                            &&
0 == $diffParts['hours'] && 0 == $diffParts['minutes']
                            &&
0 == $diffParts['seconds']
                        ) {
                            if ((
$diffParts['years'] % $stepValue) === 0) {
                                return
true;
                            }
                        }
                        break;
                    case
'months':
                        if (
0 == $diffParts['days'] && 0 == $diffParts['hours']
                            &&
0 == $diffParts['minutes'] && 0 == $diffParts['seconds']
                        ) {
                           
$months = ($diffParts['years'] * 12) + $diffParts['months'];
                            if ((
$months % $stepValue) === 0) {
                                return
true;
                            }
                        }
                        break;
                    case
'days':
                        if (
0 == $diffParts['hours'] && 0 == $diffParts['minutes']
                            &&
0 == $diffParts['seconds']
                        ) {
                           
$days = $timeDiff->format('%a'); // Total days
                           
if (($days % $stepValue) === 0) {
                                return
true;
                            }
                        }
                        break;
                }
               
$this->error(self::NOT_STEP);
                return
false;
            }

           
// Check time units
           
if (in_array($intervalUnit, ['hours', 'minutes', 'seconds'])) {
               
// Simple test if $stepValue is 1.
               
if (1 == $stepValue) {
                    if (
'hours' === $intervalUnit
                       
&& 0 == $diffParts['minutes'] && 0 == $diffParts['seconds']
                    ) {
                        return
true;
                    } elseif (
'minutes' === $intervalUnit && 0 == $diffParts['seconds']) {
                        return
true;
                    } elseif (
'seconds' === $intervalUnit) {
                        return
true;
                    }

                   
$this->error(self::NOT_STEP);

                    return
false;
                }

               
// Simple test for same day, when using default baseDate
               
if ($baseDate->format('Y-m-d') == $valueDate->format('Y-m-d')
                    &&
$baseDate->format('Y-m-d') == '1970-01-01'
               
) {
                    switch (
$intervalUnit) {
                        case
'hours':
                            if (
0 == $diffParts['minutes'] && 0 == $diffParts['seconds']) {
                                if ((
$diffParts['hours'] % $stepValue) === 0) {
                                    return
true;
                                }
                            }
                            break;
                        case
'minutes':
                            if (
0 == $diffParts['seconds']) {
                               
$minutes = ($diffParts['hours'] * 60) + $diffParts['minutes'];
                                if ((
$minutes % $stepValue) === 0) {
                                    return
true;
                                }
                            }
                            break;
                        case
'seconds':
                           
$seconds = ($diffParts['hours'] * 60 * 60)
                                       + (
$diffParts['minutes'] * 60)
                                       +
$diffParts['seconds'];
                            if ((
$seconds % $stepValue) === 0) {
                                return
true;
                            }
                            break;
                    }
                   
$this->error(self::NOT_STEP);
                    return
false;
                }
            }
        }

        return
$this->fallbackIncrementalIterationLogic($baseDate, $valueDate, $intervalParts, $diffParts, $step);
    }

   
/**
     * Fall back to slower (but accurate) method for complex intervals.
     * Keep adding steps to the base date until a match is found
     * or until the value is exceeded.
     *
     * This is really slow if the interval is small, especially if the
     * default base date of 1/1/1970 is used. We can skip a chunk of
     * iterations by starting at the lower bound of steps needed to reach
     * the target
     *
     * @param DateTime     $baseDate
     * @param DateTime     $valueDate
     * @param int[]        $intervalParts
     * @param int[]        $diffParts
     * @param DateInterval $step
     *
     * @return bool
     */
   
private function fallbackIncrementalIterationLogic(
       
DateTime $baseDate,
       
DateTime $valueDate,
        array
$intervalParts,
        array
$diffParts,
       
DateInterval $step
   
) {
        list(
$minSteps, $requiredIterations) = $this->computeMinStepAndRequiredIterations($intervalParts, $diffParts);
       
$minimumInterval                     = $this->computeMinimumInterval($intervalParts, $minSteps);
       
$isIncrementalStepping               = $baseDate < $valueDate;
       
$dateModificationOperation           = $isIncrementalStepping ? 'add' : 'sub';

        for (
$offsetIterations = 0; $offsetIterations < $requiredIterations; $offsetIterations += 1) {
           
$baseDate->{$dateModificationOperation}($minimumInterval);
        }

        while ((
$isIncrementalStepping && $baseDate < $valueDate)
            || (!
$isIncrementalStepping && $baseDate > $valueDate)
        ) {
           
$baseDate->{$dateModificationOperation}($step);

            if (
$baseDate == $valueDate) {
                return
true;
            }
        }

       
$this->error(self::NOT_STEP);

        return
false;
    }

   
/**
     * Computes minimum interval to use for iterations while checking steps
     *
     * @param int[] $intervalParts
     * @param int   $minSteps
     *
     * @return DateInterval
     */
   
private function computeMinimumInterval(array $intervalParts, $minSteps)
    {
        return new
DateInterval(sprintf(
           
'P%dY%dM%dDT%dH%dM%dS',
           
$intervalParts['years'] * $minSteps,
           
$intervalParts['months'] * $minSteps,
           
$intervalParts['days'] * $minSteps,
           
$intervalParts['hours'] * $minSteps,
           
$intervalParts['minutes'] * $minSteps,
           
$intervalParts['seconds'] * $minSteps
       
));
    }

   
/**
     * @param int[] $intervalParts
     * @param int[] $diffParts
     *
     * @return int[] (ordered tuple containing minimum steps and required step iterations
     */
   
private function computeMinStepAndRequiredIterations(array $intervalParts, array $diffParts)
    {
       
$minSteps = $this->computeMinSteps($intervalParts, $diffParts);

       
// If we use PHP_INT_MAX DateInterval::__construct falls over with a bad format error
        // before we reach the max on 64 bit machines
       
$maxInteger             = min(pow(2, 31), PHP_INT_MAX);
       
// check for integer overflow and split $minimum interval if needed
       
$maximumInterval        = max($intervalParts);
       
$requiredStepIterations = 1;

        if ((
$minSteps * $maximumInterval) > $maxInteger) {
           
$requiredStepIterations = ceil(($minSteps * $maximumInterval) / $maxInteger);
           
$minSteps               = floor($minSteps / $requiredStepIterations);
        }

        return [
$minSteps, $minSteps ? $requiredStepIterations : 0];
    }

   
/**
     * Multiply the step interval by the lower bound of steps to reach the target
     *
     * @param int[] $intervalParts
     * @param int[] $diffParts
     *
     * @return int
     */
   
private function computeMinSteps(array $intervalParts, array $diffParts)
    {
       
$intervalMaxSeconds = $this->computeIntervalMaxSeconds($intervalParts);

        return (
0 == $intervalMaxSeconds)
            ?
0
           
: max(floor($this->computeDiffMinSeconds($diffParts) / $intervalMaxSeconds) - 1, 0);
    }

   
/**
     * Get upper bound of the given interval in seconds
     * Converts a given `$intervalParts` array into seconds
     *
     * @param int[] $intervalParts
     *
     * @return int
     */
   
private function computeIntervalMaxSeconds(array $intervalParts)
    {
        return (
$intervalParts['years'] * 60 * 60 * 24 * 366)
            + (
$intervalParts['months'] * 60 * 60 * 24 * 31)
            + (
$intervalParts['days'] * 60 * 60 * 24)
            + (
$intervalParts['hours'] * 60 * 60)
            + (
$intervalParts['minutes'] * 60)
            +
$intervalParts['seconds'];
    }

   
/**
     * Get lower bound of difference in secondss
     * Converts a given `$diffParts` array into seconds
     *
     * @param int[] $diffParts
     *
     * @return int
     */
   
private function computeDiffMinSeconds(array $diffParts)
    {
        return (
$diffParts['years'] * 60 * 60 * 24 * 365)
            + (
$diffParts['months'] * 60 * 60 * 24 * 28)
            + (
$diffParts['days'] * 60 * 60 * 24)
            + (
$diffParts['hours'] * 60 * 60)
            + (
$diffParts['minutes'] * 60)
            +
$diffParts['seconds'];
    }
}