303 lines
9.8 KiB
PHP
Executable File
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;
|
|
}
|
|
}
|