first commit

This commit is contained in:
Claudecio Martins
2025-11-04 18:22:02 +01:00
commit c1184d2878
4394 changed files with 444123 additions and 0 deletions

42
app/Common/Config/Config.php Executable file
View File

@@ -0,0 +1,42 @@
<?php
namespace Zampet\Config;
use Dotenv\Dotenv;
class Config {
private $config = [];
private static $instance = null;
/**
* Método construtor
*/
public function __construct() {
// Carrega apenas para $_ENV, não para $_SERVER
$dotEnv = Dotenv::createUnsafeImmutable(
paths: realpath(path: __DIR__ . "/../../../"),
names: '.env'
);
$dotEnv->safeLoad(); // não dá erro se .env não existir
// Carrega as configurações
$this->config = require_once realpath(path: __DIR__ . "/Consts.php");
}
/**
* Retorna a instância única da classe
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
/**
* Retorna uma configuração
*/
public function get(string $key) {
return $this->config[$key] ?? null;
}
}

21
app/Common/Config/Consts.php Executable file
View File

@@ -0,0 +1,21 @@
<?php
return [
'SYSTEM_URL' => (string) getenv(name: 'SYSTEM_URL'),
'SYSTEM_VERSION' => (string) getenv(name: 'SYSTEM_VERSION'),
'SYSTEM_TIMEZONE' => (string) getenv(name: 'SYSTEM_TIMEZONE'),
'SYSTEM_ENVIRONMENT_ID' => (int) getenv(name: 'SYSTEM_ENVIRONMENT_ID'),
'SYSTEM_FRONTEND_URL' => (string) getenv(name: 'SYSTEM_FRONTEND_URL'),
'JWT_ALGO' => getenv(name: 'JWT_ALGO'),
'JWT_ISSUER' => getenv(name: 'JWT_ISSUER'),
'JWT_SECRET' => getenv(name: 'JWT_SECRET'),
'JWT_EXPIRATION' => (int) getenv(name: 'JWT_EXPIRATION'),
'DEFAULT_DATABASE_HOST' => getenv(name: 'DEFAULT_DATABASE_HOST'),
'DEFAULT_DATABASE_PORT' => getenv(name: 'DEFAULT_DATABASE_PORT'),
'DEFAULT_DATABASE_DRIVER' => getenv(name: 'DEFAULT_DATABASE_DRIVER'),
'DEFAULT_DATABASE_SCHEMA' => getenv(name: 'DEFAULT_DATABASE_SCHEMA'),
'DEFAULT_DATABASE_CHARSET' => getenv(name: 'DEFAULT_DATABASE_CHARSET'),
'DEFAULT_DATABASE_USERNAME' => getenv(name: 'DEFAULT_DATABASE_USERNAME'),
'DEFAULT_DATABASE_PASSWORD' => getenv(name: 'DEFAULT_DATABASE_PASSWORD'),
];

View File

@@ -0,0 +1,174 @@
<?php
namespace Zampet\Helpers;
class DataValidator {
private array $inputs;
private array $errors = [];
public function __construct(array $inputs) {
$this->inputs = $inputs;
}
/**
* Valida um campo usando uma regra específica.
*/
public function validate(string $field, callable|string $rule, string $message = '', array $additionalParams = []): static {
$value = $this->inputs[$field] ?? null;
// Se for string, monta callable usando a própria classe
if (is_string(value: $rule) && method_exists(object_or_class: self::class, method: $rule)) {
$rule = [self::class, $rule];
}
$params = array_merge([$value], $additionalParams);
$result = call_user_func_array(callback: $rule, args: $params);
// Caso seja validação de senha (retorna array)
if (is_array(value: $result) && isset($result['valid'])) {
if (!$result['valid']) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $result['errors']
];
}
}
// Para outras validações booleanas
elseif ($result === false) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $message
];
}
return $this;
}
public function getErrors(): array {
return $this->errors;
}
public function passes(): bool {
return empty($this->errors);
}
// ---------- Regras comuns ----------
public static function validateEmail(string $value): bool {
return (bool) filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL);
}
public static function validatePhone(string $value, int $minLength = 10, int $maxLength = 13): bool {
$digits = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
$len = strlen(string: $digits);
return $len >= $minLength && $len <= $maxLength;
}
public static function validateCpf(string $value): bool {
$cpf = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cpf) !== 11 || preg_match(pattern: '/(\d)\1{10}/', subject: $cpf)) {
return false;
}
for ($t = 9; $t < 11; $t++) {
$d = 0;
for ($c = 0; $c < $t; $c++) {
$d += $cpf[$c] * (($t + 1) - $c);
}
$d = ((10 * $d) % 11) % 10;
if ($cpf[$c] != $d) {
return false;
}
}
return true;
}
public static function validateCnpj(string $value): bool {
$cnpj = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cnpj) != 14) {
return false;
}
if (preg_match(pattern: '/(\d)\1{13}/', subject: $cnpj)) {
return false;
}
$tamanho = 12;
$multiplicadores = [5,4,3,2,9,8,7,6,5,4,3,2];
for ($i = 0; $i < 2; $i++) {
$soma = 0;
for ($j = 0; $j < $tamanho; $j++) {
$soma += $cnpj[$j] * $multiplicadores[$j + ($i == 1 ? 1 : 0)];
}
$digito = ($soma % 11 < 2) ? 0 : 11 - ($soma % 11);
if ($cnpj[$tamanho] != $digito) {
return false;
}
$tamanho++;
}
return true;
}
public static function notEmpty(mixed $value): bool {
if (is_array(value: $value)) {
return !empty(array_filter(array: $value, callback: fn($v) => $v !== null && $v !== ''));
}
return !empty(trim(string: (string) $value));
}
public static function validatePassword(string $password, array $rules = []): array {
$errors = [];
$minLength = $rules['minLength'] ?? 8;
$maxLength = $rules['maxLength'] ?? 64;
$requireUpper = $rules['upper'] ?? true;
$requireLower = $rules['lower'] ?? true;
$requireDigit = $rules['digit'] ?? true;
$requireSymbol = $rules['symbol'] ?? true;
if (strlen(string: $password) < $minLength) {
$errors[] = "Senha deve ter no mínimo {$minLength} caracteres.";
}
if (strlen(string: $password) > $maxLength) {
$errors[] = "Senha deve ter no máximo {$maxLength} caracteres.";
}
if ($requireUpper && !preg_match(pattern: '/[A-Z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra maiúscula.";
}
if ($requireLower && !preg_match(pattern: '/[a-z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra minúscula.";
}
if ($requireDigit && !preg_match(pattern: '/\d/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um número.";
}
if ($requireSymbol && !preg_match(pattern: '/[\W_]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um símbolo.";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
public static function validateList(mixed $value, array $validOptions): bool {
if (is_array(value: $value)) {
foreach ($value as $item) {
if (!in_array(needle: $item, haystack: $validOptions, strict: true)) {
return false;
}
}
return true;
}
return in_array(needle: $value, haystack: $validOptions, strict: true);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Zampet\Helpers;
use DateTime;
class Sanitizer {
public static function string(mixed $value): string|null {
if(is_null($value)) {
return null;
}
return trim(string: strip_tags(string: $value));
}
public static function int(mixed $value): int {
return filter_var(value: $value, filter: FILTER_VALIDATE_INT) ?? 0;
}
public static function float(mixed $value): float {
return filter_var(value: $value, filter: FILTER_VALIDATE_FLOAT) ?? 0.0;
}
public static function email(string $value): string {
$value = trim(string: strtolower(string: $value));
return filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL) ?: '';
}
public static function phone(string $value): string {
// remove tudo que não for número
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function document(string $value): string {
// CPF, CNPJ, RG etc. só números
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function date(string $value, string $format = 'Y-m-d'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
// tenta converter qualquer formato válido
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
public static function datetime(string $value, string $format = 'Y-m-d H:i:s'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
}

8
app/Common/Services/DB.php Executable file
View File

@@ -0,0 +1,8 @@
<?php
namespace Zampet\Services;
use AxiumPHP\Core\Database;
class DB extends Database {
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Zampet\Services;
use DateTime;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Zampet\Config\Config;
class JWTService {
private static string $issuer;
private static int $expiration;
private static string $algorithm;
private static string $secretKey;
/**
* Inicializa as configurações para a geração de tokens JWT.
*
* Este método estático carrega as informações necessárias para a criação e validação de tokens
* JWT (JSON Web Tokens) a partir de uma classe de configuração (`Config`). Ele busca a chave secreta,
* o algoritmo de criptografia, o emissor e o tempo de expiração definidos para a aplicação
* e os armazena como propriedades estáticas da classe.
*
* Este método deve ser chamado antes de qualquer operação que envolva a geração ou
* verificação de tokens, garantindo que as configurações estejam prontas para uso.
*
* @return void
*/
public static function init(): void {
$Config = Config::getInstance();
self::$issuer = $Config->get(key: 'JWT_ISSUER');
self::$algorithm = $Config->get(key: 'JWT_ALGO');
self::$secretKey = $Config->get(key: 'JWT_SECRET');
self::$expiration = $Config->get(key: 'JWT_EXPIRATION');
}
/**
* Gera um token JWT com base em um payload e nas configurações da aplicação.
*
* Este método cria um token JWT válido a partir de um array de dados fornecido (`$payload`),
* adicionando automaticamente metadados essenciais como a data de emissão, a data de expiração e o emissor.
*
* O processo detalhado inclui:
* 1. **Inicialização:** Chama o método `init()` para garantir que todas as configurações do JWT
* (chave secreta, algoritmo, etc.) estejam carregadas.
* 2. **Definição dos Timestamps:** Calcula o timestamp de emissão (`iat`) e o de expiração (`exp`)
* com base no tempo de expiração definido nas configurações.
* 3. **Montagem do Payload:** Adiciona as chaves `iat`, `exp` e `iss` (emissor) ao array `$payload`.
* 4. **Codificação do Token:** Utiliza a biblioteca `JWT::encode()` para codificar o payload
* com a chave secreta e o algoritmo definidos nas configurações.
*
* @param array $payload Um array associativo contendo os dados personalizados que serão incluídos no token.
* @return string O token JWT gerado como uma string.
*/
public static function generateToken(array $payload): string {
// Carrega as informações do arquivo de configuração
self::init();
// Define data de emissão e calcula a data de expiração do token
$expiration = self::$expiration;
$createdAt = (new DateTime())->getTimestamp();
$expireAt = (new DateTime())->modify(modifier: "+{$expiration} seconds")->getTimestamp();
// Adiciona data de criação ao payload
$payload['iat'] = $createdAt;
// Adiciona a expiração ao payload
$payload['exp'] = $expireAt;
// Adiciona o emissor ao payload
$payload['iss'] = self::$issuer;
return JWT::encode(payload: $payload, key: self::$secretKey, alg: self::$algorithm);
}
/**
* Valida um token JWT e retorna o objeto de payload decodificado.
*
* Este método estático decodifica e valida um token JWT fornecido, utilizando
* a chave secreta e o algoritmo de criptografia configurados na classe.
*
* #### Como funciona:
* 1. Recebe o token (`$token`) que precisa ser validado.
* 2. Usa a biblioteca `JWT::decode()` para decodificar o token.
* 3. Para a decodificação, ele cria uma nova instância de `Key`, passando
* a chave secreta (`self::$secretKey`) e o algoritmo (`self::$algorithm`).
* Isso garante que o token só será decodificado se tiver sido assinado com a chave e algoritmo corretos.
* 4. Se o token for válido e a assinatura corresponder, a função retorna o payload do token
* como um objeto.
* 5. Se o token for inválido (por exemplo, assinatura incorreta, expirado ou formato incorreto),
* a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser validado e decodificado.
* @return object O objeto de payload decodificado do token.
*/
public static function validateToken(string $token): object {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
return JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
}
/**
* Decodifica um token JWT e retorna seu payload como um array associativo.
*
* Este método estático é responsável por decodificar e validar um token JWT. Antes de decodificar, ele garante que as configurações de chave secreta e algoritmo (`self::$secretKey` e `self::$algorithm`) já foram inicializadas chamando o método `init()` se necessário.
*
* #### Como Funciona:
* 1. **Inicialização:** Primeiro, verifica se a chave secreta (`self::$secretKey`) já foi carregada. Se não, ele chama `self::init()` para carregar as configurações de um arquivo externo.
* 2. **Decodificação:** Usa a biblioteca `Firebase\JWT\JWT::decode()` para decodificar o token fornecido. O método passa a chave secreta e o algoritmo para verificar a assinatura do token.
* 3. **Conversão:** O payload decodificado, que por padrão é um objeto, é convertido para uma string JSON e depois para um array associativo, o que facilita o acesso aos dados.
* 4. **Retorno:** Retorna o payload do token como um array associativo. Se o token for inválido (assinatura incorreta, expirado, etc.), a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser decodificado.
* @return array O payload do token decodificado como um array associativo.
*/
public static function decode(string $token): array {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
$decoded = JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
return json_decode(json: json_encode(value: $decoded), associative: true);
}
/**
* Extrai o token de autenticação do tipo 'Bearer' do cabeçalho da requisição HTTP.
*
* Este método estático é um utilitário projetado para buscar um token JWT (JSON Web Token)
* que é enviado no cabeçalho `Authorization`. Ele é robusto o suficiente para verificar
* o token em diferentes superglobais do PHP, dependendo da configuração do servidor web.
*
* #### Fluxo de Busca:
* 1. Primeiro, tenta obter o cabeçalho `Authorization` diretamente da superglobal `$_SERVER`.
* 2. Em seguida, tenta a chave `HTTP_AUTHORIZATION`, que é um nome de variável comum em alguns ambientes.
* 3. Por último, usa a função `apache_request_headers()` para tentar obter o cabeçalho, caso as
* superglobais não estejam preenchidas. Isso garante compatibilidade com servidores Apache onde o cabeçalho pode não ser exposto.
* 4. Se o cabeçalho for encontrado, o valor é sanitizado (removendo espaços em branco) e a busca continua.
* 5. Se nenhum cabeçalho de autenticação for encontrado, o método retorna `null`.
*
* #### Extração do Token:
* - Depois de encontrar o cabeçalho, o método usa uma expressão regular (`/Bearer\s(\S+)/`)
* para verificar se o valor do cabeçalho está no formato `Bearer <token>`.
* - Se o padrão corresponder, ele extrai e retorna a string do token.
* - Se o padrão não corresponder, o método retorna `null`.
*
* @return string|null O token de autenticação do tipo 'Bearer' como uma string, ou `null`
* se o cabeçalho não for encontrado ou não estiver no formato esperado.
*/
public static function getBearerToken(): ?string {
if (!isset(self::$secretKey)) {
self::init();
}
// Tenta pegar o header de todas as fontes possíveis
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? (function_exists(function: 'apache_request_headers') ? apache_request_headers() : []);
// Se veio do apache_request_headers, normaliza chaves
if (is_array(value: $headers)) {
$headers = array_change_key_case(array: $headers, case: CASE_LOWER);
$headers = $headers['authorization'] ?? '';
}
$headers = trim(string: $headers);
if ($headers && preg_match(pattern: '/Bearer\s(\S+)/', subject: $headers, matches: $matches)) {
return $matches[1];
}
return null;
}
}