diff --git a/framework/core/composer.json b/framework/core/composer.json index 1ba12df12..ff06ace99 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -44,6 +44,9 @@ "league/flysystem": "^1.0.11", "league/oauth2-client": "~1.0", "matthiasmullie/minify": "^1.3", + "middlewares/base-path": "^1.1", + "middlewares/base-path-router": "^0.2.1", + "middlewares/request-handler": "^1.2", "monolog/monolog": "^1.16.0", "nikic/fast-route": "^0.6", "oyejorge/less.php": "^1.7", @@ -58,6 +61,7 @@ "symfony/yaml": "^3.3", "tobscure/json-api": "^0.3.0", "zendframework/zend-diactoros": "^1.8.4", + "zendframework/zend-httphandlerrunner": "^1.0", "zendframework/zend-stratigility": "^3.0" }, "require-dev": { diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 3849d183c..859b0db0c 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider; use Flarum\Frontend\RecompileFrontendAssets; use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\HandleErrorsWithView; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; use Flarum\Http\Middleware\ParseJsonBody; use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\SetLocale; @@ -46,8 +47,11 @@ class AdminServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); - $pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); + if ($app->inDebugMode()) { + $pipe->pipe($app->make(HandleErrorsWithWhoops::class)); + } else { + $pipe->pipe($app->make(HandleErrorsWithView::class)); + } $pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(StartSession::class)); @@ -58,7 +62,7 @@ class AdminServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'admin')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.admin.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.admin.routes'))); return $pipe; }); diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index b5b83d24e..f9370176b 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -66,7 +66,7 @@ class ApiServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'api')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.api.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.api.routes'))); return $pipe; }); diff --git a/framework/core/src/Bus/BusServiceProvider.php b/framework/core/src/Bus/BusServiceProvider.php index c07b9b812..938da0c55 100644 --- a/framework/core/src/Bus/BusServiceProvider.php +++ b/framework/core/src/Bus/BusServiceProvider.php @@ -12,17 +12,27 @@ namespace Flarum\Bus; use Flarum\Foundation\AbstractServiceProvider; -use Illuminate\Contracts\Bus\Dispatcher as BusContract; +use Illuminate\Bus\Dispatcher as BaseDispatcher; +use Illuminate\Contracts\Bus\Dispatcher as DispatcherContract; +use Illuminate\Contracts\Bus\QueueingDispatcher as QueueingDispatcherContract; use Illuminate\Contracts\Queue\Factory as QueueFactoryContract; class BusServiceProvider extends AbstractServiceProvider { public function register() { - $this->app->bind(BusContract::class, function ($app) { + $this->app->bind(BaseDispatcher::class, function ($app) { return new Dispatcher($app, function ($connection = null) use ($app) { return $app[QueueFactoryContract::class]->connection($connection); }); }); + + $this->app->alias( + BaseDispatcher::class, DispatcherContract::class + ); + + $this->app->alias( + BaseDispatcher::class, QueueingDispatcherContract::class + ); } } diff --git a/framework/core/src/Console/Server.php b/framework/core/src/Console/Server.php index c5a8d6b4d..a65ef9b28 100644 --- a/framework/core/src/Console/Server.php +++ b/framework/core/src/Console/Server.php @@ -12,74 +12,37 @@ namespace Flarum\Console; use Flarum\Console\Event\Configuring; -use Flarum\Database\Console\GenerateMigrationCommand; -use Flarum\Database\Console\MigrateCommand; -use Flarum\Database\Console\ResetCommand; use Flarum\Foundation\Application; -use Flarum\Foundation\Console\CacheClearCommand; -use Flarum\Foundation\Console\InfoCommand; -use Flarum\Foundation\Site; -use Flarum\Install\Console\InstallCommand; -use Flarum\Install\InstallServiceProvider; use Illuminate\Contracts\Events\Dispatcher; use Symfony\Component\Console\Application as ConsoleApplication; class Server { - /** - * @param Site $site - * @return Server - */ - public static function fromSite(Site $site) - { - return new static($site->boot()); - } + protected $commands; - public function __construct(Application $app) + public function __construct(array $commands) { - $this->app = $app; + $this->commands = $commands; } public function listen() { - $console = $this->getConsoleApplication(); + $console = new ConsoleApplication('Flarum', Application::VERSION); + + foreach ($this->commands as $command) { + $console->add($command); + } + + $this->extend($console); exit($console->run()); } - /** - * @return ConsoleApplication - */ - protected function getConsoleApplication() + private function extend(ConsoleApplication $console) { - $console = new ConsoleApplication('Flarum', $this->app->version()); + $app = Application::getInstance(); - $this->app->register(InstallServiceProvider::class); - - $commands = [ - InstallCommand::class, - MigrateCommand::class, - ResetCommand::class, - GenerateMigrationCommand::class, - ]; - - if ($this->app->isInstalled()) { - $commands = array_merge($commands, [ - InfoCommand::class, - CacheClearCommand::class - ]); - } - - foreach ($commands as $command) { - $console->add($this->app->make( - $command, - ['config' => $this->app->isInstalled() ? $this->app->make('flarum.config') : []] - )); - } - - $events = $this->app->make(Dispatcher::class); - $events->fire(new Configuring($this->app, $console)); - - return $console; + $events = $app->make(Dispatcher::class); + $events->fire(new Configuring($app, $console)); } } diff --git a/framework/core/src/Database/DatabaseServiceProvider.php b/framework/core/src/Database/DatabaseServiceProvider.php index 8d3c08262..302305266 100644 --- a/framework/core/src/Database/DatabaseServiceProvider.php +++ b/framework/core/src/Database/DatabaseServiceProvider.php @@ -50,9 +50,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider */ public function boot() { - if ($this->app->isInstalled()) { - AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface')); - AbstractModel::setEventDispatcher($this->app->make('events')); - } + AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface')); + AbstractModel::setEventDispatcher($this->app->make('events')); } } diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 625ff636b..c08d1deab 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider; use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\CollectGarbage; use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\HandleErrorsWithView; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; use Flarum\Http\Middleware\ParseJsonBody; use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\SetLocale; @@ -49,8 +50,11 @@ class ForumServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); - $pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); + if ($app->inDebugMode()) { + $pipe->pipe($app->make(HandleErrorsWithWhoops::class)); + } else { + $pipe->pipe($app->make(HandleErrorsWithView::class)); + } $pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(CollectGarbage::class)); @@ -62,7 +66,7 @@ class ForumServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'forum')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.forum.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.forum.routes'))); return $pipe; }); diff --git a/framework/core/src/Foundation/AppInterface.php b/framework/core/src/Foundation/AppInterface.php new file mode 100644 index 000000000..927c7b42e --- /dev/null +++ b/framework/core/src/Foundation/AppInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +interface AppInterface +{ + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler(); + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands(); +} diff --git a/framework/core/src/Foundation/Application.php b/framework/core/src/Foundation/Application.php index 61c8fd798..10c194520 100644 --- a/framework/core/src/Foundation/Application.php +++ b/framework/core/src/Foundation/Application.php @@ -11,7 +11,6 @@ namespace Flarum\Foundation; -use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Events\EventServiceProvider; @@ -114,27 +113,6 @@ class Application extends Container implements ApplicationContract } } - /** - * Determine if Flarum has been installed. - * - * @return bool - */ - public function isInstalled(): bool - { - return $this->bound('flarum.config'); - } - - public function isUpToDate(): bool - { - $settings = $this->make(SettingsRepositoryInterface::class); - - try { - $version = $settings->get('version'); - } finally { - return isset($version) && $version === $this->version(); - } - } - /** * @param string $key * @param mixed $default @@ -142,7 +120,7 @@ class Application extends Container implements ApplicationContract */ public function config($key, $default = null) { - return $this->isInstalled() ? array_get($this->make('flarum.config'), $key, $default) : $default; + return array_get($this->make('flarum.config'), $key, $default); } /** @@ -152,7 +130,7 @@ class Application extends Container implements ApplicationContract */ public function inDebugMode() { - return ! $this->isInstalled() || $this->config('debug'); + return $this->config('debug', true); } /** @@ -163,7 +141,7 @@ class Application extends Container implements ApplicationContract */ public function url($path = null) { - $config = $this->isInstalled() ? $this->make('flarum.config') : []; + $config = $this->make('flarum.config'); $url = array_get($config, 'url', array_get($_SERVER, 'REQUEST_URI')); if (is_array($url)) { diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php new file mode 100644 index 000000000..e25c30e1a --- /dev/null +++ b/framework/core/src/Foundation/InstalledApp.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Database\Console\GenerateMigrationCommand; +use Flarum\Database\Console\MigrateCommand; +use Flarum\Database\Console\ResetCommand; +use Flarum\Foundation\Console\CacheClearCommand; +use Flarum\Foundation\Console\InfoCommand; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Settings\SettingsRepositoryInterface; +use Illuminate\Contracts\Container\Container; +use Middlewares\BasePath; +use Middlewares\BasePathRouter; +use Middlewares\RequestHandler; +use Zend\Stratigility\MiddlewarePipe; + +class InstalledApp implements AppInterface +{ + /** + * @var Container + */ + protected $container; + + /** + * @var array + */ + protected $config; + + public function __construct(Container $container, array $config) + { + $this->container = $container; + $this->config = $config; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler() + { + if ($this->inMaintenanceMode()) { + return new MaintenanceModeHandler(); + } elseif ($this->needsUpdate()) { + return $this->getUpdaterHandler(); + } + + $pipe = new MiddlewarePipe; + + $pipe->pipe(new BasePath($this->basePath())); + $pipe->pipe( + new BasePathRouter([ + $this->subPath('api') => 'flarum.api.middleware', + $this->subPath('admin') => 'flarum.admin.middleware', + '/' => 'flarum.forum.middleware', + ]) + ); + $pipe->pipe(new RequestHandler($this->container)); + + return $pipe; + } + + private function inMaintenanceMode(): bool + { + return $this->config['offline'] ?? false; + } + + private function needsUpdate(): bool + { + $settings = $this->container->make(SettingsRepositoryInterface::class); + $version = $settings->get('version'); + + return $version !== Application::VERSION; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getUpdaterHandler() + { + $pipe = new MiddlewarePipe; + $pipe->pipe( + new DispatchRoute($this->container->make('flarum.update.routes')) + ); + + return $pipe; + } + + private function basePath(): string + { + return parse_url($this->config['url'], PHP_URL_PATH) ?: '/'; + } + + private function subPath($pathName): string + { + return '/'.$this->config['paths'][$pathName]; + } + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands() + { + return [ + $this->container->make(GenerateMigrationCommand::class), + $this->container->make(InfoCommand::class, ['config' => $this->config]), + $this->container->make(MigrateCommand::class), + $this->container->make(ResetCommand::class), + $this->container->make(CacheClearCommand::class), + ]; + } +} diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php new file mode 100644 index 000000000..c811e32cf --- /dev/null +++ b/framework/core/src/Foundation/InstalledSite.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Admin\AdminServiceProvider; +use Flarum\Api\ApiServiceProvider; +use Flarum\Bus\BusServiceProvider; +use Flarum\Database\DatabaseServiceProvider; +use Flarum\Database\MigrationServiceProvider; +use Flarum\Discussion\DiscussionServiceProvider; +use Flarum\Extension\ExtensionServiceProvider; +use Flarum\Formatter\FormatterServiceProvider; +use Flarum\Forum\ForumServiceProvider; +use Flarum\Frontend\FrontendServiceProvider; +use Flarum\Group\GroupServiceProvider; +use Flarum\Locale\LocaleServiceProvider; +use Flarum\Notification\NotificationServiceProvider; +use Flarum\Post\PostServiceProvider; +use Flarum\Search\SearchServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\Settings\SettingsServiceProvider; +use Flarum\Update\UpdateServiceProvider; +use Flarum\User\SessionServiceProvider; +use Flarum\User\UserServiceProvider; +use Illuminate\Cache\FileStore; +use Illuminate\Cache\Repository as CacheRepository; +use Illuminate\Config\Repository as ConfigRepository; +use Illuminate\Contracts\Cache\Repository; +use Illuminate\Contracts\Cache\Store; +use Illuminate\Filesystem\Filesystem; +use Illuminate\Filesystem\FilesystemServiceProvider; +use Illuminate\Hashing\HashServiceProvider; +use Illuminate\Mail\MailServiceProvider; +use Illuminate\Validation\ValidationServiceProvider; +use Illuminate\View\ViewServiceProvider; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +class InstalledSite implements SiteInterface +{ + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $publicPath; + + /** + * @var string + */ + protected $storagePath; + + /** + * @var array + */ + protected $config; + + public function __construct($basePath, $publicPath, array $config) + { + $this->basePath = $basePath; + $this->publicPath = $publicPath; + $this->config = $config; + } + + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface + { + return new InstalledApp( + $this->bootLaravel(), + $this->config + ); + } + + /** + * @param $storagePath + * @return static + */ + public function setStoragePath($storagePath) + { + $this->storagePath = $storagePath; + + return $this; + } + + protected function bootLaravel(): Application + { + $laravel = new Application($this->basePath, $this->publicPath); + + if ($this->storagePath) { + $laravel->useStoragePath($this->storagePath); + } + + $laravel->instance('env', 'production'); + $laravel->instance('flarum.config', $this->config); + $laravel->instance('config', $config = $this->getIlluminateConfig($laravel)); + + $this->registerLogger($laravel); + $this->registerCache($laravel); + + $laravel->register(DatabaseServiceProvider::class); + $laravel->register(MigrationServiceProvider::class); + $laravel->register(SettingsServiceProvider::class); + $laravel->register(LocaleServiceProvider::class); + $laravel->register(BusServiceProvider::class); + $laravel->register(FilesystemServiceProvider::class); + $laravel->register(HashServiceProvider::class); + $laravel->register(MailServiceProvider::class); + $laravel->register(ViewServiceProvider::class); + $laravel->register(ValidationServiceProvider::class); + + $settings = $laravel->make(SettingsRepositoryInterface::class); + + $config->set('mail.driver', $settings->get('mail_driver')); + $config->set('mail.host', $settings->get('mail_host')); + $config->set('mail.port', $settings->get('mail_port')); + $config->set('mail.from.address', $settings->get('mail_from')); + $config->set('mail.from.name', $settings->get('forum_title')); + $config->set('mail.encryption', $settings->get('mail_encryption')); + $config->set('mail.username', $settings->get('mail_username')); + $config->set('mail.password', $settings->get('mail_password')); + + $laravel->register(DiscussionServiceProvider::class); + $laravel->register(FormatterServiceProvider::class); + $laravel->register(FrontendServiceProvider::class); + $laravel->register(GroupServiceProvider::class); + $laravel->register(NotificationServiceProvider::class); + $laravel->register(PostServiceProvider::class); + $laravel->register(SearchServiceProvider::class); + $laravel->register(SessionServiceProvider::class); + $laravel->register(UserServiceProvider::class); + $laravel->register(UpdateServiceProvider::class); + + $laravel->register(ApiServiceProvider::class); + $laravel->register(ForumServiceProvider::class); + $laravel->register(AdminServiceProvider::class); + + $laravel->register(ExtensionServiceProvider::class); + + $laravel->boot(); + + return $laravel; + } + + /** + * @param Application $app + * @return ConfigRepository + */ + protected function getIlluminateConfig(Application $app) + { + return new ConfigRepository([ + 'view' => [ + 'paths' => [], + 'compiled' => $app->storagePath().'/views', + ], + 'mail' => [ + 'driver' => 'mail', + ], + 'filesystems' => [ + 'default' => 'local', + 'cloud' => 's3', + 'disks' => [ + 'flarum-assets' => [ + 'driver' => 'local', + 'root' => $app->publicPath().'/assets', + 'url' => $app->url('assets') + ], + 'flarum-avatars' => [ + 'driver' => 'local', + 'root' => $app->publicPath().'/assets/avatars' + ] + ] + ], + 'session' => [ + 'lifetime' => 120, + 'files' => $app->storagePath().'/sessions', + 'cookie' => 'session' + ] + ]); + } + + protected function registerLogger(Application $app) + { + $logPath = $app->storagePath().'/logs/flarum.log'; + $handler = new StreamHandler($logPath, Logger::INFO); + $handler->setFormatter(new LineFormatter(null, null, true, true)); + + $app->instance('log', new Logger($app->environment(), [$handler])); + $app->alias('log', LoggerInterface::class); + } + + protected function registerCache(Application $app) + { + $app->singleton('cache.store', function ($app) { + return new CacheRepository($app->make('cache.filestore')); + }); + $app->alias('cache.store', Repository::class); + + $app->singleton('cache.filestore', function ($app) { + return new FileStore(new Filesystem, $app->storagePath().'/cache'); + }); + $app->alias('cache.filestore', Store::class); + } +} diff --git a/framework/core/src/Foundation/MaintenanceModeHandler.php b/framework/core/src/Foundation/MaintenanceModeHandler.php new file mode 100644 index 000000000..554ac002a --- /dev/null +++ b/framework/core/src/Foundation/MaintenanceModeHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Tobscure\JsonApi\Document; +use Zend\Diactoros\Response\HtmlResponse; +use Zend\Diactoros\Response\JsonResponse; + +class MaintenanceModeHandler implements RequestHandlerInterface +{ + const MESSAGE = 'Currently down for maintenance. Please come back later.'; + + /** + * Handle the request and return a response. + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // Special handling for API requests: they get a proper API response + if ($this->isApiRequest($request)) { + return $this->apiResponse(); + } + + // By default, return a simple text message. + return new HtmlResponse(self::MESSAGE, 503); + } + + private function isApiRequest(ServerRequestInterface $request): bool + { + return str_contains( + $request->getHeaderLine('Accept'), + 'application/vnd.api+json' + ); + } + + private function apiResponse(): ResponseInterface + { + return new JsonResponse( + (new Document)->setErrors([ + 'status' => '503', + 'title' => self::MESSAGE + ]), + 503, + ['Content-Type' => 'application/vnd.api+json'] + ); + } +} diff --git a/framework/core/src/Foundation/Site.php b/framework/core/src/Foundation/Site.php index 14a4f90da..dc58cb593 100644 --- a/framework/core/src/Foundation/Site.php +++ b/framework/core/src/Foundation/Site.php @@ -11,281 +11,53 @@ namespace Flarum\Foundation; -use Flarum\Admin\AdminServiceProvider; -use Flarum\Api\ApiServiceProvider; -use Flarum\Bus\BusServiceProvider as BusProvider; -use Flarum\Database\DatabaseServiceProvider; -use Flarum\Database\MigrationServiceProvider; -use Flarum\Discussion\DiscussionServiceProvider; -use Flarum\Extension\ExtensionServiceProvider; -use Flarum\Formatter\FormatterServiceProvider; -use Flarum\Forum\ForumServiceProvider; -use Flarum\Frontend\FrontendServiceProvider; -use Flarum\Group\GroupServiceProvider; -use Flarum\Locale\LocaleServiceProvider; -use Flarum\Notification\NotificationServiceProvider; -use Flarum\Post\PostServiceProvider; -use Flarum\Search\SearchServiceProvider; -use Flarum\Settings\SettingsRepositoryInterface; -use Flarum\Settings\SettingsServiceProvider; -use Flarum\User\UserServiceProvider; -use Illuminate\Bus\BusServiceProvider; -use Illuminate\Config\Repository as ConfigRepository; -use Illuminate\Filesystem\FilesystemServiceProvider; -use Illuminate\Hashing\HashServiceProvider; -use Illuminate\Mail\MailServiceProvider; -use Illuminate\Validation\ValidationServiceProvider; -use Illuminate\View\ViewServiceProvider; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; +use InvalidArgumentException; +use RuntimeException; -// TODO: This should be an interface maybe? class Site { /** - * @var Application + * @param array $paths + * @return SiteInterface */ - protected $app; - - /** - * @var string - */ - protected $basePath; - - /** - * @var string - */ - protected $publicPath; - - /** - * @var string - */ - protected $storagePath; - - /** - * @var array - */ - protected $config; - - protected $extenders = []; - - public function __construct() + public static function fromPaths(array $paths) { - $this->basePath = getcwd(); - $this->publicPath = $this->basePath; - } - - /** - * @return Application - */ - public function boot() - { - return $this->getApp(); - } - - /** - * @param $basePath - * @return static - */ - public function setBasePath($basePath) - { - $this->basePath = $basePath; - - return $this; - } - - /** - * @param $publicPath - * @return static - */ - public function setPublicPath($publicPath) - { - $this->publicPath = $publicPath; - - return $this; - } - - /** - * @param $storagePath - * @return static - */ - public function setStoragePath($storagePath) - { - $this->storagePath = $storagePath; - - return $this; - } - - /** - * @param array $config - * @return static - */ - public function setConfig(array $config) - { - $this->config = $config; - - return $this; - } - - protected function getConfig() - { - if (empty($this->config) && file_exists($file = $this->basePath.'/config.php')) { - $this->config = include $file; + if (! isset($paths['base'])) { + throw new InvalidArgumentException( + 'No base path given' + ); } - return $this->config; - } - - /** - * @return Application - */ - protected function getApp() - { - if ($this->app !== null) { - return $this->app; + if (! isset($paths['public'])) { + $paths['public'] = $paths['base']; } date_default_timezone_set('UTC'); - $app = new Application($this->basePath, $this->publicPath); - - if ($this->storagePath) { - $app->useStoragePath($this->storagePath); - } - - $app->instance('env', 'production'); - $app->instance('flarum.config', $this->getConfig()); - $app->instance('config', $config = $this->getIlluminateConfig($app)); - - $this->registerLogger($app); - - $this->registerCache($app); - - $app->register(DatabaseServiceProvider::class); - $app->register(MigrationServiceProvider::class); - $app->register(SettingsServiceProvider::class); - $app->register(LocaleServiceProvider::class); - $app->register(BusServiceProvider::class); - $app->register(FilesystemServiceProvider::class); - $app->register(HashServiceProvider::class); - $app->register(MailServiceProvider::class); - $app->register(ViewServiceProvider::class); - $app->register(ValidationServiceProvider::class); - - $app->register(BusProvider::class); - - if ($app->isInstalled() && $app->isUpToDate()) { - $settings = $app->make(SettingsRepositoryInterface::class); - - $config->set('mail.driver', $settings->get('mail_driver')); - $config->set('mail.host', $settings->get('mail_host')); - $config->set('mail.port', $settings->get('mail_port')); - $config->set('mail.from.address', $settings->get('mail_from')); - $config->set('mail.from.name', $settings->get('forum_title')); - $config->set('mail.encryption', $settings->get('mail_encryption')); - $config->set('mail.username', $settings->get('mail_username')); - $config->set('mail.password', $settings->get('mail_password')); - - $app->register(DiscussionServiceProvider::class); - $app->register(FormatterServiceProvider::class); - $app->register(FrontendServiceProvider::class); - $app->register(GroupServiceProvider::class); - $app->register(NotificationServiceProvider::class); - $app->register(PostServiceProvider::class); - $app->register(SearchServiceProvider::class); - $app->register(UserServiceProvider::class); - - $app->register(ApiServiceProvider::class); - $app->register(ForumServiceProvider::class); - $app->register(AdminServiceProvider::class); - - foreach ($this->extenders as $extender) { - // TODO: Create extenders architecture - // $extender->apply($app); - } - - $app->register(ExtensionServiceProvider::class); - } - - $app->boot(); - - $this->app = $app; - - return $app; - } - - /** - * @param Application $app - * @return ConfigRepository - */ - protected function getIlluminateConfig(Application $app) - { - return new ConfigRepository([ - 'view' => [ - 'paths' => [], - 'compiled' => $app->storagePath().'/views', - ], - 'mail' => [ - 'driver' => 'mail', - ], - 'filesystems' => [ - 'default' => 'local', - 'cloud' => 's3', - 'disks' => [ - 'flarum-assets' => [ - 'driver' => 'local', - 'root' => $app->publicPath().'/assets', - 'url' => $app->url('assets') - ], - 'flarum-avatars' => [ - 'driver' => 'local', - 'root' => $app->publicPath().'/assets/avatars' - ] - ] - ], - 'session' => [ - 'lifetime' => 120, - 'files' => $app->storagePath().'/sessions', - 'cookie' => 'session' - ] - ]); - } - - /** - * @param Application $app - */ - protected function registerLogger(Application $app) - { - $logger = new Logger($app->environment()); - $logPath = $app->storagePath().'/logs/flarum.log'; - - $handler = new StreamHandler($logPath, Logger::DEBUG); - $handler->setFormatter(new LineFormatter(null, null, true, true)); - - $logger->pushHandler($handler); - - $app->instance('log', $logger); - $app->alias('log', 'Psr\Log\LoggerInterface'); - } - - /** - * @param Application $app - */ - protected function registerCache(Application $app) - { - $app->singleton('cache.store', function ($app) { - return new \Illuminate\Cache\Repository($app->make('cache.filestore')); - }); - - $app->singleton('cache.filestore', function ($app) { - return new \Illuminate\Cache\FileStore( - new \Illuminate\Filesystem\Filesystem(), - $app->storagePath().'/cache' + if (static::hasConfigFile($paths['base'])) { + return new InstalledSite( + $paths['base'], + $paths['public'], + static::loadConfig($paths['base']) ); - }); + } else { + return new UninstalledSite($paths['base'], $paths['public']); + } + } - $app->alias('cache.filestore', 'Illuminate\Contracts\Cache\Store'); - $app->alias('cache.store', 'Illuminate\Contracts\Cache\Repository'); + private static function hasConfigFile($basePath) + { + return file_exists("$basePath/config.php"); + } + + private static function loadConfig($basePath): array + { + $config = include "$basePath/config.php"; + + if (! is_array($config)) { + throw new RuntimeException('config.php should return an array'); + } + + return $config; } } diff --git a/framework/core/src/Foundation/SiteInterface.php b/framework/core/src/Foundation/SiteInterface.php new file mode 100644 index 000000000..9f5b1b97f --- /dev/null +++ b/framework/core/src/Foundation/SiteInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +interface SiteInterface +{ + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface; +} diff --git a/framework/core/src/Foundation/UninstalledSite.php b/framework/core/src/Foundation/UninstalledSite.php new file mode 100644 index 000000000..9519d25f2 --- /dev/null +++ b/framework/core/src/Foundation/UninstalledSite.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Install\Installer; +use Flarum\Install\InstallServiceProvider; +use Flarum\Locale\LocaleServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\Settings\UninstalledSettingsRepository; +use Flarum\User\SessionServiceProvider; +use Illuminate\Config\Repository as ConfigRepository; +use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Filesystem\FilesystemServiceProvider; +use Illuminate\Validation\ValidationServiceProvider; +use Illuminate\View\Engines\EngineResolver; +use Illuminate\View\Engines\PhpEngine; +use Illuminate\View\FileViewFinder; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +class UninstalledSite implements SiteInterface +{ + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $publicPath; + + /** + * @var string + */ + protected $storagePath; + + public function __construct($basePath, $publicPath) + { + $this->basePath = $basePath; + $this->publicPath = $publicPath; + } + + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface + { + return new Installer( + $this->bootLaravel() + ); + } + + private function bootLaravel(): Application + { + $laravel = new Application($this->basePath, $this->publicPath); + + if ($this->storagePath) { + $laravel->useStoragePath($this->storagePath); + } + + $laravel->instance('env', 'production'); + $laravel->instance('flarum.config', []); + $laravel->instance('config', $config = $this->getIlluminateConfig($laravel)); + + $this->registerLogger($laravel); + + $laravel->register(LocaleServiceProvider::class); + $laravel->register(FilesystemServiceProvider::class); + $laravel->register(SessionServiceProvider::class); + $laravel->register(ValidationServiceProvider::class); + + $laravel->register(InstallServiceProvider::class); + + $laravel->singleton( + SettingsRepositoryInterface::class, + UninstalledSettingsRepository::class + ); + + $laravel->singleton('view', function ($app) { + $engines = new EngineResolver(); + $engines->register('php', function () { + return new PhpEngine(); + }); + $finder = new FileViewFinder($app->make('files'), []); + $dispatcher = $app->make(Dispatcher::class); + + return new \Illuminate\View\Factory( + $engines, $finder, $dispatcher + ); + }); + + $laravel->boot(); + + return $laravel; + } + + /** + * @param Application $app + * @return ConfigRepository + */ + protected function getIlluminateConfig(Application $app) + { + return new ConfigRepository([ + 'session' => [ + 'lifetime' => 120, + 'files' => $app->storagePath().'/sessions', + 'cookie' => 'session' + ] + ]); + } + + protected function registerLogger(Application $app) + { + $logPath = $app->storagePath().'/logs/flarum-installer.log'; + $handler = new StreamHandler($logPath, Logger::DEBUG); + $handler->setFormatter(new LineFormatter(null, null, true, true)); + + $app->instance('log', new Logger('Flarum Installer', [$handler])); + $app->alias('log', LoggerInterface::class); + } +} diff --git a/framework/core/src/Http/Middleware/HandleErrors.php b/framework/core/src/Http/Middleware/HandleErrorsWithView.php similarity index 88% rename from framework/core/src/Http/Middleware/HandleErrors.php rename to framework/core/src/Http/Middleware/HandleErrorsWithView.php index 8df217bf3..0ef7f5139 100644 --- a/framework/core/src/Http/Middleware/HandleErrors.php +++ b/framework/core/src/Http/Middleware/HandleErrorsWithView.php @@ -13,7 +13,6 @@ namespace Flarum\Http\Middleware; use Exception; use Flarum\Settings\SettingsRepositoryInterface; -use Franzl\Middleware\Whoops\WhoopsRunner; use Illuminate\Contracts\View\Factory as ViewFactory; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -23,7 +22,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Translation\TranslatorInterface; use Zend\Diactoros\Response\HtmlResponse; -class HandleErrors implements Middleware +class HandleErrorsWithView implements Middleware { /** * @var ViewFactory @@ -45,25 +44,18 @@ class HandleErrors implements Middleware */ protected $settings; - /** - * @var bool - */ - protected $debug; - /** * @param ViewFactory $view * @param LoggerInterface $logger * @param TranslatorInterface $translator * @param SettingsRepositoryInterface $settings - * @param bool $debug */ - public function __construct(ViewFactory $view, LoggerInterface $logger, TranslatorInterface $translator, SettingsRepositoryInterface $settings, $debug = false) + public function __construct(ViewFactory $view, LoggerInterface $logger, TranslatorInterface $translator, SettingsRepositoryInterface $settings) { $this->view = $view; $this->logger = $logger; $this->translator = $translator; $this->settings = $settings; - $this->debug = $debug; } /** @@ -74,11 +66,7 @@ class HandleErrors implements Middleware try { return $handler->handle($request); } catch (Exception $e) { - if ($this->debug) { - return WhoopsRunner::handle($e, $request); - } else { - return $this->formatException($e); - } + return $this->formatException($e); } } diff --git a/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php b/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php new file mode 100644 index 000000000..57d56b2ad --- /dev/null +++ b/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Http\Middleware; + +use Exception; +use Franzl\Middleware\Whoops\WhoopsRunner; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\MiddlewareInterface as Middleware; +use Psr\Http\Server\RequestHandlerInterface as Handler; + +class HandleErrorsWithWhoops implements Middleware +{ + /** + * Catch all errors that happen during further middleware execution. + */ + public function process(Request $request, Handler $handler): Response + { + try { + return $handler->handle($request); + } catch (Exception $e) { + return WhoopsRunner::handle($e, $request); + } + } +} diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php index 9583d9180..6c24afba6 100644 --- a/framework/core/src/Http/Server.php +++ b/framework/core/src/Http/Server.php @@ -11,149 +11,36 @@ namespace Flarum\Http; -use Flarum\Foundation\Application; -use Flarum\Foundation\Site; -use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; -use Flarum\Http\Middleware\StartSession; -use Flarum\Install\InstallServiceProvider; -use Flarum\Update\UpdateServiceProvider; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\MiddlewareInterface as Middleware; -use Psr\Http\Server\RequestHandlerInterface as Handler; -use Zend\Diactoros\Response\HtmlResponse; -use Zend\Diactoros\Server as DiactorosServer; -use Zend\Stratigility\MiddlewarePipe; -use function Zend\Stratigility\middleware; -use function Zend\Stratigility\path; +use Psr\Http\Server\RequestHandlerInterface; +use Throwable; +use Zend\Diactoros\Response; +use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\ServerRequestFactory; +use Zend\HttpHandlerRunner\Emitter\SapiEmitter; +use Zend\HttpHandlerRunner\RequestHandlerRunner; +use Zend\Stratigility\Middleware\ErrorResponseGenerator; -class Server implements Middleware, Handler +class Server { - /** - * @param Site $site - * @return Server - */ - public static function fromSite(Site $site) - { - return new static($site->boot()); - } + protected $requestHandler; - public function __construct(Application $app) + public function __construct(RequestHandlerInterface $requestHandler) { - $this->app = $app; + $this->requestHandler = $requestHandler; } public function listen() { - DiactorosServer::createServer( - [$this, 'handle'], - $_SERVER, - $_GET, - $_POST, - $_COOKIE, - $_FILES - )->listen(); - } + $runner = new RequestHandlerRunner( + $this->requestHandler, + new SapiEmitter, + [ServerRequestFactory::class, 'fromGlobals'], + function (Throwable $e) { + $generator = new ErrorResponseGenerator; - /** - * Use as PSR-15 middleware. - */ - public function process(Request $request, Handler $handler): Response - { - $middleware = $this->getMiddleware($request->getUri()->getPath()); - - return $middleware->process($request, $handler); - } - - /** - * Use as PSR-15 request handler. - */ - public function handle(Request $request): Response - { - $middleware = $this->getMiddleware($request->getUri()->getPath()); - - return $middleware->handle($request); - } - - /** - * @param string $requestPath - * @return MiddlewarePipe - */ - protected function getMiddleware($requestPath) - { - $pipe = new MiddlewarePipe; - - if (! $this->app->isInstalled()) { - return $this->getInstallerMiddleware($pipe); - } - - if ($this->app->isDownForMaintenance()) { - return $this->getMaintenanceMiddleware($pipe); - } - - if (! $this->app->isUpToDate()) { - return $this->getUpdaterMiddleware($pipe); - } - - $api = parse_url($this->app->url('api'), PHP_URL_PATH); - $admin = parse_url($this->app->url('admin'), PHP_URL_PATH); - $forum = parse_url($this->app->url(''), PHP_URL_PATH) ?: '/'; - - if ($this->pathStartsWith($requestPath, $api)) { - $pipe->pipe(path($api, $this->app->make('flarum.api.middleware'))); - } elseif ($this->pathStartsWith($requestPath, $admin)) { - $pipe->pipe(path($admin, $this->app->make('flarum.admin.middleware'))); - } else { - $pipe->pipe(path($forum, $this->app->make('flarum.forum.middleware'))); - } - - return $pipe; - } - - private function pathStartsWith($path, $prefix) - { - return $path === $prefix || starts_with($path, "$prefix/"); - } - - protected function getInstallerMiddleware(MiddlewarePipe $pipe) - { - $this->app->register(InstallServiceProvider::class); - - // FIXME: Re-enable HandleErrors middleware, if possible - // (Right now it tries to resolve a database connection because of the injected settings repo instance) - // We could register a different settings repo when Flarum is not installed - //$pipe->pipe($this->app->make(HandleErrors::class, ['debug' => true])); - //$pipe->pipe($this->app->make(StartSession::class)); - $pipe->pipe($this->app->make(DispatchRoute::class, ['routes' => $this->app->make('flarum.install.routes')])); - - return $pipe; - } - - protected function getMaintenanceMiddleware(MiddlewarePipe $pipe) - { - $pipe->pipe(middleware(function () { - return new HtmlResponse(file_get_contents($this->getErrorDir().'/503.html', 503)); - })); - - // TODO: FOR API render JSON-API error document for HTTP 503 - - return $pipe; - } - - protected function getUpdaterMiddleware(MiddlewarePipe $pipe) - { - $this->app->register(UpdateServiceProvider::class); - - $pipe->pipe($this->app->make(DispatchRoute::class, ['routes' => $this->app->make('flarum.update.routes')])); - - // TODO: FOR API render JSON-API error document for HTTP 503 - - return $pipe; - } - - private function getErrorDir() - { - return __DIR__.'/../../error'; + return $generator($e, new ServerRequest, new Response); + } + ); + $runner->run(); } } diff --git a/framework/core/src/Install/Console/InstallCommand.php b/framework/core/src/Install/Console/InstallCommand.php index b91ec48b6..9c4c5fe95 100644 --- a/framework/core/src/Install/Console/InstallCommand.php +++ b/framework/core/src/Install/Console/InstallCommand.php @@ -13,26 +13,19 @@ namespace Flarum\Install\Console; use Exception; use Flarum\Console\AbstractCommand; -use Flarum\Database\AbstractModel; +use Flarum\Database\DatabaseMigrationRepository; use Flarum\Database\Migrator; -use Flarum\Discussion\DiscussionServiceProvider; use Flarum\Extension\ExtensionManager; -use Flarum\Formatter\FormatterServiceProvider; use Flarum\Group\Group; -use Flarum\Group\GroupServiceProvider; use Flarum\Install\Prerequisite\PrerequisiteInterface; -use Flarum\Notification\NotificationServiceProvider; -use Flarum\Post\PostServiceProvider; -use Flarum\Search\SearchServiceProvider; -use Flarum\Settings\SettingsRepositoryInterface; -use Flarum\User\User; -use Flarum\User\UserServiceProvider; +use Flarum\Settings\DatabaseSettingsRepository; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Translation\Translator; use Illuminate\Database\ConnectionInterface; -use Illuminate\Database\ConnectionResolverInterface; -use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Connectors\ConnectionFactory; use Illuminate\Filesystem\Filesystem; +use Illuminate\Hashing\BcryptHasher; use Illuminate\Validation\Factory; use PDO; use Symfony\Component\Console\Input\InputOption; @@ -54,6 +47,16 @@ class InstallCommand extends AbstractCommand */ protected $filesystem; + /** + * @var ConnectionInterface + */ + protected $db; + + /** + * @var Migrator + */ + protected $migrator; + /** * @param Application $application * @param Filesystem $filesystem @@ -177,22 +180,10 @@ class InstallCommand extends AbstractCommand $this->storeConfiguration($this->dataSource->isDebugMode()); - $resolver = $this->application->make(ConnectionResolverInterface::class); - AbstractModel::setConnectionResolver($resolver); - AbstractModel::setEventDispatcher($this->application->make('events')); - $this->runMigrations(); $this->writeSettings(); - $this->application->register(UserServiceProvider::class); - $this->application->register(FormatterServiceProvider::class); - $this->application->register(DiscussionServiceProvider::class); - $this->application->register(GroupServiceProvider::class); - $this->application->register(NotificationServiceProvider::class); - $this->application->register(SearchServiceProvider::class); - $this->application->register(PostServiceProvider::class); - $this->createAdminUser(); $this->enableBundledExtensions(); @@ -211,7 +202,7 @@ class InstallCommand extends AbstractCommand $config = [ 'debug' => $debugMode, - 'database' => [ + 'database' => $laravelDbConfig = [ 'driver' => $dbConfig['driver'], 'host' => $dbConfig['host'], 'database' => $dbConfig['database'], @@ -232,15 +223,22 @@ class InstallCommand extends AbstractCommand $this->info('Testing config'); - $this->application->instance('flarum.config', $config); - /* @var $db \Illuminate\Database\ConnectionInterface */ - $db = $this->application->make('flarum.db'); - $version = $db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + $factory = new ConnectionFactory($this->application); + + $this->db = $factory->make($laravelDbConfig); + $version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); if (version_compare($version, '5.5.0', '<')) { throw new Exception('MySQL version too low. You need at least MySQL 5.5.'); } + $repository = new DatabaseMigrationRepository( + $this->db, 'migrations' + ); + $files = $this->application->make('files'); + + $this->migrator = new Migrator($repository, $this->db, $files); + $this->info('Writing config'); file_put_contents( @@ -251,23 +249,17 @@ class InstallCommand extends AbstractCommand protected function runMigrations() { - $this->application->bind(Builder::class, function ($container) { - return $container->make(ConnectionInterface::class)->getSchemaBuilder(); - }); + $this->migrator->getRepository()->createRepository(); + $this->migrator->run(__DIR__.'/../../../migrations'); - $migrator = $this->application->make(Migrator::class); - $migrator->getRepository()->createRepository(); - - $migrator->run(__DIR__.'/../../../migrations'); - - foreach ($migrator->getNotes() as $note) { + foreach ($this->migrator->getNotes() as $note) { $this->info($note); } } protected function writeSettings() { - $settings = $this->application->make(SettingsRepositoryInterface::class); + $settings = new DatabaseSettingsRepository($this->db); $this->info('Writing default settings'); @@ -288,21 +280,29 @@ class InstallCommand extends AbstractCommand $this->info('Creating admin user '.$admin['username']); - $user = User::register( - $admin['username'], - $admin['email'], - $admin['password'] - ); + $uid = $this->db->table('users')->insertGetId([ + 'username' => $admin['username'], + 'email' => $admin['email'], + 'password' => (new BcryptHasher)->make($admin['password']), + 'joined_at' => time(), + 'is_email_confirmed' => 1, + ]); - $user->is_email_confirmed = 1; - $user->save(); - - $user->groups()->sync([Group::ADMINISTRATOR_ID]); + $this->db->table('users_groups')->insert([ + 'user_id' => $uid, + 'group_id' => Group::ADMINISTRATOR_ID, + ]); } protected function enableBundledExtensions() { - $extensions = $this->application->make(ExtensionManager::class); + $extensions = new ExtensionManager( + new DatabaseSettingsRepository($this->db), + $this->application, + $this->migrator, + $this->application->make(Dispatcher::class), + $this->application->make('files') + ); $migrator = $extensions->getMigrator(); diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php new file mode 100644 index 000000000..3df6a1b86 --- /dev/null +++ b/framework/core/src/Install/Installer.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Install; + +use Flarum\Foundation\AppInterface; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; +use Flarum\Http\Middleware\StartSession; +use Flarum\Install\Console\InstallCommand; +use Illuminate\Contracts\Container\Container; +use Zend\Stratigility\MiddlewarePipe; + +class Installer implements AppInterface +{ + /** + * @var Container + */ + protected $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler() + { + $pipe = new MiddlewarePipe; + $pipe->pipe($this->container->make(HandleErrorsWithWhoops::class)); + $pipe->pipe($this->container->make(StartSession::class)); + $pipe->pipe( + new DispatchRoute($this->container->make('flarum.install.routes')) + ); + + return $pipe; + } + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands() + { + return [ + $this->container->make(InstallCommand::class), + ]; + } +} diff --git a/framework/core/src/Locale/LocaleServiceProvider.php b/framework/core/src/Locale/LocaleServiceProvider.php index 74ab5331d..b9cf94b68 100644 --- a/framework/core/src/Locale/LocaleServiceProvider.php +++ b/framework/core/src/Locale/LocaleServiceProvider.php @@ -13,6 +13,7 @@ namespace Flarum\Locale; use Flarum\Event\ConfigureLocales; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Translation\Translator as TranslatorContract; use Symfony\Component\Translation\MessageSelector; @@ -54,8 +55,8 @@ class LocaleServiceProvider extends AbstractServiceProvider private function getDefaultLocale(): string { - return $this->app->isInstalled() && $this->app->isUpToDate() - ? $this->app->make('flarum.settings')->get('default_locale', 'en') - : 'en'; + $repo = $this->app->make(SettingsRepositoryInterface::class); + + return $repo->get('default_locale', 'en'); } } diff --git a/framework/core/src/Settings/UninstalledSettingsRepository.php b/framework/core/src/Settings/UninstalledSettingsRepository.php new file mode 100644 index 000000000..fb8d1af34 --- /dev/null +++ b/framework/core/src/Settings/UninstalledSettingsRepository.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Settings; + +class UninstalledSettingsRepository implements SettingsRepositoryInterface +{ + public function all() + { + return []; + } + + public function get($key, $default = null) + { + return $default; + } + + public function set($key, $value) + { + // Do nothing + } + + public function delete($keyLike) + { + // Do nothing + } +} diff --git a/framework/core/src/User/SessionServiceProvider.php b/framework/core/src/User/SessionServiceProvider.php new file mode 100644 index 000000000..87004fe20 --- /dev/null +++ b/framework/core/src/User/SessionServiceProvider.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\User; + +use Flarum\Foundation\AbstractServiceProvider; +use Illuminate\Session\FileSessionHandler; +use SessionHandlerInterface; + +class SessionServiceProvider extends AbstractServiceProvider +{ + /** + * {@inheritdoc} + */ + public function register() + { + $this->app->singleton('session.handler', function ($app) { + return new FileSessionHandler( + $app['files'], + $app['config']['session.files'], + $app['config']['session.lifetime'] + ); + }); + + $this->app->alias('session.handler', SessionHandlerInterface::class); + } +} diff --git a/framework/core/src/User/UserServiceProvider.php b/framework/core/src/User/UserServiceProvider.php index a52d128ad..12aae50fb 100644 --- a/framework/core/src/User/UserServiceProvider.php +++ b/framework/core/src/User/UserServiceProvider.php @@ -15,9 +15,7 @@ use Flarum\Event\ConfigureUserPreferences; use Flarum\Event\GetPermission; use Flarum\Foundation\AbstractServiceProvider; use Illuminate\Contracts\Container\Container; -use Illuminate\Session\FileSessionHandler; use RuntimeException; -use SessionHandlerInterface; class UserServiceProvider extends AbstractServiceProvider { @@ -26,24 +24,10 @@ class UserServiceProvider extends AbstractServiceProvider */ public function register() { - $this->registerSession(); $this->registerGate(); $this->registerAvatarsFilesystem(); } - protected function registerSession() - { - $this->app->singleton('session.handler', function ($app) { - return new FileSessionHandler( - $app['files'], - $app['config']['session.files'], - $app['config']['session.lifetime'] - ); - }); - - $this->app->alias('session.handler', SessionHandlerInterface::class); - } - protected function registerGate() { $this->app->singleton('flarum.gate', function ($app) { diff --git a/framework/core/tests/Api/Controller/CreateUserControllerTest.php b/framework/core/tests/Api/Controller/CreateUserControllerTest.php index f7e4f0bd8..772e51700 100644 --- a/framework/core/tests/Api/Controller/CreateUserControllerTest.php +++ b/framework/core/tests/Api/Controller/CreateUserControllerTest.php @@ -84,7 +84,7 @@ class CreateUserControllerTest extends ApiControllerTestCase public function disabling_sign_up_prevents_user_creation() { /** @var SettingsRepositoryInterface $settings */ - $settings = $this->app->make(SettingsRepositoryInterface::class); + $settings = app(SettingsRepositoryInterface::class); $settings->set('allow_sign_up', false); try { diff --git a/framework/core/tests/Install/DefaultInstallationCommandTest.php b/framework/core/tests/Install/DefaultInstallationCommandTest.php index 7a8014fc0..0d2fee829 100644 --- a/framework/core/tests/Install/DefaultInstallationCommandTest.php +++ b/framework/core/tests/Install/DefaultInstallationCommandTest.php @@ -12,9 +12,8 @@ namespace Flarum\Tests\Install; use Flarum\Install\Console\InstallCommand; -use Flarum\Install\InstallServiceProvider; use Flarum\Tests\Test\TestCase; -use Flarum\User\User; +use Illuminate\Database\Connectors\ConnectionFactory; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; @@ -27,13 +26,12 @@ class DefaultInstallationCommandTest extends TestCase */ public function allows_forum_installation() { - if (file_exists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php')) { - unlink($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); + if (file_exists(base_path('config.php'))) { + unlink(base_path('config.php')); } - $this->app->register(InstallServiceProvider::class); /** @var InstallCommand $command */ - $command = $this->app->make(InstallCommand::class); + $command = app(InstallCommand::class); $command->setDataSource($this->configuration); $body = fopen('php://temp', 'wb+'); @@ -42,10 +40,20 @@ class DefaultInstallationCommandTest extends TestCase $command->run($input, $output); - $this->assertFileExists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); + $this->assertFileExists(base_path('config.php')); $admin = $this->configuration->getAdminUser(); - $this->assertEquals(User::find(1)->username, $admin['username']); + $this->assertEquals( + $this->getDatabase()->table('users')->find(1)->username, + $admin['username'] + ); + } + + private function getDatabase() + { + $factory = new ConnectionFactory(app()); + + return $factory->make($this->configuration->getDatabaseConfiguration()); } } diff --git a/framework/core/tests/Test/Concerns/CreatesForum.php b/framework/core/tests/Test/Concerns/CreatesForum.php index ac4029731..9476dcfe6 100644 --- a/framework/core/tests/Test/Concerns/CreatesForum.php +++ b/framework/core/tests/Test/Concerns/CreatesForum.php @@ -11,14 +11,17 @@ namespace Flarum\Tests\Test\Concerns; +use Flarum\Database\DatabaseMigrationRepository; use Flarum\Database\Migrator; use Flarum\Foundation\Application; -use Flarum\Foundation\Site; +use Flarum\Foundation\InstalledSite; +use Flarum\Foundation\SiteInterface; +use Flarum\Foundation\UninstalledSite; use Flarum\Http\Server; use Flarum\Install\Console\DataProviderInterface; use Flarum\Install\Console\DefaultsDataProvider; use Illuminate\Database\ConnectionInterface; -use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Connectors\ConnectionFactory; trait CreatesForum { @@ -28,7 +31,7 @@ trait CreatesForum protected $http; /** - * @var Site + * @var SiteInterface */ protected $site; @@ -51,41 +54,51 @@ trait CreatesForum protected function createsSite() { - $this->site = (new Site) - ->setBasePath(__DIR__.'/../../tmp') - ->setPublicPath(__DIR__.'/../../tmp/public'); + if ($this->isInstalled) { + $this->site = new InstalledSite( + __DIR__.'/../../tmp', + __DIR__.'/../../tmp/public', + $this->getFlarumConfig() + ); + } else { + $this->site = new UninstalledSite( + __DIR__.'/../../tmp', + __DIR__.'/../../tmp/public' + ); + } } protected function createsHttpForum() { - $this->http = Server::fromSite( - $this->site - ); + $this->app = $this->site->bootApp(); - $this->app = $this->http->app; + $this->http = new Server( + $this->app->getRequestHandler() + ); } - protected function refreshApplication() + protected function collectsConfiguration() { - $this->createsSite(); + $this->configuration = new DefaultsDataProvider(); - $data = new DefaultsDataProvider(); + $this->configuration->setDebugMode(); + $this->configuration->setSetting('mail_driver', 'log'); - $data->setDebugMode(); - $data->setSetting('mail_driver', 'log'); - - $database = $data->getDatabaseConfiguration(); + $database = $this->configuration->getDatabaseConfiguration(); $database['host'] = env('DB_HOST', $database['host']); $database['database'] = env('DB_DATABASE', $database['database']); $database['username'] = env('DB_USERNAME', $database['username']); $database['password'] = env('DB_PASSWORD', $database['password']); - $data->setDatabaseConfiguration($database); + $this->configuration->setDatabaseConfiguration($database); + } - $this->configuration = $data; + protected function refreshApplication() + { + $this->collectsConfiguration(); - $this->setsApplicationConfiguration($data); + $this->seedsDatabase(); - $this->seedsApplication(); + $this->createsSite(); $this->createsHttpForum(); } @@ -93,59 +106,56 @@ trait CreatesForum protected function teardownApplication() { /** @var ConnectionInterface $connection */ - $connection = $this->app->make(ConnectionInterface::class); + $connection = app(ConnectionInterface::class); $connection->rollBack(); } - protected function setsApplicationConfiguration(DataProviderInterface $data) + protected function getFlarumConfig() { - if ($this->isInstalled) { - $dbConfig = $data->getDatabaseConfiguration(); - $this->site->setConfig( - $config = [ - 'debug' => $data->isDebugMode(), - 'database' => [ - 'driver' => $dbConfig['driver'], - 'host' => $dbConfig['host'], - 'database' => $dbConfig['database'], - 'username' => $dbConfig['username'], - 'password' => $dbConfig['password'], - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => $dbConfig['prefix'], - 'port' => $dbConfig['port'], - 'strict' => false - ], - 'url' => $data->getBaseUrl(), - 'paths' => [ - 'api' => 'api', - 'admin' => 'admin', - ], - ] - ); - } + $dbConfig = $this->configuration->getDatabaseConfiguration(); + + return [ + 'debug' => $this->configuration->isDebugMode(), + 'database' => [ + 'driver' => $dbConfig['driver'], + 'host' => $dbConfig['host'], + 'database' => $dbConfig['database'], + 'username' => $dbConfig['username'], + 'password' => $dbConfig['password'], + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => $dbConfig['prefix'], + 'port' => $dbConfig['port'], + 'strict' => false + ], + 'url' => $this->configuration->getBaseUrl(), + 'paths' => [ + 'api' => 'api', + 'admin' => 'admin', + ], + ]; } - protected function seedsApplication() + protected function seedsDatabase() { - if ($this->isInstalled) { - $app = app(\Illuminate\Contracts\Foundation\Application::class); - - $app->bind(Builder::class, function ($container) { - return $container->make(ConnectionInterface::class)->getSchemaBuilder(); - }); - - /** @var Migrator $migrator */ - $migrator = $app->make(Migrator::class); - if (! $migrator->getRepository()->repositoryExists()) { - $migrator->getRepository()->createRepository(); - } - - $migrator->run(__DIR__.'/../../../migrations'); - - /** @var ConnectionInterface $connection */ - $connection = $app->make(\Illuminate\Database\ConnectionInterface::class); - $connection->beginTransaction(); + if (! $this->isInstalled) { + return; } + + $app = app(\Illuminate\Contracts\Foundation\Application::class); + + $factory = new ConnectionFactory($app); + $db = $factory->make($this->configuration->getDatabaseConfiguration()); + + $repository = new DatabaseMigrationRepository($db, 'migrations'); + $migrator = new Migrator($repository, $db, app('files')); + + if (! $migrator->getRepository()->repositoryExists()) { + $migrator->getRepository()->createRepository(); + } + + $migrator->run(__DIR__.'/../../../migrations'); + + $db->beginTransaction(); } } diff --git a/framework/core/tests/Test/Concerns/MakesApiRequests.php b/framework/core/tests/Test/Concerns/MakesApiRequests.php index bcf69f819..3e039ad49 100644 --- a/framework/core/tests/Test/Concerns/MakesApiRequests.php +++ b/framework/core/tests/Test/Concerns/MakesApiRequests.php @@ -11,22 +11,17 @@ namespace Flarum\Tests\Test\Concerns; -use Flarum\Api\ApiServiceProvider; use Flarum\Api\Client; use Flarum\User\Guest; use Flarum\User\User; -use Flarum\User\UserServiceProvider; use Psr\Http\Message\ResponseInterface; trait MakesApiRequests { public function call(string $controller, User $actor = null, array $queryParams = [], array $body = []): ResponseInterface { - $this->app->register(UserServiceProvider::class); - $this->app->register(ApiServiceProvider::class); - $this->app->make('flarum.api.middleware'); /** @var Client $api */ - $api = $this->app->make(Client::class); + $api = app(Client::class); return $api->send($controller, $actor ?? new Guest, $queryParams, $body); }