Implement datetime filter
[MAILPOET-5000]
This commit is contained in:
@@ -4,16 +4,22 @@ namespace MailPoet\Automation\Integrations\Core;
|
||||
|
||||
use MailPoet\Automation\Engine\Integration;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\Core\Actions\DelayAction;
|
||||
|
||||
class CoreIntegration implements Integration {
|
||||
/** @var DelayAction */
|
||||
private $delayAction;
|
||||
|
||||
/** @var WordPress */
|
||||
private $wordPress;
|
||||
|
||||
public function __construct(
|
||||
DelayAction $delayAction
|
||||
DelayAction $delayAction,
|
||||
WordPress $wordPress
|
||||
) {
|
||||
$this->delayAction = $delayAction;
|
||||
$this->wordPress = $wordPress;
|
||||
}
|
||||
|
||||
public function register(Registry $registry): void {
|
||||
@@ -23,6 +29,7 @@ class CoreIntegration implements Integration {
|
||||
$registry->addFilter(new Filters\NumberFilter());
|
||||
$registry->addFilter(new Filters\IntegerFilter());
|
||||
$registry->addFilter(new Filters\StringFilter());
|
||||
$registry->addFilter(new Filters\DateTimeFilter($this->wordPress->wpTimezone()));
|
||||
$registry->addFilter(new Filters\EnumFilter());
|
||||
$registry->addFilter(new Filters\EnumArrayFilter());
|
||||
}
|
||||
|
@@ -0,0 +1,173 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\Core\Filters;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Data\Filter as FilterData;
|
||||
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
||||
use MailPoet\Automation\Engine\Integration\Filter;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
class DateTimeFilter implements Filter {
|
||||
public const CONDITION_BEFORE = 'before';
|
||||
public const CONDITION_AFTER = 'after';
|
||||
public const CONDITION_ON = 'on';
|
||||
public const CONDITION_NOT_ON = 'not-on';
|
||||
public const CONDITION_IN_THE_LAST = 'in-the-last';
|
||||
public const CONDITION_NOT_IN_THE_LAST = 'not-in-the-last';
|
||||
public const CONDITION_IS_SET = 'is-set';
|
||||
public const CONDITION_IS_NOT_SET = 'is-not-set';
|
||||
public const CONDITION_ON_THE_DAYS_OF_THE_WEEK = 'on-the-days-of-the-week';
|
||||
|
||||
public const FORMAT_DATETIME = 'Y-m-d\TH:i:s';
|
||||
public const FORMAT_DATE = 'Y-m-d';
|
||||
|
||||
public const REGEX_DATETIME = '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$';
|
||||
public const REGEX_DATE = '^\d{4}-\d{2}-\d{2}$';
|
||||
|
||||
/** @var DateTimeZone */
|
||||
private $timezone;
|
||||
|
||||
public function __construct(
|
||||
DateTimeZone $timeZone
|
||||
) {
|
||||
$this->timezone = $timeZone;
|
||||
}
|
||||
|
||||
public function getFieldType(): string {
|
||||
return Field::TYPE_DATETIME;
|
||||
}
|
||||
|
||||
public function getConditions(): array {
|
||||
return [
|
||||
self::CONDITION_BEFORE => __('before', 'mailpoet'),
|
||||
self::CONDITION_AFTER => __('after', 'mailpoet'),
|
||||
self::CONDITION_ON => __('on', 'mailpoet'),
|
||||
self::CONDITION_NOT_ON => __('not on', 'mailpoet'),
|
||||
self::CONDITION_IN_THE_LAST => __('in the last', 'mailpoet'),
|
||||
self::CONDITION_NOT_IN_THE_LAST => __('not in the last', 'mailpoet'),
|
||||
self::CONDITION_IS_SET => __('is set', 'mailpoet'),
|
||||
self::CONDITION_IS_NOT_SET => __('is not set', 'mailpoet'),
|
||||
self::CONDITION_ON_THE_DAYS_OF_THE_WEEK => __('on the day(s) of the week', 'mailpoet'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'value' => Builder::oneOf([
|
||||
Builder::string()->pattern(self::REGEX_DATETIME),
|
||||
Builder::string()->pattern(self::REGEX_DATE),
|
||||
Builder::array(Builder::integer()->minimum(0)->maximum(6))->minItems(1),
|
||||
Builder::object([
|
||||
'number' => Builder::integer()->minimum(1)->required(),
|
||||
'unit' => Builder::string()->pattern('^days|weeks|months$')->required(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function matches(FilterData $data, $value): bool {
|
||||
$filterValue = $data->getArgs()['value'] ?? null;
|
||||
$condition = $data->getCondition();
|
||||
|
||||
// is set/is not set
|
||||
if (in_array($condition, [self::CONDITION_IS_SET, self::CONDITION_IS_NOT_SET], true)) {
|
||||
return $this->matchesSet($condition, $value);
|
||||
}
|
||||
|
||||
// in the last/not in the last
|
||||
if (in_array($condition, [self::CONDITION_IN_THE_LAST, self::CONDITION_NOT_IN_THE_LAST], true)) {
|
||||
return $this->matchesInTheLast($condition, $filterValue, $value);
|
||||
}
|
||||
|
||||
// on the day(s) of the week
|
||||
if ($condition === self::CONDITION_ON_THE_DAYS_OF_THE_WEEK) {
|
||||
return $this->matchesOnTheDaysOfTheWeek($filterValue, $value);
|
||||
}
|
||||
|
||||
// other conditions
|
||||
if (!is_string($filterValue) || !$value instanceof DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$datetime = $this->convertToWpTimezone($value);
|
||||
switch ($condition) {
|
||||
case 'before':
|
||||
$ref = DateTimeImmutable::createFromFormat(self::FORMAT_DATETIME, $filterValue, $this->timezone);
|
||||
return $ref && $datetime < $ref;
|
||||
case 'after':
|
||||
$ref = DateTimeImmutable::createFromFormat(self::FORMAT_DATETIME, $filterValue, $this->timezone);
|
||||
return $ref && $datetime > $ref;
|
||||
case 'on':
|
||||
return $datetime->format(self::FORMAT_DATE) === $filterValue;
|
||||
case 'not-on':
|
||||
return $datetime->format(self::FORMAT_DATE) !== $filterValue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param mixed $value */
|
||||
private function matchesSet(string $condition, $value): bool {
|
||||
switch ($condition) {
|
||||
case self::CONDITION_IS_SET:
|
||||
return $value !== null;
|
||||
case self::CONDITION_IS_NOT_SET:
|
||||
return $value === null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $filterValue
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function matchesInTheLast(string $condition, $filterValue, $value): bool {
|
||||
if (!is_array($filterValue) || !isset($filterValue['number']) || !isset($filterValue['unit']) || !$value instanceof DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$number = $filterValue['number'];
|
||||
$unit = $filterValue['unit'];
|
||||
if (!is_integer($number) || !in_array($unit, ['days', 'weeks', 'months'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = new DateTimeImmutable('now', $this->timezone);
|
||||
$ref = $now->modify("-$number $unit");
|
||||
$matches = $ref <= $value && $value <= $now;
|
||||
return $condition === self::CONDITION_IN_THE_LAST ? $matches : !$matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $filterValue
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function matchesOnTheDaysOfTheWeek($filterValue, $value): bool {
|
||||
if (!is_array($filterValue) || !$value instanceof DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
foreach ($filterValue as $day) {
|
||||
if (!is_integer($day) || $day < 0 || $day > 6) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$date = $this->convertToWpTimezone($value);
|
||||
$day = (int)$date->format('w');
|
||||
return in_array($day, $filterValue, true);
|
||||
}
|
||||
|
||||
private function convertToWpTimezone(DateTimeInterface $datetime): DateTimeImmutable {
|
||||
$value = DateTimeImmutable::createFromFormat('U', (string)$datetime->getTimestamp(), $this->timezone);
|
||||
if (!$value) {
|
||||
throw new InvalidStateException('Failed to convert datetime to WP timezone');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user