Skip to content
Merged
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
23 changes: 23 additions & 0 deletions config/textify.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,27 @@
'events' => [
'enabled' => env('TEXTIFY_EVENTS_ENABLED', true),
],

/*
|--------------------------------------------------------------------------
| Message Validation
|--------------------------------------------------------------------------
|
| Configure validation rules for SMS messages before sending.
| Uses Laravel-style validation rules for consistency.
|
*/

'validation' => [
'message' => [
// Set to false to allow empty messages
'required' => env('TEXTIFY_MESSAGE_REQUIRED', true),

// Minimum message length
'min' => env('TEXTIFY_MESSAGE_MIN_LENGTH', 1),

// Maximum message length (null = no limit)
'max' => env('TEXTIFY_MESSAGE_MAX_LENGTH', null),
],
],
];
1 change: 0 additions & 1 deletion src/Providers/Bangladeshi/BulkSmsBdProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ protected function parseResponse(array $response): TextifyResponse
'1006' => 'Balance Validity Not Available',
'1007' => 'Balance Insufficient',
'1011' => 'User Id not found',
// Add more error codes as needed
];
$errorMessage = $errorMessages[$errorCode] ?? $responseText;
}
Expand Down
78 changes: 78 additions & 0 deletions src/Providers/BaseProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ public function send(TextifyMessage $message): TextifyResponse
);
}

// Validate message content
$messageValidationResult = $this->validateMessageContent($message->message);
if (! $messageValidationResult['valid']) {
return TextifyResponse::failed(
errorMessage: $messageValidationResult['error'],
errorCode: 'INVALID_MESSAGE_CONTENT'
);
}

// Format phone number
$formattedMessage = new TextifyMessage(
to: $this->formatPhoneNumber($message->to),
Expand Down Expand Up @@ -292,4 +301,73 @@ protected function ensureConfigKeys(): void
);
}
}

/**
* Validate message content according to configuration rules
*
* @param string $message The message content to validate
* @return array{valid: bool, error?: string}
*/
protected function validateMessageContent(string $message): array
{
// Use Laravel-style validation config structure
$required = config('textify.validation.message.required', true);
$minLength = config('textify.validation.message.min', 1);
$maxLength = config('textify.validation.message.max', null);

$trimmedMessage = trim($message);

// Check if message is required but empty
if ($required && empty($trimmedMessage)) {
return [
'valid' => false,
'error' => 'The message field is required.',
];
}

// Skip length validation if message is empty and not required
if (! $required && empty($trimmedMessage)) {
return ['valid' => true];
}

// Check minimum length (combines empty check with min length)
if (strlen($trimmedMessage) < $minLength) {
return [
'valid' => false,
'error' => sprintf('The message must be at least %d character%s.', $minLength, $minLength === 1 ? '' : 's'),
];
}

// Check maximum length if specified
if ($maxLength !== null && strlen($trimmedMessage) > $maxLength) {
return [
'valid' => false,
'error' => sprintf('The message may not be greater than %d characters.', $maxLength),
];
}

return ['valid' => true];
}

/**
* {@inheritdoc}
*/
public function validatePhoneNumber(string $phoneNumber): bool
{
// Basic validation - check if not empty and contains only digits, +, -, spaces, and parentheses
$cleaned = preg_replace('/[\s\-\(\)]+/', '', $phoneNumber);

return ! empty(trim($phoneNumber)) &&
preg_match('/^\+?[0-9]{7,15}$/', $cleaned);
}

/**
* {@inheritdoc}
*/
public function formatPhoneNumber(string $phoneNumber): string
{
// Default implementation - just trim whitespace
// Providers should override this method for their specific formatting requirements
return trim($phoneNumber);
}
}
86 changes: 86 additions & 0 deletions tests/MessageValidationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace DevWizard\Textify\Tests;

use DevWizard\Textify\DTOs\TextifyMessage;
use DevWizard\Textify\Providers\LogProvider;

it('rejects empty messages by default (required)', function () {
$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', '', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeFalse();
expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT');
expect($response->getErrorMessage())->toContain('field is required');
});

it('accepts empty messages when not required (nullable)', function () {
// Temporarily override config to make message not required (nullable)
config(['textify.validation.message.required' => false]);

$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', '', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeTrue();

// Reset config
config(['textify.validation.message.required' => true]);
});

it('rejects messages shorter than minimum length', function () {
config(['textify.validation.message.min' => 5]);

$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', 'Hi', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeFalse();
expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT');
expect($response->getErrorMessage())->toContain('must be at least 5 character');

// Reset config
config(['textify.validation.message.min' => 1]);
});

it('rejects messages longer than maximum length', function () {
config(['textify.validation.message.max' => 10]);

$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', 'This message is way too long', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeFalse();
expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT');
expect($response->getErrorMessage())->toContain('may not be greater than 10 characters');

// Reset config
config(['textify.validation.message.max' => null]);
});

it('accepts valid messages', function () {
$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', 'Valid message content', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeTrue();
});

it('validates whitespace-only messages as empty', function () {
$provider = new LogProvider([]);

$message = TextifyMessage::create('01712345678', ' ', 'TestSender');
$response = $provider->send($message);

expect($response->isSuccessful())->toBeFalse();
expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT');
expect($response->getErrorMessage())->toContain('field is required');
});