First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+653
View File
@@ -0,0 +1,653 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* This software is licensed, not sold.
* Permission is granted to use this software
* subject to the terms of the SysCMS Commercial License.
*
* Redistribution, sublicensing, resale, or
* disclosure of this software, in whole or in part,
* is strictly prohibited without prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace App\Controllers;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Modules\Auth\Models\User;
use App\Models\Module;
use Modules\Company\Models\Company;
use Illuminate\Database\Capsule\Manager as DB;
class AdminController extends BaseAdminController
{
/**
* Display the main dashboard.
*
* @param Request $request
* @return Response
*/
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new Response('', 302, ['Location' => url('/login')]);
}
// Get enabled modules for the user's company
$enabledModules = $this->getEnabledModules($user);
// Get widgets from authorized modules
$widgets = $this->getAuthorizedWidgets($enabledModules, $user);
// Get system statistics
$stats = $this->getSystemStats($user);
// Sidebar and common data are handled by renderAdminView,
// but since dashboard/index.twig expects specific variables,
// we can still pass them explicitly if we use the old way,
// OR we can use renderAdminView and just pass the extra data.
// Check if SEO module is installed but not configured
$seoPending = false;
try {
$seoModule = Module::where('modules_name', 'seo')->active()->first();
if ($seoModule && empty($seoModule->modules_settings)) {
$seoPending = true;
}
} catch (\Throwable $e) {
// Silently fail if something goes wrong with SEO check
error_log("SEO Check Error: " . $e->getMessage());
}
// Using renderAdminView for consistency:
return $this->renderAdminView('dashboard/index.twig', [
'stats' => $stats,
'recent_activity' => $this->getRecentActivity($user),
'widgets' => $widgets,
'seo_pending' => $seoPending,
'cache_cleared' => $request->query->get('cache_cleared'),
'deleted_count' => $request->query->get('deleted'),
'opcache_cleared' => $request->query->get('opcache')
]);
}
/**
* Get authorized widgets for the user.
*
* @param \Illuminate\Database\Eloquent\Collection $modules
* @param User $user
* @return array
*/
protected function getAuthorizedWidgets($modules, User $user): array
{
$widgets = [];
foreach ($modules as $module) {
if (!$this->userHasModulePermission($user, $module)) {
continue;
}
$moduleNamespace = $module->modules_namespace;
// If namespace is not defined, try to guess it from name (fallback)
if (!$moduleNamespace) {
$moduleNamespace = "Modules\\" . ucfirst($module->modules_name);
}
// Ensure namespace starts with \Modules\
if (strpos($moduleNamespace, 'Modules\\') !== 0 && strpos($moduleNamespace, '\\Modules\\') !== 0) {
$moduleNamespace = "Modules\\" . ltrim($moduleNamespace, '\\');
}
$widgetClass = (strpos($moduleNamespace, '\\') === 0 ? "" : "\\") . $moduleNamespace . "\\Widgets\\DashboardWidget";
if (class_exists($widgetClass)) {
try {
$widget = new $widgetClass();
// Check if user can view this widget
if ($this->userCanViewWidget($user, $widget)) {
$widgets[] = $widget->render();
}
} catch (\Throwable $e) {
error_log("Error loading widget {$widgetClass}: " . $e->getMessage());
}
}
}
return $widgets;
}
/**
* Check if user can view a widget.
*
* @param User $user
* @param mixed $widget
* @return bool
*/
protected function userCanViewWidget(User $user, $widget): bool
{
// Check if widget defines required permissions
if (method_exists($widget, 'getRequiredPermission')) {
$requiredPermission = $widget->getRequiredPermission();
return $user->hasPermission($requiredPermission);
}
// If no specific permission, check module access
return true;
}
/**
* Get system statistics.
*
* @param User $user
* @return array
*/
protected function getSystemStats(User $user): array
{
try {
if ($user->isSuperAdmin()) {
return $this->getSuperAdminStats();
}
return $this->getCompanyStats($user);
} catch (\Exception $e) {
error_log("Error loading system stats: " . $e->getMessage());
return [
'total_companies' => 0,
'active_companies' => 0,
'total_users' => 0,
'active_users' => 0,
'total_modules' => 0,
'active_modules' => 0,
'user_count' => 0,
'enabled_modules' => 0
];
}
}
/**
* Get statistics for Super Admin.
*
* @return array
*/
protected function getSuperAdminStats(): array
{
return [
'total_companies' => class_exists('Modules\Company\Models\Company') ? \Modules\Company\Models\Company::count() : 0,
'active_companies' => class_exists('Modules\Company\Models\Company') ? \Modules\Company\Models\Company::active()->count() : 0,
'total_users' => User::count(),
'active_users' => User::active()->count(),
'total_modules' => Module::count(),
'active_modules' => Module::active()->count()
];
}
/**
* Get statistics for company users.
*
* @param User $user
* @return array
*/
protected function getCompanyStats(User $user): array
{
$companyId = $user->users_bus_id;
return [
'user_count' => User::where('users_bus_id', $companyId)->count(),
'active_users' => User::where('users_bus_id', $companyId)->active()->count(),
'enabled_modules' => DB::table('bus_modules')
->where('bus_modules_bus_id', $companyId)
->where('bus_modules_is_enabled', true)
->count()
];
}
/**
* Get recent activity for the user.
*
* @param User $user
* @return \Illuminate\Database\Eloquent\Collection
*/
protected function getRecentActivity(User $user)
{
try {
if (!class_exists('App\Models\ActivityLog')) {
return collect([]);
}
return \App\Models\ActivityLog::with('user')
->forUser($user->users_id)
->orderByDate('desc')
->limit(10)
->get();
} catch (\Exception $e) {
error_log("Error loading recent activity: " . $e->getMessage());
return collect([]);
}
}
/**
* Show module installation form.
*
* @param Request $request
* @return Response
*/
public function showInstallModule(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('', 302, ['Location' => url('/dashboard')]);
}
$success = isset($_SESSION['flash_success']) ? (array) $_SESSION['flash_success'] : [];
$error = isset($_SESSION['flash_error']) ? (array) $_SESSION['flash_error'] : [];
unset($_SESSION['flash_success'], $_SESSION['flash_error']);
return $this->renderAdminView('modules/install.twig', [
'error' => $error,
'success' => $success
]);
}
/**
* Install a module from zip file.
*
* @param Request $request
* @return Response
*/
public function installModule(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('', 302, ['Location' => url('/dashboard')]);
}
try {
// Validate uploaded file
if (!$request->files->has('module_zip')) {
throw new \Exception(__('modules.error_no_file'));
}
$file = $request->files->get('module_zip');
// Validate file type
if ($file->getMimeType() !== 'application/zip' && $file->getMimeType() !== 'application/x-zip-compressed') {
throw new \Exception(__('modules.error_invalid_zip'));
}
// Create temporary directory
$tempDir = sys_get_temp_dir() . '/module_install_' . time();
mkdir($tempDir, 0755, true);
// Extract zip file
$zip = new \ZipArchive();
if ($zip->open($file->getPathname()) !== TRUE) {
throw new \Exception(__('modules.error_open_zip'));
}
if (!$zip->extractTo($tempDir)) {
throw new \Exception(__('modules.error_extract_zip'));
}
$zip->close();
// Validate module structure
$this->validateModuleStructure($tempDir);
// Read module configuration
$moduleConfig = $this->readModuleConfig($tempDir);
// Check if module already exists
if (Module::where('modules_name', $moduleConfig['name'])->exists()) {
throw new \Exception(__('modules.error_module_exists'));
}
// Copy module files to modules directory
$modulesPath = dirname(__DIR__, 2) . '/modules/' . $moduleConfig['name'];
$this->recursiveCopy($tempDir, $modulesPath);
// Run migrations if they exist
$this->runModuleMigrations($modulesPath);
// Check if module was registered by migrations
$module = Module::where('modules_name', $moduleConfig['name'])->first();
if (!$module) {
// Register module in database
$module = $this->registerModule($moduleConfig);
}
// Enable module for all companies if specified
if ($request->request->get('enable_for_all', false)) {
$this->enableModuleForAllCompanies($module->modules_id, $user->users_id);
}
// Clean up
$this->recursiveDelete($tempDir);
$_SESSION['flash_success'][] = __('modules.success_install');
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
} catch (\Throwable $e) {
// Clean up on error
if (isset($tempDir) && is_dir($tempDir)) {
$this->recursiveDelete($tempDir);
}
$_SESSION['flash_error'][] = $e->getMessage();
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
}
}
/**
* Validate module structure.
*
* @param string $modulePath
* @throws \Exception
*/
protected function validateModuleStructure(string $modulePath): void
{
$requiredFiles = ['module.json'];
foreach ($requiredFiles as $file) {
if (!file_exists($modulePath . '/' . $file)) {
throw new \Exception(__('modules.error_missing_file', ['file' => $file]));
}
}
}
/**
* Read module configuration.
*
* @param string $modulePath
* @return array
* @throws \Exception
*/
protected function readModuleConfig(string $modulePath): array
{
$configPath = $modulePath . '/module.json';
if (!file_exists($configPath)) {
throw new \Exception(__('modules.error_missing_config'));
}
$config = json_decode(file_get_contents($configPath), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(__('modules.error_invalid_config', ['error' => json_last_error_msg()]));
}
$requiredFields = ['name', 'version', 'description'];
foreach ($requiredFields as $field) {
if (empty($config[$field])) {
throw new \Exception(__('modules.error_missing_field', ['field' => $field]));
}
}
return $config;
}
/**
* Recursively copy directory.
*
* @param string $source
* @param string $destination
*/
protected function recursiveCopy(string $source, string $destination): void
{
if (!is_dir($destination)) {
mkdir($destination, 0755, true);
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
// Calculate subPathName manually to ensure compatibility
$len = strlen($source);
$subPathName = substr($item->getPathname(), $len + 1);
$targetPath = $destination . '/' . $subPathName;
if ($item->isDir()) {
mkdir($targetPath, 0755, true);
} else {
copy($item, $targetPath);
}
}
}
/**
* Recursively delete directory.
*
* @param string $dir
*/
protected function recursiveDelete(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (is_dir($path)) {
$this->recursiveDelete($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/**
* Run module migrations.
*
* @param string $modulePath
*/
protected function runModuleMigrations(string $modulePath): void
{
$migrationsPath = $modulePath . '/database/migrations';
if (!is_dir($migrationsPath)) {
return;
}
$migrations = glob($migrationsPath . '/*.php');
foreach ($migrations as $migration) {
require_once $migration;
// Try to find class name from file content
$content = file_get_contents($migration);
if (preg_match('/class\s+(\w+)/', $content, $matches)) {
$className = $matches[1];
if (class_exists($className)) {
$instance = new $className();
if (method_exists($instance, 'up')) {
try {
$instance->up();
} catch (\Exception $e) {
error_log("Migration failed for $className: " . $e->getMessage());
throw $e; // Re-throw to show error to user
}
}
} else {
error_log("Class $className not found in migration file: $migration");
}
} else {
error_log("Could not find class name in migration file: $migration");
}
}
}
/**
* Fix module menus in database.
*
* @return Response
*/
public function fixModuleMenus(): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access denied', 403);
}
$modules = Module::all();
$count = 0;
foreach ($modules as $module) {
$modulePath = dirname(__DIR__, 2) . '/' . $module->modules_path;
try {
$config = $this->readModuleConfig($modulePath);
// Update module settings with menu
$module->modules_settings = array_merge($module->modules_settings ?? [], $config['settings'] ?? [], ['menu' => $config['menu'] ?? []]);
// Also update top-level module fields that might be in module.json
if (isset($config['display_name'])) $module->modules_display_name = $config['display_name'];
if (isset($config['description'])) $module->modules_description = $config['description'];
if (isset($config['version'])) $module->modules_version = $config['version'];
if (isset($config['icon'])) $module->modules_icon = $config['icon'];
if (isset($config['menu_side_bar'])) $module->menu_side_bar = (bool)$config['menu_side_bar'];
if (isset($config['menu_nav_bar'])) $module->menu_nav_bar = (bool)$config['menu_nav_bar'];
if (isset($config['order'])) $module->modules_order = (int)$config['order'];
if (isset($config['namespace'])) $module->modules_namespace = $config['namespace'];
$module->save();
$count++;
} catch (\Exception $e) {
// Ignore errors for individual modules
error_log("Failed to update menu for module {$module->modules_name}: " . $e->getMessage());
}
}
$_SESSION['flash_success'][] = __('modules.success_menus_updated', ['count' => $count]);
return new Response('', 302, ['Location' => url('/admin/modules/install')]);
}
/**
* Register module in database.
*
* @param array $config
* @return Module
*/
protected function registerModule(array $config): Module
{
return Module::create([
'modules_name' => $config['name'],
'modules_display_name' => $config['display_name'] ?? $config['name'],
'modules_description' => $config['description'],
'modules_version' => $config['version'],
'modules_author' => $config['author'] ?? 'Unknown',
'modules_ative' => true,
'modules_public' => $config['public'] ?? false,
'modules_blocked' => false,
'modules_dependencies' => $config['dependencies'] ?? [],
'modules_permissions' => $config['permissions'] ?? [],
'modules_settings' => array_merge($config['settings'] ?? [], ['menu' => $config['menu'] ?? []]),
'modules_path' => 'modules/' . $config['name'],
'modules_namespace' => $config['namespace'] ?? ('Modules\\' . ucfirst($config['name'])),
'modules_icon' => $config['icon'] ?? 'bi bi-gear',
'menu_side_bar' => $config['menu_side_bar'] ?? true,
'menu_nav_bar' => $config['menu_nav_bar'] ?? false,
'modules_order' => $config['order'] ?? 999,
'modules_install_date' => \Carbon\Carbon::now(),
'modules_update_date' => \Carbon\Carbon::now()
]);
}
/**
* Enable module for all companies.
*
* @param int $moduleId
* @param int $enabledBy
*/
protected function enableModuleForAllCompanies(int $moduleId, int $enabledBy): void
{
$companies = Company::all();
foreach ($companies as $company) {
$company->enableModule($moduleId, $enabledBy);
}
}
/**
* Search for modules and users.
*
* @param Request $request
* @return Response
*/
public function search(Request $request): Response
{
$user = $this->getCurrentUser();
$query = $request->query->get('q');
if (!$user) {
return new Response('', 302, ['Location' => url('/login')]);
}
if (empty($query)) {
return $this->renderAdminView('dashboard/search_results.twig', [
'query' => '',
'results' => ['modules' => [], 'users' => []]
]);
}
$results = [
'modules' => [],
'users' => []
];
// Search Modules
$results['modules'] = Module::where('modules_name', 'LIKE', "%{$query}%")
->orWhere('modules_display_name', 'LIKE', "%{$query}%")
->orWhere('modules_description', 'LIKE', "%{$query}%")
->get();
// Search Users (Super Admin or Company Admin)
if ($user->isSuperAdmin()) {
$results['users'] = User::where('users_name', 'LIKE', "%{$query}%")
->orWhere('users_email', 'LIKE', "%{$query}%")
->get();
} elseif ($user->users_bus_id) {
$results['users'] = User::where('users_bus_id', $user->users_bus_id)
->where(function($q) use ($query) {
$q->where('users_name', 'LIKE', "%{$query}%")
->orWhere('users_email', 'LIKE', "%{$query}%");
})
->get();
}
return $this->renderAdminView('dashboard/search_results.twig', [
'query' => $query,
'results' => $results
]);
}
}