JezK
Edit File: ListingRequestValidationTrait.php
<?php declare(strict_types = 1); namespace MailPoet\API\REST; if (!defined('ABSPATH')) exit; use DateTimeImmutable; /** * Shared request validation for DataViews-backed listing endpoints * (see {@see AbstractListingEndpoint}). Provides the reusable building blocks — * sort field/order, pagination integers, and `yyyy-MM-dd` date filters — so each * listing only declares its own allowed sort fields and filter keys. * * Error codes are namespaced per listing via {@see getListingValidationErrorPrefix()} * so a 400 still says which listing rejected the request * (e.g. `mailpoet_logs_invalid_orderby`). */ trait ListingRequestValidationTrait { /** Lowercase listing slug used to build error codes, e.g. `logs` or `forms`. */ abstract protected function getListingValidationErrorPrefix(): string; private function listingValidationError(string $message, string $suffix): ApiException { return new ApiException( $message, 400, 'mailpoet_' . $this->getListingValidationErrorPrefix() . '_invalid_' . $suffix ); } /** * @param mixed $sortField * @param string[] $allowedFields */ protected function validateSortField($sortField, array $allowedFields): void { if ($sortField === null || $sortField === '') { return; } if (!is_string($sortField) || !in_array($sortField, $allowedFields, true)) { throw $this->listingValidationError( sprintf( // translators: %s is a comma-separated list of allowed sort fields. __('Unsupported sort field. Allowed values are: %s.', 'mailpoet'), implode(', ', $allowedFields) ), 'orderby' ); } } /** @param mixed $sortOrder */ protected function validateSortOrder($sortOrder): void { if ($sortOrder === null || $sortOrder === '') { return; } if (!is_string($sortOrder) || !in_array(strtolower($sortOrder), ['asc', 'desc'], true)) { throw $this->listingValidationError( sprintf( // translators: %s is a comma-separated list of allowed sort orders. __('Unsupported sort order. Allowed values are: %s.', 'mailpoet'), implode(', ', ['asc', 'desc']) ), 'order' ); } } protected function validatePagination(Request $request): void { $this->validatePositiveInteger($request->getParam('page'), 'page', 1, self::MAX_PAGE); $this->validatePositiveInteger($request->getParam('per_page'), 'per_page', 1, self::MAX_PER_PAGE); $this->validatePositiveInteger($request->getParam('limit'), 'limit', 1, self::MAX_PER_PAGE); $this->validatePositiveInteger($request->getParam('offset'), 'offset', 0, self::MAX_PAGE); } /** @param mixed $value */ private function validatePositiveInteger($value, string $name, int $min, int $max): void { if ($value === null || $value === '') { return; } $integer = $this->getIntegerValue($value); if ($integer === null || $integer < $min || $integer > $max) { throw $this->listingValidationError( sprintf( // translators: %1$s is a request parameter name, %2$d is the maximum accepted value. __('%1$s must be an integer no greater than %2$d.', 'mailpoet'), $name, $max ), $name ); } } /** @param mixed $value */ private function getIntegerValue($value): ?int { if (is_int($value)) { return $value; } if (is_string($value) && ctype_digit($value)) { return (int)$value; } return null; } /** * @param array<string, mixed> $filters */ protected function validateDateFilter(array $filters, string $field): ?DateTimeImmutable { if (!array_key_exists($field, $filters) || $filters[$field] === '') { return null; } if (!is_string($filters[$field])) { throw $this->listingValidationError( __('Date filters must use the YYYY-MM-DD format.', 'mailpoet'), $field ); } $date = DateTimeImmutable::createFromFormat('!Y-m-d', $filters[$field]); $errors = DateTimeImmutable::getLastErrors(); if (!$date || (is_array($errors) && ($errors['warning_count'] > 0 || $errors['error_count'] > 0)) || $date->format('Y-m-d') !== $filters[$field]) { throw $this->listingValidationError( __('Date filters must use the YYYY-MM-DD format.', 'mailpoet'), $field ); } return $date; } protected function validateDateRange(?DateTimeImmutable $from, ?DateTimeImmutable $to): void { if ($from && $to && $from > $to) { throw $this->listingValidationError( __('The from date must be before or equal to the to date.', 'mailpoet'), 'date_range' ); } } }