Files
2026-09-01 10:46:29 -03:00

303 lines
9.8 KiB
PHP
Executable File

<?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;
}
}