Merge branch 'master' into 1236-database-changes

This commit is contained in:
Toby Zerner 2018-08-24 21:07:00 +09:30
commit f2f9c3c21c
28 changed files with 1002 additions and 653 deletions

View File

@ -44,6 +44,9 @@
"league/flysystem": "^1.0.11", "league/flysystem": "^1.0.11",
"league/oauth2-client": "~1.0", "league/oauth2-client": "~1.0",
"matthiasmullie/minify": "^1.3", "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", "monolog/monolog": "^1.16.0",
"nikic/fast-route": "^0.6", "nikic/fast-route": "^0.6",
"oyejorge/less.php": "^1.7", "oyejorge/less.php": "^1.7",
@ -58,6 +61,7 @@
"symfony/yaml": "^3.3", "symfony/yaml": "^3.3",
"tobscure/json-api": "^0.3.0", "tobscure/json-api": "^0.3.0",
"zendframework/zend-diactoros": "^1.8.4", "zendframework/zend-diactoros": "^1.8.4",
"zendframework/zend-httphandlerrunner": "^1.0",
"zendframework/zend-stratigility": "^3.0" "zendframework/zend-stratigility": "^3.0"
}, },
"require-dev": { "require-dev": {

View File

@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Frontend\RecompileFrontendAssets; use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\AuthenticateWithSession;
use Flarum\Http\Middleware\DispatchRoute; 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\ParseJsonBody;
use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\RememberFromCookie;
use Flarum\Http\Middleware\SetLocale; use Flarum\Http\Middleware\SetLocale;
@ -46,8 +47,11 @@ class AdminServiceProvider extends AbstractServiceProvider
$pipe = new MiddlewarePipe; $pipe = new MiddlewarePipe;
// All requests should first be piped through our global error handler // All requests should first be piped through our global error handler
$debugMode = ! $app->isUpToDate() || $app->inDebugMode(); if ($app->inDebugMode()) {
$pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); $pipe->pipe($app->make(HandleErrorsWithWhoops::class));
} else {
$pipe->pipe($app->make(HandleErrorsWithView::class));
}
$pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(ParseJsonBody::class));
$pipe->pipe($app->make(StartSession::class)); $pipe->pipe($app->make(StartSession::class));
@ -58,7 +62,7 @@ class AdminServiceProvider extends AbstractServiceProvider
event(new ConfigureMiddleware($pipe, 'admin')); 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; return $pipe;
}); });

View File

@ -66,7 +66,7 @@ class ApiServiceProvider extends AbstractServiceProvider
event(new ConfigureMiddleware($pipe, 'api')); 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; return $pipe;
}); });

View File

@ -12,17 +12,27 @@
namespace Flarum\Bus; namespace Flarum\Bus;
use Flarum\Foundation\AbstractServiceProvider; 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; use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
class BusServiceProvider extends AbstractServiceProvider class BusServiceProvider extends AbstractServiceProvider
{ {
public function register() 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 new Dispatcher($app, function ($connection = null) use ($app) {
return $app[QueueFactoryContract::class]->connection($connection); return $app[QueueFactoryContract::class]->connection($connection);
}); });
}); });
$this->app->alias(
BaseDispatcher::class, DispatcherContract::class
);
$this->app->alias(
BaseDispatcher::class, QueueingDispatcherContract::class
);
} }
} }

View File

@ -12,74 +12,37 @@
namespace Flarum\Console; namespace Flarum\Console;
use Flarum\Console\Event\Configuring; 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\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 Illuminate\Contracts\Events\Dispatcher;
use Symfony\Component\Console\Application as ConsoleApplication; use Symfony\Component\Console\Application as ConsoleApplication;
class Server class Server
{ {
/** protected $commands;
* @param Site $site
* @return Server
*/
public static function fromSite(Site $site)
{
return new static($site->boot());
}
public function __construct(Application $app) public function __construct(array $commands)
{ {
$this->app = $app; $this->commands = $commands;
} }
public function listen() 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()); exit($console->run());
} }
/** private function extend(ConsoleApplication $console)
* @return ConsoleApplication
*/
protected function getConsoleApplication()
{ {
$console = new ConsoleApplication('Flarum', $this->app->version()); $app = Application::getInstance();
$this->app->register(InstallServiceProvider::class); $events = $app->make(Dispatcher::class);
$events->fire(new Configuring($app, $console));
$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;
} }
} }

View File

@ -50,9 +50,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/ */
public function boot() public function boot()
{ {
if ($this->app->isInstalled()) {
AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface')); AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface'));
AbstractModel::setEventDispatcher($this->app->make('events')); AbstractModel::setEventDispatcher($this->app->make('events'));
} }
}
} }

View File

@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\AuthenticateWithSession;
use Flarum\Http\Middleware\CollectGarbage; use Flarum\Http\Middleware\CollectGarbage;
use Flarum\Http\Middleware\DispatchRoute; 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\ParseJsonBody;
use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\RememberFromCookie;
use Flarum\Http\Middleware\SetLocale; use Flarum\Http\Middleware\SetLocale;
@ -49,8 +50,11 @@ class ForumServiceProvider extends AbstractServiceProvider
$pipe = new MiddlewarePipe; $pipe = new MiddlewarePipe;
// All requests should first be piped through our global error handler // All requests should first be piped through our global error handler
$debugMode = ! $app->isUpToDate() || $app->inDebugMode(); if ($app->inDebugMode()) {
$pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); $pipe->pipe($app->make(HandleErrorsWithWhoops::class));
} else {
$pipe->pipe($app->make(HandleErrorsWithView::class));
}
$pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(ParseJsonBody::class));
$pipe->pipe($app->make(CollectGarbage::class)); $pipe->pipe($app->make(CollectGarbage::class));
@ -62,7 +66,7 @@ class ForumServiceProvider extends AbstractServiceProvider
event(new ConfigureMiddleware($pipe, 'forum')); 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; return $pipe;
}); });

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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();
}

View File

@ -11,7 +11,6 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Container\Container; use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Events\EventServiceProvider; 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 string $key
* @param mixed $default * @param mixed $default
@ -142,7 +120,7 @@ class Application extends Container implements ApplicationContract
*/ */
public function config($key, $default = null) 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() 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) 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')); $url = array_get($config, 'url', array_get($_SERVER, 'REQUEST_URI'));
if (is_array($url)) { if (is_array($url)) {

View File

@ -0,0 +1,120 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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),
];
}
}

View File

@ -0,0 +1,221 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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']
);
}
}

View File

@ -11,281 +11,53 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Flarum\Admin\AdminServiceProvider; use InvalidArgumentException;
use Flarum\Api\ApiServiceProvider; use RuntimeException;
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;
// TODO: This should be an interface maybe?
class Site class Site
{ {
/** /**
* @var Application * @param array $paths
* @return SiteInterface
*/ */
protected $app; public static function fromPaths(array $paths)
/**
* @var string
*/
protected $basePath;
/**
* @var string
*/
protected $publicPath;
/**
* @var string
*/
protected $storagePath;
/**
* @var array
*/
protected $config;
protected $extenders = [];
public function __construct()
{ {
$this->basePath = getcwd(); if (! isset($paths['base'])) {
$this->publicPath = $this->basePath; throw new InvalidArgumentException(
'No base path given'
);
} }
/** if (! isset($paths['public'])) {
* @return Application $paths['public'] = $paths['base'];
*/
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;
}
return $this->config;
}
/**
* @return Application
*/
protected function getApp()
{
if ($this->app !== null) {
return $this->app;
} }
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
$app = new Application($this->basePath, $this->publicPath); if (static::hasConfigFile($paths['base'])) {
return new InstalledSite(
if ($this->storagePath) { $paths['base'],
$app->useStoragePath($this->storagePath); $paths['public'],
} static::loadConfig($paths['base'])
$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'
); );
}); } else {
return new UninstalledSite($paths['base'], $paths['public']);
}
}
$app->alias('cache.filestore', 'Illuminate\Contracts\Cache\Store'); private static function hasConfigFile($basePath)
$app->alias('cache.store', 'Illuminate\Contracts\Cache\Repository'); {
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;
} }
} }

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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;
}

View File

@ -0,0 +1,135 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -13,7 +13,6 @@ namespace Flarum\Http\Middleware;
use Exception; use Exception;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Franzl\Middleware\Whoops\WhoopsRunner;
use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\View\Factory as ViewFactory;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
@ -23,7 +22,7 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
use Zend\Diactoros\Response\HtmlResponse; use Zend\Diactoros\Response\HtmlResponse;
class HandleErrors implements Middleware class HandleErrorsWithView implements Middleware
{ {
/** /**
* @var ViewFactory * @var ViewFactory
@ -45,25 +44,18 @@ class HandleErrors implements Middleware
*/ */
protected $settings; protected $settings;
/**
* @var bool
*/
protected $debug;
/** /**
* @param ViewFactory $view * @param ViewFactory $view
* @param LoggerInterface $logger * @param LoggerInterface $logger
* @param TranslatorInterface $translator * @param TranslatorInterface $translator
* @param SettingsRepositoryInterface $settings * @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->view = $view;
$this->logger = $logger; $this->logger = $logger;
$this->translator = $translator; $this->translator = $translator;
$this->settings = $settings; $this->settings = $settings;
$this->debug = $debug;
} }
/** /**
@ -74,13 +66,9 @@ class HandleErrors implements Middleware
try { try {
return $handler->handle($request); return $handler->handle($request);
} catch (Exception $e) { } catch (Exception $e) {
if ($this->debug) {
return WhoopsRunner::handle($e, $request);
} else {
return $this->formatException($e); return $this->formatException($e);
} }
} }
}
protected function formatException(Exception $error) protected function formatException(Exception $error)
{ {

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}
}

View File

@ -11,149 +11,36 @@
namespace Flarum\Http; namespace Flarum\Http;
use Flarum\Foundation\Application; use Psr\Http\Server\RequestHandlerInterface;
use Flarum\Foundation\Site; use Throwable;
use Flarum\Http\Middleware\DispatchRoute; use Zend\Diactoros\Response;
use Flarum\Http\Middleware\HandleErrors; use Zend\Diactoros\ServerRequest;
use Flarum\Http\Middleware\StartSession; use Zend\Diactoros\ServerRequestFactory;
use Flarum\Install\InstallServiceProvider; use Zend\HttpHandlerRunner\Emitter\SapiEmitter;
use Flarum\Update\UpdateServiceProvider; use Zend\HttpHandlerRunner\RequestHandlerRunner;
use Psr\Http\Message\ResponseInterface as Response; use Zend\Stratigility\Middleware\ErrorResponseGenerator;
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;
class Server implements Middleware, Handler class Server
{ {
/** protected $requestHandler;
* @param Site $site
* @return Server
*/
public static function fromSite(Site $site)
{
return new static($site->boot());
}
public function __construct(Application $app) public function __construct(RequestHandlerInterface $requestHandler)
{ {
$this->app = $app; $this->requestHandler = $requestHandler;
} }
public function listen() public function listen()
{ {
DiactorosServer::createServer( $runner = new RequestHandlerRunner(
[$this, 'handle'], $this->requestHandler,
$_SERVER, new SapiEmitter,
$_GET, [ServerRequestFactory::class, 'fromGlobals'],
$_POST, function (Throwable $e) {
$_COOKIE, $generator = new ErrorResponseGenerator;
$_FILES
)->listen(); return $generator($e, new ServerRequest, new Response);
} }
);
/** $runner->run();
* 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';
} }
} }

View File

@ -13,26 +13,19 @@ namespace Flarum\Install\Console;
use Exception; use Exception;
use Flarum\Console\AbstractCommand; use Flarum\Console\AbstractCommand;
use Flarum\Database\AbstractModel; use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator; use Flarum\Database\Migrator;
use Flarum\Discussion\DiscussionServiceProvider;
use Flarum\Extension\ExtensionManager; use Flarum\Extension\ExtensionManager;
use Flarum\Formatter\FormatterServiceProvider;
use Flarum\Group\Group; use Flarum\Group\Group;
use Flarum\Group\GroupServiceProvider;
use Flarum\Install\Prerequisite\PrerequisiteInterface; use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Notification\NotificationServiceProvider; use Flarum\Settings\DatabaseSettingsRepository;
use Flarum\Post\PostServiceProvider; use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Search\SearchServiceProvider;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User;
use Flarum\User\UserServiceProvider;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Translation\Translator; use Illuminate\Contracts\Translation\Translator;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Schema\Builder;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;
use PDO; use PDO;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -54,6 +47,16 @@ class InstallCommand extends AbstractCommand
*/ */
protected $filesystem; protected $filesystem;
/**
* @var ConnectionInterface
*/
protected $db;
/**
* @var Migrator
*/
protected $migrator;
/** /**
* @param Application $application * @param Application $application
* @param Filesystem $filesystem * @param Filesystem $filesystem
@ -177,22 +180,10 @@ class InstallCommand extends AbstractCommand
$this->storeConfiguration($this->dataSource->isDebugMode()); $this->storeConfiguration($this->dataSource->isDebugMode());
$resolver = $this->application->make(ConnectionResolverInterface::class);
AbstractModel::setConnectionResolver($resolver);
AbstractModel::setEventDispatcher($this->application->make('events'));
$this->runMigrations(); $this->runMigrations();
$this->writeSettings(); $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->createAdminUser();
$this->enableBundledExtensions(); $this->enableBundledExtensions();
@ -211,7 +202,7 @@ class InstallCommand extends AbstractCommand
$config = [ $config = [
'debug' => $debugMode, 'debug' => $debugMode,
'database' => [ 'database' => $laravelDbConfig = [
'driver' => $dbConfig['driver'], 'driver' => $dbConfig['driver'],
'host' => $dbConfig['host'], 'host' => $dbConfig['host'],
'database' => $dbConfig['database'], 'database' => $dbConfig['database'],
@ -232,15 +223,22 @@ class InstallCommand extends AbstractCommand
$this->info('Testing config'); $this->info('Testing config');
$this->application->instance('flarum.config', $config); $factory = new ConnectionFactory($this->application);
/* @var $db \Illuminate\Database\ConnectionInterface */
$db = $this->application->make('flarum.db'); $this->db = $factory->make($laravelDbConfig);
$version = $db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); $version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
if (version_compare($version, '5.5.0', '<')) { if (version_compare($version, '5.5.0', '<')) {
throw new Exception('MySQL version too low. You need at least MySQL 5.5.'); 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'); $this->info('Writing config');
file_put_contents( file_put_contents(
@ -251,23 +249,17 @@ class InstallCommand extends AbstractCommand
protected function runMigrations() protected function runMigrations()
{ {
$this->application->bind(Builder::class, function ($container) { $this->migrator->getRepository()->createRepository();
return $container->make(ConnectionInterface::class)->getSchemaBuilder(); $this->migrator->run(__DIR__.'/../../../migrations');
});
$migrator = $this->application->make(Migrator::class); foreach ($this->migrator->getNotes() as $note) {
$migrator->getRepository()->createRepository();
$migrator->run(__DIR__.'/../../../migrations');
foreach ($migrator->getNotes() as $note) {
$this->info($note); $this->info($note);
} }
} }
protected function writeSettings() protected function writeSettings()
{ {
$settings = $this->application->make(SettingsRepositoryInterface::class); $settings = new DatabaseSettingsRepository($this->db);
$this->info('Writing default settings'); $this->info('Writing default settings');
@ -288,21 +280,29 @@ class InstallCommand extends AbstractCommand
$this->info('Creating admin user '.$admin['username']); $this->info('Creating admin user '.$admin['username']);
$user = User::register( $uid = $this->db->table('users')->insertGetId([
$admin['username'], 'username' => $admin['username'],
$admin['email'], 'email' => $admin['email'],
$admin['password'] 'password' => (new BcryptHasher)->make($admin['password']),
); 'joined_at' => time(),
'is_email_confirmed' => 1,
]);
$user->is_email_confirmed = 1; $this->db->table('users_groups')->insert([
$user->save(); 'user_id' => $uid,
'group_id' => Group::ADMINISTRATOR_ID,
$user->groups()->sync([Group::ADMINISTRATOR_ID]); ]);
} }
protected function enableBundledExtensions() 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(); $migrator = $extensions->getMigrator();

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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),
];
}
}

View File

@ -13,6 +13,7 @@ namespace Flarum\Locale;
use Flarum\Event\ConfigureLocales; use Flarum\Event\ConfigureLocales;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Translation\Translator as TranslatorContract; use Illuminate\Contracts\Translation\Translator as TranslatorContract;
use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\MessageSelector;
@ -54,8 +55,8 @@ class LocaleServiceProvider extends AbstractServiceProvider
private function getDefaultLocale(): string private function getDefaultLocale(): string
{ {
return $this->app->isInstalled() && $this->app->isUpToDate() $repo = $this->app->make(SettingsRepositoryInterface::class);
? $this->app->make('flarum.settings')->get('default_locale', 'en')
: 'en'; return $repo->get('default_locale', 'en');
} }
} }

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -15,9 +15,7 @@ use Flarum\Event\ConfigureUserPreferences;
use Flarum\Event\GetPermission; use Flarum\Event\GetPermission;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Session\FileSessionHandler;
use RuntimeException; use RuntimeException;
use SessionHandlerInterface;
class UserServiceProvider extends AbstractServiceProvider class UserServiceProvider extends AbstractServiceProvider
{ {
@ -26,24 +24,10 @@ class UserServiceProvider extends AbstractServiceProvider
*/ */
public function register() public function register()
{ {
$this->registerSession();
$this->registerGate(); $this->registerGate();
$this->registerAvatarsFilesystem(); $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() protected function registerGate()
{ {
$this->app->singleton('flarum.gate', function ($app) { $this->app->singleton('flarum.gate', function ($app) {

View File

@ -84,7 +84,7 @@ class CreateUserControllerTest extends ApiControllerTestCase
public function disabling_sign_up_prevents_user_creation() public function disabling_sign_up_prevents_user_creation()
{ {
/** @var SettingsRepositoryInterface $settings */ /** @var SettingsRepositoryInterface $settings */
$settings = $this->app->make(SettingsRepositoryInterface::class); $settings = app(SettingsRepositoryInterface::class);
$settings->set('allow_sign_up', false); $settings->set('allow_sign_up', false);
try { try {

View File

@ -12,9 +12,8 @@
namespace Flarum\Tests\Install; namespace Flarum\Tests\Install;
use Flarum\Install\Console\InstallCommand; use Flarum\Install\Console\InstallCommand;
use Flarum\Install\InstallServiceProvider;
use Flarum\Tests\Test\TestCase; use Flarum\Tests\Test\TestCase;
use Flarum\User\User; use Illuminate\Database\Connectors\ConnectionFactory;
use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Output\StreamOutput;
@ -27,13 +26,12 @@ class DefaultInstallationCommandTest extends TestCase
*/ */
public function allows_forum_installation() public function allows_forum_installation()
{ {
if (file_exists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php')) { if (file_exists(base_path('config.php'))) {
unlink($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); unlink(base_path('config.php'));
} }
$this->app->register(InstallServiceProvider::class);
/** @var InstallCommand $command */ /** @var InstallCommand $command */
$command = $this->app->make(InstallCommand::class); $command = app(InstallCommand::class);
$command->setDataSource($this->configuration); $command->setDataSource($this->configuration);
$body = fopen('php://temp', 'wb+'); $body = fopen('php://temp', 'wb+');
@ -42,10 +40,20 @@ class DefaultInstallationCommandTest extends TestCase
$command->run($input, $output); $command->run($input, $output);
$this->assertFileExists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); $this->assertFileExists(base_path('config.php'));
$admin = $this->configuration->getAdminUser(); $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());
} }
} }

View File

@ -11,14 +11,17 @@
namespace Flarum\Tests\Test\Concerns; namespace Flarum\Tests\Test\Concerns;
use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator; use Flarum\Database\Migrator;
use Flarum\Foundation\Application; 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\Http\Server;
use Flarum\Install\Console\DataProviderInterface; use Flarum\Install\Console\DataProviderInterface;
use Flarum\Install\Console\DefaultsDataProvider; use Flarum\Install\Console\DefaultsDataProvider;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Schema\Builder; use Illuminate\Database\Connectors\ConnectionFactory;
trait CreatesForum trait CreatesForum
{ {
@ -28,7 +31,7 @@ trait CreatesForum
protected $http; protected $http;
/** /**
* @var Site * @var SiteInterface
*/ */
protected $site; protected $site;
@ -51,41 +54,51 @@ trait CreatesForum
protected function createsSite() protected function createsSite()
{ {
$this->site = (new Site) if ($this->isInstalled) {
->setBasePath(__DIR__.'/../../tmp') $this->site = new InstalledSite(
->setPublicPath(__DIR__.'/../../tmp/public'); __DIR__.'/../../tmp',
__DIR__.'/../../tmp/public',
$this->getFlarumConfig()
);
} else {
$this->site = new UninstalledSite(
__DIR__.'/../../tmp',
__DIR__.'/../../tmp/public'
);
}
} }
protected function createsHttpForum() protected function createsHttpForum()
{ {
$this->http = Server::fromSite( $this->app = $this->site->bootApp();
$this->site
);
$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(); $database = $this->configuration->getDatabaseConfiguration();
$data->setSetting('mail_driver', 'log');
$database = $data->getDatabaseConfiguration();
$database['host'] = env('DB_HOST', $database['host']); $database['host'] = env('DB_HOST', $database['host']);
$database['database'] = env('DB_DATABASE', $database['database']); $database['database'] = env('DB_DATABASE', $database['database']);
$database['username'] = env('DB_USERNAME', $database['username']); $database['username'] = env('DB_USERNAME', $database['username']);
$database['password'] = env('DB_PASSWORD', $database['password']); $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(); $this->createsHttpForum();
} }
@ -93,17 +106,16 @@ trait CreatesForum
protected function teardownApplication() protected function teardownApplication()
{ {
/** @var ConnectionInterface $connection */ /** @var ConnectionInterface $connection */
$connection = $this->app->make(ConnectionInterface::class); $connection = app(ConnectionInterface::class);
$connection->rollBack(); $connection->rollBack();
} }
protected function setsApplicationConfiguration(DataProviderInterface $data) protected function getFlarumConfig()
{ {
if ($this->isInstalled) { $dbConfig = $this->configuration->getDatabaseConfiguration();
$dbConfig = $data->getDatabaseConfiguration();
$this->site->setConfig( return [
$config = [ 'debug' => $this->configuration->isDebugMode(),
'debug' => $data->isDebugMode(),
'database' => [ 'database' => [
'driver' => $dbConfig['driver'], 'driver' => $dbConfig['driver'],
'host' => $dbConfig['host'], 'host' => $dbConfig['host'],
@ -116,36 +128,34 @@ trait CreatesForum
'port' => $dbConfig['port'], 'port' => $dbConfig['port'],
'strict' => false 'strict' => false
], ],
'url' => $data->getBaseUrl(), 'url' => $this->configuration->getBaseUrl(),
'paths' => [ 'paths' => [
'api' => 'api', 'api' => 'api',
'admin' => 'admin', 'admin' => 'admin',
], ],
] ];
);
}
} }
protected function seedsApplication() protected function seedsDatabase()
{ {
if ($this->isInstalled) { if (! $this->isInstalled) {
return;
}
$app = app(\Illuminate\Contracts\Foundation\Application::class); $app = app(\Illuminate\Contracts\Foundation\Application::class);
$app->bind(Builder::class, function ($container) { $factory = new ConnectionFactory($app);
return $container->make(ConnectionInterface::class)->getSchemaBuilder(); $db = $factory->make($this->configuration->getDatabaseConfiguration());
});
$repository = new DatabaseMigrationRepository($db, 'migrations');
$migrator = new Migrator($repository, $db, app('files'));
/** @var Migrator $migrator */
$migrator = $app->make(Migrator::class);
if (! $migrator->getRepository()->repositoryExists()) { if (! $migrator->getRepository()->repositoryExists()) {
$migrator->getRepository()->createRepository(); $migrator->getRepository()->createRepository();
} }
$migrator->run(__DIR__.'/../../../migrations'); $migrator->run(__DIR__.'/../../../migrations');
/** @var ConnectionInterface $connection */ $db->beginTransaction();
$connection = $app->make(\Illuminate\Database\ConnectionInterface::class);
$connection->beginTransaction();
}
} }
} }

View File

@ -11,22 +11,17 @@
namespace Flarum\Tests\Test\Concerns; namespace Flarum\Tests\Test\Concerns;
use Flarum\Api\ApiServiceProvider;
use Flarum\Api\Client; use Flarum\Api\Client;
use Flarum\User\Guest; use Flarum\User\Guest;
use Flarum\User\User; use Flarum\User\User;
use Flarum\User\UserServiceProvider;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
trait MakesApiRequests trait MakesApiRequests
{ {
public function call(string $controller, User $actor = null, array $queryParams = [], array $body = []): ResponseInterface 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 */ /** @var Client $api */
$api = $this->app->make(Client::class); $api = app(Client::class);
return $api->send($controller, $actor ?? new Guest, $queryParams, $body); return $api->send($controller, $actor ?? new Guest, $queryParams, $body);
} }