first commit

This commit is contained in:
Claudecio Martins
2026-06-16 10:04:10 -03:00
commit a951944997
4463 changed files with 419677 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace WorkbloomERP\Utils;
use WorkbloomERP\Services\CacheService;
use WorkbloomERP\Exceptions\AppException;
class CacheUtil {
public static function set(string $key, int $ttl, mixed $value): bool {
return CacheService::set(
key: $key,
value: is_string($value) ? $value : json_encode(
value: match (true) {
is_array($value), is_object($value) => $value,
default => throw new AppException(message: 'Valor para cache deve ser string, array ou objeto.', code: 400)
},
flags: JSON_UNESCAPED_UNICODE
),
ttl: max(-1, $ttl)
);
}
public static function get(string $key): ?array {
$cachedValue = CacheService::get(key: $key);
return $cachedValue ? json_decode($cachedValue, true) : null;
}
public static function delete(array $keys): bool {
return CacheService::del(keys: $keys);
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
namespace WorkbloomERP\Utils;
use Exception;
use JsonException;
class CryptoUtil {
/**
* Criptografa uma string.
*
* @param string $data
* @return string
* @throws Exception
*/
public static function encrypt(string $data): string {
$algorithm = self::getAlgorithm();
$key = self::getKey();
$ivLength = openssl_cipher_iv_length($algorithm);
if ($ivLength === false) {
throw new Exception('Unable to determine IV length');
}
$iv = random_bytes($ivLength);
$cipherText = openssl_encrypt(
$data,
$algorithm,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($cipherText === false) {
throw new Exception('Failed to encrypt data');
}
return self::encodePayload([
'iv' => base64_encode($iv),
'tag' => base64_encode($tag),
'value' => base64_encode($cipherText)
]);
}
/**
* Descriptografa uma string.
*
* @param string $payload
* @return string
* @throws Exception
*/
public static function decrypt(string $payload): string {
$algorithm = self::getAlgorithm();
$key = self::getKey();
$decodedPayload = self::decodePayload($payload);
$plainText = openssl_decrypt(
base64_decode($decodedPayload['value']),
$algorithm,
$key,
OPENSSL_RAW_DATA,
base64_decode($decodedPayload['iv']),
base64_decode($decodedPayload['tag'])
);
if ($plainText === false) {
throw new Exception('Failed to decrypt data');
}
return $plainText;
}
/**
* Retorna o algoritmo configurado.
*
* @return string
* @throws Exception
*/
private static function getAlgorithm(): string {
$algorithm = $_ENV['SYSTEM_CRYPTO_ALGO'];
if (!in_array($algorithm, openssl_get_cipher_methods(), true)) {
throw new Exception('Invalid cipher algorithm');
}
return $algorithm;
}
/**
* Retorna a chave de criptografia.
*
* A chave deve ser uma string hexadecimal de 64 caracteres (32 bytes).
*
* @return string
* @throws Exception
*/
private static function getKey(): string {
$hexKey = $_ENV['SYSTEM_CRYPTO_KEY'];
if (empty($hexKey)) {
throw new Exception('Encryption key not configured');
}
$key = hex2bin($hexKey);
if ($key === false || strlen($key) !== 32) {
throw new Exception(message: 'Invalid encryption key. Expected 32 bytes (64 hex characters).');
}
return $key;
}
/**
* Codifica o payload para armazenamento.
*
* @param array $payload
* @return string
* @throws JsonException
*/
private static function encodePayload(array $payload): string {
return base64_encode(
json_encode(
$payload,
JSON_THROW_ON_ERROR
)
);
}
/**
* Decodifica o payload criptografado.
*
* @param string $payload
* @return array
* @throws Exception
*/
private static function decodePayload(string $payload): array {
try {
$decoded = json_decode(
base64_decode($payload),
true,
512,
JSON_THROW_ON_ERROR
);
} catch (JsonException $e) {
throw new Exception(message: 'Invalid encrypted payload', previous: $e);
}
foreach (['iv', 'tag', 'value'] as $field) {
if (!isset($decoded[$field])) {
throw new Exception(sprintf('Missing "%s" field in encrypted payload', $field));
}
}
return $decoded;
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace WorkbloomERP\Utils;
use Exception;
class CypherUtil {
public static function hash(string $data): string {
if (!isset($_ENV['SYSTEM_CYPHER_ALGO']) || !isset($_ENV['SYSTEM_CYPHER_PEPPER'])) {
throw new Exception('Missing required environment variables for hashing');
}
return hash_hmac(algo: $_ENV['SYSTEM_CYPHER_ALGO'], data: $data, key: $_ENV['SYSTEM_CYPHER_PEPPER']);
}
public static function verify(string $data, string $hash): bool {
return hash_equals(known_string: self::hash($data), user_string: $hash);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace WorkbloomERP\Utils;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use DateTimeImmutable;
class JwtUtil {
private int $ttl;
private string $issuer;
private string $secretKey;
private string $algorithm;
public function __construct(string $secretKey, string $algorithm = 'HS256', int $ttl = 3600, string $issuer = 'workbloomerp') {
if (empty($secretKey)) {
throw new Exception(message: 'JWT secret não configurado.');
}
$this->ttl = $ttl;
$this->secretKey = $secretKey;
$this->algorithm = $algorithm;
$this->issuer = $issuer;
}
public function generate(array $payload): string {
$defaultClaims = [];
$defaultClaims['iat'] = $payload['iat'] ?? (new DateTimeImmutable())->getTimestamp();
$defaultClaims['exp'] = $payload['exp'] ?? ((new DateTimeImmutable())->modify("+{$this->ttl} seconds")->getTimestamp());
$finalPayload = array_merge($payload, $defaultClaims);
return JWT::encode(
payload: $finalPayload,
key: $this->secretKey,
alg: $this->algorithm
);
}
public function validate(string $token): object {
return JWT::decode(
jwt: $token,
keyOrKeyArray: new Key(
keyMaterial: $this->secretKey,
algorithm: $this->algorithm
)
);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace WorkbloomERP\Utils;
class SanitizeUtil {
public static function string(mixed $value): ?string {
if ($value === null) {
return null;
}
$value = trim(string: $value);
$value = strip_tags(string: $value);
return $value;
}
public static function email(mixed $value): ?string {
if ($value === null) {
return null;
}
$value = filter_var(value: $value, filter: FILTER_SANITIZE_EMAIL);
return $value ?: '';
}
public static function int(mixed $value): ?int {
if ($value === null) {
return null;
}
return (int) filter_var(value: $value, filter: FILTER_SANITIZE_NUMBER_INT);
}
public static function float(mixed $value): ?float {
if($value === null) {
return null;
}
return (float) filter_var(
value: $value,
filter: FILTER_SANITIZE_NUMBER_FLOAT,
options: FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND
);
}
public static function document(mixed $value): ?string {
if ($value === null) {
return null;
}
// remove espaços
$value = trim($value);
// converte caracteres especiais (Ç -> C, Á -> A, etc.)
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
// mantém apenas letras e números
$value = preg_replace('/[^a-zA-Z0-9]/', '', $value);
return $value;
}
public static function boolean(mixed $value): ?bool {
if ($value === null) {
return null;
}
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
$value = strtolower($value);
if (in_array($value, ['true', '1', 'yes'], true)) {
return true;
}
if (in_array($value, ['false', '0', 'no'], true)) {
return false;
}
}
if (is_int($value)) {
return $value === 1;
}
// Se não for possível converter, retorna null
return null;
}
public static function phone(mixed $value, bool $withCountryCode = false): ?string {
if ($value === null) {
return null;
}
// remove tudo que não for número
$value = preg_replace('/\D/', '', $value);
if (!$value) {
return '';
}
// adiciona DDI do Brasil se não tiver
if ($withCountryCode) {
if (strlen($value) === 10 || strlen($value) === 11) {
$value = "55{$value}";
}
}
return $value;
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace WorkbloomERP\Utils;
class ValidateUtil {
public static function cpf(?string $cpf): bool {
if ($cpf == null) {
return false;
}
// Remove tudo que não for número
$cpf = preg_replace('/\D/', '', $cpf);
// Precisa ter 11 dígitos
if (strlen($cpf) !== 11) {
return false;
}
// Bloqueia CPFs com todos os dígitos iguais (11111111111, 00000000000, etc)
if (preg_match('/^(\d)\1{10}$/', $cpf)) {
return false;
}
// Valida primeiro dígito verificador
for ($t = 9; $t < 11; $t++) {
$soma = 0;
for ($i = 0; $i < $t; $i++) {
$soma += $cpf[$i] * (($t + 1) - $i);
}
$digito = ((10 * $soma) % 11) % 10;
if ($cpf[$t] != $digito) {
return false;
}
}
return true;
}
public static function cnpj(?string $cnpj): bool {
if ($cnpj == null) {
return false;
}
// Remove máscara
$cnpj = strtoupper(preg_replace('/[^A-Z0-9]/', '', $cnpj));
// Tamanho fixo
if (strlen($cnpj) !== 14) {
return false;
}
// Bloqueia CNPJs com todos os dígitos iguais (11111111111111, 00000000000000, etc)
if (preg_match('/^(\d)\1{13}$/', $cnpj)) {
return false;
}
// Regex: 12 alfanum + 2 numéricos
if (!preg_match('/^[A-Z0-9]{12}[0-9]{2}$/', $cnpj)) {
return false;
}
// Converte para valores numéricos
$valores = [];
for ($i = 0; $i < 14; $i++) {
$char = $cnpj[$i];
if (ctype_digit($char)) {
$valores[$i] = (int)$char;
} else {
$valores[$i] = ord($char) - 48;
}
}
// === Primeiro DV ===
$pesos1 = [5,4,3,2,9,8,7,6,5,4,3,2];
$soma = 0;
for ($i = 0; $i < 12; $i++) {
$soma += $valores[$i] * $pesos1[$i];
}
$resto = $soma % 11;
$dv1 = ($resto < 2) ? 0 : 11 - $resto;
if ($dv1 !== $valores[12]) {
return false;
}
// === Segundo DV ===
$pesos2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
$soma = 0;
for ($i = 0; $i < 13; $i++) {
$soma += $valores[$i] * $pesos2[$i];
}
$resto = $soma % 11;
$dv2 = ($resto < 2) ? 0 : 11 - $resto;
return $dv2 === $valores[13];
}
}