First Commit
This commit is contained in:
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+72
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+80
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+73
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+108
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
Executable
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+58
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+60
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -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
@@ -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>");
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+52
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+51
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+78
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+72
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+94
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+129
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+61
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+144
@@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+46
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+87
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+85
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+60
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+34
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+130
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+77
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+80
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+51
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+653
@@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
Executable
+465
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+108
@@ -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)));
|
||||
}
|
||||
}
|
||||
Executable
+97
@@ -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]);
|
||||
}
|
||||
}
|
||||
Executable
+98
@@ -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';
|
||||
}
|
||||
}
|
||||
Executable
+302
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+42
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+132
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+173
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+58
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+221
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+151
@@ -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/');
|
||||
}
|
||||
}
|
||||
Executable
+385
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+283
@@ -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());
|
||||
}
|
||||
}
|
||||
Executable
+129
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+178
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+26
@@ -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';
|
||||
}
|
||||
}
|
||||
Executable
+90
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+108
@@ -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";
|
||||
}
|
||||
|
||||
?>
|
||||
Executable
+288
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user