First Commit
This commit is contained in:
Executable
+24
@@ -0,0 +1,24 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.php]
|
||||
indent_size = 4
|
||||
|
||||
[*.js]
|
||||
indent_size = 4
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.twig]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,138 @@
|
||||
# .env file
|
||||
|
||||
# Application settings
|
||||
APP_NAME=FluxoDeCaixaPro
|
||||
APP_KEY=base64:UyhxNfcM54vUsw9aNjsMenLjjsK5vTTdIGNLHpBdG4g=
|
||||
APP_ENV=production
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://fluxodecaixapro.syspanel.com.br
|
||||
# Database configuration
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=evoluterSys5
|
||||
DB_USERNAME=evoluterSys5
|
||||
DB_PASSWORD=Tech788959ax10
|
||||
|
||||
APP_MULTI_LANG=true
|
||||
APP_LANG_DEFAULT=pt_BR
|
||||
|
||||
# Editor configuration
|
||||
|
||||
# Session configuration
|
||||
SESSION_DRIVER=file # options: file, database, redis, etc.
|
||||
SESSION_LIFETIME=480 # in minutes
|
||||
|
||||
# Mail configuration (example for SMTP)
|
||||
MAIL_TRANSPORT=smtp
|
||||
MAIL_HOST=sandbox.smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=1c2c80d1d45507
|
||||
MAIL_PASSWORD=e32909821a29de
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=marcocosta@gmx.com
|
||||
MAIL_FROM_NAME=MarcoCosta
|
||||
MAILER_DSN=smtp://1c2c80d1d45507:e32909821a29de@sandbox.smtp.mailtrap.io:2525?encryption=tls
|
||||
MAIL_URL=syscms.syspanel.com.br
|
||||
|
||||
# Logging configuration
|
||||
LOG_CHANNEL=stack # options: daily, single, syslog, etc.
|
||||
|
||||
# JWT Secret for authentication (if you're using JWT)
|
||||
JWT_SECRET=your_jwt_secret_key
|
||||
|
||||
# Other third-party services
|
||||
PUSHER_APP_ID=your_pusher_app_id
|
||||
PUSHER_APP_KEY=your_pusher_app_key
|
||||
PUSHER_APP_SECRET=your_pusher_app_secret
|
||||
|
||||
# Redis configuration (if you're using Redis)
|
||||
REDIS_HOST=syscms-redis
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
|
||||
# Storage paths (example for image uploads)
|
||||
UPLOADS_DIR=/var/www/sysmvc/public/uploads
|
||||
|
||||
# Other sensitive credentials
|
||||
STRIPE_SECRET_KEY=your_stripe_secret_key
|
||||
STRIPE_PUBLISHABLE_KEY=your_stripe_publishable_key
|
||||
|
||||
# PayPal
|
||||
PAYPAL_CLIENT_ID=your_paypal_client_id
|
||||
PAYPAL_SECRET=your_paypal_secret_key
|
||||
PAYPAL_MODE=sandbox # ou "live" para produção
|
||||
|
||||
# MercadoPago
|
||||
MERCADOPAGO_ACCESS_TOKEN=your_access_token
|
||||
MERCADOPAGO_PUBLIC_KEY=your_public_key
|
||||
|
||||
# Pagar.me
|
||||
PAGARME_API_KEY=your_api_key
|
||||
PAGARME_ENCRYPTION_KEY=your_encryption_key
|
||||
|
||||
# Braintree
|
||||
BRAINTREE_MERCHANT_ID=your_merchant_id
|
||||
BRAINTREE_PUBLIC_KEY=your_public_key
|
||||
BRAINTREE_PRIVATE_KEY=your_private_key
|
||||
|
||||
# PagSeguro
|
||||
PAGSEGURO_EMAIL=your_pagseguro_email
|
||||
PAGSEGURO_TOKEN=your_pagseguro_token
|
||||
|
||||
API_PUBLIC_KEY=9d8f87ae56d7806759d59a8e1119b47d
|
||||
API_SECRET_KEY=7958d0834f85a08beef4d4d23cd1d184f9dcfc2b60b2c30f0a67a677960d62c2
|
||||
|
||||
|
||||
|
||||
# Optional for development
|
||||
APP_TIMEZONE=America/Sao_Paulo
|
||||
|
||||
IMG_QUALITY = 40
|
||||
IMG_FORMAT = jpeg
|
||||
|
||||
|
||||
|
||||
# PHP Configuration
|
||||
|
||||
# Limite de memória do PHP (aumente se precisar processar muitos dados)
|
||||
PHP_MEMORY_LIMIT=1024M
|
||||
|
||||
# Tempo máximo de execução de scripts (evita timeout em processos longos)
|
||||
PHP_MAX_EXECUTION_TIME=300
|
||||
|
||||
# Tempo máximo que um script pode aguardar por entrada (upload, por exemplo)
|
||||
PHP_MAX_INPUT_TIME=120
|
||||
|
||||
# Tamanho máximo de arquivos para upload
|
||||
PHP_UPLOAD_MAX_FILESIZE=256M
|
||||
|
||||
# Tamanho máximo de POST (afeta upload de arquivos grandes)
|
||||
PHP_POST_MAX_SIZE=256M
|
||||
|
||||
# Número máximo de arquivos enviados por requisição
|
||||
PHP_MAX_FILE_UPLOADS=50
|
||||
|
||||
# Exibir erros do PHP (use false em produção)
|
||||
PHP_DISPLAY_ERRORS=false
|
||||
|
||||
# Exibir startup errors do PHP
|
||||
PHP_DISPLAY_STARTUP_ERRORS=true
|
||||
|
||||
# Nível de erro do PHP (E_ALL para capturar tudo, E_ERROR para apenas erros graves)
|
||||
PHP_ERROR_REPORTING=E_ALL
|
||||
|
||||
# Habilita ou desabilita logs de erro do PHP
|
||||
PHP_LOG_ERRORS=true
|
||||
|
||||
# Arquivo de log de erros do PHP
|
||||
PHP_ERROR_LOG=/var/log/php_errors.log
|
||||
|
||||
|
||||
# Arquivo de log de erros gerais
|
||||
ERROR_REPORTING=true
|
||||
|
||||
# Arquivo de log de envio de emails
|
||||
LOG_EMAILS=true
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
RewriteEngine on
|
||||
|
||||
# Allow access to storage directory directly
|
||||
RewriteRule ^storage - [L]
|
||||
|
||||
RewriteRule ^$ public/ [L]
|
||||
RewriteRule (.*) public/$1 [L]
|
||||
@@ -0,0 +1,36 @@
|
||||
SysCMS Commercial License
|
||||
|
||||
Copyright (c) 2025 Marco Costa <contato@syspanel.com.br>
|
||||
All rights reserved.
|
||||
|
||||
This software is licensed, not sold.
|
||||
|
||||
Permission is granted to the purchaser to use this software
|
||||
for internal or commercial purposes, subject to the terms below.
|
||||
|
||||
1. You may install and use the software on systems owned or
|
||||
controlled by you or your organization.
|
||||
|
||||
2. You may modify the software for your own internal use.
|
||||
|
||||
3. You may NOT redistribute, sublicense, sell, rent, lease,
|
||||
share, publish, or make available this software, in whole
|
||||
or in part, to any third party.
|
||||
|
||||
4. You may NOT resell this software or include it as part of
|
||||
another product or service without prior written permission
|
||||
from the copyright holder.
|
||||
|
||||
5. This license does NOT grant any rights to the source code
|
||||
beyond the right to use and modify it internally.
|
||||
|
||||
6. This license is non-transferable unless explicitly authorized
|
||||
in writing.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DAMAGES
|
||||
ARISING FROM THE USE OR INABILITY TO USE THIS SOFTWARE.
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
+1769
File diff suppressed because one or more lines are too long
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
/* Tema Default - SysCMS */
|
||||
|
||||
/* Variáveis CSS */
|
||||
:root {
|
||||
--primary-color: #667eea;
|
||||
--secondary-color: #764ba2;
|
||||
--font-family: 'Roboto', sans-serif;
|
||||
--card-border-radius: 15px;
|
||||
--button-border-radius: 50px;
|
||||
--transition-duration: 0.3s;
|
||||
--hero-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
/* Estilos Globais */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Seção Hero */
|
||||
.hero-section {
|
||||
background: var(--hero-gradient);
|
||||
color: white;
|
||||
padding: 100px 0;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 30px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-hero {
|
||||
padding: 15px 30px;
|
||||
font-size: 1.2rem;
|
||||
border-radius: var(--button-border-radius);
|
||||
font-weight: 600;
|
||||
transition: all var(--transition-duration) ease;
|
||||
}
|
||||
|
||||
.btn-hero:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.btn-outline-hero {
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
color: white;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.btn-outline-hero:hover {
|
||||
background: white;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Cards de Recursos */
|
||||
.feature-card {
|
||||
transition: transform var(--transition-duration) ease, box-shadow var(--transition-duration) ease;
|
||||
border: none;
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-10px);
|
||||
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Seção de Estatísticas */
|
||||
.stats-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Cabeçalho de Página */
|
||||
.page-header {
|
||||
background: var(--hero-gradient);
|
||||
color: white;
|
||||
padding: 80px 0;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
/* Seção Sobre */
|
||||
.about-section {
|
||||
padding: 50px 0;
|
||||
}
|
||||
|
||||
.team-member {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-member img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 5px solid #fff;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Cards de Serviços */
|
||||
.service-card {
|
||||
transition: all var(--transition-duration) ease;
|
||||
border: none;
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
transform: translateY(-10px);
|
||||
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Cards de Preços */
|
||||
.price-card {
|
||||
border: none;
|
||||
border-radius: var(--card-border-radius);
|
||||
transition: all var(--transition-duration);
|
||||
}
|
||||
|
||||
.price-card:hover {
|
||||
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.price-header {
|
||||
background-color: #f8f9fa;
|
||||
padding: 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.price-body {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.price-amount {
|
||||
font-size: 3rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Seção de Títulos */
|
||||
.section-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-title:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80px;
|
||||
height: 4px;
|
||||
background: var(--primary-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
text-align: center;
|
||||
max-width: 700px;
|
||||
margin: 0 auto 60px;
|
||||
font-size: 1.1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* Seção CTA */
|
||||
.cta-section {
|
||||
background: var(--hero-gradient);
|
||||
color: white;
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Classes Específicas do Tema */
|
||||
.theme-default {
|
||||
/* Estilos específicos do tema podem ser adicionados aqui */
|
||||
}
|
||||
|
||||
.page-home {
|
||||
/* Estilos específicos para a página home */
|
||||
}
|
||||
|
||||
.page-about {
|
||||
/* Estilos específicos para a página sobre */
|
||||
}
|
||||
|
||||
.page-services {
|
||||
/* Estilos específicos para a página de serviços */
|
||||
}
|
||||
|
||||
/* Responsividade */
|
||||
@media (max-width: 768px) {
|
||||
.hero-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
Executable
+224
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Default Theme - JavaScript
|
||||
* SysCMS - Content Management System
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Theme initialization
|
||||
initTheme();
|
||||
|
||||
// Initialize animated counters
|
||||
initAnimatedCounters();
|
||||
|
||||
// Initialize scroll animations
|
||||
initScrollAnimations();
|
||||
|
||||
// Initialize hover effects
|
||||
initHoverEffects();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the theme
|
||||
*/
|
||||
function initTheme() {
|
||||
// Add theme class to body
|
||||
document.body.classList.add('theme-default');
|
||||
|
||||
// Check for custom theme configurations
|
||||
applyThemeConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies custom theme configurations
|
||||
*/
|
||||
function applyThemeConfig() {
|
||||
// Here we could apply dynamic theme configurations
|
||||
// For example, custom colors, fonts, etc.
|
||||
|
||||
// Example of how to apply custom colors if they exist
|
||||
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color');
|
||||
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary-color');
|
||||
|
||||
if (primaryColor && primaryColor.trim() !== '') {
|
||||
document.documentElement.style.setProperty('--primary-color', primaryColor.trim());
|
||||
}
|
||||
|
||||
if (secondaryColor && secondaryColor.trim() !== '') {
|
||||
document.documentElement.style.setProperty('--secondary-color', secondaryColor.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes animated counters for statistics
|
||||
*/
|
||||
function initAnimatedCounters() {
|
||||
const counters = document.querySelectorAll('.stat-number');
|
||||
const speed = 200;
|
||||
|
||||
const countUp = function() {
|
||||
counters.forEach(counter => {
|
||||
const target = +counter.getAttribute('data-count');
|
||||
const count = +counter.innerText;
|
||||
const increment = target / speed;
|
||||
|
||||
if (count < target) {
|
||||
counter.innerText = Math.ceil(count + increment);
|
||||
setTimeout(countUp, 10);
|
||||
} else {
|
||||
counter.innerText = target;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Trigger animation when statistics section is visible
|
||||
const statsSection = document.querySelector('.stats-section');
|
||||
if (statsSection) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
countUp();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
observer.observe(statsSection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes scroll animations
|
||||
*/
|
||||
function initScrollAnimations() {
|
||||
// Animation for elements appearing on scroll
|
||||
const animatedElements = document.querySelectorAll('.feature-card, .service-card, .price-card, .team-member');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
animatedElements.forEach(element => {
|
||||
// Configure initial state
|
||||
element.style.opacity = '0';
|
||||
element.style.transform = 'translateY(20px)';
|
||||
element.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
||||
|
||||
// Observe element
|
||||
observer.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes hover effects
|
||||
*/
|
||||
function initHoverEffects() {
|
||||
// Parallax effect in hero section
|
||||
const heroSection = document.querySelector('.hero-section');
|
||||
if (heroSection) {
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
const parallax = heroSection.querySelector('img');
|
||||
|
||||
if (parallax) {
|
||||
const speed = 0.5;
|
||||
parallax.style.transform = `translateY(${scrolled * speed}px)`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Elevation effect on cards
|
||||
const cards = document.querySelectorAll('.feature-card, .service-card, .price-card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-10px)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to animate elements
|
||||
*/
|
||||
function animateElement(element, animation, duration = 1000) {
|
||||
element.style.animation = `${animation} ${duration}ms ease-in-out`;
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.animation = '';
|
||||
}, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to add class with delay
|
||||
*/
|
||||
function addClassWithDelay(element, className, delay = 100) {
|
||||
setTimeout(() => {
|
||||
element.classList.add(className);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create background particles (optional)
|
||||
*/
|
||||
function createBackgroundParticles() {
|
||||
const particlesContainer = document.createElement('div');
|
||||
particlesContainer.className = 'particles-container';
|
||||
particlesContainer.style.position = 'fixed';
|
||||
particlesContainer.style.top = '0';
|
||||
particlesContainer.style.left = '0';
|
||||
particlesContainer.style.width = '100%';
|
||||
particlesContainer.style.height = '100%';
|
||||
particlesContainer.style.pointerEvents = 'none';
|
||||
particlesContainer.style.zIndex = '-1';
|
||||
particlesContainer.style.overflow = 'hidden';
|
||||
|
||||
// Create particles
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'particle';
|
||||
particle.style.position = 'absolute';
|
||||
particle.style.width = Math.random() * 5 + 'px';
|
||||
particle.style.height = particle.style.width;
|
||||
particle.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
|
||||
particle.style.borderRadius = '50%';
|
||||
particle.style.left = Math.random() * 100 + '%';
|
||||
particle.style.top = Math.random() * 100 + '%';
|
||||
particle.style.opacity = Math.random() * 0.5 + 0.2;
|
||||
|
||||
// Float animation
|
||||
const duration = Math.random() * 20 + 10;
|
||||
const delay = Math.random() * 5;
|
||||
particle.style.animation = `float ${duration}s ${delay}s infinite ease-in-out`;
|
||||
|
||||
particlesContainer.appendChild(particle);
|
||||
}
|
||||
|
||||
// Add CSS for float animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0) translateX(0); }
|
||||
25% { transform: translateY(-20px) translateX(10px); }
|
||||
50% { transform: translateY(10px) translateX(-10px); }
|
||||
75% { transform: translateY(-10px) translateX(20px); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.body.appendChild(particlesContainer);
|
||||
}
|
||||
|
||||
// Export functions for global use
|
||||
window.ThemeDefault = {
|
||||
animateElement,
|
||||
addClassWithDelay,
|
||||
createBackgroundParticles
|
||||
};
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SysCMS Console Wrapper
|
||||
* This file is maintained for backward compatibility.
|
||||
* Please use the 'syscli' file in the root directory.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../syscli';
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Kernel\Application;
|
||||
|
||||
$app = new Application();
|
||||
|
||||
// Run Migrations
|
||||
$migrations = [
|
||||
__DIR__ . '/../modules/eventsguide/database/migrations/001_create_events_table.php',
|
||||
__DIR__ . '/../modules/eventsguide/database/migrations/002_create_event_gallery_table.php',
|
||||
__DIR__ . '/../modules/eventsguide/database/migrations/003_create_event_reports_table.php',
|
||||
__DIR__ . '/../modules/eventsguide/database/migrations/004_create_event_ads_table.php',
|
||||
__DIR__ . '/../modules/eventsguide/database/migrations/005_insert_module_record.php',
|
||||
];
|
||||
|
||||
foreach ($migrations as $file) {
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
|
||||
// Extract class name from file content
|
||||
$content = file_get_contents($file);
|
||||
if (preg_match('/class\s+(\w+)/', $content, $matches)) {
|
||||
$className = $matches[1];
|
||||
if (class_exists($className)) {
|
||||
echo "Running $className...\n";
|
||||
$migration = new $className();
|
||||
$migration->up();
|
||||
echo "$className done.\n";
|
||||
} else {
|
||||
echo "Class $className not found in $file\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "File not found: $file\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Installation complete.\n";
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user