diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 92f187a579..7181baec0b 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -63,6 +63,7 @@ public function register(): void HttpMiddleware\ReferrerPolicyHeader::class, HttpMiddleware\ContentTypeOptionsHeader::class, Middleware\DisableBrowserCache::class, + Middleware\GatherDebugInformation::class, ]; }); diff --git a/framework/core/src/Admin/Middleware/GatherDebugInformation.php b/framework/core/src/Admin/Middleware/GatherDebugInformation.php new file mode 100644 index 0000000000..af70266459 --- /dev/null +++ b/framework/core/src/Admin/Middleware/GatherDebugInformation.php @@ -0,0 +1,66 @@ +upsertWebUser(); + + $this->upsertOpCacheStatus(); + + return $handler->handle($request); + } + + /** + * Read current web user, so we can compare that against CLI executions, + * these often cause file permission issues. + */ + public function upsertWebUser(): void + { + $user = $this->settings->get('core.debug.web_user'); + $currentUser = get_current_user(); + + if ($user !== $currentUser) { + $this->settings->set( + 'core.debug.web_user', + $currentUser + ); + } + } + + /** + * Read the opcache situation, this is only visible in web. + * Cli has opcache disabled by default. + */ + public function upsertOpCacheStatus(): void + { + $opcache = $this->settings->get('core.debug.opcache'); + $opcacheStatus = function_exists('opcache_get_configuration') && opcache_get_configuration() !== false ? 'on' : 'off'; + + if ($opcache !== $opcacheStatus) { + $this->settings->set( + 'core.debug.opcache', + $opcacheStatus + ); + } + } +} diff --git a/framework/core/src/Foundation/ApplicationInfoProvider.php b/framework/core/src/Foundation/ApplicationInfoProvider.php index 7bdbec5085..764ea0e098 100644 --- a/framework/core/src/Foundation/ApplicationInfoProvider.php +++ b/framework/core/src/Foundation/ApplicationInfoProvider.php @@ -40,18 +40,21 @@ public function scheduledTasksRegistered(): bool return count($this->schedule->events()) > 0; } - public function getSchedulerStatus(): string + public function getSchedulerStatus(bool $translated = true): string { $status = $this->settings->get('schedule.last_run'); - if (! $status) { - return $this->translator->trans('core.admin.dashboard.status.scheduler.never-run'); + $key = match (true) { + ! $status => 'never-run', + Carbon::parse($status) > Carbon::now()->subMinutes(5) => 'active', + default => 'inactive' + }; + + if ($translated) { + return $this->translator->trans("core.admin.dashboard.status.scheduler.$key"); } - // If the schedule has not run in the last 5 minutes, mark it as inactive. - return Carbon::parse($status) > Carbon::now()->subMinutes(5) - ? $this->translator->trans('core.admin.dashboard.status.scheduler.active') - : $this->translator->trans('core.admin.dashboard.status.scheduler.inactive'); + return $key; } public function identifyQueueDriver(): string diff --git a/framework/core/src/Foundation/Console/InfoCommand.php b/framework/core/src/Foundation/Console/InfoCommand.php index 06b9c6e791..d26f1a2a68 100644 --- a/framework/core/src/Foundation/Console/InfoCommand.php +++ b/framework/core/src/Foundation/Console/InfoCommand.php @@ -10,24 +10,15 @@ namespace Flarum\Foundation\Console; use Flarum\Console\AbstractCommand; -use Flarum\Extension\ExtensionManager; -use Flarum\Foundation\Application; -use Flarum\Foundation\ApplicationInfoProvider; -use Flarum\Foundation\Config; -use Flarum\Settings\SettingsRepositoryInterface; -use Illuminate\Database\ConnectionInterface; +use Flarum\Foundation\Info\Renderer\CliRenderer; +use Flarum\Foundation\Info\Report; +use Illuminate\Contracts\Container\Container; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\Table; -use Symfony\Component\Console\Helper\TableStyle; class InfoCommand extends AbstractCommand { public function __construct( - protected ExtensionManager $extensions, - protected Config $config, - protected SettingsRepositoryInterface $settings, - protected ConnectionInterface $db, - protected ApplicationInfoProvider $appInfo + protected Container $container ) { parent::__construct(); } @@ -41,83 +32,13 @@ protected function configure(): void protected function fire(): int { - $coreVersion = $this->findPackageVersion(__DIR__.'/../../../', Application::VERSION); - $this->output->writeln("Flarum core: $coreVersion"); + $report = new Report( + new CliRenderer($this->output), + $this->container + ); - $this->output->writeln('PHP version: '.$this->appInfo->identifyPHPVersion()); - $this->output->writeln(''.$this->appInfo->identifyDatabaseDriver().' version: '.$this->appInfo->identifyDatabaseVersion()); - - $phpExtensions = implode(', ', get_loaded_extensions()); - $this->output->writeln("Loaded extensions: $phpExtensions"); - - $this->getExtensionTable()->render(); - - $this->output->writeln('Base URL: '.$this->config->url()); - $this->output->writeln('Installation path: '.getcwd()); - $this->output->writeln('Queue driver: '.$this->appInfo->identifyQueueDriver()); - $this->output->writeln('Session driver: '.$this->appInfo->identifySessionDriver()); - - if ($this->appInfo->scheduledTasksRegistered()) { - $this->output->writeln('Scheduler status: '.$this->appInfo->getSchedulerStatus()); - } - - $this->output->writeln('Mail driver: '.$this->settings->get('mail_driver', 'unknown')); - $this->output->writeln('Debug mode: '.($this->config->inDebugMode() ? 'ON' : 'off')); - - if ($this->config->inDebugMode()) { - $this->output->writeln(''); - $this->error( - "Don't forget to turn off debug mode! It should never be turned on in a production system." - ); - } + $report->render(); return Command::SUCCESS; } - - private function getExtensionTable(): Table - { - $table = (new Table($this->output)) - ->setHeaders([ - ['Flarum Extensions'], - ['ID', 'Version', 'Commit'] - ])->setStyle( - (new TableStyle)->setCellHeaderFormat('%s') - ); - - foreach ($this->extensions->getEnabledExtensions() as $extension) { - $table->addRow([ - $extension->getId(), - $extension->getVersion(), - $this->findPackageVersion($extension->getPath()) - ]); - } - - return $table; - } - - /** - * Try to detect a package's exact version. - * - * If the package seems to be a Git version, we extract the currently - * checked out commit using the command line. - */ - private function findPackageVersion(string $path, ?string $fallback = null): ?string - { - if (file_exists("$path/.git")) { - $cwd = getcwd(); - chdir($path); - - $output = []; - $status = null; - exec('git rev-parse HEAD 2>&1', $output, $status); - - chdir($cwd); - - if ($status == 0) { - return isset($fallback) ? "$fallback ($output[0])" : $output[0]; - } - } - - return $fallback; - } } diff --git a/framework/core/src/Foundation/Info/Concerns/PackageVersionFromPath.php b/framework/core/src/Foundation/Info/Concerns/PackageVersionFromPath.php new file mode 100644 index 0000000000..bc61113d36 --- /dev/null +++ b/framework/core/src/Foundation/Info/Concerns/PackageVersionFromPath.php @@ -0,0 +1,39 @@ +&1', $output, $status); + + chdir($cwd); + + if ($status == 0) { + return isset($fallback) ? "$fallback ($output[0])" : $output[0]; + } + } + + return $fallback; + } +} diff --git a/framework/core/src/Foundation/Info/Renderer/CliRenderer.php b/framework/core/src/Foundation/Info/Renderer/CliRenderer.php new file mode 100644 index 0000000000..1d206027fa --- /dev/null +++ b/framework/core/src/Foundation/Info/Renderer/CliRenderer.php @@ -0,0 +1,54 @@ +output->writeln("$title"); + } + + public function keyValue(string $key, mixed $value): void + { + $this->output->writeln("$key: $value"); + } + + public function table(array $headers, array $rows): void + { + $table = (new Table($this->output)) + ->setHeaders($headers)->setStyle( + (new TableStyle)->setCellHeaderFormat('%s') + ); + + foreach ($rows as $row) { + $table->addRow($row); + } + + $table->render(); + } + + public function open(): void + { + } + + public function close(): void + { + } +} diff --git a/framework/core/src/Foundation/Info/RendererInterface.php b/framework/core/src/Foundation/Info/RendererInterface.php new file mode 100644 index 0000000000..236ee2785b --- /dev/null +++ b/framework/core/src/Foundation/Info/RendererInterface.php @@ -0,0 +1,23 @@ +sections; + + $this->renderer->open(); + + foreach ($sections as &$section) { + $section = $this->container->make($section); + + $section($this->renderer); + } + + $this->renderer->close(); + } +} diff --git a/framework/core/src/Foundation/Info/Section/CoreVersion.php b/framework/core/src/Foundation/Info/Section/CoreVersion.php new file mode 100644 index 0000000000..0c9b5b6925 --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/CoreVersion.php @@ -0,0 +1,28 @@ +keyValue( + 'Flarum Core', + $this->findPackageVersion(__DIR__.'/../../../../', Application::VERSION) + ); + } +} diff --git a/framework/core/src/Foundation/Info/Section/Database.php b/framework/core/src/Foundation/Info/Section/Database.php new file mode 100644 index 0000000000..f2c5fa46f5 --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/Database.php @@ -0,0 +1,34 @@ +keyValue( + 'Database driver', + $this->appInfo->identifyDatabaseDriver() + ); + + $renderer->keyValue( + 'Database version', + $this->appInfo->identifyDatabaseVersion() + ); + } +} diff --git a/framework/core/src/Foundation/Info/Section/Debug.php b/framework/core/src/Foundation/Info/Section/Debug.php new file mode 100644 index 0000000000..6360a2aa9d --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/Debug.php @@ -0,0 +1,33 @@ +keyValue( + 'Debug mode', + $this->config->inDebugMode() ? 'on' : 'off' + ); + + if ($this->config->inDebugMode()) { + $renderer->heading('Debug mode should not be enabled in production; this will cause javascript and stylesheets to be recompiled on each visit.'); + } + } +} diff --git a/framework/core/src/Foundation/Info/Section/EnabledExtensions.php b/framework/core/src/Foundation/Info/Section/EnabledExtensions.php new file mode 100644 index 0000000000..e912fc12af --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/EnabledExtensions.php @@ -0,0 +1,40 @@ +extensions->getEnabledExtensions()) + ->map(fn (Extension $extension) => [ + $extension->getId(), + $this->findPackageVersion($extension->getPath(), $extension->getVersion()) + ]) + ->toArray(); + + $renderer->table([ + ['Flarum Extensions'], + ['ID', 'Version'] + ], $rows); + } +} diff --git a/framework/core/src/Foundation/Info/Section/Features.php b/framework/core/src/Foundation/Info/Section/Features.php new file mode 100644 index 0000000000..6034f875d9 --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/Features.php @@ -0,0 +1,67 @@ +keyValue( + 'Scheduler required', + $this->appInfo->scheduledTasksRegistered() ? 'yes' : 'no' + ); + + $renderer->keyValue( + 'Scheduler status', + $this->appInfo->getSchedulerStatus(translated: false) + ); + + $renderer->keyValue( + 'Queue driver', + $this->appInfo->identifyQueueDriver() + ); + + $renderer->keyValue( + 'Session driver', + $this->appInfo->identifySessionDriver() + ); + + $renderer->keyValue( + 'Mail driver', + $this->mail() + ); + } + + protected function mail() + { + $driver = $this->container->make('mail.driver'); + $configured = $this->settings->get('mail_driver'); + + if ($driver instanceof NullDriver) { + $configured .= ' (not active)'; + } + + return $configured; + } +} diff --git a/framework/core/src/Foundation/Info/Section/PHP.php b/framework/core/src/Foundation/Info/Section/PHP.php new file mode 100644 index 0000000000..05928ee7ec --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/PHP.php @@ -0,0 +1,42 @@ +keyValue( + 'PHP version', + $this->appInfo->identifyPHPVersion() + ); + + $renderer->keyValue( + 'PHP extensions', + implode(', ', get_loaded_extensions()) + ); + + $renderer->keyValue( + 'PHP OpCache', + $this->settings->get('core.debug.opcache') ?? 'visit admin to identify' + ); + } +} diff --git a/framework/core/src/Foundation/Info/Section/ServiceUsers.php b/framework/core/src/Foundation/Info/Section/ServiceUsers.php new file mode 100644 index 0000000000..dd23d6e889 --- /dev/null +++ b/framework/core/src/Foundation/Info/Section/ServiceUsers.php @@ -0,0 +1,40 @@ +settings->get('core.debug.web_user') ?? 'visit admin to identify'] + ]; + + if (php_sapi_name() === 'cli') { + $rows[] = [ + 'Current user', + posix_getpwuid(posix_geteuid())['name'] + ]; + } + + $renderer->table([ + ['Service/process users'], + ['Type', 'User'] + ], $rows); + } +} diff --git a/framework/core/src/Foundation/Info/SectionInterface.php b/framework/core/src/Foundation/Info/SectionInterface.php new file mode 100644 index 0000000000..2074e8578b --- /dev/null +++ b/framework/core/src/Foundation/Info/SectionInterface.php @@ -0,0 +1,15 @@ +