Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ Checks if the field under validation falls within the range of `:min` and `:max`
- For string data, the value corresponds to the number of characters.
- For numeric data, the value corresponds to a given integer value.
- For an array, the value corresponds to the count of the array.
- For file uploads (`$_FILES` array or WordPress attachment ID), the value corresponds to the file size in kilobytes.<br/>
e.g. `['avatar' => ['required', 'file', 'between:100,2048']]` (between 100KB and 2MB)
4. **`date`**<br/>
Checks if the field under validation is a valid date according to the `strtotime` PHP function.
5. **`digit_between:min,max`**<br/>
Expand Down Expand Up @@ -185,12 +187,39 @@ Checks if the field under validation has exactly the same size as `:size`.
- For string data, the value corresponds to the number of characters.
- For numeric data, the value corresponds to a given integer value.
- For an array, the value corresponds to the count of the array.
- For file uploads (`$_FILES` array or WordPress attachment ID), the value corresponds to the file size in kilobytes.<br/>
e.g. `['document' => ['required', 'file', 'size:512']]` (exactly 512KB)
23. **`string`**<br/>
Checks if the given value is a string.
24. **`uppercase`**<br/>
Checks if the string value consists of all uppercase letters.
25. **`url`**<br/>
Checks if the value is a valid URL.
26. **`file`**<br/>
Checks if the field under validation is a valid file upload. Supports standard PHP `$_FILES` arrays and WordPress attachment IDs.<br/>
e.g. `['avatar' => ['required', 'file']]`
27. **`mimes:type1,type2,...`**<br/>
Checks if the file under validation has one of the allowed MIME types or extensions. Accepts comma-separated values (e.g., `jpg`, `png`, `pdf`).<br/>
e.g. `['document' => ['required', 'file', 'mimes:pdf,doc,docx']]`
28. **`max_file:size`**<br/>
Checks if the file size (in kilobytes) is less than or equal to the specified maximum size.<br/>
e.g. `['avatar' => ['required', 'file', 'max_file:2048']]` (2MB limit)
29. **`image`**<br/>
Checks if the file under validation is an image using WordPress allowed image MIME types.<br/>
e.g. `['photo' => ['required', 'file', 'image']]`
30. **`dimension:constraint=value,...`**<br/>
Checks image dimensions against one or more constraints. Available constraints: `min_width`, `max_width`, `min_height`, `max_height`, `width` (exact), `height` (exact), `ratio` (as fraction `3/2` or float `1.5`).<br/>
e.g. `['avatar' => ['required', 'image', 'dimension:max_width=1920,max_height=1080']]`<br/>
e.g. `['avatar' => ['required', 'image', 'dimension:min_width=100,min_height=100,ratio=16/9']]`
31. **`extensions:ext1,ext2,...`**<br/>
Checks if the file has one of the specified extensions (based on filename only, no content inspection). Accepts comma-separated extensions without dots.<br/>
e.g. `['document' => ['required', 'file', 'extensions:pdf,doc,docx']]`
32. **`mimetypes:type1,type2,...`**<br/>
Checks if the file's actual MIME type (verified from file content via `wp_check_filetype_and_ext`) matches one of the specified types. More secure than `mimes` because it inspects file content, not just the filename or browser-supplied type.<br/>
e.g. `['upload' => ['required', 'file', 'mimetypes:image/jpeg,image/png,application/pdf']]`
33. **`encoding:enc1,enc2,...`**<br/>
Checks if the file's character encoding matches one of the specified encodings. Useful for validating text files (CSV, JSON, XML, etc.). Accepts comma-separated encoding names supported by PHP's `mb_detect_encoding`.<br/>
e.g. `['csv' => ['required', 'file', 'encoding:UTF-8,ASCII']]`

Missing any validation rule that you need? Refer to the [Custom Validation Rule](#custom-validation-rule) section to know how you can create and use custom validation rules in your project alongside the library.

Expand Down
10 changes: 10 additions & 0 deletions src/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ protected function getValueLength($value)

}

protected function getAttachmentSizeBytes(int $attachmentId, string $filePath)
{
$metadata = wp_get_attachment_metadata($attachmentId);
if ($metadata && isset($metadata['filesize'])) {
return (int) $metadata['filesize'];
}

return filesize($filePath);
}

public function setNestedElement(&$data, $keys, $value)
{
$current = &$data;
Expand Down
5 changes: 4 additions & 1 deletion src/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public function getParamKeys()

public function setParameterValues($paramKeys, $paramValues): void
{
if (count($paramKeys) === count($paramValues)) {
if (count($paramKeys) === 1 && count($paramValues) > 1) {
// Single-key rules that accept comma-separated values (e.g. mimes:jpg,png,pdf)
$this->params = [$paramKeys[0] => implode(',', $paramValues)];
} elseif (count($paramKeys) === count($paramValues)) {
$this->params = array_combine($paramKeys, $paramValues);
}
}
Expand Down
21 changes: 16 additions & 5 deletions src/Rules/BetweenRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,28 @@ public function validate($value)
$this->checkRequiredParameter($this->requireParameters);

$min = (int) $this->getParameter('min');

$max = (int) $this->getParameter('max');

$length = $this->getValueLength($value);
if (is_array($value) && isset($value['size'])) {
$sizeKB = (int) round($value['size'] / 1024);
return $sizeKB >= $min && $sizeKB <= $max;
}

if ($length) {
return $length >= $min && $length <= $max;
if (is_numeric($value) && function_exists('get_attached_file')) {
$attachmentId = (int) $value;
$filePath = get_attached_file($attachmentId);
if (! empty($filePath) && file_exists($filePath)) {
$sizeKB = (int) round($this->getAttachmentSizeBytes($attachmentId, $filePath) / 1024);
return $sizeKB >= $min && $sizeKB <= $max;
}
}

return false;
$length = $this->getValueLength($value);
if ($length === false) {
return false;
}

return $length >= $min && $length <= $max;
}

public function getParamKeys()
Expand Down
146 changes: 146 additions & 0 deletions src/Rules/DimensionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
namespace BitApps\WPValidator\Rules;

use BitApps\WPValidator\Helpers;
use BitApps\WPValidator\Rule;

class DimensionRule extends Rule
{
use Helpers;

protected $message = "The :attribute has invalid image dimensions";

protected $requireParameters = ['dimensions'];

public function validate($value)
{
$this->checkRequiredParameter($this->requireParameters);

if ($this->isEmpty($value)) {
return false;
}

$constraints = $this->parseConstraints();
if (empty($constraints)) {
return false;
}

$dimensions = $this->getDimensions($value);
if (! $dimensions) {
return false;
}

return $this->checkConstraints($dimensions['width'], $dimensions['height'], $constraints);
}

private function parseConstraints(): array
{
$raw = $this->getParameter('dimensions');
if (empty($raw)) {
return [];
}

$constraints = [];
foreach (explode(',', $raw) as $item) {
$parts = explode('=', $item, 2);
if (count($parts) === 2) {
$constraints[trim($parts[0])] = trim($parts[1]);
}
}

return $constraints;
}

private function checkConstraints(int $width, int $height, array $constraints): bool
{
$checks = [
'width' => static function ($w, $h, $v) { return $w === (int) $v; },
'height' => static function ($w, $h, $v) { return $h === (int) $v; },
'min_width' => static function ($w, $h, $v) { return $w >= (int) $v; },
'max_width' => static function ($w, $h, $v) { return $w <= (int) $v; },
'min_height' => static function ($w, $h, $v) { return $h >= (int) $v; },
'max_height' => static function ($w, $h, $v) { return $h <= (int) $v; },
];

foreach ($constraints as $key => $val) {
if ($key === 'ratio') {
if (! $this->checkRatio($width, $height, $val)) {
return false;
}
continue;
}

if (isset($checks[$key]) && ! $checks[$key]($width, $height, $val)) {
return false;
}
}

return true;
}

private function checkRatio(int $width, int $height, string $ratio): bool
{
if ($height === 0) {
return false;
}

if (strpos($ratio, '/') !== false) {
list($ratioW, $ratioH) = explode('/', $ratio, 2);
$expected = (float) $ratioW / (float) $ratioH;
} else {
$expected = (float) $ratio;
}

return abs(($width / $height) - $expected) < 0.01;
}

private function getDimensions($value)
{
$filePath = null;

if (is_numeric($value)) {
$attachmentId = (int) $value;
$filePath = get_attached_file($attachmentId);
if (empty($filePath) || ! file_exists($filePath)) {
return null;
}

$metadata = wp_get_attachment_metadata($attachmentId);
if ($metadata && isset($metadata['width'], $metadata['height'])) {
return [
'width' => (int) $metadata['width'],
'height' => (int) $metadata['height'],
];
}
}

if (is_array($value) && isset($value['tmp_name'])) {
if (empty($value['tmp_name']) || ! is_uploaded_file($value['tmp_name'])) {
return null;
}
$filePath = $value['tmp_name'];
}

if ($filePath && file_exists($filePath)) {
$imageSize = @getimagesize($filePath);
if ($imageSize && isset($imageSize[0], $imageSize[1])) {
return [
'width' => (int) $imageSize[0],
'height' => (int) $imageSize[1],
];
}
}

return null;
}

public function getParamKeys()
{
return $this->requireParameters;
}

public function message()
{
return $this->message;
}
}
72 changes: 72 additions & 0 deletions src/Rules/EncodingRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
namespace BitApps\WPValidator\Rules;

use BitApps\WPValidator\Helpers;
use BitApps\WPValidator\Rule;

class EncodingRule extends Rule
{
use Helpers;

protected $message = "The :attribute must be encoded as one of: :encoding";

protected $requireParameters = ['encoding'];

public function validate($value)
{
$this->checkRequiredParameter($this->requireParameters);

if ($this->isEmpty($value)) {
return false;
}

$allowedEncodings = array_map('trim', explode(',', $this->getParameter('encoding')));

$filePath = $this->getFilePath($value);
if (! $filePath) {
return false;
}

$contents = $this->getFileContentForEncodingDetection($filePath);
if ($contents === false) {
return false;
}

return mb_detect_encoding($contents, $allowedEncodings, true) !== false;
}

private function getFileContentForEncodingDetection($filePath)
{
if (function_exists('WP_Filesystem') && WP_Filesystem()) {
global $wp_filesystem;
$contents = $wp_filesystem->get_contents($filePath);
return $contents !== false ? substr($contents, 0, 32768) : false;
}

return file_get_contents($filePath, false, null, 0, 32768);
}

private function getFilePath($value)
{
if (is_numeric($value)) {
$filePath = get_attached_file((int) $value);
return (! empty($filePath) && file_exists($filePath)) ? $filePath : null;
}

if (is_array($value) && isset($value['tmp_name']) && is_uploaded_file($value['tmp_name'])) {
return $value['tmp_name'];
}

return null;
}

public function getParamKeys()
{
return $this->requireParameters;
}

public function message()
{
return $this->message;
}
}
61 changes: 61 additions & 0 deletions src/Rules/ExtensionsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
namespace BitApps\WPValidator\Rules;

use BitApps\WPValidator\Helpers;
use BitApps\WPValidator\Rule;

class ExtensionsRule extends Rule
{
use Helpers;

protected $message = "The :attribute must have one of the following extensions: :extensions";

protected $requireParameters = ['extensions'];

public function validate($value)
{
$this->checkRequiredParameter($this->requireParameters);

if ($this->isEmpty($value)) {
return false;
}

$allowedExtensions = array_map(function ($e) {
return strtolower(trim($e));
}, explode(',', $this->getParameter('extensions')));

$fileName = $this->getFileName($value);
if (! $fileName) {
return false;
}

$wpFileType = wp_check_filetype($fileName);
$extension = strtolower($wpFileType['ext'] ?? '');

return ! empty($extension) && in_array($extension, $allowedExtensions, true);
}

private function getFileName($value)
{
if (is_numeric($value)) {
$filePath = get_attached_file((int) $value);
return (! empty($filePath) && file_exists($filePath)) ? basename($filePath) : null;
}

if (is_array($value) && isset($value['name'])) {
return $value['name'];
}

return null;
}

public function getParamKeys()
{
return $this->requireParameters;
}

public function message()
{
return $this->message;
}
}
Loading