First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Illuminate\Database\Capsule\Manager as Capsule;
class ActivityLogsClearCommand extends Command
{
protected static $defaultName = 'logs:activity-clear';
protected static $defaultDescription = 'Limpa todos os registros da tabela activity_logs';
protected function configure(): void
{
$this->setHelp('Este comando executa um TRUNCATE na tabela activity_logs para remover todo o histórico de atividades.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Tem certeza que deseja limpar TODOS os logs de atividade? (y/n) [n]: ', false);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('<info>Operação cancelada.</info>');
return Command::SUCCESS;
}
try {
Capsule::table('activity_logs')->truncate();
$output->writeln('<info>Tabela activity_logs limpa com sucesso!</info>');
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln('<error>Erro ao limpar logs: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Kernel\Application;
class AppAboutCommand extends Command
{
protected static $defaultName = 'app:about';
protected static $defaultDescription = 'Exibe informações detalhadas sobre a aplicação';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Sobre a Aplicação SysCMS</info>');
$output->writeln('===========================');
$table = new Table($output);
$table->setHeaders(['Informação', 'Valor']);
$table->addRow(['Nome', $_ENV['APP_NAME'] ?? 'SysCMS']);
$table->addRow(['Versão', '1.0.0']);
$table->addRow(['Ambiente', $_ENV['APP_ENV'] ?? 'local']);
$table->addRow(['Debug', ($_ENV['APP_DEBUG'] ?? 'false') === 'true' ? '<info>Ativo</info>' : '<comment>Inativo</comment>']);
$table->addRow(['URL', $_ENV['APP_URL'] ?? 'http://localhost']);
$table->addRow(['PHP Version', PHP_VERSION]);
$table->addRow(['OS', PHP_OS]);
$table->addRow(['Database', $_ENV['DB_DATABASE'] ?? 'syscms']);
$table->addRow(['Database Driver', $_ENV['DB_CONNECTION'] ?? 'mysql']);
$table->addRow(['Timezone', date_default_timezone_get()]);
$table->addRow(['Locale', $_SESSION['lang'] ?? 'pt_BR']);
$table->render();
return Command::SUCCESS;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppInfoCommand extends Command
{
protected static $defaultName = 'app:info';
protected static $defaultDescription = 'Displays application information';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln([
'<info>SysCMS - PHP Content Management System</info>',
'========================================',
'',
]);
$output->writeln("PHP Version: " . PHP_VERSION);
$output->writeln("Environment: " . ($_ENV['APP_ENV'] ?? 'production'));
$output->writeln("Debug Mode: " . (($_ENV['APP_DEBUG'] ?? 'false') === 'true' ? 'Enabled' : 'Disabled'));
$output->writeln("URL: " . ($_ENV['APP_URL'] ?? 'Not set'));
$output->writeln([
'',
'<comment>Database:</comment>',
"Connection: " . ($_ENV['DB_CONNECTION'] ?? 'mysql'),
"Host: " . ($_ENV['DB_HOST'] ?? 'localhost'),
"Database: " . ($_ENV['DB_DATABASE'] ?? 'syscms'),
]);
return Command::SUCCESS;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\ArrayInput;
class AppOptimizeCommand extends Command
{
protected static $defaultName = 'app:optimize';
protected static $defaultDescription = 'Otimiza a aplicação limpando caches e bancos de dados (comandos combinados)';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Iniciando otimização completa do sistema...</info>');
// 1. Clear Cache
$output->writeln("\n<comment>Limpando caches...</comment>");
$cacheCommand = $this->getApplication()->find('cache:clear');
$cacheCommand->run(new ArrayInput([]), $output);
// 2. Clear Logs
$output->writeln("\n<comment>Limpando logs...</comment>");
$logsCommand = $this->getApplication()->find('logs:clear');
$logsCommand->run(new ArrayInput([]), $output);
// 3. Optimize DB
$output->writeln("\n<comment>Otimizando banco de dados...</comment>");
$dbCommand = $this->getApplication()->find('db:optimize');
$dbCommand->run(new ArrayInput([]), $output);
$output->writeln("\n<info>Aplicação otimizada com sucesso!</info>");
return Command::SUCCESS;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppRenameCommand extends Command
{
protected static $defaultName = 'app:rename';
protected static $defaultDescription = 'Altera o nome da aplicação no arquivo .env';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O novo nome da aplicação');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$newName = $input->getArgument('name');
$envFile = dirname(__DIR__, 3) . '/.env';
if (!file_exists($envFile)) {
$output->writeln('<error>Arquivo .env não encontrado.</error>');
return Command::FAILURE;
}
$content = file_get_contents($envFile);
$pattern = "/APP_NAME=(.*)/";
$replacement = "APP_NAME=\"{$newName}\"";
if (preg_match($pattern, $content)) {
$newContent = preg_replace($pattern, $replacement, $content);
} else {
$newContent = $content . "\nAPP_NAME=\"{$newName}\"";
}
if (file_put_contents($envFile, $newContent)) {
$output->writeln("<info>Aplicação renomeada para '{$newName}' com sucesso!</info>");
return Command::SUCCESS;
}
$output->writeln('<error>Falha ao renomear a aplicação.</error>');
return Command::FAILURE;
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CacheClearCommand extends Command
{
protected static $defaultName = 'cache:clear';
protected static $defaultDescription = 'Clears the application cache';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Clearing cache...');
$cacheDirs = [
dirname(__DIR__, 3) . '/cache/twig',
// Add other cache directories here
];
$hasError = false;
foreach ($cacheDirs as $dir) {
if (is_dir($dir)) {
$output->write("Limpando <info>{$dir}</info>... ");
if ($this->deleteDirectory($dir)) {
$output->writeln('<info>OK</info>');
// Recreate empty directory
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
chmod($dir, 0777);
}
} else {
$output->writeln('<error>FALHA</error>');
$hasError = true;
}
}
}
if ($hasError) {
$output->writeln('<error>Alguns caches não puderam ser limpos.</error>');
return Command::FAILURE;
}
$output->writeln('<info>Cache limpo com sucesso!</info>');
return Command::SUCCESS;
}
private function deleteDirectory($dir) {
if (!file_exists($dir)) {
return true;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!$this->deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
return rmdir($dir);
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Modules\Auth\Models\User;
use Carbon\Carbon;
class CreateSuperAdminCommand extends Command
{
protected static $defaultName = 'user:create-super-admin';
protected static $defaultDescription = 'Creates the default Super Admin user';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$email = 'superadmin@syscms.com';
$password = 'SuperAdmin123!';
try {
// Remove global scopes to ensure we can find the user regardless of company context
$user = User::withoutGlobalScopes()->where('users_email', $email)->first();
if ($user) {
$user->users_role = 'super_admin';
$user->users_password = password_hash($password, PASSWORD_DEFAULT);
$user->users_status = 'active';
$user->save();
$output->writeln('<info>Usuário Super Admin atualizado com sucesso!</info>');
} else {
$user = new User();
$user->users_name = 'Super Admin';
$user->users_email = $email;
$user->users_password = password_hash($password, PASSWORD_DEFAULT);
$user->users_role = 'super_admin';
$user->users_status = 'active';
$user->save();
$output->writeln('<info>Usuário Super Admin criado com sucesso!</info>');
}
$output->writeln([
'',
'Credenciais:',
"Email: $email",
"Senha: $password",
"Role: super_admin",
''
]);
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Modules\Auth\Models\User;
class CreateUserCommand extends Command
{
protected static $defaultName = 'user:create';
protected static $defaultDescription = 'Creates a new user';
protected function configure(): void
{
$this
->addArgument('email', InputArgument::OPTIONAL, 'User email');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$helper = $this->getHelper('question');
// Email
$email = $input->getArgument('email');
if (!$email) {
$question = new Question('Email do usuário: ');
$email = $helper->ask($input, $output, $question);
}
if (User::where('users_email', $email)->exists()) {
$output->writeln("<error>Já existe um usuário com o email {$email}</error>");
return Command::FAILURE;
}
// Name
$question = new Question('Nome do usuário: ');
$name = $helper->ask($input, $output, $question);
// Password
$question = new Question('Senha: ');
$question->setHidden(true);
$question->setHiddenFallback(false);
$password = $helper->ask($input, $output, $question);
// Role
$question = new Question('Role (padrão: user): ', 'user');
$role = $helper->ask($input, $output, $question);
// Bus ID
$question = new Question('ID Bus (opcional): ');
$busId = $helper->ask($input, $output, $question);
try {
$user = new User();
$user->users_name = $name;
$user->users_email = $email;
$user->users_password = password_hash($password, PASSWORD_DEFAULT);
$user->users_role = $role;
$user->users_bus_id = $busId;
$user->users_status = 'active';
$user->save();
$output->writeln('<info>Usuário criado com sucesso!</info>');
$output->writeln("ID: {$user->users_id}");
$output->writeln("Nome: {$user->users_name}");
$output->writeln("Email: {$user->users_email}");
$output->writeln("Role: {$user->users_role}");
$output->writeln("ID Bus: " . ($user->users_bus_id ?? '-'));
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao criar usuário: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class DbBackupCommand extends Command
{
protected static $defaultName = 'db:backup';
protected static $defaultDescription = 'Cria um backup do banco de dados';
protected function configure(): void
{
$this
->setHelp('Este comando gera um dump SQL do banco de dados atual.')
->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Diretório para salvar o backup', 'storage/backups')
->addOption('compress', 'c', InputOption::VALUE_NONE, 'Comprimir o backup (gzip)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Iniciando backup do banco de dados...</info>');
$host = $_ENV['DB_HOST'] ?? 'localhost';
$port = $_ENV['DB_PORT'] ?? '3306';
$database = $_ENV['DB_DATABASE'] ?? 'syscms';
$username = $_ENV['DB_USERNAME'] ?? 'root';
$password = $_ENV['DB_PASSWORD'] ?? '';
$backupDir = $input->getOption('path');
// Se o caminho for relativo, resolvemos a partir da raiz do projeto
if (!str_starts_with($backupDir, '/')) {
$backupDir = dirname(__DIR__, 3) . '/' . ltrim($backupDir, '/');
}
if (!is_dir($backupDir)) {
if (!mkdir($backupDir, 0755, true)) {
$output->writeln("<error>Não foi possível criar o diretório de backup: {$backupDir}</error>");
return Command::FAILURE;
}
}
$filename = $database . '_' . date('Y-m-d_H-i-s') . '.sql';
$fullPath = rtrim($backupDir, '/') . '/' . $filename;
// Escapamos a senha para o shell
$escapedPassword = escapeshellarg($password);
// Usamos mysqldump para criar o backup
// Note: Se o mysqldump não estiver no PATH, este comando pode falhar
$command = "mysqldump --host={$host} --port={$port} --user={$username} --password={$escapedPassword} {$database} > " . escapeshellarg($fullPath);
if ($input->getOption('compress')) {
$command .= " && gzip " . escapeshellarg($fullPath);
$fullPath .= '.gz';
}
exec($command . " 2>&1", $outputArray, $resultCode);
if ($resultCode === 0) {
$output->writeln("<info>Backup concluído com sucesso: {$fullPath}</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Erro ao realizar o backup (Código: {$resultCode}).</error>");
$output->writeln("<error>Saída: " . implode("\n", $outputArray) . "</error>");
return Command::FAILURE;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Database\Capsule\Manager as Capsule;
class DbExportCommand extends Command
{
protected static $defaultName = 'db:export';
protected static $defaultDescription = 'Exporta os dados de uma tabela para CSV ou JSON';
protected function configure(): void
{
$this
->addArgument('table', InputArgument::REQUIRED, 'O nome da tabela')
->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'O formato do arquivo (csv, json)', 'csv')
->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'O caminho para salvar o arquivo', 'storage/exports');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$tableName = $input->getArgument('table');
$format = strtolower($input->getOption('format'));
$path = $input->getOption('path');
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
if (!Capsule::schema()->hasTable($tableName)) {
$output->writeln("<error>Tabela '{$tableName}' não encontrada no banco de dados.</error>");
return Command::FAILURE;
}
$results = $connection->table($tableName)->get();
if ($results->isEmpty()) {
$output->writeln("<info>A tabela '{$tableName}' está vazia.</info>");
return Command::SUCCESS;
}
$output->writeln("<info>Exportando dados da tabela: {$tableName}</info>");
$directory = dirname(__DIR__, 3) . '/' . ltrim($path, '/');
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$filename = "{$tableName}_" . date('Y-m-d_H-i-s') . ".{$format}";
$fullPath = rtrim($directory, '/') . '/' . $filename;
if ($format === 'json') {
file_put_contents($fullPath, json_encode($results, JSON_PRETTY_PRINT));
} elseif ($format === 'csv') {
$fp = fopen($fullPath, 'w');
// Adiciona o cabeçalho
$headers = array_keys((array) $results[0]);
fputcsv($fp, $headers);
foreach ($results as $row) {
fputcsv($fp, (array) $row);
}
fclose($fp);
} else {
$output->writeln("<error>Formato '{$format}' não suportado.</error>");
return Command::FAILURE;
}
$output->writeln("<info>Dados exportados com sucesso para: {$fullPath}</info>");
return Command::SUCCESS;
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
class DbImportCommand extends Command
{
protected static $defaultName = 'db:import';
protected static $defaultDescription = 'Importa um arquivo SQL para o banco de dados';
protected function configure(): void
{
$this
->addArgument('file', InputArgument::REQUIRED, 'O caminho do arquivo SQL a ser importado');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
if (!file_exists($file)) {
$output->writeln("<error>Arquivo não encontrado: {$file}</error>");
return Command::FAILURE;
}
$output->writeln("<info>Importando arquivo: {$file}</info>");
try {
$sql = file_get_contents($file);
if (!$sql) {
$output->writeln("<error>Falha ao ler o arquivo SQL ou arquivo vazio.</error>");
return Command::FAILURE;
}
$connection = Capsule::connection();
$queries = $this->splitSqlQueries($sql);
$totalQueries = count($queries);
$successCount = 0;
$output->writeln("<comment>Total de queries encontradas: {$totalQueries}</comment>");
foreach ($queries as $index => $query) {
$query = trim($query);
if (empty($query)) {
continue;
}
try {
$connection->statement($query);
$successCount++;
$output->writeln("<info>Query " . ($index + 1) . "/{$totalQueries} executada com sucesso</info>");
} catch (\Exception $e) {
$output->writeln("<error>Erro na query " . ($index + 1) . ": " . $e->getMessage() . "</error>");
$output->writeln("<comment>Query: " . substr($query, 0, 100) . "...</comment>");
}
}
$output->writeln("<info>Importação concluída. {$successCount}/{$totalQueries} queries executadas com sucesso.</info>");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao importar arquivo: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
private function splitSqlQueries(string $sql): array
{
$queries = [];
$current = '';
$inString = false;
$stringChar = '';
$length = strlen($sql);
for ($i = 0; $i < $length; $i++) {
$char = $sql[$i];
if (($char === "'" || $char === '"') && ($i === 0 || $sql[$i - 1] !== '\\')) {
if (!$inString) {
$inString = true;
$stringChar = $char;
} elseif ($char === $stringChar) {
$inString = false;
}
}
if (!$inString && $char === ';') {
$queries[] = trim($current);
$current = '';
} else {
$current .= $char;
}
}
$current = trim($current);
if (!empty($current)) {
$queries[] = $current;
}
return array_filter($queries, function($query) {
return !empty(trim($query));
});
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbListCommand extends Command
{
protected static $defaultName = 'db:list';
protected static $defaultDescription = 'Lista todas as tabelas do banco de dados';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
$output->writeln("<info>Tabelas no banco de dados: {$dbName}</info>");
$tables = $connection->select('SHOW TABLES');
$key = "Tables_in_{$dbName}";
if (empty($tables)) {
$output->writeln('<info>Nenhuma tabela encontrada no banco de dados.</info>');
return Command::SUCCESS;
}
$table = new Table($output);
$table->setHeaders(['Tabela', 'Total de Registros']);
foreach ($tables as $t) {
$tableName = $t->$key;
$count = $connection->table($tableName)->count();
$table->addRow([$tableName, $count]);
}
$table->render();
$output->writeln("<info>Total de tabelas: " . count($tables) . "</info>");
return Command::SUCCESS;
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbMonitorCommand extends Command
{
protected static $defaultName = 'db:monitor';
protected static $defaultDescription = 'Monitora o status atual do banco de dados e processos ativos';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
$output->writeln("<info>Monitorando banco de dados: {$dbName}</info>");
$output->writeln("===============================================");
// Active processes
$processes = $connection->select('SHOW PROCESSLIST');
$output->writeln("<comment>Processos Ativos:</comment>");
$processTable = new Table($output);
$processTable->setHeaders(['ID', 'User', 'Host', 'Command', 'Time', 'State', 'Info']);
foreach ($processes as $p) {
$processTable->addRow([
$p->Id,
$p->User,
$p->Host,
$p->Command,
$p->Time,
$p->State,
substr($p->Info ?? '', 0, 50) . (strlen($p->Info ?? '') > 50 ? '...' : '')
]);
}
$processTable->render();
$output->writeln("");
// Database status variables
$status = $connection->select("SHOW STATUS WHERE Variable_name IN ('Threads_connected', 'Threads_running', 'Uptime', 'Questions')");
$output->writeln("<comment>Variáveis de Status:</comment>");
$statusTable = new Table($output);
$statusTable->setHeaders(['Variável', 'Valor']);
foreach ($status as $s) {
$statusTable->addRow([$s->Variable_name, $s->Value]);
}
$statusTable->render();
return Command::SUCCESS;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
class DbOptimizeCommand extends Command
{
protected static $defaultName = 'db:optimize';
protected static $defaultDescription = 'Otimiza as tabelas do banco de dados';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
$output->writeln("<info>Otimizando banco de dados: {$dbName}</info>");
$tables = $connection->select('SHOW TABLES');
$key = "Tables_in_{$dbName}";
foreach ($tables as $table) {
$tableName = $table->$key;
$output->write("Otimizando tabela: <comment>{$tableName}</comment>... ");
try {
$connection->statement("OPTIMIZE TABLE {$tableName}");
$output->writeln('<info>OK</info>');
} catch (\Exception $e) {
$output->writeln('<error>Erro ao otimizar</error>');
}
}
$output->writeln('<info>Otimização concluída!</info>');
return Command::SUCCESS;
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbQueryCommand extends Command
{
protected static $defaultName = 'db:query';
protected static $defaultDescription = 'Executa uma query SQL diretamente no banco de dados';
protected function configure(): void
{
$this
->addArgument('query', InputArgument::REQUIRED, 'A query SQL a ser executada');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$query = $input->getArgument('query');
$connection = Capsule::connection();
$output->writeln("<info>Executando query:</info> <comment>{$query}</comment>");
try {
if (stripos($query, 'select') === 0 || stripos($query, 'show') === 0 || stripos($query, 'describe') === 0) {
$results = $connection->select($query);
if (empty($results)) {
$output->writeln('<info>Nenhum resultado encontrado.</info>');
return Command::SUCCESS;
}
$table = new Table($output);
$headers = array_keys((array) $results[0]);
$table->setHeaders($headers);
foreach ($results as $row) {
$table->addRow((array) $row);
}
$table->render();
$output->writeln("<info>Total de registros: " . count($results) . "</info>");
} else {
$affected = $connection->affectingStatement($query);
$output->writeln("<info>Query executada com sucesso. Linhas afetadas: {$affected}</info>");
}
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao executar query: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbRelationshipsCommand extends Command
{
protected static $defaultName = 'db:relationships';
protected static $defaultDescription = 'Exibe as chaves estrangeiras de uma tabela';
protected function configure(): void
{
$this
->addArgument('table', InputArgument::REQUIRED, 'O nome da tabela');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$tableName = $input->getArgument('table');
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
$output->writeln("<info>Chaves Estrangeiras da Tabela: {$tableName}</info>");
try {
$query = "
SELECT
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = ? AND
TABLE_NAME = ? AND
REFERENCED_TABLE_NAME IS NOT NULL";
$relationships = $connection->select($query, [$dbName, $tableName]);
if (empty($relationships)) {
$output->writeln('<comment>Nenhuma chave estrangeira encontrada para esta tabela.</comment>');
return Command::SUCCESS;
}
$table = new Table($output);
$table->setHeaders(['Coluna', 'Constraint', 'Tabela Referenciada', 'Coluna Referenciada']);
foreach ($relationships as $rel) {
$table->addRow([
$rel->COLUMN_NAME,
$rel->CONSTRAINT_NAME,
$rel->REFERENCED_TABLE_NAME,
$rel->REFERENCED_COLUMN_NAME
]);
}
$table->render();
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao buscar relacionamentos: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DbSeedCommand extends Command
{
protected static $defaultName = 'db:seed';
protected static $defaultDescription = 'Executa as classes de seeder';
protected function configure(): void
{
$this
->addArgument('class', InputArgument::OPTIONAL, 'O nome da classe seeder específica para rodar');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$className = $input->getArgument('class');
$seedersPath = dirname(__DIR__, 3) . '/database/seeders';
if (!is_dir($seedersPath)) {
$output->writeln('<error>Diretório de seeders não encontrado.</error>');
return Command::FAILURE;
}
if ($className) {
return $this->runSeeder($className, $seedersPath, $output);
}
// Se não especificou classe, tenta rodar o DatabaseSeeder ou todos os arquivos .php
$dbSeederFile = $seedersPath . '/DatabaseSeeder.php';
if (file_exists($dbSeederFile)) {
return $this->runSeeder('DatabaseSeeder', $seedersPath, $output);
}
$files = glob($seedersPath . '/*.php');
if (empty($files)) {
$output->writeln('<info>Nenhum seeder encontrado.</info>');
return Command::SUCCESS;
}
foreach ($files as $file) {
$name = basename($file, '.php');
if ($name === 'DatabaseSeeder') continue; // Já rodamos se existisse
$this->runSeeder($name, $seedersPath, $output);
}
return Command::SUCCESS;
}
protected function runSeeder(string $name, string $path, OutputInterface $output): int
{
$file = $path . '/' . $name . '.php';
if (!file_exists($file)) {
$output->writeln("<error>Seeder não encontrado: {$name}</error>");
return Command::FAILURE;
}
$output->write("Rodando seeder: <info>{$name}</info>... ");
try {
require_once $file;
if (class_exists($name)) {
$seeder = new $name();
if (method_exists($seeder, 'run')) {
$seeder->run();
$output->writeln('<info>OK</info>');
return Command::SUCCESS;
} else {
$output->writeln('<comment>Método run() não encontrado.</comment>');
return Command::FAILURE;
}
} else {
$output->writeln("<error>Classe {$name} não encontrada no arquivo.</error>");
return Command::FAILURE;
}
} catch (\Exception $e) {
$output->writeln("<error>FALHA</error>");
$output->writeln("<error>Erro: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbShowCommand extends Command
{
protected static $defaultName = 'db:show';
protected static $defaultDescription = 'Exibe informações sobre o banco de dados';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connection = Capsule::connection();
$dbName = $connection->getDatabaseName();
$output->writeln("<info>Banco de Dados: {$dbName}</info>");
$output->writeln("Driver: " . $connection->getDriverName());
$tables = $connection->select('SHOW TABLE STATUS');
$table = new Table($output);
$table->setHeaders(['Tabela', 'Engine', 'Rows', 'Size (MB)', 'Collation']);
foreach ($tables as $t) {
$size = round(($t->Data_length + $t->Index_length) / 1024 / 1024, 2);
$table->addRow([
$t->Name,
$t->Engine,
$t->Rows,
$size,
$t->Collation
]);
}
$table->render();
return Command::SUCCESS;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class DbSyncSisTablesCommand extends Command
{
protected static $defaultName = 'db:sync-sis-tables';
protected static $defaultDescription = 'Substitui as tabelas sis_*** no evoluterSys2.sql pelas tabelas de syscms.sql';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$targetFile = 'evoluterSys2.sql';
$sourceFile = 'syscms.sql';
if (!file_exists($targetFile)) {
$output->writeln("<error>Arquivo de destino não encontrado: $targetFile</error>");
return Command::FAILURE;
}
if (!file_exists($sourceFile)) {
$output->writeln("<error>Arquivo de origem não encontrado: $sourceFile</error>");
return Command::FAILURE;
}
$output->writeln("<info>Iniciando a extração das tabelas sis_*** de $sourceFile...</info>");
$sisTablesDefinitions = $this->extractSisTables($sourceFile);
if (empty($sisTablesDefinitions)) {
$output->writeln("<error>Nenhuma tabela sis_*** encontrada em $sourceFile.</error>");
return Command::FAILURE;
}
$output->writeln("<info>Encontradas " . count($sisTablesDefinitions) . " tabelas sis_***. Substituindo em $targetFile...</info>");
$this->replaceSisTables($targetFile, $sisTablesDefinitions, $output);
$output->writeln("<info>Operação concluída com sucesso!</info>");
return Command::SUCCESS;
}
private function extractSisTables(string $filePath): array
{
$handle = fopen($filePath, 'r');
if (!$handle) return [];
$tables = [];
$currentTable = null;
$capture = false;
$buffer = "";
while (($line = fgets($handle)) !== false) {
// Detecta início de um CREATE TABLE sis_***
if (preg_match("/CREATE TABLE `?(sis_[^`\s(]+)`?/i", $line, $matches)) {
$currentTable = $matches[1];
$capture = true;
$buffer = $line;
continue;
}
if ($capture) {
$buffer .= $line;
// Detecta fim do CREATE TABLE (assume-se que termina com ); ou ); seguido de comentários/inserts se for dump compactado,
// mas para simplificar, buscamos o padrão );
if (preg_match("/\);/", $line)) {
$capture = false;
$tables[$currentTable] = $buffer;
$buffer = "";
$currentTable = null;
}
}
}
fclose($handle);
return $tables;
}
private function replaceSisTables(string $filePath, array $definitions, OutputInterface $output): void
{
$tempFile = $filePath . '.tmp';
$inHandle = fopen($filePath, 'r');
$outHandle = fopen($tempFile, 'w');
$skip = false;
$replacedCount = 0;
while (($line = fgets($inHandle)) !== false) {
// Verifica se a linha inicia uma tabela que queremos substituir
if (preg_match("/CREATE TABLE `?(sis_[^`\s(]+)`?/i", $line, $matches)) {
$tableName = $matches[1];
if (isset($definitions[$tableName])) {
$output->writeln("Substituindo tabela: <comment>$tableName</comment>");
fwrite($outHandle, $definitions[$tableName]);
$skip = true;
$replacedCount++;
unset($definitions[$tableName]); // Marca como usada
continue;
}
}
// Se estiver pulando, ignora as linhas até o fim da definição da tabela
if ($skip) {
if (preg_match("/\);/", $line)) {
$skip = false;
}
continue;
}
fwrite($outHandle, $line);
}
// Se sobraram tabelas no source que não existiam no target, podemos adicioná-las ao final ou apenas informar
if (!empty($definitions)) {
$output->writeln("<info>Adicionando novas tabelas sis_*** que não existiam no destino...</info>");
foreach ($definitions as $tableName => $def) {
$output->writeln("Adicionando: <comment>$tableName</comment>");
fwrite($outHandle, "\n" . $def . "\n");
$replacedCount++;
}
}
fclose($inHandle);
fclose($outHandle);
rename($tempFile, $filePath);
$output->writeln("<info>Total de tabelas processadas: $replacedCount</info>");
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
use Symfony\Component\Console\Helper\Table;
class DbTableCommand extends Command
{
protected static $defaultName = 'db:table';
protected static $defaultDescription = 'Exibe a estrutura de uma tabela específica';
protected function configure(): void
{
$this
->addArgument('table', InputArgument::REQUIRED, 'O nome da tabela');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$tableName = $input->getArgument('table');
$connection = Capsule::connection();
try {
$columns = $connection->select("SHOW COLUMNS FROM {$tableName}");
$output->writeln("<info>Estrutura da tabela: {$tableName}</info>");
$table = new Table($output);
$table->setHeaders(['Field', 'Type', 'Null', 'Key', 'Default', 'Extra']);
foreach ($columns as $column) {
$table->addRow([
$column->Field,
$column->Type,
$column->Null,
$column->Key,
$column->Default,
$column->Extra
]);
}
$table->render();
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Tabela '{$tableName}' não encontrada ou erro na query.</error>");
return Command::FAILURE;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Database\Capsule\Manager as Capsule;
class DbWipeCommand extends Command
{
protected static $defaultName = 'db:wipe';
protected static $defaultDescription = 'Drop all tables, views, and types';
protected function configure(): void
{
$this
->setHelp('This command drops all tables from the database without removing the database itself.')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the operation to run');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$input->getOption('force')) {
$output->writeln('<error>Atenção: Este comando irá apagar TODAS as tabelas do banco de dados!</error>');
$output->writeln('<comment>Use a opção --force para confirmar.</comment>');
return Command::FAILURE;
}
$output->writeln('<info>Limpando banco de dados...</info>');
$schema = Capsule::schema();
$tables = Capsule::connection()->select('SHOW FULL TABLES WHERE Table_type = "BASE TABLE"');
$dbName = Capsule::connection()->getDatabaseName();
$key = "Tables_in_{$dbName}";
Capsule::connection()->statement('SET FOREIGN_KEY_CHECKS = 0');
foreach ($tables as $table) {
$tableName = $table->$key;
$output->write("Dropando tabela: <comment>{$tableName}</comment>... ");
$schema->drop($tableName);
$output->writeln('<info>OK</info>');
}
Capsule::connection()->statement('SET FOREIGN_KEY_CHECKS = 1');
$output->writeln('<info>Banco de dados limpo com sucesso!</info>');
return Command::SUCCESS;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Kernel\Application;
class DebugContainerCommand extends Command
{
protected static $defaultName = 'debug:container';
protected static $defaultDescription = 'Lista todos os serviços registrados no container de injeção de dependência';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = Application::getInstance();
if (!$app) {
$output->writeln('<error>Não foi possível obter a instância da aplicação.</error>');
return Command::FAILURE;
}
$container = $app->getContainer();
// PHP-DI doesn't easily expose all registered definitions unless we use internal reflection
// or a specific configuration. For simplicity in this environment, we'll list what's common.
$output->writeln('<info>Serviços no Container DI:</info>');
$table = new Table($output);
$table->setHeaders(['ID', 'Status']);
$commonServices = [
\DI\Container::class,
\Kernel\Application::class,
\Kernel\ModuleLoader::class,
\Kernel\View::class,
'db',
'logging',
'mailer'
];
foreach ($commonServices as $service) {
$status = $container->has($service) ? '<info>Registrado</info>' : '<comment>Não definido</comment>';
$table->addRow([$service, $status]);
}
$table->render();
return Command::SUCCESS;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Kernel\Application;
class DebugRouterCommand extends Command
{
protected static $defaultName = 'debug:router';
protected static $defaultDescription = 'Lista detalhes das rotas registradas na aplicação';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = Application::getInstance();
if (!$app) {
$output->writeln('<error>Não foi possível obter a instância da aplicação.</error>');
return Command::FAILURE;
}
$routes = $app->getRoutes();
$output->writeln('<info>Rotas Registradas:</info>');
$table = new Table($output);
$table->setHeaders(['Método', 'Caminho', 'Handler']);
foreach ($routes as $route) {
[$method, $path, $handler] = $route;
$handlerStr = is_array($handler) ? implode('@', $handler) : 'Closure';
$table->addRow([$method, $path, $handlerStr]);
}
$table->render();
return Command::SUCCESS;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class DownCommand extends Command
{
protected static $defaultName = 'down';
protected static $defaultDescription = 'Coloca a aplicação em modo de manutenção';
protected function configure(): void
{
$this
->addOption('message', 'm', InputOption::VALUE_OPTIONAL, 'A mensagem a ser exibida durante a manutenção', 'O sistema está em manutenção.')
->addOption('retry', 'r', InputOption::VALUE_OPTIONAL, 'O tempo estimado para o retorno');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$directory = dirname(__DIR__, 3) . '/storage/framework';
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$downFile = $directory . '/down';
if (file_exists($downFile)) {
$output->writeln('<comment>A aplicação já está em modo de manutenção.</comment>');
return Command::SUCCESS;
}
$payload = [
'time' => time(),
'message' => $input->getOption('message'),
'retry' => $input->getOption('retry'),
];
if (file_put_contents($downFile, json_encode($payload, JSON_PRETTY_PRINT))) {
$output->writeln('<info>Aplicação agora está em modo de manutenção.</info>');
return Command::SUCCESS;
}
$output->writeln('<error>Falha ao colocar a aplicação em modo de manutenção.</error>');
return Command::FAILURE;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
class EnvListCommand extends Command
{
protected static $defaultName = 'env:list';
protected static $defaultDescription = 'Lista as variáveis de ambiente carregadas do .env';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Variáveis de Ambiente (.env):</info>');
$envFile = dirname(__DIR__, 3) . '/.env';
if (!file_exists($envFile)) {
$output->writeln('<error>Arquivo .env não encontrado.</error>');
return Command::FAILURE;
}
$table = new Table($output);
$table->setHeaders(['Variável', 'Valor']);
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// Ignorar comentários
if (str_starts_with(trim($line), '#')) {
continue;
}
if (str_contains($line, '=')) {
[$key, $value] = explode('=', $line, 2);
// Ocultar senhas e chaves sensíveis por segurança
if (preg_match('/(PASSWORD|SECRET|KEY|TOKEN)/i', $key)) {
$value = '********';
}
$table->addRow([trim($key), trim($value, '"\' ')]);
}
}
$table->render();
return Command::SUCCESS;
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class KeyGenerateCommand extends Command
{
protected static $defaultName = 'key:generate';
protected static $defaultDescription = 'Gera uma nova chave de aplicação (APP_KEY)';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$key = bin2hex(random_bytes(32));
$envFile = dirname(__DIR__, 3) . '/.env';
if (!file_exists($envFile)) {
$output->writeln("<error>Arquivo .env não encontrado.</error>");
return Command::FAILURE;
}
$content = file_get_contents($envFile);
if (strpos($content, 'APP_KEY=') !== false) {
$content = preg_replace('/APP_KEY=.*/', 'APP_KEY=' . $key, $content);
} else {
$content .= "\nAPP_KEY=" . $key;
}
if (file_put_contents($envFile, $content)) {
$output->writeln("<info>APP_KEY gerada com sucesso!</info>");
$output->writeln("<comment>Chave:</comment> {$key}");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao escrever no arquivo .env.</error>");
return Command::FAILURE;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LogsClearCommand extends Command
{
protected static $defaultName = 'logs:clear';
protected static $defaultDescription = 'Limpa os arquivos de log do sistema';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$logDir = dirname(__DIR__, 3) . '/logs';
if (!is_dir($logDir)) {
$output->writeln('<error>Diretório de logs não encontrado.</error>');
return Command::FAILURE;
}
$files = glob($logDir . '/*.log');
if (empty($files)) {
$output->writeln('<info>Nenhum arquivo de log para limpar.</info>');
return Command::SUCCESS;
}
foreach ($files as $file) {
$fileName = basename($file);
if (unlink($file)) {
$output->writeln("<info>Arquivo de log '{$fileName}' removido.</info>");
} else {
$output->writeln("<error>Falha ao remover o arquivo '{$fileName}'.</error>");
}
}
return Command::SUCCESS;
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeCommandCommand extends Command
{
protected static $defaultName = 'make:command';
protected static $defaultDescription = 'Cria um novo comando para o syscli';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe do comando (ex: TestCommand)')
->addArgument('command', InputArgument::REQUIRED, 'O comando a ser digitado (ex: app:test)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$commandName = $input->getArgument('command');
// Garantir que termina com Command
if (!str_ends_with($name, 'Command')) {
$name .= 'Command';
}
$commandsPath = __DIR__;
$filePath = $commandsPath . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O comando '{$name}' já existe.</error>");
return Command::FAILURE;
}
$template = <<<PHP
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class {$name} extends Command
{
protected static \$defaultName = '{$commandName}';
protected static \$defaultDescription = 'Descrição do comando {$commandName}';
protected function configure(): void
{
// Configure argumentos e opções aqui
}
protected function execute(InputInterface \$input, OutputInterface \$output): int
{
// Sua lógica aqui
\$output->writeln('<info>Executando comando {$commandName}...</info>');
return Command::SUCCESS;
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Comando '{$name}' criado com sucesso em app/Console/Commands/</info>");
$output->writeln("<comment>Lembre-se de registrar o comando no syscli se necessário.</comment>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do comando.</error>");
return Command::FAILURE;
}
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeControllerCommand extends Command
{
protected static $defaultName = 'make:controller';
protected static $defaultDescription = 'Cria um novo controlador em um módulo específico';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo (ex: blog)')
->addArgument('name', InputArgument::REQUIRED, 'O nome do controlador (ex: PostController)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$module = strtolower($input->getArgument('module'));
$name = $input->getArgument('name');
// Garantir que termina com Controller
if (!str_ends_with($name, 'Controller')) {
$name .= 'Controller';
}
$modulePath = dirname(__DIR__, 3) . '/modules/' . $module;
if (!is_dir($modulePath)) {
$output->writeln("<error>O módulo '{$module}' não existe.</error>");
return Command::FAILURE;
}
$controllersPath = $modulePath . '/classes/Controllers';
if (!is_dir($controllersPath)) {
mkdir($controllersPath, 0755, true);
}
$filePath = $controllersPath . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O controlador '{$name}' já existe no módulo '{$module}'.</error>");
return Command::FAILURE;
}
$namespace = "Modules\\" . ucfirst($module) . "\\Controllers";
$template = <<<PHP
<?php
namespace {$namespace};
use App\Controllers\BaseController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Kernel\View;
/**
* Controller {$name}
*/
class {$name} extends BaseController
{
/**
* Default action
*
* @param Request \$request
* @param Response \$response
* @return Response
*/
public function index(Request \$request, Response \$response): Response
{
// Sua lógica aqui
return \$response;
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Controlador '{$name}' criado com sucesso em modules/{$module}/classes/Controllers/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do controlador.</error>");
return Command::FAILURE;
}
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeExceptionCommand extends Command
{
protected static $defaultName = 'make:exception';
protected static $defaultDescription = 'Cria uma nova classe de exceção';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe de exceção (ex: InvalidPaymentException)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
if (!str_ends_with($name, 'Exception')) {
$name .= 'Exception';
}
$directory = dirname(__DIR__, 2) . '/Exceptions';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>A exceção '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace App\Exceptions;
use Exception;
class {$name} extends Exception
{
/**
* Exception logic here.
*/
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Exceção '{$name}' criada com sucesso em app/Exceptions/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo da exceção.</error>");
return Command::FAILURE;
}
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeHelperCommand extends Command
{
protected static $defaultName = 'make:helper';
protected static $defaultDescription = 'Cria um novo arquivo de helper';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome do arquivo helper (ex: string_helpers)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
if (!str_ends_with($name, '.php')) {
$name .= '.php';
}
$directory = dirname(__DIR__, 2) . '/Helpers';
$filePath = $directory . '/' . $name;
if (file_exists($filePath)) {
$output->writeln("<error>O helper '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
if (!function_exists('your_function_name')) {
/**
* Helper description.
*/
function your_function_name()
{
//
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Helper '{$name}' criado com sucesso em app/Helpers/</info>");
$output->writeln("<comment>Lembre-se de incluir o arquivo no composer.json ou carregar manualmente.</comment>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do helper.</error>");
return Command::FAILURE;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeInterfaceCommand extends Command
{
protected static $defaultName = 'make:interface';
protected static $defaultDescription = 'Cria uma nova interface PHP';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da interface (ex: PaymentInterface)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
if (!str_ends_with($name, 'Interface')) {
$name .= 'Interface';
}
$directory = dirname(__DIR__, 2) . '/Interfaces';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>A interface '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace App\Interfaces;
interface {$name}
{
/**
* Define interface methods.
*/
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Interface '{$name}' criada com sucesso em app/Interfaces/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo da interface.</error>");
return Command::FAILURE;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeMiddlewareCommand extends Command
{
protected static $defaultName = 'make:middleware';
protected static $defaultDescription = 'Cria um novo middleware na aplicação';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe do middleware (ex: CheckAgeMiddleware)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
// Garante que o nome termine com Middleware para padronização
if (!str_ends_with($name, 'Middleware')) {
$name .= 'Middleware';
}
$directory = dirname(__DIR__, 2) . '/Middlewares';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O middleware '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$template = $this->getTemplate($name);
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Middleware '{$name}' criado com sucesso em app/Middlewares/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do middleware.</error>");
return Command::FAILURE;
}
}
protected function getTemplate(string $className): string
{
$year = date('Y');
return <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*/
namespace App\Middlewares;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class {$className}
{
/**
* Handle an incoming request.
*
* @param Request \$request
* @param Closure \$next
* @return mixed
*/
public function handle(Request \$request, Closure \$next)
{
// Lógica do middleware aqui
return \$next(\$request);
}
}
PHP;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeMigrationCommand extends Command
{
protected static $defaultName = 'make:migration';
protected static $defaultDescription = 'Creates a new migration file';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'The migration name (e.g. create_users_table)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
// Convert name to class format (CreateUsersTable)
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
// Generate timestamp for ordering
$timestamp = date('YmdHis');
// Define file name
$fileName = "{$timestamp}_{$name}.php";
// Migrations directory path
$migrationsPath = dirname(__DIR__, 3) . '/database/migrations';
if (!is_dir($migrationsPath)) {
mkdir($migrationsPath, 0755, true);
}
$filePath = $migrationsPath . '/' . $fileName;
// Migration template
$template = <<<PHP
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class {$className}
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
if (!Capsule::schema()->hasTable('table_name')) {
Capsule::schema()->create('table_name', function (Blueprint \$table) {
\$table->id();
\$table->timestamps();
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Capsule::schema()->dropIfExists('table_name');
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Migração criada com sucesso:</info> {$fileName}");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo de migração.</error>");
return Command::FAILURE;
}
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Support\Str;
class MakeMigrationModuleCommand extends Command
{
protected static $defaultName = 'make:migration-module';
protected static $defaultDescription = 'Cria uma nova migração dentro de um módulo específico';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo')
->addArgument('name', InputArgument::REQUIRED, 'O nome da migração (ex: create_posts_table)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$module = strtolower($input->getArgument('module'));
$name = Str::snake($input->getArgument('name'));
$modulePath = dirname(__DIR__, 3) . "/modules/{$module}";
if (!is_dir($modulePath)) {
$output->writeln("<error>Módulo '{$module}' não encontrado.</error>");
return Command::FAILURE;
}
$migrationsPath = "{$modulePath}/database/migrations";
if (!is_dir($migrationsPath)) {
mkdir($migrationsPath, 0755, true);
}
// Busca o próximo número sequencial para o prefixo (SysCMS usa 001, 002...)
$existingFiles = glob("{$migrationsPath}/*.php");
$nextNumber = str_pad(count($existingFiles) + 1, 3, '0', STR_PAD_LEFT);
$fileName = "{$nextNumber}_{$name}.php";
$filePath = "{$migrationsPath}/{$fileName}";
$className = Str::studly($name);
$template = <<<PHP
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class {$className}
{
public function up()
{
Capsule::schema()->create('table_name', function (Blueprint \$table) {
\$table->bigIncrements('id');
// \$table->string('name');
\$table->timestamps();
});
}
public function down()
{
Capsule::schema()->dropIfExists('table_name');
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Migração criada com sucesso: modules/{$module}/database/migrations/{$fileName}</info>");
return Command::SUCCESS;
}
$output->writeln("<error>Falha ao criar o arquivo de migração.</error>");
return Command::FAILURE;
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeModelCommand extends Command
{
protected static $defaultName = 'make:model';
protected static $defaultDescription = 'Cria um novo modelo Eloquent em um módulo específico';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo (ex: blog)')
->addArgument('name', InputArgument::REQUIRED, 'O nome do modelo (ex: Post)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$module = strtolower($input->getArgument('module'));
$name = $input->getArgument('name');
$modulePath = dirname(__DIR__, 3) . '/modules/' . $module;
if (!is_dir($modulePath)) {
$output->writeln("<error>O módulo '{$module}' não existe.</error>");
return Command::FAILURE;
}
$modelsPath = $modulePath . '/classes/Models';
if (!is_dir($modelsPath)) {
mkdir($modelsPath, 0755, true);
}
$filePath = $modelsPath . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O modelo '{$name}' já existe no módulo '{$module}'.</error>");
return Command::FAILURE;
}
$namespace = "Modules\\" . ucfirst($module) . "\\Models";
$tableName = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $name)) . 's';
$template = <<<PHP
<?php
namespace {$namespace};
use Illuminate\Database\Eloquent\Model;
/**
* Model {$name}
*/
class {$name} extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected \$table = '{$tableName}';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected \$fillable = [
// Adicione os campos aqui
];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public \$timestamps = true;
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Modelo '{$name}' criado com sucesso em modules/{$module}/classes/Models/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do modelo.</error>");
return Command::FAILURE;
}
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeModuleCommand extends Command
{
protected static $defaultName = 'make:module';
protected static $defaultDescription = 'Cria a estrutura básica de um novo módulo';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome do módulo (ex: blog)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = strtolower($input->getArgument('name'));
$modulePath = dirname(__DIR__, 3) . '/modules/' . $name;
if (is_dir($modulePath)) {
$output->writeln("<error>O módulo '{$name}' já existe.</error>");
return Command::FAILURE;
}
// Criar estrutura de diretórios
$directories = [
$modulePath,
$modulePath . '/routes',
$modulePath . '/views',
$modulePath . '/classes',
$modulePath . '/classes/Controllers',
$modulePath . '/classes/Models',
];
foreach ($directories as $dir) {
if (!mkdir($dir, 0755, true)) {
$output->writeln("<error>Falha ao criar diretório: {$dir}</error>");
return Command::FAILURE;
}
}
// Criar module.json
$moduleJson = [
'name' => ucfirst($name),
'version' => '1.0.0',
'description' => 'Descrição do módulo ' . ucfirst($name),
'author' => 'SysCMS',
'enabled' => true
];
file_put_contents($modulePath . '/module.json', json_encode($moduleJson, JSON_PRETTY_PRINT));
// Criar rotas web.php
$webRoutes = <<<PHP
<?php
/** @var \Slim\App \$app */
\$app->group('/{$name}', function (\$group) {
\$group->get('', [\Modules\\ucfirst($name)\Controllers\\ucfirst($name)Controller::class, 'index']);
});
PHP;
file_put_contents($modulePath . '/routes/web.php', $webRoutes);
// Criar rotas api.php
$apiRoutes = "<?php\n\n/** @var \Slim\App \$app */\n";
file_put_contents($modulePath . '/routes/api.php', $apiRoutes);
// Criar Controlador inicial
$controllerName = ucfirst($name) . 'Controller';
$namespace = "Modules\\" . ucfirst($name) . "\\Controllers";
$controllerTemplate = <<<PHP
<?php
namespace {$namespace};
use App\Controllers\BaseController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Kernel\View;
/**
* Controller for {$name} module
*/
class {$controllerName} extends BaseController
{
/**
* Index action
*
* @param Request \$request
* @param Response \$response
* @return Response
*/
public function index(Request \$request, Response \$response): Response
{
return View::render(\$response, '@{$name}/index.twig', [
'title' => 'Módulo ' . ucfirst('{$name}')
]);
}
}
PHP;
file_put_contents($modulePath . '/classes/Controllers/' . $controllerName . '.php', $controllerTemplate);
// Criar view inicial
$viewTemplate = <<<HTML
{% extends "layouts/dashboard.twig" %}
{% block content %}
<div class="container-fluid">
<h1 class="mt-4">{{ title }}</h1>
<div class="card mb-4">
<div class="card-body">
Este é o módulo {{ title }} gerado automaticamente.
</div>
</div>
</div>
{% endblock %}
HTML;
file_put_contents($modulePath . '/views/index.twig', $viewTemplate);
$output->writeln("<info>Módulo '{$name}' criado com sucesso em modules/{$name}</info>");
return Command::SUCCESS;
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeRepositoryCommand extends Command
{
protected static $defaultName = 'make:repository';
protected static $defaultDescription = 'Cria uma nova classe de repositório';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe de repositório (ex: UserRepository)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
if (!str_ends_with($name, 'Repository')) {
$name .= 'Repository';
}
$directory = dirname(__DIR__, 2) . '/Repositories';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O repositório '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace App\Repositories;
class {$name}
{
/**
* Repository logic here.
*/
public function all()
{
// Return all records
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Repositório '{$name}' criado com sucesso em app/Repositories/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do repositório.</error>");
return Command::FAILURE;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeSeedCommand extends Command
{
protected static $defaultName = 'make:seed';
protected static $defaultDescription = 'Cria uma nova classe de seeder';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe seeder (ex: UserSeeder)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
// Garantir que termina com Seeder
if (!str_ends_with($name, 'Seeder')) {
$name .= 'Seeder';
}
$seedersPath = dirname(__DIR__, 3) . '/database/seeders';
if (!is_dir($seedersPath)) {
mkdir($seedersPath, 0755, true);
}
$filePath = $seedersPath . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O seeder '{$name}' já existe.</error>");
return Command::FAILURE;
}
$template = <<<PHP
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
class {$name}
{
/**
* Run the database seeds.
*
* @return void
*/
public function run(): void
{
// Capsule::table('users')->insert([
// 'user_name' => 'Admin',
// 'user_email' => 'admin@example.com',
// 'user_password' => password_hash('password', PASSWORD_DEFAULT),
// ]);
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Seeder criado com sucesso:</info> database/seeders/{$name}.php");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do seeder.</error>");
return Command::FAILURE;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeServiceCommand extends Command
{
protected static $defaultName = 'make:service';
protected static $defaultDescription = 'Cria uma nova classe de serviço';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da classe de serviço (ex: PaymentService)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
if (!str_ends_with($name, 'Service')) {
$name .= 'Service';
}
$directory = dirname(__DIR__, 2) . '/Services';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>O serviço '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace App\Services;
class {$name}
{
/**
* Service logic here.
*/
public function execute()
{
// Implement logic
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Serviço '{$name}' criado com sucesso em app/Services/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do serviço.</error>");
return Command::FAILURE;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MakeTraitCommand extends Command
{
protected static $defaultName = 'make:trait';
protected static $defaultDescription = 'Cria uma nova Trait PHP';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome da trait (ex: HasUuid)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$directory = dirname(__DIR__, 2) . '/Traits';
$filePath = $directory . '/' . $name . '.php';
if (file_exists($filePath)) {
$output->writeln("<error>A trait '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace App\Traits;
trait {$name}
{
/**
* Trait logic here.
*/
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Trait '{$name}' criada com sucesso em app/Traits/</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo da trait.</error>");
return Command::FAILURE;
}
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class MakeViewCommand extends Command
{
protected static $defaultName = 'make:view';
protected static $defaultDescription = 'Cria um novo template Twig';
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'O nome do template (ex: users/index)')
->addOption('module', 'm', InputOption::VALUE_OPTIONAL, 'O módulo onde o template será criado');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$module = $input->getOption('module');
// Garante a extensão .twig
if (!str_ends_with($name, '.twig')) {
$name .= '.twig';
}
if ($module) {
$directory = dirname(__DIR__, 3) . "/modules/{$module}/views";
} else {
$directory = dirname(__DIR__, 3) . "/templates";
}
$filePath = $directory . '/' . $name;
$fileDir = dirname($filePath);
if (file_exists($filePath)) {
$output->writeln("<error>O template '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($fileDir)) {
mkdir($fileDir, 0755, true);
}
$template = "{% extends \"layouts/base.twig\" %}\n\n{% block content %}\n<div class=\"container mt-4\">\n <h1>" . ucfirst(basename($name, '.twig')) . "</h1>\n <p>Novo template criado via CLI.</p>\n</div>\n{% endblock %}\n";
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Template '{$name}' criado com sucesso em " . ($module ? "modules/{$module}/views/" : "templates/") . "</info>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do template.</error>");
return Command::FAILURE;
}
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
class MigrateCommand extends Command
{
protected static $defaultName = 'migrate';
protected static $defaultDescription = 'Executes database migrations';
protected function configure(): void
{
$this
->setHelp('This command checks and executes all pending database migrations.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln([
'Iniciando execução das migrations...',
'=====================================',
'',
]);
// Ensure migrations table exists
$this->ensureMigrationsTableExists();
$migrationsPath = dirname(__DIR__, 3) . '/database/migrations';
$modulesPath = dirname(__DIR__, 3) . '/modules';
$migrationFiles = [];
// Core migrations
if (is_dir($migrationsPath)) {
$coreFiles = glob($migrationsPath . '/*.php');
if ($coreFiles) {
$migrationFiles = array_merge($migrationFiles, $coreFiles);
}
} else {
$output->writeln("<error>Diretório de migrations não encontrado: $migrationsPath</error>");
}
// Module migrations
if (is_dir($modulesPath)) {
$moduleFiles = glob($modulesPath . '/*/database/migrations/*.php');
if ($moduleFiles) {
$migrationFiles = array_merge($migrationFiles, $moduleFiles);
}
}
// Sort migration files by filename (keeping chronological order if timestamp/numbering exists)
usort($migrationFiles, function($a, $b) {
return basename($a) <=> basename($b);
});
if (empty($migrationFiles)) {
$output->writeln('<info>Nenhuma migração encontrada.</info>');
return Command::SUCCESS;
}
// Get already executed migrations
$executedMigrations = Capsule::table('migrations')->pluck('migration')->toArray();
$nextBatch = Capsule::table('migrations')->max('batch') + 1;
$count = 0;
foreach ($migrationFiles as $file) {
$fileName = basename($file, '.php');
// Skip if already executed
if (in_array($fileName, $executedMigrations)) {
continue;
}
// Remove numeric prefix (e.g. 001_)
$cleanName = preg_replace('/^\d+_/', '', $fileName);
// Convert snake_case to PascalCase (e.g. create_bus_table -> CreateBusTable)
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $cleanName)));
$output->write("Executando migration: <info>{$fileName}</info>... ");
try {
require_once $file;
if (class_exists($className)) {
$migration = new $className();
// Check if up method exists
if (method_exists($migration, 'up')) {
$migration->up();
// Record migration as executed
Capsule::table('migrations')->insert([
'migration' => $fileName,
'batch' => $nextBatch
]);
$output->writeln('<info>OK</info>');
$count++;
} else {
$output->writeln('<comment>SKIPPED (método up não encontrado)</comment>');
}
} else {
$output->writeln("<error>ERRO (Classe {$className} não encontrada)</error>");
}
} catch (\Exception $e) {
$output->writeln("<error>FALHA</error>");
$output->writeln("<error>Erro: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
if ($count === 0) {
$output->writeln('<info>Nada para migrar.</info>');
} else {
$output->writeln([
'',
"<info>Total de {$count} migration(s) processada(s) com sucesso!</info>",
]);
}
return Command::SUCCESS;
}
/**
* Ensure the migrations table exists in the database.
*
* @return void
*/
protected function ensureMigrationsTableExists(): void
{
if (!Capsule::schema()->hasTable('migrations')) {
Capsule::schema()->create('migrations', function ($table) {
$table->increments('id');
$table->string('migration');
$table->integer('batch');
});
}
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\ArrayInput;
class MigrateFreshCommand extends Command
{
protected static $defaultName = 'migrate:fresh';
protected static $defaultDescription = 'Drop all tables and re-run all migrations';
protected function configure(): void
{
$this
->addOption('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed command should be re-run');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Iniciando limpeza do banco de dados (fresh)...</info>');
// 1. Wipe database
$wipeCommand = $this->getApplication()->find('db:wipe');
$wipeInput = new ArrayInput(['--force' => true]);
$wipeCommand->run($wipeInput, $output);
// 2. Run migrations
$migrateCommand = $this->getApplication()->find('migrate');
$migrateInput = new ArrayInput([]);
$migrateCommand->run($migrateInput, $output);
// 3. Optional seed
if ($input->getOption('seed')) {
$seedCommand = $this->getApplication()->find('db:seed');
$seedInput = new ArrayInput([]);
$seedCommand->run($seedInput, $output);
}
$output->writeln('<info>Operação migrate:fresh concluída com sucesso!</info>');
return Command::SUCCESS;
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\ArrayInput;
class MigrateRefreshCommand extends Command
{
protected static $defaultName = 'migrate:refresh';
protected static $defaultDescription = 'Reset and re-run all migrations';
protected function configure(): void
{
$this
->addOption('step', 's', InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted', 0)
->addOption('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed command should be re-run');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$step = (int) $input->getOption('step');
if ($step > 0) {
$output->writeln("<info>Revertendo {$step} passos de migração...</info>");
$rollbackCommand = $this->getApplication()->find('migrate:rollback');
$rollbackInput = new ArrayInput(['--step' => $step]);
$rollbackCommand->run($rollbackInput, $output);
} else {
$output->writeln('<info>Revertendo todas as migrações (reset)...</info>');
// Reset all by calling rollback in loop or a specific reset command
// Assuming we'll use rollback in loop for simplicity or adding a reset later
// Here we use step=999 as a convention for "all" in our rollback implementation
$rollbackCommand = $this->getApplication()->find('migrate:rollback');
$rollbackInput = new ArrayInput(['--step' => 999]);
$rollbackCommand->run($rollbackInput, $output);
}
$output->writeln('<info>Executando migrações novamente...</info>');
$migrateCommand = $this->getApplication()->find('migrate');
$migrateInput = new ArrayInput([]);
$migrateCommand->run($migrateInput, $output);
if ($input->getOption('seed')) {
$output->writeln('<info>Executando seeders...</info>');
$seedCommand = $this->getApplication()->find('db:seed');
$seedInput = new ArrayInput([]);
$seedCommand->run($seedInput, $output);
}
$output->writeln('<info>Operação migrate:refresh concluída com sucesso!</info>');
return Command::SUCCESS;
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Illuminate\Database\Capsule\Manager as Capsule;
class MigrationStatusCommand extends Command
{
protected static $defaultName = 'migrate:status';
protected static $defaultDescription = 'Mostra o status de todas as migrações';
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!Capsule::schema()->hasTable('migrations')) {
$output->writeln('<error>Tabela de migrations não encontrada.</error>');
return Command::FAILURE;
}
$migrationsPath = dirname(__DIR__, 3) . '/database/migrations';
$modulesPath = dirname(__DIR__, 3) . '/modules';
$migrationFiles = [];
// Core migrations
if (is_dir($migrationsPath)) {
$coreFiles = glob($migrationsPath . '/*.php');
if ($coreFiles) {
$migrationFiles = array_merge($migrationFiles, $coreFiles);
}
}
// Module migrations
if (is_dir($modulesPath)) {
$moduleFiles = glob($modulesPath . '/*/database/migrations/*.php');
if ($moduleFiles) {
$migrationFiles = array_merge($migrationFiles, $moduleFiles);
}
}
// Sort migration files
usort($migrationFiles, function($a, $b) {
return basename($a) <=> basename($b);
});
// Get executed migrations
$executedMigrations = Capsule::table('migrations')->get()->keyBy('migration');
$table = new Table($output);
$table->setHeaders(['Executada?', 'Migration', 'Batch']);
foreach ($migrationFiles as $file) {
$fileName = basename($file, '.php');
$executed = $executedMigrations->has($fileName);
$table->addRow([
$executed ? '<info>Sim</info>' : '<comment>Não</comment>',
$fileName,
$executed ? $executedMigrations->get($fileName)->batch : '-'
]);
}
$table->render();
return Command::SUCCESS;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Database\Capsule\Manager as Capsule;
class ModuleDeleteCommand extends Command
{
protected static $defaultName = 'module:delete';
protected static $defaultDescription = 'Remove fisicamente um módulo e suas tabelas associadas';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo a ser removido')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Confirmar a remoção sem prompt');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$moduleName = $input->getArgument('module');
$force = $input->getOption('force');
$modulePath = dirname(__DIR__, 3) . '/modules/' . $moduleName;
if (!is_dir($modulePath)) {
$output->writeln("<error>Módulo '{$moduleName}' não encontrado em modules/.</error>");
return Command::FAILURE;
}
if (!$force) {
$output->writeln("<comment>Você tem certeza que deseja remover o módulo '{$moduleName}' e TODAS as suas tabelas associadas?</comment>");
$output->writeln("<info>Use a opção --force para confirmar.</info>");
return Command::FAILURE;
}
// 1. Drop associated tables if listed in module.json
$jsonPath = $modulePath . '/module.json';
if (file_exists($jsonPath)) {
$config = json_decode(file_get_contents($jsonPath), true);
if (isset($config['tables']) && is_array($config['tables'])) {
$output->writeln("<info>Limpando tabelas do banco de dados...</info>");
Capsule::connection()->statement('SET FOREIGN_KEY_CHECKS = 0');
foreach ($config['tables'] as $table) {
$output->write("Dropando tabela: <comment>{$table}</comment>... ");
Capsule::schema()->dropIfExists($table);
$output->writeln('<info>OK</info>');
}
Capsule::connection()->statement('SET FOREIGN_KEY_CHECKS = 1');
}
}
// 2. Remove files
$output->writeln("<info>Removendo arquivos do módulo...</info>");
$this->deleteDirectory($modulePath);
$output->writeln("<info>Módulo '{$moduleName}' removido com sucesso!</info>");
return Command::SUCCESS;
}
protected function deleteDirectory($dir): bool
{
if (!file_exists($dir)) {
return true;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!$this->deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
return rmdir($dir);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ModuleDisableCommand extends Command
{
protected static $defaultName = 'module:disable';
protected static $defaultDescription = 'Desativa um módulo no arquivo module.json';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo a ser desativado');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$moduleName = $input->getArgument('module');
$modulePath = dirname(__DIR__, 3) . '/modules/' . $moduleName . '/module.json';
if (!file_exists($modulePath)) {
$output->writeln("<error>Módulo '{$moduleName}' não encontrado ou arquivo module.json inexistente.</error>");
return Command::FAILURE;
}
$config = json_decode(file_get_contents($modulePath), true);
if ($config === null) {
$output->writeln("<error>Erro ao ler o arquivo module.json do módulo '{$moduleName}'.</error>");
return Command::FAILURE;
}
$isActive = $config['module_active'] ?? $config['module_ative'] ?? false;
if ($isActive === false) {
$output->writeln("<comment>O módulo '{$moduleName}' já está desativado.</comment>");
return Command::SUCCESS;
}
// Maintain the key already used in the file if possible
if (isset($config['module_ative'])) {
$config['module_ative'] = false;
} else {
$config['module_active'] = false;
}
if (file_put_contents($modulePath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))) {
$output->writeln("<info>Módulo '{$moduleName}' desativado com sucesso!</info>");
return Command::SUCCESS;
}
$output->writeln("<error>Falha ao atualizar o arquivo module.json do módulo '{$moduleName}'.</error>");
return Command::FAILURE;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ModuleEnableCommand extends Command
{
protected static $defaultName = 'module:enable';
protected static $defaultDescription = 'Ativa um módulo no arquivo module.json';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo a ser ativado');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$moduleName = $input->getArgument('module');
$modulePath = dirname(__DIR__, 3) . '/modules/' . $moduleName . '/module.json';
if (!file_exists($modulePath)) {
$output->writeln("<error>Módulo '{$moduleName}' não encontrado ou arquivo module.json inexistente.</error>");
return Command::FAILURE;
}
$config = json_decode(file_get_contents($modulePath), true);
if ($config === null) {
$output->writeln("<error>Erro ao ler o arquivo module.json do módulo '{$moduleName}'.</error>");
return Command::FAILURE;
}
$isActive = $config['module_active'] ?? $config['module_ative'] ?? false;
if ($isActive === true) {
$output->writeln("<comment>O módulo '{$moduleName}' já está ativado.</comment>");
return Command::SUCCESS;
}
// Maintain the key already used in the file if possible
if (isset($config['module_ative'])) {
$config['module_ative'] = true;
} else {
$config['module_active'] = true;
}
if (file_put_contents($modulePath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))) {
$output->writeln("<info>Módulo '{$moduleName}' ativado com sucesso!</info>");
return Command::SUCCESS;
}
$output->writeln("<error>Falha ao atualizar o arquivo module.json do módulo '{$moduleName}'.</error>");
return Command::FAILURE;
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Illuminate\Database\Capsule\Manager as Capsule;
use Kernel\ModuleLoader;
class ModuleListCommand extends Command
{
protected static $defaultName = 'module:list';
protected static $defaultDescription = 'Lista todos os módulos instalados e suas informações';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$modulesPath = dirname(__DIR__, 3) . '/modules';
$loader = new ModuleLoader($modulesPath);
try {
// Get all directories in modules/
$dirs = glob($modulesPath . '/*', GLOB_ONLYDIR);
if (empty($dirs)) {
$output->writeln('<info>Nenhum módulo encontrado em modules/.</info>');
return Command::SUCCESS;
}
$output->writeln('<info>Módulos do Sistema:</info>');
$table = new Table($output);
$table->setHeaders(['Nome', 'Versão', 'Ativo', 'Pronto', 'DB', 'Descrição']);
// Get registered modules from DB using correct column name
$dbModules = [];
if (Capsule::schema()->hasTable('modules')) {
$dbModules = Capsule::table('modules')->pluck('modules_name')->toArray();
}
foreach ($dirs as $dir) {
$name = basename($dir);
$jsonFile = $dir . '/module.json';
$version = 'N/A';
$active = '<comment>Não</comment>';
$ready = '<comment>Não</comment>';
$dbStatus = in_array($name, $dbModules) ? '<info>Sim</info>' : '<comment>Não</comment>';
$description = 'Sem descrição';
if (file_exists($jsonFile)) {
$config = json_decode(file_get_contents($jsonFile), true);
if ($config) {
$version = $config['version'] ?? '1.0.0';
// Check both module_active and module_ative, default to true if missing
$isActive = $config['module_active'] ?? $config['module_ative'] ?? true;
$active = $isActive ? '<info>Sim</info>' : '<comment>Não</comment>';
$isReady = $config['ready'] ?? true;
$ready = $isReady ? '<info>Sim</info>' : '<comment>Não</comment>';
$description = $config['description'] ?? 'Sem descrição';
}
}
$table->addRow([
$name,
$version,
$active,
$ready,
$dbStatus,
substr($description, 0, 40) . (strlen($description) > 40 ? '...' : '')
]);
}
$table->render();
$output->writeln("<info>Total de módulos: " . count($dirs) . "</info>");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln('<error>Erro ao listar módulos: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ModuleMakeProviderCommand extends Command
{
protected static $defaultName = 'module:make-provider';
protected static $defaultDescription = 'Cria um novo Service Provider para um módulo específico';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo')
->addArgument('name', InputArgument::REQUIRED, 'O nome do Service Provider (ex: EventServiceProvider)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$moduleName = $input->getArgument('module');
$name = $input->getArgument('name');
if (!str_ends_with($name, 'ServiceProvider')) {
$name .= 'ServiceProvider';
}
$modulePath = dirname(__DIR__, 3) . '/modules/' . $moduleName;
$directory = $modulePath . '/Providers';
$filePath = $directory . '/' . $name . '.php';
if (!is_dir($modulePath)) {
$output->writeln("<error>Módulo '{$moduleName}' não encontrado.</error>");
return Command::FAILURE;
}
if (file_exists($filePath)) {
$output->writeln("<error>O Service Provider '{$name}' já existe!</error>");
return Command::FAILURE;
}
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$year = date('Y');
$moduleNamespace = "Modules\\" . ucfirst($moduleName);
$template = <<<PHP
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) {$year} Marco Costa
* All rights reserved.
*/
namespace {$moduleNamespace}\Providers;
class {$name}
{
/**
* Register the service provider.
*/
public function register()
{
// Register services in the container
}
/**
* Boot the service provider.
*/
public function boot()
{
// Boot logic
}
}
PHP;
if (file_put_contents($filePath, $template)) {
$output->writeln("<info>Service Provider '{$name}' criado com sucesso em modules/{$moduleName}/Providers/</info>");
$output->writeln("<comment>Lembre-se de registrar o provider no arquivo module.json se necessário.</comment>");
return Command::SUCCESS;
} else {
$output->writeln("<error>Falha ao criar o arquivo do Service Provider.</error>");
return Command::FAILURE;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ModulePublishCommand extends Command
{
protected static $defaultName = 'module:publish';
protected static $defaultDescription = 'Copia os assets de um módulo para a pasta public do sistema';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do módulo');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$module = strtolower($input->getArgument('module'));
$modulePath = dirname(__DIR__, 3) . "/modules/{$module}";
if (!is_dir($modulePath)) {
$output->writeln("<error>Módulo '{$module}' não encontrado.</error>");
return Command::FAILURE;
}
$assetsPath = "{$modulePath}/assets";
if (!is_dir($assetsPath)) {
$output->writeln("<comment>Nenhum diretório 'assets' encontrado no módulo '{$module}'.</comment>");
return Command::SUCCESS;
}
$publicPath = dirname(__DIR__, 3) . "/public/assets/modules/{$module}";
if (!is_dir($publicPath)) {
mkdir($publicPath, 0755, true);
}
$output->writeln("<info>Copiando assets de '{$module}' para 'public/assets/modules/{$module}'...</info>");
$this->copyDirectory($assetsPath, $publicPath);
$output->writeln("<info>Assets publicados com sucesso!</info>");
return Command::SUCCESS;
}
protected function copyDirectory($source, $destination)
{
$dir = opendir($source);
@mkdir($destination);
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (is_dir($source . '/' . $file)) {
$this->copyDirectory($source . '/' . $file, $destination . '/' . $file);
} else {
copy($source . '/' . $file, $destination . '/' . $file);
}
}
}
closedir($dir);
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
class ModuleRegisterCommand extends Command
{
protected static $defaultName = 'module:register';
protected static $defaultDescription = 'Registra um módulo existente na tabela modules do banco de dados';
protected function configure(): void
{
$this
->addArgument('module', InputArgument::REQUIRED, 'O nome do diretório do módulo');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$moduleName = $input->getArgument('module');
$modulePath = dirname(__DIR__, 3) . '/modules/' . $moduleName . '/module.json';
if (!file_exists($modulePath)) {
$output->writeln("<error>Módulo '{$moduleName}' não encontrado ou arquivo module.json inexistente.</error>");
return Command::FAILURE;
}
$config = json_decode(file_get_contents($modulePath), true);
if ($config === null) {
$output->writeln("<error>Erro ao processar o arquivo module.json do módulo '{$moduleName}'.</error>");
return Command::FAILURE;
}
// Verifica se a tabela modules existe
if (!Capsule::schema()->hasTable('modules')) {
$output->writeln("<error>A tabela 'modules' não existe no banco de dados. Execute as migrations primeiro.</error>");
return Command::FAILURE;
}
// Verifica se o módulo já está registrado usando a coluna correta
$existing = Capsule::table('modules')->where('modules_name', $moduleName)->first();
$data = [
'modules_name' => $moduleName,
'modules_display_name' => $config['display_name'] ?? $moduleName,
'modules_description' => $config['description'] ?? '',
'modules_version' => $config['version'] ?? '1.0.0',
'modules_ative' => $config['module_active'] ?? $config['module_ative'] ?? true,
'updated_at' => date('Y-m-d H:i:s')
];
if ($existing) {
Capsule::table('modules')->where('modules_name', $moduleName)->update($data);
$output->writeln("<info>Registro do módulo '{$moduleName}' atualizado com sucesso!</info>");
} else {
$data['created_at'] = date('Y-m-d H:i:s');
Capsule::table('modules')->insert($data);
$output->writeln("<info>Módulo '{$moduleName}' registrado com sucesso na tabela modules!</info>");
}
return Command::SUCCESS;
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModuleTestCommand extends Command
{
protected static $defaultName = 'module:test';
protected function configure()
{
$this
->setDescription('Executa testes automatizados para um módulo específico')
->addArgument('name', InputArgument::REQUIRED, 'Nome do módulo');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$moduleName = strtolower($input->getArgument('name'));
$modulePath = dirname(dirname(dirname(__DIR__))) . '/modules/' . $moduleName;
if (!is_dir($modulePath)) {
$io->error("Módulo '{$moduleName}' não encontrado em {$modulePath}");
return Command::FAILURE;
}
$testPath = $modulePath . '/tests';
if (!is_dir($testPath)) {
$io->warning("O módulo '{$moduleName}' não possui uma pasta de testes em {$testPath}.");
return Command::INVALID;
}
$io->title("Iniciando testes para o módulo: " . ucfirst($moduleName));
// Execute PHPUnit pointing to the module's test directory
// Using vendor/bin/phpunit which is standard for the project
$phpunitExec = dirname(dirname(dirname(__DIR__))) . '/vendor/bin/phpunit';
// We use the full path to phpunit to avoid command not found issues
$command = "php \"{$phpunitExec}\" \"{$testPath}\"";
$io->note("Executando: {$command}");
passthru($command, $exitCode);
if ($exitCode === 0) {
$io->success("Todos os testes do módulo '{$moduleName}' passaram!");
} else {
$io->error("Falha nos testes do módulo '{$moduleName}'.");
}
return $exitCode === 0 ? Command::SUCCESS : Command::FAILURE;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class OpcacheClearCommand extends Command
{
protected static $defaultName = 'opcache:clear';
protected static $defaultDescription = 'Limpa o cache do OPcache (PHP)';
protected function configure(): void
{
$this->setHelp('Este comando executa a função opcache_reset() para invalidar todo o cache de bytecode.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!function_exists('opcache_reset')) {
$output->writeln('<error>OPcache não está habilitado ou a função opcache_reset() não está disponível.</error>');
return Command::FAILURE;
}
if (opcache_reset()) {
$output->writeln('<info>OPcache limpo com sucesso!</info>');
return Command::SUCCESS;
}
$output->writeln('<error>Falha ao limpar o OPcache.</error>');
return Command::FAILURE;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Illuminate\Database\Capsule\Manager as Capsule;
class PermissionListCommand extends Command
{
protected static $defaultName = 'permission:list';
protected static $defaultDescription = 'Lista todas as permissões cadastradas no sistema';
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!Capsule::schema()->hasTable('permissions')) {
$output->writeln('<error>Tabela de permissões não encontrada.</error>');
return Command::FAILURE;
}
$permissions = Capsule::table('permissions')
->orderBy('permission_name')
->get();
if ($permissions->isEmpty()) {
$output->writeln('<info>Nenhuma permissão encontrada.</info>');
return Command::SUCCESS;
}
$table = new Table($output);
$table->setHeaders(['ID', 'Nome', 'Slug', 'Descrição']);
foreach ($permissions as $permission) {
$table->addRow([
$permission->permission_id,
$permission->permission_name,
$permission->permission_slug,
$permission->permission_description ?? '-'
]);
}
$table->render();
return Command::SUCCESS;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\Capsule\Manager as Capsule;
class RollbackCommand extends Command
{
protected static $defaultName = 'migrate:rollback';
protected static $defaultDescription = 'Reverte a última leva de migrações do banco de dados';
protected function configure(): void
{
$this
->setHelp('Este comando reverte as migrações que foram executadas na última operação de migração.')
->addOption('step', 's', \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted', 0);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$step = (int) $input->getOption('step');
$output->writeln([
'Iniciando rollback das migrations...',
'=====================================',
'',
]);
if (!Capsule::schema()->hasTable('migrations')) {
$output->writeln('<error>Tabela de migrations não encontrada.</error>');
return Command::FAILURE;
}
if ($step === 999) {
// Special case for migrate:reset or fresh
$migrationsToRollback = Capsule::table('migrations')
->orderBy('id', 'desc')
->get();
} elseif ($step > 0) {
// Revert specific number of migrations
$migrationsToRollback = Capsule::table('migrations')
->orderBy('id', 'desc')
->limit($step)
->get();
} else {
// Pegar o último batch
$lastBatch = Capsule::table('migrations')->max('batch');
if (!$lastBatch) {
$output->writeln('<info>Nenhuma migração para reverter.</info>');
return Command::SUCCESS;
}
$migrationsToRollback = Capsule::table('migrations')
->where('batch', $lastBatch)
->orderBy('id', 'desc')
->get();
}
if ($migrationsToRollback->isEmpty()) {
$output->writeln('<info>Nenhuma migração para reverter.</info>');
return Command::SUCCESS;
}
$migrationsPath = dirname(__DIR__, 3) . '/database/migrations';
$modulesPath = dirname(__DIR__, 3) . '/modules';
foreach ($migrationsToRollback as $row) {
$fileName = $row->migration;
$file = $this->findMigrationFile($fileName, $migrationsPath, $modulesPath);
if (!$file) {
$output->writeln("<error>Arquivo de migração não encontrado: {$fileName}.php</error>");
continue;
}
// Converter nome para classe
$cleanName = preg_replace('/^\d+_/', '', $fileName);
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $cleanName)));
$output->write("Revertendo migration: <info>{$fileName}</info>... ");
try {
require_once $file;
if (class_exists($className)) {
$migration = new $className();
if (method_exists($migration, 'down')) {
$migration->down();
// Remover registro da tabela de migrations
Capsule::table('migrations')->where('id', $row->id)->delete();
$output->writeln('<info>OK</info>');
} else {
$output->writeln('<comment>Método down() não encontrado.</comment>');
}
} else {
$output->writeln("<error>Classe {$className} não encontrada.</error>");
}
} catch (\Exception $e) {
$output->writeln("<error>Erro: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
return Command::SUCCESS;
}
protected function findMigrationFile(string $name, string $corePath, string $modulesPath): ?string
{
// Procurar no core
$coreFile = $corePath . '/' . $name . '.php';
if (file_exists($coreFile)) {
return $coreFile;
}
// Procurar nos módulos
$moduleFiles = glob($modulesPath . '/*/database/migrations/' . $name . '.php');
if ($moduleFiles && count($moduleFiles) > 0) {
return $moduleFiles[0];
}
return null;
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Kernel\Application;
use Kernel\ModuleLoader;
class RouteListCommand extends Command
{
protected static $defaultName = 'route:list';
protected function configure(): void
{
$this->setDescription('Lists all routes registered in the application.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// We need to instantiate the application to load the routes
// But be careful not to execute session_start() if it is already active or causes output
// Simulate application environment
if (session_status() == PHP_SESSION_NONE) {
// Avoid headers error if something attempts to start session
// But Application initSession attempts to start.
// Let's suppress session errors for the command
@session_start();
}
$app = new Application();
// The Application constructor already loads routes via initRouter and initModuleLoader
// But we must ensure it loads everything that adds routes
// Since Application is a singleton, we can try to get the instance or create it
// If we create new Application(), it runs the constructor.
$routes = $app->getRoutes();
if (empty($routes)) {
$output->writeln('<info>Nenhuma rota encontrada.</info>');
return Command::SUCCESS;
}
$table = new Table($output);
$table->setHeaders(['Método', 'URI', 'Handler']);
foreach ($routes as $route) {
[$method, $uri, $handler] = $route;
$handlerName = 'Closure';
if (is_array($handler)) {
if (is_object($handler[0])) {
$handlerName = get_class($handler[0]) . '@' . $handler[1];
} elseif (is_string($handler[0])) {
$handlerName = $handler[0] . '@' . $handler[1];
}
} elseif (is_string($handler)) {
$handlerName = $handler;
}
$table->addRow([
$method,
$uri,
$handlerName
]);
}
$table->render();
return Command::SUCCESS;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class StorageLinkCommand extends Command
{
protected static $defaultName = 'storage:link';
protected static $defaultDescription = 'Create the symbolic link from "public/storage" to "storage/uploads"';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$root = dirname(__DIR__, 3);
$target = $root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'uploads';
$link = $root . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'storage';
if (!is_dir($target)) {
if (!mkdir($target, 0755, true)) {
$output->writeln('<error>Could not create directory: ' . $target . '</error>');
return Command::FAILURE;
}
$output->writeln('<info>Created directory: ' . $target . '</info>');
}
if (file_exists($link) || is_link($link)) {
$output->writeln('<error>The "public/storage" already exists (as a file, directory or link).</error>');
return Command::FAILURE;
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// On Windows, try Junction first as it doesn't require admin privileges
$command = sprintf('mklink /J "%s" "%s"', $link, $target);
exec($command, $cmdOutput, $returnVar);
if ($returnVar === 0) {
$output->writeln('<info>The [public/storage] link has been connected to [storage/uploads] (Junction).</info>');
return Command::SUCCESS;
}
// Fallback to symlink if junction failed (though unlikely)
if (@symlink($target, $link)) {
$output->writeln('<info>The [public/storage] link has been connected to [storage/uploads] (Symlink).</info>');
return Command::SUCCESS;
}
} else {
// On Linux/Unix
if (@symlink($target, $link)) {
$output->writeln('<info>The [public/storage] link has been connected to [storage/uploads].</info>');
return Command::SUCCESS;
}
}
$output->writeln('<error>The links could not be created. Please check permissions.</error>');
return Command::FAILURE;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpCommand extends Command
{
protected static $defaultName = 'up';
protected static $defaultDescription = 'Tira a aplicação do modo de manutenção';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$downFile = dirname(__DIR__, 3) . '/storage/framework/down';
if (!file_exists($downFile)) {
$output->writeln('<comment>A aplicação já está ativa.</comment>');
return Command::SUCCESS;
}
if (unlink($downFile)) {
$output->writeln('<info>Aplicação agora está ativa.</info>');
return Command::SUCCESS;
}
$output->writeln('<error>Falha ao tirar a aplicação do modo de manutenção.</error>');
return Command::FAILURE;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Modules\Auth\Models\User;
class UserDeleteCommand extends Command
{
protected static $defaultName = 'user:delete';
protected static $defaultDescription = 'Remove um usuário do sistema';
protected function configure(): void
{
$this
->addArgument('user', InputArgument::REQUIRED, 'O ID ou E-mail do usuário a ser removido')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Confirmar a remoção sem prompt');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$userInput = $input->getArgument('user');
$force = $input->getOption('force');
$user = is_numeric($userInput)
? User::find($userInput)
: User::where('users_email', $userInput)->first();
if (!$user) {
$output->writeln("<error>Usuário '{$userInput}' não encontrado.</error>");
return Command::FAILURE;
}
if (!$force) {
$output->writeln("<comment>Você tem certeza que deseja remover o usuário '{$user->users_name}' ({$user->users_email})?</comment>");
$output->writeln("<info>Use a opção --force para confirmar.</info>");
return Command::FAILURE;
}
try {
$user->delete();
$output->writeln("<info>Usuário '{$user->users_name}' removido com sucesso!</info>");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao remover usuário: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Modules\Auth\Models\User;
use Symfony\Component\Console\Helper\Table;
class UserListCommand extends Command
{
protected static $defaultName = 'user:list';
protected static $defaultDescription = 'Lista todos os usuários cadastrados no sistema';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$users = User::all();
if ($users->isEmpty()) {
$output->writeln('<info>Nenhum usuário encontrado.</info>');
return Command::SUCCESS;
}
$table = new Table($output);
$table->setHeaders(['ID', 'ID Bus', 'Nome', 'Email', 'Status', 'Criado em']);
foreach ($users as $user) {
$table->addRow([
$user->users_id,
$user->users_bus_id ?? '-',
$user->users_name,
$user->users_email,
($user->users_status === 'active') ? '<info>Ativo</info>' : '<comment>' . ucfirst($user->users_status) . '</comment>',
$user->created_at
]);
}
$table->render();
$output->writeln("<info>Total de usuários: " . $users->count() . "</info>");
return Command::SUCCESS;
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Modules\Auth\Models\User;
class UserPasswordCommand extends Command
{
protected static $defaultName = 'user:password';
protected static $defaultDescription = 'Altera a senha de um usuário cadastrado no sistema';
protected function configure(): void
{
$this
->addArgument('user', InputArgument::REQUIRED, 'O ID ou E-mail do usuário')
->addArgument('password', InputArgument::REQUIRED, 'A nova senha');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$userInput = $input->getArgument('user');
$newPassword = $input->getArgument('password');
$user = is_numeric($userInput)
? User::find($userInput)
: User::where('users_email', $userInput)->first();
if (!$user) {
$output->writeln("<error>Usuário '{$userInput}' não encontrado.</error>");
return Command::FAILURE;
}
try {
$user->users_password = password_hash($newPassword, PASSWORD_DEFAULT);
$user->save();
$output->writeln("<info>Senha do usuário '{$user->users_name}' alterada com sucesso!</info>");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao alterar senha: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Modules\Auth\Models\User;
class UserRegisterCommand extends Command
{
protected static $defaultName = 'user:register';
protected static $defaultDescription = 'Registra um novo usuário de forma interativa';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$helper = $this->getHelper('question');
$output->writeln('<info>Cadastro de Novo Usuário</info>');
$output->writeln('-------------------------');
// Name
$nameQuestion = new Question('Nome completo: ');
$name = $helper->ask($input, $output, $nameQuestion);
if (empty($name)) {
$output->writeln('<error>O nome é obrigatório.</error>');
return Command::FAILURE;
}
// Email
$emailQuestion = new Question('E-mail: ');
$email = $helper->ask($input, $output, $emailQuestion);
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$output->writeln('<error>E-mail inválido ou vazio.</error>');
return Command::FAILURE;
}
if (User::where('users_email', $email)->exists()) {
$output->writeln("<error>Já existe um usuário com o e-mail {$email}.</error>");
return Command::FAILURE;
}
// Password
$passwordQuestion = new Question('Senha: ');
$passwordQuestion->setHidden(true);
$passwordQuestion->setHiddenFallback(false);
$password = $helper->ask($input, $output, $passwordQuestion);
if (strlen($password) < 6) {
$output->writeln('<error>A senha deve ter pelo menos 6 caracteres.</error>');
return Command::FAILURE;
}
// Bus ID
$busIdQuestion = new Question('ID Bus (opcional): ');
$busId = $helper->ask($input, $output, $busIdQuestion);
try {
$user = new User();
$user->users_name = $name;
$user->users_email = $email;
$user->users_password = password_hash($password, PASSWORD_DEFAULT);
$user->users_role = 'user';
$user->users_bus_id = $busId;
$user->users_status = 'active';
$user->save();
$output->writeln("\n<info>Usuário '{$name}' cadastrado com sucesso!</info>");
$output->writeln("ID: {$user->users_id}");
$output->writeln("E-mail: {$user->users_email}");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao cadastrar usuário: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Modules\Auth\Models\User;
class UserRoleCommand extends Command
{
protected static $defaultName = 'user:role';
protected static $defaultDescription = 'Altera o nível de acesso (role) de um usuário';
protected function configure(): void
{
$this
->addArgument('user', InputArgument::REQUIRED, 'O ID ou E-mail do usuário')
->addArgument('role', InputArgument::REQUIRED, 'O novo nível de acesso (ex: user, admin, super_admin)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$userInput = $input->getArgument('user');
$newRole = strtolower($input->getArgument('role'));
$user = is_numeric($userInput)
? User::find($userInput)
: User::where('users_email', $userInput)->first();
if (!$user) {
$output->writeln("<error>Usuário '{$userInput}' não encontrado.</error>");
return Command::FAILURE;
}
try {
$oldRole = $user->users_role;
$user->users_role = $newRole;
$user->save();
$output->writeln("<info>Nível de acesso do usuário '{$user->users_name}' alterado com sucesso!</info>");
$output->writeln("De: <comment>{$oldRole}</comment> para: <info>{$newRole}</info>");
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Erro ao alterar nível de acesso: " . $e->getMessage() . "</error>");
return Command::FAILURE;
}
}
}
+653
View File
@@ -0,0 +1,653 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Controllers;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Modules\Auth\Models\User;
use App\Models\Module;
use Modules\Company\Models\Company;
use Illuminate\Database\Capsule\Manager as DB;
class AdminController extends BaseAdminController
{
/**
* Display the main dashboard.
*
* @param Request $request
* @return Response
*/
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new Response('', 302, ['Location' => url('/login')]);
}
// Get enabled modules for the user's company
$enabledModules = $this->getEnabledModules($user);
// Get widgets from authorized modules
$widgets = $this->getAuthorizedWidgets($enabledModules, $user);
// Get system statistics
$stats = $this->getSystemStats($user);
// Sidebar and common data are handled by renderAdminView,
// but since dashboard/index.twig expects specific variables,
// we can still pass them explicitly if we use the old way,
// OR we can use renderAdminView and just pass the extra data.
// Check if SEO module is installed but not configured
$seoPending = false;
try {
$seoModule = Module::where('modules_name', 'seo')->active()->first();
if ($seoModule && empty($seoModule->modules_settings)) {
$seoPending = true;
}
} catch (\Throwable $e) {
// Silently fail if something goes wrong with SEO check
error_log("SEO Check Error: " . $e->getMessage());
}
// Using renderAdminView for consistency:
return $this->renderAdminView('dashboard/index.twig', [
'stats' => $stats,
'recent_activity' => $this->getRecentActivity($user),
'widgets' => $widgets,
'seo_pending' => $seoPending,
'cache_cleared' => $request->query->get('cache_cleared'),
'deleted_count' => $request->query->get('deleted'),
'opcache_cleared' => $request->query->get('opcache')
]);
}
/**
* Get authorized widgets for the user.
*
* @param \Illuminate\Database\Eloquent\Collection $modules
* @param User $user
* @return array
*/
protected function getAuthorizedWidgets($modules, User $user): array
{
$widgets = [];
foreach ($modules as $module) {
if (!$this->userHasModulePermission($user, $module)) {
continue;
}
$moduleNamespace = $module->modules_namespace;
// If namespace is not defined, try to guess it from name (fallback)
if (!$moduleNamespace) {
$moduleNamespace = "Modules\\" . ucfirst($module->modules_name);
}
// Ensure namespace starts with \Modules\
if (strpos($moduleNamespace, 'Modules\\') !== 0 && strpos($moduleNamespace, '\\Modules\\') !== 0) {
$moduleNamespace = "Modules\\" . ltrim($moduleNamespace, '\\');
}
$widgetClass = (strpos($moduleNamespace, '\\') === 0 ? "" : "\\") . $moduleNamespace . "\\Widgets\\DashboardWidget";
if (class_exists($widgetClass)) {
try {
$widget = new $widgetClass();
// Check if user can view this widget
if ($this->userCanViewWidget($user, $widget)) {
$widgets[] = $widget->render();
}
} catch (\Throwable $e) {
error_log("Error loading widget {$widgetClass}: " . $e->getMessage());
}
}
}
return $widgets;
}
/**
* Check if user can view a widget.
*
* @param User $user
* @param mixed $widget
* @return bool
*/
protected function userCanViewWidget(User $user, $widget): bool
{
// Check if widget defines required permissions
if (method_exists($widget, 'getRequiredPermission')) {
$requiredPermission = $widget->getRequiredPermission();
return $user->hasPermission($requiredPermission);
}
// If no specific permission, check module access
return true;
}
/**
* Get system statistics.
*
* @param User $user
* @return array
*/
protected function getSystemStats(User $user): array
{
try {
if ($user->isSuperAdmin()) {
return $this->getSuperAdminStats();
}
return $this->getCompanyStats($user);
} catch (\Exception $e) {
error_log("Error loading system stats: " . $e->getMessage());
return [
'total_companies' => 0,
'active_companies' => 0,
'total_users' => 0,
'active_users' => 0,
'total_modules' => 0,
'active_modules' => 0,
'user_count' => 0,
'enabled_modules' => 0
];
}
}
/**
* Get statistics for Super Admin.
*
* @return array
*/
protected function getSuperAdminStats(): array
{
return [
'total_companies' => class_exists('Modules\Company\Models\Company') ? \Modules\Company\Models\Company::count() : 0,
'active_companies' => class_exists('Modules\Company\Models\Company') ? \Modules\Company\Models\Company::active()->count() : 0,
'total_users' => User::count(),
'active_users' => User::active()->count(),
'total_modules' => Module::count(),
'active_modules' => Module::active()->count()
];
}
/**
* Get statistics for company users.
*
* @param User $user
* @return array
*/
protected function getCompanyStats(User $user): array
{
$companyId = $user->users_bus_id;
return [
'user_count' => User::where('users_bus_id', $companyId)->count(),
'active_users' => User::where('users_bus_id', $companyId)->active()->count(),
'enabled_modules' => DB::table('bus_modules')
->where('bus_modules_bus_id', $companyId)
->where('bus_modules_is_enabled', true)
->count()
];
}
/**
* Get recent activity for the user.
*
* @param User $user
* @return \Illuminate\Database\Eloquent\Collection
*/
protected function getRecentActivity(User $user)
{
try {
if (!class_exists('App\Models\ActivityLog')) {
return collect([]);
}
return \App\Models\ActivityLog::with('user')
->forUser($user->users_id)
->orderByDate('desc')
->limit(10)
->get();
} catch (\Exception $e) {
error_log("Error loading recent activity: " . $e->getMessage());
return collect([]);
}
}
/**
* Show module installation form.
*
* @param Request $request
* @return Response
*/
public function showInstallModule(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('', 302, ['Location' => url('/dashboard')]);
}
$success = isset($_SESSION['flash_success']) ? (array) $_SESSION['flash_success'] : [];
$error = isset($_SESSION['flash_error']) ? (array) $_SESSION['flash_error'] : [];
unset($_SESSION['flash_success'], $_SESSION['flash_error']);
return $this->renderAdminView('modules/install.twig', [
'error' => $error,
'success' => $success
]);
}
/**
* Install a module from zip file.
*
* @param Request $request
* @return Response
*/
public function installModule(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('', 302, ['Location' => url('/dashboard')]);
}
try {
// Validate uploaded file
if (!$request->files->has('module_zip')) {
throw new \Exception(__('modules.error_no_file'));
}
$file = $request->files->get('module_zip');
// Validate file type
if ($file->getMimeType() !== 'application/zip' && $file->getMimeType() !== 'application/x-zip-compressed') {
throw new \Exception(__('modules.error_invalid_zip'));
}
// Create temporary directory
$tempDir = sys_get_temp_dir() . '/module_install_' . time();
mkdir($tempDir, 0755, true);
// Extract zip file
$zip = new \ZipArchive();
if ($zip->open($file->getPathname()) !== TRUE) {
throw new \Exception(__('modules.error_open_zip'));
}
if (!$zip->extractTo($tempDir)) {
throw new \Exception(__('modules.error_extract_zip'));
}
$zip->close();
// Validate module structure
$this->validateModuleStructure($tempDir);
// Read module configuration
$moduleConfig = $this->readModuleConfig($tempDir);
// Check if module already exists
if (Module::where('modules_name', $moduleConfig['name'])->exists()) {
throw new \Exception(__('modules.error_module_exists'));
}
// Copy module files to modules directory
$modulesPath = dirname(__DIR__, 2) . '/modules/' . $moduleConfig['name'];
$this->recursiveCopy($tempDir, $modulesPath);
// Run migrations if they exist
$this->runModuleMigrations($modulesPath);
// Check if module was registered by migrations
$module = Module::where('modules_name', $moduleConfig['name'])->first();
if (!$module) {
// Register module in database
$module = $this->registerModule($moduleConfig);
}
// Enable module for all companies if specified
if ($request->request->get('enable_for_all', false)) {
$this->enableModuleForAllCompanies($module->modules_id, $user->users_id);
}
// Clean up
$this->recursiveDelete($tempDir);
$_SESSION['flash_success'][] = __('modules.success_install');
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
} catch (\Throwable $e) {
// Clean up on error
if (isset($tempDir) && is_dir($tempDir)) {
$this->recursiveDelete($tempDir);
}
$_SESSION['flash_error'][] = $e->getMessage();
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
}
}
/**
* Validate module structure.
*
* @param string $modulePath
* @throws \Exception
*/
protected function validateModuleStructure(string $modulePath): void
{
$requiredFiles = ['module.json'];
foreach ($requiredFiles as $file) {
if (!file_exists($modulePath . '/' . $file)) {
throw new \Exception(__('modules.error_missing_file', ['file' => $file]));
}
}
}
/**
* Read module configuration.
*
* @param string $modulePath
* @return array
* @throws \Exception
*/
protected function readModuleConfig(string $modulePath): array
{
$configPath = $modulePath . '/module.json';
if (!file_exists($configPath)) {
throw new \Exception(__('modules.error_missing_config'));
}
$config = json_decode(file_get_contents($configPath), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(__('modules.error_invalid_config', ['error' => json_last_error_msg()]));
}
$requiredFields = ['name', 'version', 'description'];
foreach ($requiredFields as $field) {
if (empty($config[$field])) {
throw new \Exception(__('modules.error_missing_field', ['field' => $field]));
}
}
return $config;
}
/**
* Recursively copy directory.
*
* @param string $source
* @param string $destination
*/
protected function recursiveCopy(string $source, string $destination): void
{
if (!is_dir($destination)) {
mkdir($destination, 0755, true);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
// Calculate subPathName manually to ensure compatibility
$len = strlen($source);
$subPathName = substr($item->getPathname(), $len + 1);
$targetPath = $destination . '/' . $subPathName;
if ($item->isDir()) {
mkdir($targetPath, 0755, true);
} else {
copy($item, $targetPath);
}
}
}
/**
* Recursively delete directory.
*
* @param string $dir
*/
protected function recursiveDelete(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (is_dir($path)) {
$this->recursiveDelete($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/**
* Run module migrations.
*
* @param string $modulePath
*/
protected function runModuleMigrations(string $modulePath): void
{
$migrationsPath = $modulePath . '/database/migrations';
if (!is_dir($migrationsPath)) {
return;
}
$migrations = glob($migrationsPath . '/*.php');
foreach ($migrations as $migration) {
require_once $migration;
// Try to find class name from file content
$content = file_get_contents($migration);
if (preg_match('/class\s+(\w+)/', $content, $matches)) {
$className = $matches[1];
if (class_exists($className)) {
$instance = new $className();
if (method_exists($instance, 'up')) {
try {
$instance->up();
} catch (\Exception $e) {
error_log("Migration failed for $className: " . $e->getMessage());
throw $e; // Re-throw to show error to user
}
}
} else {
error_log("Class $className not found in migration file: $migration");
}
} else {
error_log("Could not find class name in migration file: $migration");
}
}
}
/**
* Fix module menus in database.
*
* @return Response
*/
public function fixModuleMenus(): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access denied', 403);
}
$modules = Module::all();
$count = 0;
foreach ($modules as $module) {
$modulePath = dirname(__DIR__, 2) . '/' . $module->modules_path;
try {
$config = $this->readModuleConfig($modulePath);
// Update module settings with menu
$module->modules_settings = array_merge($module->modules_settings ?? [], $config['settings'] ?? [], ['menu' => $config['menu'] ?? []]);
// Also update top-level module fields that might be in module.json
if (isset($config['display_name'])) $module->modules_display_name = $config['display_name'];
if (isset($config['description'])) $module->modules_description = $config['description'];
if (isset($config['version'])) $module->modules_version = $config['version'];
if (isset($config['icon'])) $module->modules_icon = $config['icon'];
if (isset($config['menu_side_bar'])) $module->menu_side_bar = (bool)$config['menu_side_bar'];
if (isset($config['menu_nav_bar'])) $module->menu_nav_bar = (bool)$config['menu_nav_bar'];
if (isset($config['order'])) $module->modules_order = (int)$config['order'];
if (isset($config['namespace'])) $module->modules_namespace = $config['namespace'];
$module->save();
$count++;
} catch (\Exception $e) {
// Ignore errors for individual modules
error_log("Failed to update menu for module {$module->modules_name}: " . $e->getMessage());
}
}
$_SESSION['flash_success'][] = __('modules.success_menus_updated', ['count' => $count]);
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
}
/**
* Register module in database.
*
* @param array $config
* @return Module
*/
protected function registerModule(array $config): Module
{
return Module::create([
'modules_name' => $config['name'],
'modules_display_name' => $config['display_name'] ?? $config['name'],
'modules_description' => $config['description'],
'modules_version' => $config['version'],
'modules_author' => $config['author'] ?? 'Unknown',
'modules_ative' => true,
'modules_public' => $config['public'] ?? false,
'modules_blocked' => false,
'modules_dependencies' => $config['dependencies'] ?? [],
'modules_permissions' => $config['permissions'] ?? [],
'modules_settings' => array_merge($config['settings'] ?? [], ['menu' => $config['menu'] ?? []]),
'modules_path' => 'modules/' . $config['name'],
'modules_namespace' => $config['namespace'] ?? ('Modules\\' . ucfirst($config['name'])),
'modules_icon' => $config['icon'] ?? 'bi bi-gear',
'menu_side_bar' => $config['menu_side_bar'] ?? true,
'menu_nav_bar' => $config['menu_nav_bar'] ?? false,
'modules_order' => $config['order'] ?? 999,
'modules_install_date' => \Carbon\Carbon::now(),
'modules_update_date' => \Carbon\Carbon::now()
]);
}
/**
* Enable module for all companies.
*
* @param int $moduleId
* @param int $enabledBy
*/
protected function enableModuleForAllCompanies(int $moduleId, int $enabledBy): void
{
$companies = Company::all();
foreach ($companies as $company) {
$company->enableModule($moduleId, $enabledBy);
}
}
/**
* Search for modules and users.
*
* @param Request $request
* @return Response
*/
public function search(Request $request): Response
{
$user = $this->getCurrentUser();
$query = $request->query->get('q');
if (!$user) {
return new Response('', 302, ['Location' => url('/login')]);
}
if (empty($query)) {
return $this->renderAdminView('dashboard/search_results.twig', [
'query' => '',
'results' => ['modules' => [], 'users' => []]
]);
}
$results = [
'modules' => [],
'users' => []
];
// Search Modules
$results['modules'] = Module::where('modules_name', 'LIKE', "%{$query}%")
->orWhere('modules_display_name', 'LIKE', "%{$query}%")
->orWhere('modules_description', 'LIKE', "%{$query}%")
->get();
// Search Users (Super Admin or Company Admin)
if ($user->isSuperAdmin()) {
$results['users'] = User::where('users_name', 'LIKE', "%{$query}%")
->orWhere('users_email', 'LIKE', "%{$query}%")
->get();
} elseif ($user->users_bus_id) {
$results['users'] = User::where('users_bus_id', $user->users_bus_id)
->where(function($q) use ($query) {
$q->where('users_name', 'LIKE', "%{$query}%")
->orWhere('users_email', 'LIKE', "%{$query}%");
})
->get();
}
return $this->renderAdminView('dashboard/search_results.twig', [
'query' => $query,
'results' => $results
]);
}
}
+465
View File
@@ -0,0 +1,465 @@
<?php
namespace App\Controllers;
use Kernel\View;
use Modules\Auth\Models\User;
use App\Models\Module;
use Modules\Company\Models\Company;
use Illuminate\Database\Capsule\Manager as DB;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
abstract class BaseAdminController
{
protected View $view;
public function __construct(View $view)
{
$this->view = $view;
}
/**
* Render an admin view with common data (sidebar, user, etc).
*
* @param string $viewName
* @param array $data
* @return Response
*/
protected function renderAdminView(string $viewName, array $data = []): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new Response('', 302, ['Location' => url('/login')]);
}
// Get flash messages
$flashMessages = [];
if (isset($_SESSION['flash'])) {
$flashMessages = $_SESSION['flash'];
unset($_SESSION['flash']);
}
// Get enabled modules for the user's company
$enabledModules = $this->getEnabledModules($user);
// Build sidebar based on user permissions
$sidebarItems = $this->buildUserSidebar($enabledModules, $user);
// Build navbar based on user permissions
$navbarItems = $this->buildUserNavbar($enabledModules, $user);
// Get admin theme slug (default sb-admin)
$adminThemeSlug = 'sb-admin';
// Get config pages visibility
$configPagesVisibility = [];
if (class_exists('Modules\Configindex\Models\ConfigPage')) {
$baseDir = dirname(dirname(__DIR__));
$activeTheme = \Kernel\View::getActiveTheme() ?? 'default';
$indexViewsDir = $baseDir . '/modules/index/views';
$themeViewsDir = $baseDir . '/themes/' . $activeTheme . '/modules/index/views';
$pages = \Modules\Configindex\Models\ConfigPage::where('config_index_bus_id', $user->users_bus_id)->get();
foreach ($pages as $page) {
$key = $page->config_index_key;
// Verifica se o arquivo físico existe
$fileExists = false;
if ($key === 'index') {
$fileExists = true;
} else {
$possiblePaths = [
$indexViewsDir . '/' . $key . '.twig',
$indexViewsDir . '/' . $key . '.php',
$themeViewsDir . '/' . $key . '.twig',
$themeViewsDir . '/' . $key . '.php',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.twig',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.php',
$baseDir . '/modules/' . $key . '/views/index.twig',
];
foreach ($possiblePaths as $path) {
if (is_file($path)) {
$fileExists = true;
break;
}
}
}
$configPagesVisibility[$key] = [
'public' => (bool)$page->config_index_show_public_nav && $fileExists,
'dashboard' => (bool)$page->config_index_show_dashboard_nav && $fileExists,
'title' => $page->config_index_title ?: ucfirst($key)
];
}
// Garante que as páginas padrão tenham uma configuração inicial, mas respeita o banco de dados
foreach (['about', 'contact', 'services'] as $key) {
if (!isset($configPagesVisibility[$key])) {
$configPagesVisibility[$key] = ['public' => false, 'dashboard' => false, 'title' => ucfirst($key)];
}
}
}
$commonData = [
'auth' => ['user' => $user],
'user' => $user,
'sidebar_items' => $sidebarItems,
'navbar_items' => $navbarItems,
'current_company' => $this->getCurrentCompany($user),
'admin_theme_slug' => $adminThemeSlug,
'config_pages_visibility' => $configPagesVisibility,
'flash_messages' => $flashMessages
];
return new Response($this->view->render($viewName, array_merge($commonData, $data)));
}
protected function redirect(string $path, int $status = 302): Response
{
return new RedirectResponse(url($path), $status);
}
/**
* Get current authenticated user.
*
* @return User|null
*/
protected function getCurrentUser(): ?User
{
if (!isset($_SESSION['user_id'])) {
return null;
}
try {
return User::find($_SESSION['user_id']);
} catch (\Exception $e) {
error_log("Error loading user: " . $e->getMessage());
return null;
}
}
/**
* Check if current user can edit/delete an object.
* Only author, admin or super_admin are allowed.
*
* @param int|string $authorId
* @return bool
*/
protected function canEditObject($authorId): bool
{
$user = $this->getCurrentUser();
if (!$user) {
return false;
}
if ($user->isSuperAdmin() || $user->isAdmin()) {
return true;
}
return (int) $user->users_id === (int) $authorId;
}
/**
* Get enabled modules for the user.
*
* @param User $user
* @return \Illuminate\Database\Eloquent\Collection
*/
protected function getEnabledModules(User $user)
{
if ($user->isSuperAdmin()) {
// Super Admin sees all active modules
return Module::where('modules_ative', true)
->orderBy('modules_order', 'asc')
->get();
}
// For other users, get modules enabled for their company
$modules = Module::join('bus_modules', 'modules.modules_id', '=', 'bus_modules.bus_modules_module_id')
->where('bus_modules.bus_modules_bus_id', $user->users_bus_id)
->where('bus_modules.bus_modules_is_enabled', true)
->where('modules.modules_ative', true)
->orderBy('modules.modules_order', 'asc')
->select('modules.*', 'bus_modules.bus_modules_show_public_nav', 'bus_modules.bus_modules_show_dashboard_nav')
->get();
// If user is Admin, they see everything enabled for the company.
// If user is Member or User, we MUST ensure they see modules they have explicit permissions for,
// even if the module doesn't have bus_modules_show_dashboard_nav = true (if that was the blocker).
// If we're missing core modules (auth, index) in the company's enabled list,
// we should add them manually if they are active system-wide,
// because they are essential for the dashboard to function.
// We also add 'company' to ensure Admins can always manage their company details.
$coreModules = Module::whereIn('modules_name', ['auth', 'index', 'company'])
->where('modules_ative', true)
->get();
foreach ($coreModules as $core) {
if (!$modules->contains('modules_name', $core->modules_name)) {
$modules->push($core);
}
}
return $modules;
}
/**
* Build user sidebar based on permissions.
*
* @param \Illuminate\Database\Eloquent\Collection $modules
* @param User $user
* @return array
*/
protected function buildUserSidebar($modules, User $user): array
{
$sidebarItems = [];
// Ensure company settings link is always present for Admins
if ($user->isAdmin() || $user->isSuperAdmin()) {
$sidebarItems[] = [
'icon' => 'bi bi-building',
'label' => __('company.menu.dashboard'),
'route' => 'admin/company/settings',
'order' => 30
];
}
foreach ($modules as $module) {
// Skip company module if we already added it manually, or auth/index
if ($module->modules_name === 'auth' || $module->modules_name === 'index' || $module->modules_name === 'company') {
continue;
}
// Check if module is enabled for sidebar
if (!$module->menu_side_bar) {
continue;
}
$moduleConfig = $module->modules_settings ?: [];
// If settings are empty, try to load from module.json file as a fallback
if (empty($moduleConfig) || !isset($moduleConfig['menu'])) {
$moduleJsonPath = dirname(__DIR__, 2) . '/modules/' . $module->modules_name . '/module.json';
if (file_exists($moduleJsonPath)) {
$jsonContent = json_decode(file_get_contents($moduleJsonPath), true);
if ($jsonContent && isset($jsonContent['menu'])) {
$moduleConfig['menu'] = $jsonContent['menu'];
}
}
}
// Fix for company module if settings are still empty (due to migration issue)
if ((empty($moduleConfig) || !isset($moduleConfig['menu'])) && $module->modules_name === 'company') {
$moduleConfig['menu'] = [
'dashboard' => [
'icon' => 'bi bi-building',
'label' => 'company.menu.dashboard',
'route' => 'admin/company/settings',
'order' => 30
]
];
}
if (isset($moduleConfig['menu']['dashboard'])) {
$menuItem = $moduleConfig['menu']['dashboard'];
// Check if user can access this menu item
// Respect company visibility preference when not super admin or admin
// $companyAllows = ($user->isSuperAdmin() || $user->isAdmin()) || (isset($module->bus_modules_show_dashboard_nav) ? (bool) $module->bus_modules_show_dashboard_nav : true);
// Simplified logic: if it's in the $modules list (which already filters by company enablement)
// we only need to check the user's specific permissions.
if ($this->userCanAccessMenuItem($user, $menuItem, $module)) {
$sidebarItems[] = [
'icon' => $menuItem['icon'] ?? $module->modules_icon,
'label' => __($menuItem['label'] ?? $module->modules_display_name),
'route' => $menuItem['route'] ?? 'admin/' . $module->modules_name,
'order' => $menuItem['order'] ?? $module->modules_order
];
}
}
}
// Sort by order
usort($sidebarItems, function ($a, $b) {
return $a['order'] <=> $b['order'];
});
return $sidebarItems;
}
/**
* Build user navbar based on permissions.
*
* @param \Illuminate\Database\Eloquent\Collection $modules
* @param User $user
* @return array
*/
protected function buildUserNavbar($modules, User $user): array
{
$navbarItems = [];
foreach ($modules as $module) {
// Check if module is enabled for navbar
if (!$module->menu_nav_bar) {
continue;
}
$moduleConfig = $module->modules_settings ?: [];
// If settings are empty, try to load from module.json file as a fallback
if (empty($moduleConfig) || !isset($moduleConfig['menu'])) {
$moduleJsonPath = dirname(__DIR__, 2) . '/modules/' . $module->modules_name . '/module.json';
if (file_exists($moduleJsonPath)) {
$jsonContent = json_decode(file_get_contents($moduleJsonPath), true);
if ($jsonContent && isset($jsonContent['menu'])) {
$moduleConfig['menu'] = $jsonContent['menu'];
}
}
}
// Try to get specific navbar config, otherwise fallback to public or dashboard/general
if (isset($moduleConfig['menu']['navbar'])) {
$menuItem = $moduleConfig['menu']['navbar'];
} elseif (isset($moduleConfig['menu']['public'])) {
$menuItem = $moduleConfig['menu']['public'];
} elseif (isset($moduleConfig['menu']['dashboard'])) {
$menuItem = $moduleConfig['menu']['dashboard'];
} else {
continue;
}
// Check if user can access this menu item
// Respect company visibility preference when not super admin or admin
// $companyAllows = ($user->isSuperAdmin() || $user->isAdmin()) || (isset($module->bus_modules_show_public_nav) ? (bool) $module->bus_modules_show_public_nav : true);
if ($this->userCanAccessMenuItem($user, $menuItem, $module)) {
$route = $menuItem['route'] ?? $module->modules_name;
$navbarItems[] = [
'icon' => $menuItem['icon'] ?? $module->modules_icon,
'label' => __($menuItem['label'] ?? $module->modules_display_name),
'route' => $route,
'order' => $menuItem['order'] ?? $module->modules_order
];
}
}
// Sort by order
usort($navbarItems, function ($a, $b) {
return $a['order'] <=> $b['order'];
});
return $navbarItems;
}
/**
* Check if user can access a menu item.
*
* @param User $user
* @param array $menuItem
* @param Module|null $module
* @return bool
*/
protected function userCanAccessMenuItem(User $user, array $menuItem, ?Module $module = null): bool
{
// Custom restriction for companies management (Must check before general admin/superadmin bypass)
if (isset($menuItem['route']) && $menuItem['route'] === 'admin/companies') {
return $user->isSuperAdmin();
}
// Super Admin and Admin have access to everything authorized for the company
if ($user->isSuperAdmin() || $user->isAdmin()) {
return true;
}
// If specific permission defined for menu item, check it
if (!empty($menuItem['permission'])) {
return $user->hasPermission($menuItem['permission']);
}
// If no specific permission, but a module is provided
if ($module) {
// If the module is a core/blocked module, it's generally accessible
// (like Auth or Index)
if ($module->isBlocked()) {
return true;
}
// For Member role, if no specific permission is required for the menu item,
// we check if they have ANY permission for this module.
// This ensures that if an Admin gave them "pagebuilder.view", the Page Builder
// module link appears in the sidebar.
return $this->userHasModulePermission($user, $module);
}
// Default to allowed for items without permissions or module context
return true;
}
/**
* Get current company for the user.
*
* @param User $user
* @return Company|null
*/
protected function getCurrentCompany(User $user): ?Company
{
if ($user->isSuperAdmin()) {
return null; // Super Admin can switch companies
}
return Company::find($user->users_bus_id);
}
/**
* Check if user has permission for module.
*
* @param User $user
* @param Module $module
* @return bool
*/
protected function userHasModulePermission(User $user, Module $module): bool
{
// Super Admin and Admin have access to all modules enabled for their company
if ($user->isSuperAdmin() || $user->isAdmin()) {
return true;
}
// Core modules are accessible to all authenticated users
if ($module->isBlocked()) {
return true;
}
$modulePermissions = $module->getPermissionsAttribute();
// If module has no permissions defined and is public, it might be a general utility
if (empty($modulePermissions) && $module->isPublic()) {
return true;
}
foreach ($modulePermissions as $permission) {
if ($user->hasPermission($permission)) {
return true;
}
}
return false;
}
/**
* Add a flash message to the session.
*
* @param string $type success|error|info|warning
* @param string $message
*/
protected function addFlash(string $type, string $message): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION['flash'][$type] = $message;
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Controllers;
use Kernel\View;
use Symfony\Component\HttpFoundation\Response;
abstract class BaseController
{
protected View $view;
public function __construct(View $view)
{
$this->view = $view;
}
/**
* Render a view with data.
*
* @param string $viewName
* @param array $data
* @return Response
*/
protected function renderView(string $viewName, array $data = []): Response
{
// Get config pages visibility
$configPagesVisibility = [];
if (class_exists('Modules\Configindex\Models\ConfigPage')) {
$busId = $_SESSION['user_idbus'] ?? $_SESSION['current_company_id'] ?? 1;
$baseDir = dirname(dirname(__DIR__));
$activeTheme = \Kernel\View::getActiveTheme() ?? 'default';
$indexViewsDir = $baseDir . '/modules/index/views';
$themeViewsDir = $baseDir . '/themes/' . $activeTheme . '/modules/index/views';
$pages = \Modules\Configindex\Models\ConfigPage::where('config_index_bus_id', $busId)->get();
foreach ($pages as $page) {
$key = $page->config_index_key;
// Verifica se o arquivo físico existe
$fileExists = false;
if ($key === 'index') {
$fileExists = true;
} else {
$possiblePaths = [
$indexViewsDir . '/' . $key . '.twig',
$indexViewsDir . '/' . $key . '.php',
$themeViewsDir . '/' . $key . '.twig',
$themeViewsDir . '/' . $key . '.php',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.twig',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.php',
$baseDir . '/modules/' . $key . '/views/index.twig', // Adicionado para checar módulos
];
foreach ($possiblePaths as $path) {
if (is_file($path)) {
$fileExists = true;
break;
}
}
}
$configPagesVisibility[$key] = [
'public' => (bool)$page->config_index_show_public_nav && $fileExists,
'dashboard' => (bool)$page->config_index_show_dashboard_nav && $fileExists,
'title' => $page->config_index_title ?: ucfirst($key)
];
}
// Garante que as páginas padrão tenham uma configuração inicial, mas respeita o banco de dados
foreach (['about', 'contact', 'services'] as $key) {
if (!isset($configPagesVisibility[$key])) {
// Verifica se o arquivo físico existe para os padrões também
$fileExists = false;
$possiblePaths = [
$indexViewsDir . '/' . $key . '.twig',
$indexViewsDir . '/' . $key . '.php',
$themeViewsDir . '/' . $key . '.twig',
$themeViewsDir . '/' . $key . '.php',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.twig',
$baseDir . '/themes/' . $activeTheme . '/' . $key . '.php',
];
foreach ($possiblePaths as $path) {
if (is_file($path)) {
$fileExists = true;
break;
}
}
// Se não estiver no banco, o padrão é oculto (false) para evitar exibição indesejada
$configPagesVisibility[$key] = ['public' => false, 'dashboard' => false, 'title' => ucfirst($key)];
}
}
}
// Get SEO settings
$seoSettings = [];
if (class_exists('App\Models\Module')) {
$seoModule = \App\Models\Module::where('modules_name', 'seo')->first();
if ($seoModule && $seoModule->modules_settings) {
$seoSettings = $seoModule->modules_settings;
}
}
$commonData = [
'config_pages_visibility' => $configPagesVisibility,
'seo' => $seoSettings
];
return new Response($this->view->render($viewName, array_merge($commonData, $data)));
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace App\Controllers;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class QuillDemoController extends BaseAdminController
{
/**
* Displays the Quill demo page
*/
public function index(Request $request): Response
{
// Check if the user is authenticated
if (!$this->isAuthenticated()) {
return $this->redirectToRoute('login');
}
// Render the demo view
$view = new View();
$content = $view->render('examples/quill-demo.twig');
return new Response($content);
}
/**
* Processes the demo form submission
*/
public function submit(Request $request): Response
{
// Check if the user is authenticated
if (!$this->isAuthenticated()) {
return $this->redirectToRoute('login');
}
// Get form data
$title = $request->get('title');
$content = $request->get('content');
$advancedContent = $request->get('advanced_content');
$simpleContent = $request->get('simple_content');
// Save to session to display on results page
$_SESSION['form_data'] = [
'title' => $title,
'content' => $content,
'advanced_content' => $advancedContent,
'simple_content' => $simpleContent
];
// Redirect to results page
return $this->redirectToRoute('quill-demo-results');
}
/**
* Displays form results
*/
public function results(Request $request): Response
{
// Check if the user is authenticated
if (!$this->isAuthenticated()) {
return $this->redirectToRoute('login');
}
// Get session data
$formData = $_SESSION['form_data'] ?? [];
// Clear session data
unset($_SESSION['form_data']);
// Render the results view
$view = new View();
$content = $view->render('examples/quill-results.twig', [
'formData' => $formData
]);
return new Response($content);
}
/**
* Checks if the user is authenticated
*/
private function isAuthenticated(): bool
{
return isset($_SESSION['user_id']);
}
/**
* Redirects to a route
*/
private function redirectToRoute(string $route): Response
{
$url = url($route);
return new Response("", 302, ["Location" => $url]);
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace App\Controllers;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class StorageController extends BaseController
{
/**
* Serve a file from storage/uploads/{module}/{filename}
*
* @param Request $request
* @param string $module
* @param string $filename
* @return Response
*/
public function serve(Request $request, string $module, string $filename): Response
{
// Sanitize inputs to prevent directory traversal
$module = preg_replace('/[^a-zA-Z0-9_-]/', '', $module);
// Allow subfolders in filename (e.g. "123/image.jpg")
$filename = ltrim($filename, '/');
if (strpos($filename, '..') !== false) {
return new Response('Access denied.', 403);
}
// Build the physical path
$basePath = dirname(__DIR__, 2) . '/storage/uploads';
$filePath = $basePath . '/' . $module . '/' . $filename;
// Security check: Ensure the file is actually inside storage/uploads
$realPath = realpath($filePath);
if (!$realPath || strpos($realPath, realpath($basePath)) !== 0) {
return new Response('File not found or access denied.', 404);
}
if (!file_exists($realPath)) {
return new Response('File not found.', 404);
}
// Determine MIME type
$mimeType = $this->getMimeType($realPath);
// Create BinaryFileResponse for efficient file serving
$response = new BinaryFileResponse($realPath);
$response->headers->set('Content-Type', $mimeType);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_INLINE,
$filename
);
// Add cache headers (cache for 30 days)
$response->setPublic();
$response->setMaxAge(2592000);
$response->setSharedMaxAge(2592000);
return $response;
}
/**
* Get MIME type of a file
*/
private function getMimeType(string $path): string
{
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mimes = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
'svg' => 'image/svg+xml',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'txt' => 'text/plain',
'zip' => 'application/zip',
];
if (isset($mimes[$extension])) {
return $mimes[$extension];
}
if (function_exists('mime_content_type')) {
return mime_content_type($path);
}
return 'application/octet-stream';
}
}
+302
View File
@@ -0,0 +1,302 @@
<?php
namespace App\Controllers;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Modules\Auth\Models\User;
use Illuminate\Database\Capsule\Manager as DB;
class SystemController extends BaseAdminController
{
private $logPath;
private $cachePath;
public function __construct(View $view)
{
parent::__construct($view);
$this->logPath = dirname(__DIR__, 2) . '/logs';
$this->cachePath = dirname(__DIR__, 2) . '/cache';
}
/**
* Display system information.
*
* @param Request $request
* @return Response
*/
public function info(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response($this->view->render('components/alerts.twig', [
'type' => 'danger',
'message' => 'Access denied.'
]), 403);
}
$dbVersion = 'Unknown';
try {
$pdo = DB::connection()->getPdo();
$dbVersion = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
} catch (\Exception $e) {
$dbVersion = 'Error: ' . $e->getMessage();
}
$info = [
'app' => [
'name' => 'SysCMS',
'version' => '1.0.0', // Should be from config or const
'env' => getenv('APP_ENV') ?: 'production',
'debug' => getenv('APP_DEBUG') ?: false,
],
'system' => [
'os' => php_uname('s') . ' ' . php_uname('r'),
'hostname' => gethostname(),
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
'timezone' => date_default_timezone_get(),
],
'php' => [
'version' => phpversion(),
'sapi' => php_sapi_name(),
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time') . 's',
'upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size'),
'extensions' => get_loaded_extensions(),
],
'database' => [
'driver' => DB::connection()->getDriverName(),
'version' => $dbVersion,
'database' => DB::connection()->getDatabaseName(),
],
'disk' => [
'total' => $this->formatBytes(disk_total_space('.')),
'free' => $this->formatBytes(disk_free_space('.')),
'used' => $this->formatBytes(disk_total_space('.') - disk_free_space('.')),
]
];
return $this->renderAdminView('system/info.twig', [
'page_title' => 'System Information',
'info' => $info
]);
}
/**
* Display PHP Info.
*
* @param Request $request
* @return Response
*/
public function phpInfo(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access denied', 403);
}
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
// Extract body content to embed cleanly if desired, or just output raw.
// For admin panel consistency, usually raw in a new tab is easiest,
// but embedding provides better UX.
// Let's strip body tags to embed.
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $phpinfo);
return $this->renderAdminView('system/phpinfo.twig', [
'page_title' => 'PHP Info',
'phpinfo' => $phpinfo
]);
}
/**
* List all log files.
*
* @param Request $request
* @return Response
*/
public function logsIndex(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response($this->view->render('components/alerts.twig', [
'type' => 'danger',
'message' => 'Access denied.'
]), 403);
}
$files = [];
if (is_dir($this->logPath)) {
$files = glob($this->logPath . '/*.log');
}
// Sort by modification time desc
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$logs = [];
foreach ($files as $file) {
$logs[] = [
'name' => basename($file),
'size' => $this->formatBytes(filesize($file)),
'modified' => date('Y-m-d H:i:s', filemtime($file))
];
}
return $this->renderAdminView('logs/index.twig', [
'page_title' => 'System Logs',
'logs' => $logs
]);
}
/**
* Show log file content.
*
* @param Request $request
* @return Response
*/
public function logsShow(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response($this->view->render('components/alerts.twig', [
'type' => 'danger',
'message' => 'Access denied.'
]), 403);
}
$filename = $request->query->get('file');
if (!$filename) {
return new RedirectResponse(url('admin/logs'));
}
// Security check: prevent directory traversal
if (strpos($filename, '..') !== false || strpos($filename, '/') !== false || strpos($filename, '\\') !== false) {
return new Response('Invalid filename', 400);
}
$filePath = $this->logPath . '/' . $filename;
if (!file_exists($filePath)) {
return new Response('File not found', 404);
}
// Limit file size read to avoid memory issues (e.g., 5MB)
if (filesize($filePath) > 5 * 1024 * 1024) {
// Read only last 500 lines if file is too big
$content = $this->tailCustom($filePath, 500);
$content = "File too large, showing last 500 lines:\n\n" . $content;
} else {
$content = file_get_contents($filePath);
}
return $this->renderAdminView('logs/show.twig', [
'page_title' => 'Log: ' . $filename,
'filename' => $filename,
'content' => $content
]);
}
/**
* Clear system cache.
*
* @param Request $request
* @return Response
*/
public function clearCache(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response($this->view->render('components/alerts.twig', [
'type' => 'danger',
'message' => 'Access denied.'
]), 403);
}
$deletedCount = 0;
// Clear file cache
if (is_dir($this->cachePath)) {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->cachePath, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
// Skip .gitignore
if ($fileinfo->getFilename() === '.gitignore') {
continue;
}
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
if (@$todo($fileinfo->getRealPath())) {
if ($todo === 'unlink') {
$deletedCount++;
}
}
}
}
// Clear OpCache
$opcacheCleared = false;
if (function_exists('opcache_reset')) {
$opcacheCleared = opcache_reset();
}
// Add a success flash message (if flash system exists, otherwise just redirect with query param)
// Since we don't have a flash message system clearly defined in context, let's use query param
return new RedirectResponse(url('dashboard?cache_cleared=1&deleted=' . $deletedCount . '&opcache=' . ($opcacheCleared ? 1 : 0)));
}
private function formatBytes($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
/**
* Slightly modified tail function
*/
private function tailCustom($filepath, $lines = 1) {
$f = @fopen($filepath, "rb");
if ($f === false) return false;
$buffer = 4096;
fseek($f, -1, SEEK_END);
if (fread($f, 1) != "\n") $lines -= 1;
$output = '';
$chunk = '';
while (ftell($f) > 0 && $lines >= 0) {
$seek = min(ftell($f), $buffer);
fseek($f, -$seek, SEEK_CUR);
$chunk = fread($f, $seek);
$output = $chunk . $output;
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
$lines -= substr_count($chunk, "\n");
}
while ($lines++ < 0) {
$output = substr($output, strpos($output, "\n") + 1);
}
fclose($f);
return $output;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Helpers;
/**
* SysCMS - Encryption Helper
*
* Simple AES-256-CBC encryption for sensitive data like API Keys.
*/
class Crypto
{
private static $cipher = "AES-256-CBC";
/**
* Encrypts a string using the APP_KEY as secret
*/
public static function encrypt(string $data): string
{
$key = substr(hash('sha256', env('APP_KEY', 'syscms-default-key')), 0, 32);
$ivlen = openssl_cipher_iv_length(self::$cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext = openssl_encrypt($data, self::$cipher, $key, $options = 0, $iv);
return base64_encode($iv . $ciphertext);
}
/**
* Decrypts a string
*/
public static function decrypt(string $data): string
{
try {
$key = substr(hash('sha256', env('APP_KEY', 'syscms-default-key')), 0, 32);
$decoded = base64_decode($data);
$ivlen = openssl_cipher_iv_length(self::$cipher);
$iv = substr($decoded, 0, $ivlen);
$ciphertext = substr($decoded, $ivlen);
return openssl_decrypt($ciphertext, self::$cipher, $key, $options = 0, $iv);
} catch (\Exception $e) {
return $data; // Return original if decryption fails (e.g. not encrypted yet)
}
}
}
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace App\Helpers;
use Illuminate\Database\Capsule\Manager as DB;
class DateFormatter
{
private static $defaultFormat = 'd/m/Y';
private static $cachedFormat = null;
/**
* Format a date according to the company's configuration.
*
* @param string|null $date The date string (Y-m-d or Y-m-d H:i:s).
* @param bool $includeTime Whether to include time in the formatted string.
* @return string
*/
public static function format(?string $date, bool $includeTime = false): string
{
if (empty($date)) {
return '';
}
// Try to detect if date is already formatted (simple check for '/')
if (strpos($date, '/') !== false) {
return $date;
}
try {
$format = self::getCompanyDateFormat();
// If the format is just date but time is requested, append time format
if ($includeTime) {
// Heuristic: if format doesn't seem to have time, add it
if (strpos($format, 'H') === false) {
$format .= ' H:i';
}
}
$timestamp = strtotime($date);
if ($timestamp === false) {
return $date; // Return original if parsing fails
}
return date($format, $timestamp);
} catch (\Exception $e) {
// Fallback
return date(self::$defaultFormat . ($includeTime ? ' H:i' : ''), strtotime($date));
}
}
/**
* Get the date format for the current company.
* Caches the result in memory (static property) and session.
*
* @return string
*/
private static function getCompanyDateFormat(): string
{
if (self::$cachedFormat) {
return self::$cachedFormat;
}
// Check session
if (isset($_SESSION['bus_date_format'])) {
self::$cachedFormat = $_SESSION['bus_date_format'];
return self::$cachedFormat;
}
// Fetch from database if user is logged in
if (isset($_SESSION['user_bus_id'])) {
try {
$busId = $_SESSION['user_bus_id'];
$company = DB::table('bus')
->where('bus_id', $busId)
->select('bus_date_format')
->first();
if ($company && !empty($company->bus_date_format)) {
$_SESSION['bus_date_format'] = $company->bus_date_format;
self::$cachedFormat = $company->bus_date_format;
return self::$cachedFormat;
}
} catch (\Exception $e) {
// Log error if needed, but return default for now
}
}
// Fallback default
self::$cachedFormat = self::$defaultFormat;
return self::$cachedFormat;
}
/**
* Converts a formatted date back to MySQL format (Y-m-d).
*
* @param string $date Formatted date string
* @return string|null Y-m-d formatted date or null on failure
*/
public static function toMySQL(string $date): ?string
{
if (empty($date)) {
return null;
}
// If already Y-m-d (contains - and no /)
if (strpos($date, '-') !== false && strpos($date, '/') === false) {
return $date;
}
// Try to parse from company format
$format = self::getCompanyDateFormat();
$d = \DateTime::createFromFormat($format, $date);
if ($d && $d->format($format) === $date) {
return $d->format('Y-m-d');
}
// Try common fallback formats
$formats = ['d/m/Y', 'm/d/Y', 'Y/m/d'];
foreach ($formats as $fmt) {
$d = \DateTime::createFromFormat($fmt, $date);
if ($d && $d->format($fmt) === $date) {
return $d->format('Y-m-d');
}
}
return null;
}
}
+173
View File
@@ -0,0 +1,173 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
use Dotenv\Dotenv;
if (!function_exists('load_env')) {
/**
* Loads environment variables from the .env file
*
* @param string|null $path Path to the project root directory
* @return void
*/
function load_env(?string $path = null): void
{
static $loaded = false;
if ($loaded) {
return;
}
$path = $path ?? dirname(__DIR__, 2);
if (file_exists($path . '/.env')) {
$dotenv = Dotenv::createImmutable($path);
$dotenv->load();
$loaded = true;
}
}
}
if (!function_exists('get_env')) {
/**
* Gets an environment variable with a default value
*
* @param string $key Variable name
* @param mixed $default Default value if not exists
* @return mixed
*/
function get_env(string $key, $default = null)
{
return $_ENV[$key] ?? $_SERVER[$key] ?? $default;
}
}
if (!function_exists('env')) {
/**
* Alias for get_env to maintain compatibility
*
* @param string $key Variable name
* @param mixed $default Default value if not exists
* @return mixed
*/
function env(string $key, $default = null)
{
return get_env($key, $default);
}
}
if (!function_exists('env_bool')) {
/**
* Gets an environment variable as boolean
*
* @param string $key Variable name
* @param bool $default Default value if not exists
* @return bool
*/
function env_bool(string $key, bool $default = false): bool
{
$value = get_env($key, $default);
if (is_bool($value)) {
return $value;
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
}
if (!function_exists('env_int')) {
/**
* Gets an environment variable as integer
*
* @param string $key Variable name
* @param int $default Default value if not exists
* @return int
*/
function env_int(string $key, int $default = 0): int
{
return (int) get_env($key, $default);
}
}
if (!function_exists('get_db_config')) {
/**
* Gets database configuration from environment variables
*
* @return array
*/
function get_db_config(): array
{
return [
'driver' => get_env('DB_DRIVER', get_env('DB_CONNECTION', 'mysql')),
'host' => get_env('DB_HOST', 'localhost'),
'port' => get_env('DB_PORT', '3306'),
'database' => get_env('DB_DATABASE', 'syscms'),
'username' => get_env('DB_USERNAME', 'root'),
'password' => get_env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
];
}
}
if (!function_exists('get_app_config')) {
/**
* Gets application configuration from environment variables
*
* @return array
*/
function get_app_config(): array
{
return [
'name' => get_env('APP_NAME', 'SysCMS'),
'env' => get_env('APP_ENV', 'development'),
'debug' => env_bool('APP_DEBUG', true),
'url' => get_env('APP_URL', 'http://localhost:16240'),
'timezone' => get_env('APP_TIMEZONE', 'America/Sao_Paulo'),
'key' => get_env('APP_KEY', ''),
];
}
}
if (!function_exists('get_mail_config')) {
/**
* Gets email configuration from environment variables
*
* @return array
*/
function get_mail_config(): array
{
return [
'transport' => get_env('MAIL_TRANSPORT', 'smtp'),
'host' => get_env('MAIL_HOST', 'localhost'),
'port' => get_env('MAIL_PORT', '587'),
'username' => get_env('MAIL_USERNAME', ''),
'password' => get_env('MAIL_PASSWORD', ''),
'encryption' => get_env('MAIL_ENCRYPTION', 'tls'),
'from_address' => get_env('MAIL_FROM_ADDRESS', ''),
'from_name' => get_env('MAIL_FROM_NAME', 'SysCMS'),
];
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace App\Helpers;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class Log
{
private static $logger;
private static function getLogger()
{
if (!self::$logger) {
self::$logger = new Logger('syscms');
// Logs should be in /logs as per RULES.md
$logPath = __DIR__ . '/../../logs/app.log';
$logDir = dirname($logPath);
if (!is_dir($logDir)) {
mkdir($logDir, 0777, true);
}
self::$logger->pushHandler(new StreamHandler($logPath, Logger::DEBUG));
}
return self::$logger;
}
public static function info($message, array $context = [])
{
self::getLogger()->info($message, $context);
}
public static function error($message, array $context = [])
{
self::getLogger()->error($message, $context);
}
public static function warning($message, array $context = [])
{
self::getLogger()->warning($message, $context);
}
public static function debug($message, array $context = [])
{
self::getLogger()->debug($message, $context);
}
/**
* Specialized write for audit logs
*/
public static function write($message, $module = 'general', $context = [])
{
if (!is_array($context)) {
$context = [];
}
$context['module'] = $module;
self::getLogger()->info($message, $context);
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
namespace App\Helpers;
/**
* Helper para gerenciar o Editor Quill no sistema SysCMS
*/
class QuillHelper
{
/**
* Renderiza um campo de texto com o editor Quill
*
* @param string $name Nome do campo
* @param string $id ID do elemento
* @param string $value Valor inicial
* @param array $options Opções adicionais
* @return string HTML do campo com o editor
*/
public static function renderEditor(string $name, string $id, string $value = '', array $options = []): string
{
// Opções padrão
$defaultOptions = [
'class' => 'quill-editor',
'style' => 'height: 300px;',
'placeholder' => 'Digite seu conteúdo aqui...'
];
// Mesclar opções
$options = array_merge($defaultOptions, $options);
// Construir atributos
$attributes = '';
foreach ($options as $key => $value) {
if ($key !== 'value') {
$attributes .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
}
}
// Retornar HTML do campo
return sprintf(
'<textarea name="%s" id="%s"%s>%s</textarea>',
htmlspecialchars($name),
htmlspecialchars($id),
$attributes,
htmlspecialchars($value)
);
}
/**
* Renderiza um campo de texto simples (sem Quill)
*
* @param string $name Nome do campo
* @param string $id ID do elemento
* @param string $value Valor inicial
* @param array $options Opções adicionais
* @return string HTML do campo
*/
public static function renderTextarea(string $name, string $id, string $value = '', array $options = []): string
{
// Opções padrão
$defaultOptions = [
'class' => 'form-control',
'rows' => 5
];
// Mesclar opções
$options = array_merge($defaultOptions, $options);
// Construir atributos
$attributes = '';
foreach ($options as $key => $value) {
if ($key !== 'value') {
$attributes .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
}
}
// Retornar HTML do campo
return sprintf(
'<textarea name="%s" id="%s"%s>%s</textarea>',
htmlspecialchars($name),
htmlspecialchars($id),
$attributes,
htmlspecialchars($value)
);
}
/**
* Renderiza um campo de texto com editor (Quill ou textarea simples)
* baseado na configuração do sistema
*
* @param string $name Nome do campo
* @param string $id ID do elemento
* @param string $value Valor inicial
* @param array $options Opções adicionais
* @return string HTML do campo
*/
public static function renderField(string $name, string $id, string $value = '', array $options = []): string
{
// Verificar se o Quill está habilitado
$useQuill = self::isQuillEnabled();
if ($useQuill) {
return self::renderEditor($name, $id, $value, $options);
} else {
return self::renderTextarea($name, $id, $value, $options);
}
}
/**
* Verifica se o editor Quill está habilitado no sistema
*
* @return bool True se o Quill estiver habilitado
*/
public static function isQuillEnabled(): bool
{
// Verificar variável de ambiente
$useQuill = getenv('APP_EDITOR_QUILL') ?: 'true';
return filter_var($useQuill, FILTER_VALIDATE_BOOLEAN);
}
/**
* Obtém a configuração do Quill em formato JSON
*
* @return string Configuração em formato JSON
*/
public static function getQuillConfig(): string
{
$config = [
'theme' => 'snow',
'placeholder' => 'Digite seu conteúdo aqui...',
'modules' => [
'toolbar' => [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'header': [1, 2] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['link', 'image'],
['clean']
]
]
];
return json_encode($config);
}
/**
* Renderiza o script de inicialização do Quill
*
* @return string Script de inicialização
*/
public static function renderInitScript(): string
{
if (!self::isQuillEnabled()) {
return '';
}
$config = self::getQuillConfig();
$configJson = json_encode($config);
return <<<HTML
<script>
// Configuração personalizada do Quill
const quillSysConfig = {$configJson};
// Sobrescrever configuração padrão se necessário
if (typeof window.quillDefaultConfig !== 'undefined') {
window.quillDefaultConfig = Object.assign(window.quillDefaultConfig, quillSysConfig);
}
</script>
HTML;
}
/**
* Limpa o conteúdo HTML gerado pelo Quill para segurança
*
* @param string $html Conteúdo HTML
* @return string HTML limpo
*/
public static function sanitizeHtml(string $html): string
{
// Lista de tags permitidas
$allowedTags = '<p><br><strong><em><u><s><h1><h2><h3><h4><h5><h6><ul><ol><li><blockquote><pre><code><a><img>';
// Remover atributos perigosos
$html = strip_tags($html, $allowedTags);
// Remover atributos de estilo e eventos
$html = preg_replace('/\s*(on\w+|style|id|class)=["\'][^"\']*["\']/', '', $html);
return $html;
}
/**
* Converte o conteúdo do Quill para texto puro
*
* @param string $html Conteúdo HTML
* @return string Texto puro
*/
public static function htmlToText(string $html): string
{
// Remover tags HTML
$text = strip_tags($html);
// Converter entidades HTML
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// Normalizar espaços em branco
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Middlewares;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class PermissionMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string $permission
* @return mixed
*/
public function handle(Request $request, Closure $next, string $permission)
{
$user = $this->getCurrentUser();
if (!$user) {
return $this->unauthorizedResponse($request);
}
// Check if user has the required permission
if (!$this->userHasPermission($user, $permission)) {
return $this->forbiddenResponse($request, $permission);
}
return $next($request);
}
/**
* Get current authenticated user.
*
* @return \Modules\Auth\Models\User|null
*/
protected function getCurrentUser(): ?\Modules\Auth\Models\User
{
if (!isset($_SESSION['user_id'])) {
return null;
}
try {
return \Modules\Auth\Models\User::find($_SESSION['user_id']);
} catch (\Exception $e) {
error_log("Error loading user: " . $e->getMessage());
return null;
}
}
/**
* Check if user has the required permission.
*
* @param \Modules\Auth\Models\User $user
* @param string $permission
* @return bool
*/
protected function userHasPermission(\Modules\Auth\Models\User $user, string $permission): bool
{
return $user->hasPermission($permission);
}
/**
* Create unauthorized response.
*
* @param Request $request
* @return Response
*/
protected function unauthorizedResponse(Request $request): Response
{
if ($this->expectsJson($request)) {
return new JsonResponse([
'success' => false,
'message' => __('auth.unauthenticated'),
'timestamp' => time()
], 401);
}
return new Response('', 302, [
'Location' => url('/login')
]);
}
/**
* Create forbidden response.
*
* @param Request $request
* @param string $permission
* @return Response
*/
protected function forbiddenResponse(Request $request, string $permission): Response
{
$message = __('auth.permission_denied', ['permission' => $permission]);
if ($this->expectsJson($request)) {
return new JsonResponse([
'success' => false,
'message' => $message,
'permission' => $permission,
'timestamp' => time()
], 403);
}
// For web requests, show error page
try {
$view = new \Kernel\View();
// We can pass the message to the view if we update the template
return new Response($view->render('errors/403.twig'), 403);
} catch (\Throwable $e) {
return new Response($message, 403);
}
}
/**
* Check if request expects JSON response.
*
* @param Request $request
* @return bool
*/
protected function expectsJson(Request $request): bool
{
return $request->headers->get('Accept') === 'application/json' ||
$request->headers->get('Content-Type') === 'application/json' ||
str_contains($request->getPathInfo(), '/api/');
}
}
+385
View File
@@ -0,0 +1,385 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Modules\Company\Models\Company;
use App\Helpers\Log;
use Illuminate\Database\Capsule\Manager as Capsule;
class ActivityLog extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'activity_logs';
/**
* The primary key associated with table.
*
* @var string
*/
protected $primaryKey = 'activity_logs_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'activity_logs_user_id',
'activity_logs_bus_id',
'activity_logs_action',
'activity_logs_resource_type',
'activity_logs_resource_id',
'activity_logs_old_values',
'activity_logs_new_values',
'activity_logs_ip_address',
'activity_logs_user_agent'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'activity_logs_old_values' => 'array',
'activity_logs_new_values' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at'
];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
/**
* Get the user that performed the activity.
*/
public function user(): BelongsTo
{
return $this->belongsTo(\Modules\Auth\Models\User::class, 'activity_logs_user_id');
}
/**
* Get the company where the activity occurred.
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class, 'activity_logs_bus_id');
}
/**
* Scope to get logs for a specific user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int|null $userId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForUser($query, ?int $userId)
{
return $query->where('activity_logs_user_id', $userId);
}
/**
* Scope to get logs for a specific company.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int|null $companyId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForCompany($query, ?int $companyId)
{
return $query->where('activity_logs_bus_id', $companyId);
}
/**
* Scope to get logs for a specific action.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $action
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForAction($query, string $action)
{
return $query->where('activity_logs_action', $action);
}
/**
* Scope to get logs for a specific resource type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $resourceType
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForResourceType($query, string $resourceType)
{
return $query->where('activity_logs_resource_type', $resourceType);
}
/**
* Scope to get logs for a specific resource.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $resourceType
* @param int $resourceId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForResource($query, string $resourceType, int $resourceId)
{
return $query->where('activity_logs_resource_type', $resourceType)
->where('activity_logs_resource_id', $resourceId);
}
/**
* Scope to get logs within a date range.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \DateTime|string $startDate
* @param \DateTime|string $endDate
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeBetweenDates($query, $startDate, $endDate)
{
return $query->whereBetween('created_at', [$startDate, $endDate]);
}
/**
* Scope to order logs by creation date.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $direction
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOrderByDate($query, string $direction = 'desc')
{
return $query->orderBy('created_at', $direction);
}
/**
* Log an activity.
*
* @param int|null $userId
* @param int|null $companyId
* @param string $action
* @param string $resourceType
* @param int|null $resourceId
* @param array|null $oldValues
* @param array|null $newValues
* @param string|null $ipAddress
* @param string|null $userAgent
* @return static
*/
public static function log(
?int $userId,
?int $companyId,
string $action,
string $resourceType,
?int $resourceId = null,
?array $oldValues = null,
?array $newValues = null,
?string $ipAddress = null,
?string $userAgent = null
): self {
// Also log to file for monitoring
Log::info("Activity: {$action}", [
'user_id' => $userId,
'company_id' => $companyId,
'resource' => "{$resourceType}:{$resourceId}",
'ip' => $ipAddress ?? ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1')
]);
$resolvedCompanyId = $companyId ?? ($_SESSION['current_company_id'] ?? ($_SESSION['user_idbus'] ?? null));
// Validate company id exists to satisfy FK constraints
if ($resolvedCompanyId !== null) {
$exists = Capsule::table('bus')->where('bus_id', $resolvedCompanyId)->exists();
if (!$exists) {
$resolvedCompanyId = null;
}
}
$attributes = [
'activity_logs_user_id' => $userId,
'activity_logs_bus_id' => $resolvedCompanyId,
'activity_logs_action' => $action,
'activity_logs_resource_type' => $resourceType,
'activity_logs_resource_id' => $resourceId,
'activity_logs_old_values' => $oldValues,
'activity_logs_new_values' => $newValues,
'activity_logs_ip_address' => $ipAddress ?? ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'),
'activity_logs_user_agent' => $userAgent ?? ($_SERVER['HTTP_USER_AGENT'] ?? 'Console')
];
// If company id is invalid/null and DB enforces NOT NULL or FK, avoid DB insert
if ($resolvedCompanyId === null) {
return new self($attributes);
}
return self::create($attributes);
}
/**
* Get the action attribute.
*
* @return string
*/
public function getActionAttribute(): string
{
return $this->activity_logs_action;
}
/**
* Get the resource type attribute.
*
* @return string
*/
public function getResourceTypeAttribute(): string
{
return $this->activity_logs_resource_type;
}
/**
* Get the resource id attribute.
*
* @return int|null
*/
public function getResourceIdAttribute(): ?int
{
return $this->activity_logs_resource_id;
}
/**
* Get the user name attribute.
*
* @return string
*/
public function getUserNameAttribute(): string
{
return $this->user ? ($this->user->users_name ?: $this->user->users_email) : __('activity.unknown_user');
}
/**
* Get the action color attribute.
*
* @return string
*/
public function getActionColorAttribute(): string
{
$colors = [
'created' => 'success',
'updated' => 'primary',
'deleted' => 'danger',
'login' => 'success',
'logout' => 'warning',
'password_changed' => 'info',
'profile_updated' => 'info',
'module_enabled' => 'success',
'module_disabled' => 'secondary',
'permission_granted' => 'success',
'permission_revoked' => 'danger'
];
return $colors[$this->activity_logs_action] ?? 'secondary';
}
/**
* Get the formatted action description.
*
* @return string
*/
public function getFormattedActionAttribute(): string
{
$actions = [
'created' => __('activity.created'),
'updated' => __('activity.updated'),
'deleted' => __('activity.deleted'),
'login' => __('activity.login'),
'logout' => __('activity.logout'),
'password_changed' => __('activity.password_changed'),
'profile_updated' => __('activity.profile_updated'),
'module_enabled' => __('activity.module_enabled'),
'module_disabled' => __('activity.module_disabled'),
'permission_granted' => __('activity.permission_granted'),
'permission_revoked' => __('activity.permission_revoked')
];
return $actions[$this->activity_logs_action] ?? $this->activity_logs_action;
}
/**
* Get the formatted resource description.
*
* @return string
*/
public function getFormattedResourceAttribute(): string
{
if ($this->activity_logs_resource_type && $this->activity_logs_resource_id) {
return "{$this->activity_logs_resource_type} #{$this->activity_logs_resource_id}";
}
return $this->activity_logs_resource_type ?: __('activity.unknown_resource');
}
/**
* Get the formatted changes description.
*
* @return string
*/
public function getFormattedChangesAttribute(): string
{
if (!$this->activity_logs_old_values && !$this->activity_logs_new_values) {
return '';
}
$changes = [];
if ($this->activity_logs_old_values && $this->activity_logs_new_values) {
foreach ($this->activity_logs_old_values as $key => $oldValue) {
if (isset($this->activity_logs_new_values[$key])) {
$newValue = $this->activity_logs_new_values[$key];
$changes[] = "{$key}: '{$oldValue}' → '{$newValue}'";
}
}
}
return implode(', ', $changes);
}
}
+283
View File
@@ -0,0 +1,283 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Modules\Company\Models\Company;
class Module extends Model
{
/**
* The table associated with model.
*
* @var string
*/
protected $table = 'modules';
/**
* The primary key associated with table.
*
* @var string
*/
protected $primaryKey = 'modules_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'modules_name',
'modules_display_name',
'modules_description',
'modules_version',
'modules_author',
'modules_ative',
'modules_public',
'modules_blocked',
'modules_dependencies',
'modules_permissions',
'modules_settings',
'modules_path',
'modules_namespace',
'modules_icon',
'menu_side_bar',
'menu_nav_bar',
'modules_order'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'modules_ative' => 'boolean',
'modules_public' => 'boolean',
'modules_blocked' => 'boolean',
'menu_side_bar' => 'boolean',
'menu_nav_bar' => 'boolean',
'modules_dependencies' => 'array',
'modules_permissions' => 'array',
'modules_settings' => 'array',
'modules_install_date' => 'datetime',
'modules_update_date' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'modules_install_date',
'modules_update_date',
'created_at',
'updated_at'
];
/**
* Get companies that use this module.
*/
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class, 'bus_modules', 'bus_modules_module_id', 'bus_modules_bus_id')
->withPivot(['bus_modules_is_enabled', 'bus_modules_settings', 'bus_modules_enabled_by', 'bus_modules_enabled_at', 'bus_modules_show_public_nav', 'bus_modules_show_dashboard_nav'])
->withTimestamps();
}
/**
* Scope to get only active modules.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('modules_ative', true);
}
/**
* Scope to get only public modules.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePublic($query)
{
return $query->where('modules_public', true);
}
/**
* Scope to get only blocked modules.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeBlocked($query)
{
return $query->where('modules_blocked', true);
}
/**
* Scope to get modules by name.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $name
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeByName($query, string $name)
{
return $query->where('modules_name', $name);
}
/**
* Scope to order modules by order field.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOrdered($query)
{
return $query->orderBy('modules_order', 'asc');
}
/**
* Check if module is active.
*
* @return bool
*/
public function isActive(): bool
{
return $this->modules_ative;
}
/**
* Check if module is public.
*
* @return bool
*/
public function isPublic(): bool
{
return $this->modules_public;
}
/**
* Check if module is blocked.
*
* @return bool
*/
public function isBlocked(): bool
{
return $this->modules_blocked;
}
/**
* Get module icon with Bootstrap Icons classes.
*
* @return string
*/
public function getIconAttribute(): string
{
return $this->attributes['modules_icon'] ?? 'bi bi-gear';
}
/**
* Get module display name.
*
* @return string
*/
public function getDisplayNameAttribute(): string
{
return $this->modules_display_name ?: $this->modules_name;
}
/**
* Get module dependencies.
*
* @return array
*/
public function getDependenciesAttribute(): array
{
return $this->modules_dependencies ?? [];
}
/**
* Get module permissions.
*
* @return array
*/
public function getPermissionsAttribute(): array
{
return $this->modules_permissions ?? [];
}
/**
* Get module settings.
*
* @return array
*/
public function getSettingsAttribute(): array
{
return $this->modules_settings ?? [];
}
/**
* Get setting value.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function getSetting(string $key, $default = null)
{
$settings = $this->getSettingsAttribute();
return $settings[$key] ?? $default;
}
/**
* Check if module has specific permission.
*
* @param string $permission
* @return bool
*/
public function hasPermission(string $permission): bool
{
return in_array($permission, $this->getPermissionsAttribute());
}
/**
* Check if module depends on another module.
*
* @param string $dependency
* @return bool
*/
public function hasDependency(string $dependency): bool
{
return in_array($dependency, $this->getDependenciesAttribute());
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Modules\Company\Models\Company;
class RolePermission extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'role_permissions';
/**
* The primary key associated with table.
*
* @var string
*/
protected $primaryKey = 'role_permissions_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'role_permissions_role',
'role_permissions_bus_id',
'role_permissions_permission',
'role_permissions_created_by'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at'
];
/**
* Get the company that owns the role permission.
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class, 'role_permissions_bus_id');
}
/**
* Get the user who created the role permission.
*/
public function createdByUser(): BelongsTo
{
return $this->belongsTo(\Modules\Auth\Models\User::class, 'role_permissions_created_by');
}
/**
* Scope to get permissions for a specific role.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $role
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForRole($query, string $role)
{
return $query->where('role_permissions_role', $role);
}
/**
* Scope to get permissions for a specific company.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $companyId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForCompany($query, int $companyId)
{
return $query->where('role_permissions_bus_id', $companyId);
}
/**
* Scope to get permissions with specific permission.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $permission
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithPermission($query, string $permission)
{
return $query->where('role_permissions_permission', $permission);
}
}
+178
View File
@@ -0,0 +1,178 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Modules\Company\Models\Company;
use Modules\Auth\Models\User;
use Carbon\Carbon;
class UserPermission extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user_permissions';
/**
* The primary key associated with table.
*
* @var string
*/
protected $primaryKey = 'user_permissions_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'user_permissions_user_id',
'user_permissions_bus_id',
'user_permissions_permission',
'user_permissions_granted_by',
'user_permissions_expires_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'user_permissions_granted_at' => 'datetime',
'user_permissions_expires_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'user_permissions_granted_at',
'user_permissions_expires_at',
'created_at',
'updated_at'
];
/**
* Get the user that owns the permission.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_permissions_user_id');
}
/**
* Get the company that owns the permission.
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class, 'user_permissions_bus_id');
}
/**
* Get the user who granted the permission.
*/
public function grantedByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'user_permissions_granted_by');
}
/**
* Check if permission is expired.
*
* @return bool
*/
public function isExpired(): bool
{
return $this->user_permissions_expires_at && $this->user_permissions_expires_at->isPast();
}
/**
* Check if permission is active.
*
* @return bool
*/
public function isActive(): bool
{
return !$this->isExpired();
}
/**
* Scope to get only active permissions.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where(function ($q) {
$q->whereNull('user_permissions_expires_at')
->orWhere('user_permissions_expires_at', '>', Carbon::now());
});
}
/**
* Scope to get permissions for a specific user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $userId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForUser($query, int $userId)
{
return $query->where('user_permissions_user_id', $userId);
}
/**
* Scope to get permissions for a specific company.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $companyId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForCompany($query, int $companyId)
{
return $query->where('user_permissions_bus_id', $companyId);
}
/**
* Scope to get permissions with specific permission.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $permission
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithPermission($query, string $permission)
{
return $query->where('user_permissions_permission', $permission);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Services;
use Modules\Company\Models\Company;
class PlatformService
{
/**
* Obtém o nome da plataforma da company com ID 1
* A company ID 1 representa a identidade da plataforma
*/
public function getPlatformName(): string
{
try {
$company = Company::find(1);
if ($company && !empty($company->platform_name) && $company->platform_name !== ':platformName') {
return $company->platform_name;
}
} catch (\Exception $e) {
// Log error silently, return default
}
return 'SysCMS';
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Services;
use Modules\Erpstock\Models\StockMovement;
use Modules\Erpproducts\Models\Product;
use App\Helpers\Log;
use Illuminate\Database\Capsule\Manager as DB;
class ProductStockMovementService
{
/**
* Create a new product stock movement.
*
* @param array $data Movement data
* @param object $user User performing the action
* @return StockMovement The created movement
* @throws \Exception
*/
public function createMovement(array $data, $user)
{
DB::beginTransaction();
try {
$product = Product::where('pr_id', $data['product_id'])
->where('pr_idbus', $user->users_bus_id)
->first();
if (!$product) {
throw new \Exception(__('erpstock.erpstock.movements.error_product_not_found'));
}
$quantity = (float)$data['quantity'];
$type = $data['type']; // 'E' for Entry, 'S' for Exit
// Update product stock
if ($type === 'E') {
$product->pr_estocado += $quantity;
} else {
$product->pr_estocado -= $quantity;
}
$product->save();
// Create movement record
$movementData = [
'mepro_data' => date('Y-m-d'), // Fixed: Legacy system uses DATE type, not DATETIME
'mepro_idpr' => $product->pr_id,
'mepro_idoperador' => $user->users_id,
'mepro_tipoes' => $type,
'mepro_qtdade' => $quantity,
'mepro_historico' => $data['history'] ?? '',
'mepro_documento' => $data['document'] ?? '',
'mepro_lote' => $data['batch'] ?? '',
'mepro_custo' => $data['cost'] ?? 0,
'mepro_preco' => $data['price'] ?? 0,
'mepro_ipi' => $data['ipi'] ?? 0,
'mepro_icms' => $data['icms'] ?? 0,
'mepro_oimpostos' => $data['other_taxes'] ?? 0,
'mepro_obs' => $data['observation'] ?? '',
'mepro_idbus' => $user->users_bus_id,
'mepro_idfornecedor' => $data['supplier_id'] ?? 0,
'mepro_idcliente' => $data['customer_id'] ?? 0,
'mepro_idarmazem' => $data['warehouse_id'] ?? 0,
'mepro_nfantasia' => $product->pr_descricao, // Fixed: Product model doesn't have pr_nome
'mepro_descricao' => $product->pr_descricao,
'mepro_codigo' => $product->pr_codigo,
'mepro_exp1' => $data['extra1'] ?? '',
'mepro_exp2' => $data['extra2'] ?? '',
'mepro_exp3' => $data['extra3'] ?? '',
// Legacy required fields with default values
'mepro_idhistorico' => 0,
'mepro_idoc' => 0,
'mepro_idcoc' => 0,
'mepro_idcot' => 0,
'mepro_pn' => '',
'mepro_bp' => ''
];
$movement = StockMovement::create($movementData);
Log::write('erpstock', 'Product Stock Movement', "Created movement ID: {$movement->mepro_id} with history: " . ($movementData['mepro_historico'] ?? 'NULL'));
DB::commit();
return $movement;
} catch (\Exception $e) {
DB::rollBack();
Log::write('erpstock', 'Error', 'Failed to create movement: ' . $e->getMessage());
throw $e;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\Services;
use Modules\Erprawmaterials\Models\RawMaterial;
use Modules\ErpStock\Models\RawMaterialStockMovement;
use Illuminate\Database\Capsule\Manager as DB;
use App\Helpers\Log;
class RawMaterialStockMovementService
{
/**
* Create a stock movement for a raw material.
*
* @param array $data Normalized data for the movement
* @param object $user The current user
* @return RawMaterialStockMovement
* @throws \Exception
*/
public function createMovement(array $data, $user)
{
// Validate required fields
if (empty($data['mp_id'])) {
throw new \Exception("Raw Material ID is required.");
}
// Find the raw material
$material = RawMaterial::where('mp_id', $data['mp_id'])
->where('mp_idbus', $user->users_bus_id)
->first();
if (!$material) {
throw new \Exception("Raw Material not found.");
}
$quantity = $data['quantity'] ?? 0;
if ($quantity <= 0) {
throw new \Exception("Invalid quantity: must be greater than zero.");
}
$type = $data['type'] ?? 'E'; // E = Entry, S = Exit
// Prepare data for model
$movementData = [
'memp_idbus' => $user->users_bus_id,
'memp_idmp' => $material->mp_id,
'memp_data' => date('Y-m-d'),
'memp_idoperador' => $user->users_id,
'memp_tipoes' => $type,
'memp_qtdade' => $quantity,
'memp_historico' => $data['history'] ?? '',
'memp_documento' => $data['document'] ?? '',
'memp_lote' => $data['batch'] ?? '',
'memp_custo' => $data['cost'] ?? 0,
'memp_preco' => $data['price'] ?? 0,
'memp_ipi' => $data['ipi'] ?? 0,
'memp_icms' => $data['icms'] ?? 0,
'memp_oimpostos' => $data['other_taxes'] ?? 0,
'memp_obs' => $data['observation'] ?? '',
'memp_codigo' => $material->mp_codigo,
'memp_nome' => $material->mp_descricao,
'memp_idfornecedor' => $data['supplier_id'] ?? 0,
'memp_idcliente' => $data['customer_id'] ?? 0,
'memp_idarmazem' => $data['warehouse_id'] ?? 0,
];
// Begin transaction
DB::beginTransaction();
try {
// Create movement record using the ErpStock model which includes warehouse support
$movement = RawMaterialStockMovement::create($movementData);
// Update raw material stock level
if ($type === 'E') {
$material->mp_estocado += $quantity;
} else {
$material->mp_estocado -= $quantity;
}
$material->save();
DB::commit();
Log::write("Movimentação de estoque ({$type}) para: {$material->mp_descricao} (Qtd: {$quantity})", 'erprawmaterials');
return $movement;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Dotenv\Dotenv;
use Illuminate\Database\Capsule\Manager as Capsule;
// Load environment variables
if (file_exists(__DIR__ . '/.env')) {
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
}
// Helper function to get environment variables
if (!function_exists('get_env')) {
function get_env(string $key, $default = null)
{
return $_ENV[$key] ?? $_SERVER[$key] ?? $default;
}
}
// Helper function to get database configuration
if (!function_exists('get_db_config')) {
function get_db_config(): array
{
return [
'driver' => get_env('DB_DRIVER', get_env('DB_CONNECTION', 'mysql')),
'host' => get_env('DB_HOST', 'localhost'),
'port' => get_env('DB_PORT', '3306'),
'database' => get_env('DB_DATABASE', 'syscms'),
'username' => get_env('DB_USERNAME', 'root'),
'password' => get_env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
];
}
}
// Initialize Capsule (Eloquent)
$capsule = new Capsule;
$dbConfig = get_db_config();
$capsule->addConnection([
'driver' => $dbConfig['driver'],
'host' => $dbConfig['host'],
'port' => $dbConfig['port'],
'database' => $dbConfig['database'],
'username' => $dbConfig['username'],
'password' => $dbConfig['password'],
'charset' => $dbConfig['charset'],
'collation' => $dbConfig['collation'],
'prefix' => $dbConfig['prefix'],
]);
// Set the event dispatcher used by Eloquent models... (optional)
// use Illuminate\Events\Dispatcher;
// use Illuminate\Container\Container;
// $capsule->setEventDispatcher(new Dispatcher(new Container));
// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();
// Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher())
$capsule->bootEloquent();
echo "Conectado ao banco de dados: " . $dbConfig['database'] . " em " . $dbConfig['host'] . ":" . $dbConfig['port'] . "\n";
try {
// Remove entries from 'modules' table
echo "Removendo módulos 'affiliates' e 'affiliatesadmin' da tabela 'modules'...\n";
$deletedModules = Capsule::table('modules')
->whereIn('name', ['affiliates', 'affiliatesadmin'])
->delete();
echo "Removidos $deletedModules módulos da tabela 'modules'.\n";
// Remove entries from 'bus_modules' table
echo "Removendo módulos 'affiliates' e 'affiliatesadmin' da tabela 'bus_modules'...\n";
$deletedBusModules = Capsule::table('bus_modules')
->whereIn('module_name', ['affiliates', 'affiliatesadmin'])
->delete();
echo "Removidos $deletedBusModules módulos da tabela 'bus_modules'.\n";
// Drop affiliate tables
echo "Removendo tabelas de afiliados...\n";
$tablesToDrop = [
'affiliate_clicks',
'affiliate_banners',
'affiliate_commissions',
'affiliates',
];
foreach ($tablesToDrop as $table) {
if (Capsule::schema()->hasTable($table)) {
Capsule::schema()->drop($table);
echo "Tabela '$table' removida.\n";
} else {
echo "Tabela '$table' não existe, pulando.\n";
}
}
echo "Processo de remoção de dados e tabelas de afiliados concluído com sucesso.\n";
} catch (\Exception $e) {
echo "Erro durante a remoção: " . $e->getMessage() . "\n";
}
?>
+288
View File
@@ -0,0 +1,288 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
// Loads the environment helper first
require_once __DIR__ . '/Helpers/Environment.php';
use Kernel\Translator;
use App\Helpers\Log;
use Kernel\Application;
if (!function_exists('__')) {
/**
* Translate a string using the translator
*
* @param string $key The translation key
* @param array $params Parameters to replace in the translation
* @return string The translated string
*/
function __(string $key, array $params = []): string
{
return Translator::translate($key, $params);
}
}
if (!function_exists('add_action')) {
function add_action(string $hook, callable $listener, int $priority = 10): void
{
Application::getInstance()->getHookManager()->addAction($hook, $listener, $priority);
}
}
if (!function_exists('do_action')) {
function do_action(string $hook, ...$args): void
{
Application::getInstance()->getHookManager()->doAction($hook, ...$args);
}
}
if (!function_exists('add_filter')) {
function add_filter(string $hook, callable $listener, int $priority = 10): void
{
Application::getInstance()->getHookManager()->addFilter($hook, $listener, $priority);
}
}
if (!function_exists('apply_filters')) {
function apply_filters(string $hook, $value, ...$args)
{
return Application::getInstance()->getHookManager()->applyFilters($hook, $value, ...$args);
}
}
if (!function_exists('get_active_theme')) {
/**
* Get the currently active theme name
*/
function get_active_theme(): string
{
static $activeTheme = null;
if ($activeTheme === null) {
try {
$themeConfig = \Illuminate\Database\Capsule\Manager::table('system_config')
->where('system_config_key', 'active_theme')
->first();
$activeTheme = $themeConfig ? $themeConfig->system_config_value : 'default';
} catch (\Exception $e) {
$activeTheme = 'default';
}
}
return $activeTheme;
}
}
if (!function_exists('asset_url')) {
/**
* Generate asset URL with theme support
*
* @param string $path The asset path
* @return string The complete asset URL
*/
function asset_url(string $path): string
{
$activeTheme = get_active_theme();
$themeAssetPath = 'themes/' . $activeTheme . '/assets/' . ltrim($path, '/');
// Check if the asset exists in the theme folder
if (file_exists(dirname(__DIR__) . '/' . $themeAssetPath)) {
return '/' . $themeAssetPath;
}
return '/assets/' . ltrim($path, '/');
}
}
if (!function_exists('url')) {
/**
* Generate application URL
*
* @param string $path The path to append to the base URL
* @return string The complete URL
*/
function url(string $path = ''): string
{
return '/' . ltrim($path, '/');
}
}
if (!function_exists('route')) {
/**
* Generate route URL (placeholder for future implementation)
*
* @param string $name The route name
* @param array $params Route parameters
* @return string The route URL
*/
function route(string $name, array $params = []): string
{
// This will be implemented when we have a named route system
return url($name);
}
}
if (!function_exists('csrf_field')) {
/**
* Generate CSRF token field
*
* @return string HTML input with CSRF token
*/
function csrf_field(): string
{
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($token) . '">';
}
}
if (!function_exists('csrf_token')) {
/**
* Get CSRF token value
*
* @return string The CSRF token
*/
function csrf_token(): string
{
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
}
if (!function_exists('verify_csrf_token')) {
/**
* Verify CSRF token
*
* @param string $token The token to verify
* @return bool True if valid, false otherwise
*/
function verify_csrf_token(string $token): bool
{
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
}
if (!function_exists('logger')) {
/**
* Log a message to the application log
*
* @param string $message The message to log
* @param array $context Contextual information
* @param string $level Log level (info, error, warning, debug)
*/
function logger(string $message, array $context = [], string $level = 'info'): void
{
switch ($level) {
case 'error':
Log::error($message, $context);
break;
case 'warning':
Log::warning($message, $context);
break;
case 'debug':
Log::debug($message, $context);
break;
default:
Log::info($message, $context);
break;
}
}
}
if (!function_exists('is_multi_lang_enabled')) {
/**
* Check if multi-language is enabled
*
* @return bool True if multi-language is enabled, false otherwise
*/
function is_multi_lang_enabled(): bool
{
return ($_ENV['APP_MULTI_LANG'] ?? 'false') === 'true';
}
}
if (!function_exists('bus_module_enabled')) {
/**
* Check if a module is enabled for the current authenticated user's company.
*
* @param string $moduleName The module name to check (e.g., 'erppoe')
* @return bool
*/
function bus_module_enabled(string $moduleName): bool
{
if (session_status() === PHP_SESSION_NONE) {
return false;
}
if (!isset($_SESSION['user_id'])) {
return false;
}
try {
$user = \Modules\Auth\Models\User::find($_SESSION['user_id']);
if (!$user) {
return false;
}
if ($user->users_role === 'super_admin') {
return \App\Models\Module::where('modules_name', $moduleName)
->where('modules_ative', true)
->exists();
}
return \App\Models\Module::join('bus_modules', 'modules.modules_id', '=', 'bus_modules.bus_modules_module_id')
->where('bus_modules.bus_modules_bus_id', $user->users_bus_id)
->where('bus_modules.bus_modules_is_enabled', true)
->where('modules.modules_name', $moduleName)
->where('modules.modules_ative', true)
->exists();
} catch (\Exception $e) {
return false;
}
}
}
if (!function_exists('get_config')) {
/**
* Get a system configuration value from the database.
*
* @param string $key The configuration key.
* @param mixed $default Default value if the key is not found.
* @return mixed
*/
function get_config(string $key, $default = null)
{
try {
$config = \Illuminate\Database\Capsule\Manager::table('system_config')
->where('system_config_key', $key)
->first();
return $config ? $config->system_config_value : $default;
} catch (\Exception $e) {
return $default;
}
}
}