mirror of
https://github.com/flarum/core.git
synced 2025-08-28 18:40:46 +02:00
chore: replace request handling with illuminate http & router
This commit is contained in:
@@ -12,6 +12,7 @@ namespace Flarum\Admin;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\ViewFormatter;
|
||||
@@ -22,39 +23,39 @@ use Flarum\Frontend\AddTranslations;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Frontend\RecompileFrontendAssets;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\Router;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
|
||||
class AdminServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) {
|
||||
return $url->addCollection('admin', $container->make('flarum.admin.routes'), 'admin');
|
||||
});
|
||||
$this->booted(function (Container $container) {
|
||||
/** @var Router $router */
|
||||
$router = $container->make(Router::class);
|
||||
/** @var Config $config */
|
||||
$config = $container->make(Config::class);
|
||||
|
||||
$this->container->singleton('flarum.admin.routes', function () {
|
||||
$routes = new RouteCollection;
|
||||
$this->populateRoutes($routes);
|
||||
$router->middlewareGroup('admin', $container->make('flarum.admin.middleware'));
|
||||
|
||||
return $routes;
|
||||
$factory = $container->make(RouteHandlerFactory::class);
|
||||
|
||||
$router->middleware('admin')->prefix($config->path('admin'))->group(
|
||||
fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.admin.middleware', function () {
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
'flarum.admin.error_handler',
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.admin.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\RequireAdministrateAbility::class,
|
||||
HttpMiddleware\ReferrerPolicyHeader::class,
|
||||
@@ -71,22 +72,6 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.admin.route_resolver', function (Container $container) {
|
||||
return new HttpMiddleware\ResolveRoute($container->make('flarum.admin.routes'));
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.admin.handler', function (Container $container) {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
foreach ($container->make('flarum.admin.middleware') as $middleware) {
|
||||
$pipe->pipe($container->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.assets.admin', function (Container $container) {
|
||||
/** @var \Flarum\Frontend\Assets $assets */
|
||||
$assets = $container->make('flarum.assets.factory')('admin');
|
||||
@@ -143,12 +128,4 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function populateRoutes(RouteCollection $routes): void
|
||||
{
|
||||
$factory = $this->container->make(RouteHandlerFactory::class);
|
||||
|
||||
$callback = include __DIR__.'/routes.php';
|
||||
$callback($routes, $factory);
|
||||
}
|
||||
}
|
||||
|
@@ -9,17 +9,22 @@
|
||||
|
||||
namespace Flarum\Admin\Middleware;
|
||||
|
||||
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 Closure;
|
||||
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class DisableBrowserCache implements Middleware
|
||||
class DisableBrowserCache implements IlluminateMiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
$response = $next($request);
|
||||
|
||||
return $response->withHeader('Cache-Control', 'max-age=0, no-store');
|
||||
$response->headers->set('Cache-Control', 'max-age=0, no-store');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@@ -9,18 +9,18 @@
|
||||
|
||||
namespace Flarum\Admin\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
|
||||
use Flarum\Http\RequestUtil;
|
||||
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 Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RequireAdministrateAbility implements Middleware
|
||||
class RequireAdministrateAbility implements IlluminateMiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
return $handler->handle($request);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@@ -9,19 +9,17 @@
|
||||
|
||||
use Flarum\Admin\Content\Index;
|
||||
use Flarum\Admin\Controller\UpdateExtensionController;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\Router;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
|
||||
return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$map->get(
|
||||
'/',
|
||||
'index',
|
||||
$route->toAdmin(Index::class)
|
||||
);
|
||||
return function (Router $router, RouteHandlerFactory $factory) {
|
||||
|
||||
$router
|
||||
->get('/', $factory->toAdmin(Index::class))
|
||||
->name('index');
|
||||
|
||||
$router
|
||||
->post('/extensions/{name}', $factory->toController(UpdateExtensionController::class))
|
||||
->name('extensions.update');
|
||||
|
||||
$map->post(
|
||||
'/extensions/{name}',
|
||||
'extensions.update',
|
||||
$route->toController(UpdateExtensionController::class)
|
||||
);
|
||||
};
|
||||
|
@@ -14,29 +14,29 @@ use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\Api\Serializer\BasicDiscussionSerializer;
|
||||
use Flarum\Api\Serializer\NotificationSerializer;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Http\Router;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
|
||||
class ApiServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) {
|
||||
return $url->addCollection('api', $container->make('flarum.api.routes'), 'api');
|
||||
});
|
||||
$this->booted(function (Container $container) {
|
||||
/** @var Router $router */
|
||||
$router = $container->make(Router::class);
|
||||
/** @var Config $config */
|
||||
$config = $container->make(Config::class);
|
||||
|
||||
$this->container->singleton('flarum.api.routes', function () {
|
||||
$routes = new RouteCollection;
|
||||
$this->populateRoutes($routes);
|
||||
$router->middlewareGroup('api', $container->make('flarum.api.middleware'));
|
||||
|
||||
return $routes;
|
||||
$router->middleware('api')->prefix($config->path('api'))->group(
|
||||
fn (Router $router) => (include __DIR__.'/routes.php')($router)
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.api.throttlers', function () {
|
||||
@@ -57,14 +57,12 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
'flarum.api.error_handler',
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
Middleware\FakeHttpMethods::class,
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\AuthenticateWithHeader::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.api.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\ThrottleApi::class
|
||||
];
|
||||
@@ -78,22 +76,6 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.api.route_resolver', function (Container $container) {
|
||||
return new HttpMiddleware\ResolveRoute($container->make('flarum.api.routes'));
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.api.handler', function (Container $container) {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
foreach ($this->container->make('flarum.api.middleware') as $middleware) {
|
||||
$pipe->pipe($container->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.api.notification_serializers', function () {
|
||||
return [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
@@ -103,7 +85,6 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
$this->container->singleton('flarum.api_client.exclude_middleware', function () {
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
Middleware\FakeHttpMethods::class,
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
@@ -113,22 +94,14 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
];
|
||||
});
|
||||
|
||||
$this->container->singleton(Client::class, function ($container) {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
$this->container->singleton(Client::class, function (Container $container) {
|
||||
$exclude = $container->make('flarum.api_client.exclude_middleware');
|
||||
|
||||
$middlewareStack = array_filter($container->make('flarum.api.middleware'), function ($middlewareClass) use ($exclude) {
|
||||
return ! in_array($middlewareClass, $exclude);
|
||||
});
|
||||
|
||||
foreach ($middlewareStack as $middleware) {
|
||||
$pipe->pipe($container->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return new Client($pipe);
|
||||
return new Client($middlewareStack, $container);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,12 +122,4 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
NotificationSerializer::setSubjectSerializer($type, $serializer);
|
||||
}
|
||||
}
|
||||
|
||||
protected function populateRoutes(RouteCollection $routes): void
|
||||
{
|
||||
$factory = $this->container->make(RouteHandlerFactory::class);
|
||||
|
||||
$callback = include __DIR__.'/routes.php';
|
||||
$callback($routes, $factory);
|
||||
}
|
||||
}
|
||||
|
@@ -9,23 +9,23 @@
|
||||
|
||||
namespace Flarum\Api\Middleware;
|
||||
|
||||
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 Closure;
|
||||
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class FakeHttpMethods implements Middleware
|
||||
class FakeHttpMethods implements IlluminateMiddlewareInterface
|
||||
{
|
||||
const HEADER_NAME = 'x-http-method-override';
|
||||
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->getMethod() === 'POST' && $request->hasHeader(self::HEADER_NAME)) {
|
||||
$fakeMethod = $request->getHeaderLine(self::HEADER_NAME);
|
||||
$fakeMethod = $request->header(self::HEADER_NAME);
|
||||
|
||||
$request = $request->withMethod(strtoupper($fakeMethod));
|
||||
$request->setMethod(strtoupper($fakeMethod));
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@@ -9,26 +9,26 @@
|
||||
|
||||
namespace Flarum\Api\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
|
||||
use Flarum\Post\Exception\FloodingException;
|
||||
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 Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ThrottleApi implements Middleware
|
||||
class ThrottleApi implements IlluminateMiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected array $throttlers
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($this->throttle($request)) {
|
||||
throw new FloodingException;
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
public function throttle(Request $request): bool
|
||||
|
@@ -8,58 +8,45 @@
|
||||
*/
|
||||
|
||||
use Flarum\Api\Controller;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\Router;
|
||||
|
||||
return function (Router $router, RouteHandlerFactory $factory) {
|
||||
|
||||
return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
// Get forum information
|
||||
$map->get(
|
||||
'/',
|
||||
'forum.show',
|
||||
$route->toController(Controller\ShowForumController::class)
|
||||
);
|
||||
$router
|
||||
->get('/', $factory->toController(Controller\ShowForumController::class))
|
||||
->name('forum.show');
|
||||
|
||||
// List access tokens
|
||||
$map->get(
|
||||
'/access-tokens',
|
||||
'access-tokens.index',
|
||||
$route->toController(Controller\ListAccessTokensController::class)
|
||||
);
|
||||
$router
|
||||
->get('/access-tokens', $factory->toController(Controller\ListAccessTokensController::class))
|
||||
->name('access-tokens.index');
|
||||
|
||||
// Create access token
|
||||
$map->post(
|
||||
'/access-tokens',
|
||||
'access-tokens.create',
|
||||
$route->toController(Controller\CreateAccessTokenController::class)
|
||||
);
|
||||
$router
|
||||
->post('/access-tokens', $factory->toController(Controller\CreateAccessTokenController::class))
|
||||
->name('access-tokens.create');
|
||||
|
||||
// Delete access token
|
||||
$map->delete(
|
||||
'/access-tokens/{id}',
|
||||
'access-tokens.delete',
|
||||
$route->toController(Controller\DeleteAccessTokenController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/access-tokens/{id}', $factory->toController(Controller\DeleteAccessTokenController::class))
|
||||
->name('access-tokens.delete');
|
||||
|
||||
// Create authentication token
|
||||
$map->post(
|
||||
'/token',
|
||||
'token',
|
||||
$route->toController(Controller\CreateTokenController::class)
|
||||
);
|
||||
$router
|
||||
->post('/token', $factory->toController(Controller\CreateTokenController::class))
|
||||
->name('token');
|
||||
|
||||
// Terminate all other sessions
|
||||
$map->delete(
|
||||
'/sessions',
|
||||
'sessions.delete',
|
||||
$route->toController(Controller\TerminateAllOtherSessionsController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/sessions', $factory->toController(Controller\TerminateAllOtherSessionsController::class))
|
||||
->name('sessions.delete');
|
||||
|
||||
// Send forgot password email
|
||||
$map->post(
|
||||
'/forgot',
|
||||
'forgot',
|
||||
$route->toController(Controller\ForgotPasswordController::class)
|
||||
);
|
||||
$router
|
||||
->post('/forgot', $factory->toController(Controller\ForgotPasswordController::class))
|
||||
->name('forgot');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -68,60 +55,50 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// List users
|
||||
$map->get(
|
||||
'/users',
|
||||
'users.index',
|
||||
$route->toController(Controller\ListUsersController::class)
|
||||
);
|
||||
$router
|
||||
->get('/users', $factory->toController(Controller\ListUsersController::class))
|
||||
->name('users.index');
|
||||
|
||||
// Register a user
|
||||
$map->post(
|
||||
'/users',
|
||||
'users.create',
|
||||
$route->toController(Controller\CreateUserController::class)
|
||||
);
|
||||
$router
|
||||
->post('/users', $factory->toController(Controller\CreateUserController::class))
|
||||
->name('users.create');
|
||||
|
||||
// Get a single user
|
||||
$map->get(
|
||||
'/users/{id}',
|
||||
'users.show',
|
||||
$route->toController(Controller\ShowUserController::class)
|
||||
);
|
||||
$router
|
||||
->get('/users/{id}', $factory->toController(Controller\ShowUserController::class))
|
||||
->name('users.show')
|
||||
->whereNumber('id');
|
||||
|
||||
// Edit a user
|
||||
$map->patch(
|
||||
'/users/{id}',
|
||||
'users.update',
|
||||
$route->toController(Controller\UpdateUserController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/users/{id}', $factory->toController(Controller\UpdateUserController::class))
|
||||
->name('users.update')
|
||||
->whereNumber('id');
|
||||
|
||||
// Delete a user
|
||||
$map->delete(
|
||||
'/users/{id}',
|
||||
'users.delete',
|
||||
$route->toController(Controller\DeleteUserController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/users/{id}', $factory->toController(Controller\DeleteUserController::class))
|
||||
->name('users.delete')
|
||||
->whereNumber('id');
|
||||
|
||||
// Upload avatar
|
||||
$map->post(
|
||||
'/users/{id}/avatar',
|
||||
'users.avatar.upload',
|
||||
$route->toController(Controller\UploadAvatarController::class)
|
||||
);
|
||||
$router
|
||||
->post('/users/{id}/avatar', $factory->toController(Controller\UploadAvatarController::class))
|
||||
->name('users.avatar.upload')
|
||||
->whereNumber('id');
|
||||
|
||||
// Remove avatar
|
||||
$map->delete(
|
||||
'/users/{id}/avatar',
|
||||
'users.avatar.delete',
|
||||
$route->toController(Controller\DeleteAvatarController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/users/{id}/avatar', $factory->toController(Controller\DeleteAvatarController::class))
|
||||
->name('users.avatar.delete')
|
||||
->whereNumber('id');
|
||||
|
||||
// send confirmation email
|
||||
$map->post(
|
||||
'/users/{id}/send-confirmation',
|
||||
'users.confirmation.send',
|
||||
$route->toController(Controller\SendConfirmationEmailController::class)
|
||||
);
|
||||
$router
|
||||
->post('/users/{id}/send-confirmation', $factory->toController(Controller\SendConfirmationEmailController::class))
|
||||
->name('users.confirmation.send')
|
||||
->whereNumber('id');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -130,32 +107,25 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// List notifications for the current user
|
||||
$map->get(
|
||||
'/notifications',
|
||||
'notifications.index',
|
||||
$route->toController(Controller\ListNotificationsController::class)
|
||||
);
|
||||
$router
|
||||
->get('/notifications', $factory->toController(Controller\ListNotificationsController::class))
|
||||
->name('notifications.index');
|
||||
|
||||
// Mark all notifications as read
|
||||
$map->post(
|
||||
'/notifications/read',
|
||||
'notifications.readAll',
|
||||
$route->toController(Controller\ReadAllNotificationsController::class)
|
||||
);
|
||||
$router
|
||||
->post('/notifications/read', $factory->toController(Controller\ReadAllNotificationsController::class))
|
||||
->name('notifications.readAll');
|
||||
|
||||
// Mark a single notification as read
|
||||
$map->patch(
|
||||
'/notifications/{id}',
|
||||
'notifications.update',
|
||||
$route->toController(Controller\UpdateNotificationController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/notifications/{id}', $factory->toController(Controller\UpdateNotificationController::class))
|
||||
->name('notifications.update')
|
||||
->whereNumber('id');
|
||||
|
||||
// Delete all notifications for the current user.
|
||||
$map->delete(
|
||||
'/notifications',
|
||||
'notifications.deleteAll',
|
||||
$route->toController(Controller\DeleteAllNotificationsController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/notifications', $factory->toController(Controller\DeleteAllNotificationsController::class))
|
||||
->name('notifications.deleteAll');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -164,39 +134,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// List discussions
|
||||
$map->get(
|
||||
'/discussions',
|
||||
'discussions.index',
|
||||
$route->toController(Controller\ListDiscussionsController::class)
|
||||
);
|
||||
$router
|
||||
->get('/discussions', $factory->toController(Controller\ListDiscussionsController::class))
|
||||
->name('discussions.index');
|
||||
|
||||
// Create a discussion
|
||||
$map->post(
|
||||
'/discussions',
|
||||
'discussions.create',
|
||||
$route->toController(Controller\CreateDiscussionController::class)
|
||||
);
|
||||
$router
|
||||
->post('/discussions', $factory->toController(Controller\CreateDiscussionController::class))
|
||||
->name('discussions.create');
|
||||
|
||||
// Show a single discussion
|
||||
$map->get(
|
||||
'/discussions/{id}',
|
||||
'discussions.show',
|
||||
$route->toController(Controller\ShowDiscussionController::class)
|
||||
);
|
||||
$router
|
||||
->get('/discussions/{id}', $factory->toController(Controller\ShowDiscussionController::class))
|
||||
->name('discussions.show')
|
||||
->whereNumber('id');
|
||||
|
||||
// Edit a discussion
|
||||
$map->patch(
|
||||
'/discussions/{id}',
|
||||
'discussions.update',
|
||||
$route->toController(Controller\UpdateDiscussionController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/discussions/{id}', $factory->toController(Controller\UpdateDiscussionController::class))
|
||||
->name('discussions.update')
|
||||
->whereNumber('id');
|
||||
|
||||
// Delete a discussion
|
||||
$map->delete(
|
||||
'/discussions/{id}',
|
||||
'discussions.delete',
|
||||
$route->toController(Controller\DeleteDiscussionController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/discussions/{id}', $factory->toController(Controller\DeleteDiscussionController::class))
|
||||
->name('discussions.delete')
|
||||
->whereNumber('id');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -205,39 +168,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// List posts, usually for a discussion
|
||||
$map->get(
|
||||
'/posts',
|
||||
'posts.index',
|
||||
$route->toController(Controller\ListPostsController::class)
|
||||
);
|
||||
$router
|
||||
->get('/posts', $factory->toController(Controller\ListPostsController::class))
|
||||
->name('posts.index');
|
||||
|
||||
// Create a post
|
||||
$map->post(
|
||||
'/posts',
|
||||
'posts.create',
|
||||
$route->toController(Controller\CreatePostController::class)
|
||||
);
|
||||
$router
|
||||
->post('/posts', $factory->toController(Controller\CreatePostController::class))
|
||||
->name('posts.create');
|
||||
|
||||
// Show a single or multiple posts by ID
|
||||
$map->get(
|
||||
'/posts/{id}',
|
||||
'posts.show',
|
||||
$route->toController(Controller\ShowPostController::class)
|
||||
);
|
||||
$router
|
||||
->get('/posts/{id}', $factory->toController(Controller\ShowPostController::class))
|
||||
->name('posts.show')
|
||||
->whereNumber('id');
|
||||
|
||||
// Edit a post
|
||||
$map->patch(
|
||||
'/posts/{id}',
|
||||
'posts.update',
|
||||
$route->toController(Controller\UpdatePostController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/posts/{id}', $factory->toController(Controller\UpdatePostController::class))
|
||||
->name('posts.update')
|
||||
->whereNumber('id');
|
||||
|
||||
// Delete a post
|
||||
$map->delete(
|
||||
'/posts/{id}',
|
||||
'posts.delete',
|
||||
$route->toController(Controller\DeletePostController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/posts/{id}', $factory->toController(Controller\DeletePostController::class))
|
||||
->name('posts.delete')
|
||||
->whereNumber('id');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -246,39 +202,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// List groups
|
||||
$map->get(
|
||||
'/groups',
|
||||
'groups.index',
|
||||
$route->toController(Controller\ListGroupsController::class)
|
||||
);
|
||||
$router
|
||||
->get('/groups', $factory->toController(Controller\ListGroupsController::class))
|
||||
->name('groups.index');
|
||||
|
||||
// Create a group
|
||||
$map->post(
|
||||
'/groups',
|
||||
'groups.create',
|
||||
$route->toController(Controller\CreateGroupController::class)
|
||||
);
|
||||
$router
|
||||
->post('/groups', $factory->toController(Controller\CreateGroupController::class))
|
||||
->name('groups.create');
|
||||
|
||||
// Show a single group
|
||||
$map->get(
|
||||
'/groups/{id}',
|
||||
'groups.show',
|
||||
$route->toController(Controller\ShowGroupController::class)
|
||||
);
|
||||
$router
|
||||
->get('/groups/{id}', $factory->toController(Controller\ShowGroupController::class))
|
||||
->name('groups.show')
|
||||
->whereNumber('id');
|
||||
|
||||
// Edit a group
|
||||
$map->patch(
|
||||
'/groups/{id}',
|
||||
'groups.update',
|
||||
$route->toController(Controller\UpdateGroupController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/groups/{id}', $factory->toController(Controller\UpdateGroupController::class))
|
||||
->name('groups.update')
|
||||
->whereNumber('id');
|
||||
|
||||
// Delete a group
|
||||
$map->delete(
|
||||
'/groups/{id}',
|
||||
'groups.delete',
|
||||
$route->toController(Controller\DeleteGroupController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/groups/{id}', $factory->toController(Controller\DeleteGroupController::class))
|
||||
->name('groups.delete')
|
||||
->whereNumber('id');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -287,86 +236,63 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
*/
|
||||
|
||||
// Toggle an extension
|
||||
$map->patch(
|
||||
'/extensions/{name}',
|
||||
'extensions.update',
|
||||
$route->toController(Controller\UpdateExtensionController::class)
|
||||
);
|
||||
$router
|
||||
->patch('/extensions/{name}', $factory->toController(Controller\UpdateExtensionController::class))
|
||||
->name('extensions.update');
|
||||
|
||||
// Uninstall an extension
|
||||
$map->delete(
|
||||
'/extensions/{name}',
|
||||
'extensions.delete',
|
||||
$route->toController(Controller\UninstallExtensionController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/extensions/{name}', $factory->toController(Controller\UninstallExtensionController::class))
|
||||
->name('extensions.delete');
|
||||
|
||||
// Get readme for an extension
|
||||
$map->get(
|
||||
'/extension-readmes/{name}',
|
||||
'extension-readmes.show',
|
||||
$route->toController(Controller\ShowExtensionReadmeController::class)
|
||||
);
|
||||
$router
|
||||
->get('/extension-readmes/{name}', $factory->toController(Controller\ShowExtensionReadmeController::class))
|
||||
->name('extension-readmes.show');
|
||||
|
||||
// Update settings
|
||||
$map->post(
|
||||
'/settings',
|
||||
'settings',
|
||||
$route->toController(Controller\SetSettingsController::class)
|
||||
);
|
||||
$router
|
||||
->post('/settings', $factory->toController(Controller\SetSettingsController::class))
|
||||
->name('settings');
|
||||
|
||||
// Update a permission
|
||||
$map->post(
|
||||
'/permission',
|
||||
'permission',
|
||||
$route->toController(Controller\SetPermissionController::class)
|
||||
);
|
||||
$router
|
||||
->post('/permission', $factory->toController(Controller\SetPermissionController::class))
|
||||
->name('permission');
|
||||
|
||||
// Upload a logo
|
||||
$map->post(
|
||||
'/logo',
|
||||
'logo',
|
||||
$route->toController(Controller\UploadLogoController::class)
|
||||
);
|
||||
$router
|
||||
->post('/logo', $factory->toController(Controller\UploadLogoController::class))
|
||||
->name('logo');
|
||||
|
||||
// Remove the logo
|
||||
$map->delete(
|
||||
'/logo',
|
||||
'logo.delete',
|
||||
$route->toController(Controller\DeleteLogoController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/logo', $factory->toController(Controller\DeleteLogoController::class))
|
||||
->name('logo.delete');
|
||||
|
||||
// Upload a favicon
|
||||
$map->post(
|
||||
'/favicon',
|
||||
'favicon',
|
||||
$route->toController(Controller\UploadFaviconController::class)
|
||||
);
|
||||
$router
|
||||
->post('/favicon', $factory->toController(Controller\UploadFaviconController::class))
|
||||
->name('favicon');
|
||||
|
||||
// Remove the favicon
|
||||
$map->delete(
|
||||
'/favicon',
|
||||
'favicon.delete',
|
||||
$route->toController(Controller\DeleteFaviconController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/favicon', $factory->toController(Controller\DeleteFaviconController::class))
|
||||
->name('favicon.delete');
|
||||
|
||||
// Clear the cache
|
||||
$map->delete(
|
||||
'/cache',
|
||||
'cache.clear',
|
||||
$route->toController(Controller\ClearCacheController::class)
|
||||
);
|
||||
$router
|
||||
->delete('/cache', $factory->toController(Controller\ClearCacheController::class))
|
||||
->name('cache.clear');
|
||||
|
||||
// List available mail drivers, available fields and validation status
|
||||
$map->get(
|
||||
'/mail/settings',
|
||||
'mailSettings.index',
|
||||
$route->toController(Controller\ShowMailSettingsController::class)
|
||||
);
|
||||
$router
|
||||
->get('/mail/settings', $factory->toController(Controller\ShowMailSettingsController::class))
|
||||
->name('mailSettings.index');
|
||||
|
||||
// Send test mail post
|
||||
$map->post(
|
||||
'/mail/test',
|
||||
'mailTest',
|
||||
$route->toController(Controller\SendTestMailController::class)
|
||||
);
|
||||
$router
|
||||
->post('/mail/test', $factory->toController(Controller\SendTestMailController::class))
|
||||
->name('mailTest');
|
||||
|
||||
};
|
||||
|
@@ -13,6 +13,7 @@ use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\ViewFormatter;
|
||||
@@ -24,9 +25,8 @@ use Flarum\Frontend\Assets;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Frontend\RecompileFrontendAssets;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Http\Router;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Flarum\Settings\Event\Saving;
|
||||
@@ -34,39 +34,41 @@ use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ForumServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) {
|
||||
return $url->addCollection('forum', $container->make('flarum.forum.routes'));
|
||||
});
|
||||
$this->booted(function (Container $container) {
|
||||
/** @var Router $router */
|
||||
$router = $container->make(Router::class);
|
||||
/** @var Config $config */
|
||||
$config = $container->make(Config::class);
|
||||
|
||||
$this->container->singleton('flarum.forum.routes', function (Container $container) {
|
||||
$routes = new RouteCollection;
|
||||
$this->populateRoutes($routes, $container);
|
||||
$router->middlewareGroup('forum', $container->make('flarum.forum.middleware'));
|
||||
|
||||
return $routes;
|
||||
});
|
||||
$factory = $container->make(RouteHandlerFactory::class);
|
||||
|
||||
$this->container->afterResolving('flarum.forum.routes', function (RouteCollection $routes, Container $container) {
|
||||
$this->setDefaultRoute($routes, $container);
|
||||
$router->middleware('forum')->prefix($config->path('forum'))->group(
|
||||
fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)
|
||||
);
|
||||
|
||||
$this->setDefaultRoute(
|
||||
$router,
|
||||
$container->make(SettingsRepositoryInterface::class)
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.forum.middleware', function () {
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
'flarum.forum.error_handler',
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
HttpMiddleware\CollectGarbage::class,
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.forum.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\ShareErrorsFromSession::class,
|
||||
HttpMiddleware\FlarumPromotionHeader::class,
|
||||
@@ -83,22 +85,6 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.forum.route_resolver', function (Container $container) {
|
||||
return new HttpMiddleware\ResolveRoute($container->make('flarum.forum.routes'));
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.forum.handler', function (Container $container) {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
foreach ($container->make('flarum.forum.middleware') as $middleware) {
|
||||
$pipe->pipe($container->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.assets.forum', function (Container $container) {
|
||||
/** @var Assets $assets */
|
||||
$assets = $container->make('flarum.assets.factory')('forum');
|
||||
@@ -194,29 +180,10 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
}
|
||||
|
||||
protected function populateRoutes(RouteCollection $routes, Container $container): void
|
||||
protected function setDefaultRoute(Router $router, SettingsRepositoryInterface $settings): void
|
||||
{
|
||||
$factory = $container->make(RouteHandlerFactory::class);
|
||||
|
||||
$callback = include __DIR__.'/routes.php';
|
||||
$callback($routes, $factory);
|
||||
}
|
||||
|
||||
protected function setDefaultRoute(RouteCollection $routes, Container $container): void
|
||||
{
|
||||
$factory = $container->make(RouteHandlerFactory::class);
|
||||
$defaultRoute = $container->make('flarum.settings')->get('default_route');
|
||||
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute]['handler'])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute]['handler'];
|
||||
} else {
|
||||
$toDefaultController = $factory->toForum(Content\Index::class);
|
||||
}
|
||||
|
||||
$routes->get(
|
||||
'/',
|
||||
'default',
|
||||
$toDefaultController
|
||||
);
|
||||
$defaultRoute = $settings->get('default_route');
|
||||
$action = $router->getRoutes()->getByName($defaultRoute)?->getAction() ?? 'index';
|
||||
$router->get('/', $action)->name('default');
|
||||
}
|
||||
}
|
||||
|
@@ -9,85 +9,64 @@
|
||||
|
||||
use Flarum\Forum\Content;
|
||||
use Flarum\Forum\Controller;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\Router;
|
||||
|
||||
return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$map->get(
|
||||
'/all',
|
||||
'index',
|
||||
$route->toForum(Content\Index::class)
|
||||
);
|
||||
return function (Router $router, RouteHandlerFactory $factory) {
|
||||
|
||||
$map->get(
|
||||
'/d/{id:\d+(?:-[^/]*)?}[/{near:[^/]*}]',
|
||||
'discussion',
|
||||
$route->toForum(Content\Discussion::class)
|
||||
);
|
||||
$router
|
||||
->get('/all', $factory->toForum(Content\Index::class))
|
||||
->name('index');
|
||||
|
||||
$map->get(
|
||||
'/u/{username}[/{filter:[^/]*}]',
|
||||
'user',
|
||||
$route->toForum(Content\User::class)
|
||||
);
|
||||
$router
|
||||
->get('/d/{id}/{near?}', $factory->toForum(Content\Discussion::class))
|
||||
->where('id', '\d+(?:-[^/]*)?')
|
||||
->where('near', '[^/]*')
|
||||
->name('discussion');
|
||||
|
||||
$map->get(
|
||||
'/settings',
|
||||
'settings',
|
||||
$route->toForum(Content\AssertRegistered::class)
|
||||
);
|
||||
$router
|
||||
->get('/u/{username}/{filter?}', $factory->toForum(Content\User::class))
|
||||
->where('filter', '[^/]*')
|
||||
->name('user');
|
||||
|
||||
$map->get(
|
||||
'/notifications',
|
||||
'notifications',
|
||||
$route->toForum(Content\AssertRegistered::class)
|
||||
);
|
||||
$router
|
||||
->get('/settings', $factory->toForum(Content\AssertRegistered::class))
|
||||
->name('settings');
|
||||
|
||||
$map->get(
|
||||
'/logout',
|
||||
'logout',
|
||||
$route->toController(Controller\LogOutController::class)
|
||||
);
|
||||
$router
|
||||
->get('/notifications', $factory->toForum(Content\AssertRegistered::class))
|
||||
->name('notifications');
|
||||
|
||||
$map->post(
|
||||
'/global-logout',
|
||||
'globalLogout',
|
||||
$route->toController(Controller\GlobalLogOutController::class)
|
||||
);
|
||||
$router
|
||||
->get('/logout', $factory->toController(Controller\LogOutController::class))
|
||||
->name('logout');
|
||||
|
||||
$map->post(
|
||||
'/login',
|
||||
'login',
|
||||
$route->toController(Controller\LogInController::class)
|
||||
);
|
||||
$router
|
||||
->post('/global-logout', $factory->toController(Controller\GlobalLogOutController::class))
|
||||
->name('globalLogout');
|
||||
|
||||
$map->post(
|
||||
'/register',
|
||||
'register',
|
||||
$route->toController(Controller\RegisterController::class)
|
||||
);
|
||||
$router
|
||||
->post('/login', $factory->toController(Controller\LogInController::class))
|
||||
->name('login');
|
||||
|
||||
$map->get(
|
||||
'/confirm/{token}',
|
||||
'confirmEmail',
|
||||
$route->toController(Controller\ConfirmEmailViewController::class),
|
||||
);
|
||||
$router
|
||||
->post('/register', $factory->toController(Controller\RegisterController::class))
|
||||
->name('register');
|
||||
|
||||
$map->post(
|
||||
'/confirm/{token}',
|
||||
'confirmEmail.submit',
|
||||
$route->toController(Controller\ConfirmEmailController::class),
|
||||
);
|
||||
$router
|
||||
->get('/confirm/{token}', $factory->toController(Controller\ConfirmEmailViewController::class))
|
||||
->name('confirmEmail');
|
||||
|
||||
$map->get(
|
||||
'/reset/{token}',
|
||||
'resetPassword',
|
||||
$route->toController(Controller\ResetPasswordController::class)
|
||||
);
|
||||
$router
|
||||
->post('/confirm/{token}', $factory->toController(Controller\ConfirmEmailController::class))
|
||||
->name('confirmEmail.submit');
|
||||
|
||||
$router
|
||||
->get('/reset/{token}', $factory->toController(Controller\ResetPasswordController::class))
|
||||
->name('resetPassword');
|
||||
|
||||
$router
|
||||
->post('/reset', $factory->toController(Controller\SavePasswordController::class))
|
||||
->name('savePassword');
|
||||
|
||||
$map->post(
|
||||
'/reset',
|
||||
'savePassword',
|
||||
$route->toController(Controller\SavePasswordController::class)
|
||||
);
|
||||
};
|
||||
|
@@ -10,14 +10,13 @@
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
interface AppInterface
|
||||
{
|
||||
public function getContainer(): Container;
|
||||
|
||||
public function getRequestHandler(): RequestHandlerInterface;
|
||||
public function getMiddlewareStack(): array;
|
||||
|
||||
/**
|
||||
* @return Command[]
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Foundation\Concerns\InteractsWithLaravel;
|
||||
use Flarum\Http\RoutingServiceProvider;
|
||||
use Illuminate\Container\Container as IlluminateContainer;
|
||||
use Illuminate\Contracts\Foundation\Application as LaravelApplication;
|
||||
use Illuminate\Events\EventServiceProvider;
|
||||
@@ -69,21 +70,15 @@ class Application extends IlluminateContainer implements LaravelApplication
|
||||
IlluminateContainer::setInstance($this);
|
||||
|
||||
$this->instance('app', $this);
|
||||
$this->alias('app', IlluminateContainer::class);
|
||||
|
||||
$this->instance('container', $this);
|
||||
$this->alias('container', IlluminateContainer::class);
|
||||
|
||||
$this->instance('flarum', $this);
|
||||
$this->alias('flarum', self::class);
|
||||
|
||||
$this->instance('flarum.paths', $this->paths);
|
||||
$this->alias('flarum.paths', Paths::class);
|
||||
}
|
||||
|
||||
protected function registerBaseServiceProviders(): void
|
||||
{
|
||||
$this->register(new EventServiceProvider($this));
|
||||
$this->register(new RoutingServiceProvider($this));
|
||||
}
|
||||
|
||||
public function register($provider, $force = false): ServiceProvider
|
||||
@@ -166,13 +161,15 @@ class Application extends IlluminateContainer implements LaravelApplication
|
||||
$this->fireAppCallbacks($this->bootedCallbacks);
|
||||
}
|
||||
|
||||
protected function bootProvider(ServiceProvider $provider): mixed
|
||||
protected function bootProvider(ServiceProvider $provider): void
|
||||
{
|
||||
$provider->callBootingCallbacks();
|
||||
|
||||
if (method_exists($provider, 'boot')) {
|
||||
return $this->call([$provider, 'boot']);
|
||||
$this->call([$provider, 'boot']);
|
||||
}
|
||||
|
||||
return null;
|
||||
$provider->callBootedCallbacks();
|
||||
}
|
||||
|
||||
public function booting(mixed $callback): void
|
||||
@@ -199,11 +196,12 @@ class Application extends IlluminateContainer implements LaravelApplication
|
||||
public function registerCoreContainerAliases(): void
|
||||
{
|
||||
$aliases = [
|
||||
'app' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
|
||||
'app' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
|
||||
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
|
||||
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
|
||||
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
|
||||
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
|
||||
'container' => [\Illuminate\Contracts\Container\Container::class, \Psr\Container\ContainerInterface::class],
|
||||
'db' => [\Illuminate\Database\DatabaseManager::class],
|
||||
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
|
||||
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
|
||||
@@ -211,8 +209,13 @@ class Application extends IlluminateContainer implements LaravelApplication
|
||||
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
|
||||
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
|
||||
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
|
||||
'flarum' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class, self::class],
|
||||
'flarum.paths' => [Paths::class],
|
||||
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
|
||||
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
|
||||
'router' => [\Flarum\Http\Router::class, \Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
|
||||
'session' => [\Illuminate\Session\SessionManager::class],
|
||||
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
|
||||
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
|
||||
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
|
||||
];
|
||||
|
@@ -39,6 +39,15 @@ class Config implements ArrayAccess
|
||||
return $this->data['offline'] ?? false;
|
||||
}
|
||||
|
||||
public function path(string $frontend): string
|
||||
{
|
||||
return match(true) {
|
||||
isset($this->data['paths'][$frontend]) => $this->data['paths'][$frontend],
|
||||
$frontend === 'forum' => '/',
|
||||
default => $frontend,
|
||||
};
|
||||
}
|
||||
|
||||
private function requireKeys(mixed ...$keys): void
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
|
@@ -33,29 +33,16 @@ class InstalledApp implements AppInterface
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
public function getRequestHandler(): RequestHandlerInterface
|
||||
public function getMiddlewareStack(): array
|
||||
{
|
||||
if ($this->config->inMaintenanceMode()) {
|
||||
return $this->container->make('flarum.maintenance.handler');
|
||||
} elseif ($this->needsUpdate()) {
|
||||
return $this->getUpdaterHandler();
|
||||
}
|
||||
// if ($this->config->inMaintenanceMode()) {
|
||||
// return $this->container->make('flarum.maintenance.handler');
|
||||
// }
|
||||
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ProcessIp());
|
||||
$pipe->pipe(new BasePath($this->basePath()));
|
||||
$pipe->pipe(new OriginalMessages);
|
||||
$pipe->pipe(
|
||||
new BasePathRouter([
|
||||
$this->subPath('api') => 'flarum.api.handler',
|
||||
$this->subPath('admin') => 'flarum.admin.handler',
|
||||
'/' => 'flarum.forum.handler',
|
||||
])
|
||||
);
|
||||
$pipe->pipe(new RequestHandler($this->container));
|
||||
|
||||
return $pipe;
|
||||
return match ($this->needsUpdate()) {
|
||||
true => $this->getUpdaterMiddlewareStack(),
|
||||
false => $this->getStandardMiddlewareStack(),
|
||||
};
|
||||
}
|
||||
|
||||
protected function needsUpdate(): bool
|
||||
@@ -66,16 +53,19 @@ class InstalledApp implements AppInterface
|
||||
return $version !== Application::VERSION;
|
||||
}
|
||||
|
||||
protected function getUpdaterHandler(): RequestHandlerInterface|MiddlewarePipe
|
||||
protected function getUpdaterMiddlewareStack(): array
|
||||
{
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new BasePath($this->basePath()));
|
||||
$pipe->pipe(
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.update.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
return [
|
||||
new BasePath($this->basePath()),
|
||||
];
|
||||
}
|
||||
|
||||
return $pipe;
|
||||
protected function getStandardMiddlewareStack(): array
|
||||
{
|
||||
return [
|
||||
new BasePath($this->basePath()),
|
||||
new OriginalMessages,
|
||||
];
|
||||
}
|
||||
|
||||
protected function basePath(): string
|
||||
@@ -83,11 +73,6 @@ class InstalledApp implements AppInterface
|
||||
return $this->config->url()->getPath() ?: '/';
|
||||
}
|
||||
|
||||
protected function subPath(string $pathName): string
|
||||
{
|
||||
return '/'.($this->config['paths'][$pathName] ?? $pathName);
|
||||
}
|
||||
|
||||
public function getConsoleCommands(): array
|
||||
{
|
||||
return array_map(function ($command) {
|
||||
|
@@ -9,22 +9,22 @@
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Http\Controller\AbstractController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Laminas\Diactoros\Response\HtmlResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class MaintenanceModeHandler implements RequestHandlerInterface
|
||||
class MaintenanceModeHandler extends AbstractController
|
||||
{
|
||||
const MESSAGE = 'Currently down for maintenance. Please come back later.';
|
||||
|
||||
/**
|
||||
* Handle the request and return a response.
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
public function __invoke(Request $request): ResponseInterface
|
||||
{
|
||||
// Special handling for API requests: they get a proper API response
|
||||
if ($this->isApiRequest($request)) {
|
||||
@@ -35,10 +35,10 @@ class MaintenanceModeHandler implements RequestHandlerInterface
|
||||
return new HtmlResponse(self::MESSAGE, 503);
|
||||
}
|
||||
|
||||
private function isApiRequest(ServerRequestInterface $request): bool
|
||||
private function isApiRequest(Request $request): bool
|
||||
{
|
||||
return Str::contains(
|
||||
$request->getHeaderLine('Accept'),
|
||||
$request->header('Accept'),
|
||||
'application/vnd.api+json'
|
||||
);
|
||||
}
|
||||
|
@@ -15,9 +15,8 @@ use Flarum\Database\ScopeVisibilityTrait;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@@ -96,7 +95,7 @@ class AccessToken extends AbstractModel
|
||||
* Update the time of last usage of a token.
|
||||
* If a request object is provided, the IP address and User Agent will also be logged.
|
||||
*/
|
||||
public function touch($attribute = null, ServerRequestInterface $request = null): bool
|
||||
public function touch($attribute = null, Request $request = null): bool
|
||||
{
|
||||
$now = Carbon::now();
|
||||
|
||||
@@ -105,11 +104,11 @@ class AccessToken extends AbstractModel
|
||||
}
|
||||
|
||||
if ($request) {
|
||||
$this->last_ip_address = $request->getAttribute('ipAddress');
|
||||
$this->last_ip_address = $request->ip();
|
||||
// We truncate user agent so it fits in the database column
|
||||
// The length is hard-coded as the column length
|
||||
// It seems like MySQL or Laravel already truncates values, but we'll play safe and do it ourselves
|
||||
$agent = Arr::get($request->getServerParams(), 'HTTP_USER_AGENT');
|
||||
$agent = $request->server->get('HTTP_USER_AGENT');
|
||||
$this->last_user_agent = substr($agent ?? '', 0, 255);
|
||||
} else {
|
||||
// If no request is provided, we set the values back to null
|
||||
|
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Dflydev\FigCookies\Modifier\SameSite;
|
||||
use Dflydev\FigCookies\SetCookie;
|
||||
use DateTime;
|
||||
use Flarum\Foundation\Config;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
|
||||
class CookieFactory
|
||||
{
|
||||
@@ -40,16 +40,14 @@ class CookieFactory
|
||||
* This method returns a cookie instance for use with the Set-Cookie HTTP header.
|
||||
* It will be pre-configured according to Flarum's base URL and protocol.
|
||||
*/
|
||||
public function make(string $name, ?string $value = null, ?int $maxAge = null): SetCookie
|
||||
public function make(string $name, ?string $value = null, ?int $maxAge = null): Cookie
|
||||
{
|
||||
$cookie = SetCookie::create($this->getName($name), $value);
|
||||
$cookie = Cookie::create($this->getName($name), $value);
|
||||
|
||||
// Make sure we send both the MaxAge and Expires parameters (the former
|
||||
// is not supported by all browser versions)
|
||||
if ($maxAge) {
|
||||
$cookie = $cookie
|
||||
->withMaxAge($maxAge)
|
||||
->withExpires(time() + $maxAge);
|
||||
$cookie = $cookie->withExpires(time() + $maxAge);
|
||||
}
|
||||
|
||||
if ($this->domain != null) {
|
||||
@@ -57,20 +55,20 @@ class CookieFactory
|
||||
}
|
||||
|
||||
// Explicitly set SameSite value, use sensible default if no value provided
|
||||
$cookie = $cookie->withSameSite(SameSite::{$this->samesite ?? 'lax'}());
|
||||
$cookie = $cookie->withSameSite($this->samesite ?? 'lax');
|
||||
|
||||
return $cookie
|
||||
->withPath($this->path)
|
||||
->withSecure($this->secure)
|
||||
->withHttpOnly(true);
|
||||
->withHttpOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an expired cookie instance.
|
||||
*/
|
||||
public function expire(string $name): SetCookie
|
||||
public function expire(string $name): Cookie
|
||||
{
|
||||
return $this->make($name)->expire();
|
||||
return $this->make($name)->withExpires(new DateTime('-5 years'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
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 ExecuteRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* Executes the route handler resolved in ResolveRoute.
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$handler = $request->getAttribute('routeHandler');
|
||||
$parameters = $request->getAttribute('routeParameters');
|
||||
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
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 ParseJsonBody implements Middleware
|
||||
{
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
if (Str::contains($request->getHeaderLine('content-type'), 'json')) {
|
||||
$input = json_decode($request->getBody(), true);
|
||||
|
||||
$request = $request->withParsedBody($input ?: []);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ProcessIp implements Middleware
|
||||
{
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$ipAddress = Arr::get($request->getServerParams(), 'REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
return $handler->handle($request->withAttribute('ipAddress', $ipAddress));
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
use Flarum\Http\Exception\MethodNotAllowedException;
|
||||
use Flarum\Http\Exception\RouteNotFoundException;
|
||||
use Flarum\Http\RouteCollection;
|
||||
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 ResolveRoute implements Middleware
|
||||
{
|
||||
protected ?Dispatcher\GroupCountBased $dispatcher = null;
|
||||
|
||||
public function __construct(
|
||||
protected RouteCollection $routes
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given request from our route collection.
|
||||
*
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws RouteNotFoundException
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$method = $request->getMethod();
|
||||
$uri = $request->getUri()->getPath() ?: '/';
|
||||
|
||||
$routeInfo = $this->getDispatcher()->dispatch($method, $uri);
|
||||
|
||||
switch ($routeInfo[0]) {
|
||||
case Dispatcher::NOT_FOUND:
|
||||
throw new RouteNotFoundException($uri);
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
throw new MethodNotAllowedException($method);
|
||||
default:
|
||||
$request = $request
|
||||
->withAttribute('routeName', $routeInfo[1]['name'])
|
||||
->withAttribute('routeHandler', $routeInfo[1]['handler'])
|
||||
->withAttribute('routeParameters', $routeInfo[2]);
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDispatcher(): Dispatcher\GroupCountBased
|
||||
{
|
||||
if (! isset($this->dispatcher)) {
|
||||
$this->dispatcher = new Dispatcher\GroupCountBased($this->routes->getRouteData());
|
||||
}
|
||||
|
||||
return $this->dispatcher;
|
||||
}
|
||||
}
|
@@ -9,8 +9,7 @@
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Dflydev\FigCookies\FigResponseCookies;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Rememberer
|
||||
{
|
||||
@@ -24,19 +23,21 @@ class Rememberer
|
||||
/**
|
||||
* Sets the remember cookie on a response.
|
||||
*/
|
||||
public function remember(ResponseInterface $response, RememberAccessToken $token): ResponseInterface
|
||||
public function remember(Response $response, RememberAccessToken $token): Response
|
||||
{
|
||||
return FigResponseCookies::set(
|
||||
$response,
|
||||
$response->headers->setCookie(
|
||||
$this->cookie->make(self::COOKIE_NAME, $token->token, RememberAccessToken::rememberCookieLifeTime())
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function forget(ResponseInterface $response): ResponseInterface
|
||||
public function forget(Response $response): Response
|
||||
{
|
||||
return FigResponseCookies::set(
|
||||
$response,
|
||||
$response->headers->setCookie(
|
||||
$this->cookie->expire(self::COOKIE_NAME)
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@@ -10,29 +10,78 @@
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Illuminate\Http\Request as IlluminateRequest;
|
||||
use Laminas\Diactoros\ResponseFactory;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Laminas\Diactoros\StreamFactory;
|
||||
use Laminas\Diactoros\UploadedFileFactory;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
|
||||
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
|
||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
|
||||
class RequestUtil
|
||||
{
|
||||
public static function getActor(Request $request): User
|
||||
public static function getActor(ServerRequestInterface|SymfonyRequest $request): User
|
||||
{
|
||||
return $request->getAttribute('actorReference')->getActor();
|
||||
return self::getActorReference($request)->getActor();
|
||||
}
|
||||
|
||||
public static function withActor(Request $request, User $actor): Request
|
||||
public static function withActor(ServerRequestInterface|SymfonyRequest $request, User $actor): ServerRequestInterface|SymfonyRequest
|
||||
{
|
||||
$actorReference = $request->getAttribute('actorReference');
|
||||
$actorReference = self::getActorReference($request);
|
||||
|
||||
if (! $actorReference) {
|
||||
$actorReference = new ActorReference;
|
||||
$request = $request->withAttribute('actorReference', $actorReference);
|
||||
$request = self::setActorReference($request, $actorReference);
|
||||
}
|
||||
|
||||
$actorReference->setActor($actor);
|
||||
|
||||
// @deprecated in 1.0
|
||||
$request = $request->withAttribute('actor', $actor);
|
||||
return $request;
|
||||
}
|
||||
|
||||
private static function setActorReference(ServerRequestInterface|SymfonyRequest $request, ActorReference $reference): ServerRequestInterface|SymfonyRequest
|
||||
{
|
||||
if ($request instanceof ServerRequestInterface) {
|
||||
$request = $request->withAttribute('actorReference', $reference);
|
||||
} else {
|
||||
$request->attributes->set('actorReference', $reference);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private static function getActorReference(ServerRequestInterface|SymfonyRequest $request): ?ActorReference
|
||||
{
|
||||
if ($request instanceof ServerRequestInterface) {
|
||||
return $request->getAttribute('actorReference');
|
||||
}
|
||||
|
||||
return $request->attributes->get('actorReference');
|
||||
}
|
||||
|
||||
public static function toIlluminate(ServerRequestInterface $request): IlluminateRequest
|
||||
{
|
||||
$httpFoundationFactory = new HttpFoundationFactory();
|
||||
|
||||
return IlluminateRequest::createFromBase(
|
||||
$httpFoundationFactory->createRequest($request)
|
||||
);
|
||||
}
|
||||
|
||||
public static function toPsr7(SymfonyRequest $request): ServerRequestInterface
|
||||
{
|
||||
$psrHttpFactory = new PsrHttpFactory(
|
||||
new ServerRequestFactory(), new StreamFactory(), new UploadedFileFactory(), new ResponseFactory()
|
||||
);
|
||||
|
||||
return $psrHttpFactory->createRequest($request);
|
||||
}
|
||||
|
||||
public static function responseToSymfony(\Psr\Http\Message\ResponseInterface $response): SymfonyResponse
|
||||
{
|
||||
return (new HttpFoundationFactory())->createResponse($response);
|
||||
}
|
||||
}
|
||||
|
@@ -9,142 +9,28 @@
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use FastRoute\DataGenerator;
|
||||
use FastRoute\RouteParser;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Routing\RouteCollection as IlluminateRouteCollection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RouteCollection
|
||||
class RouteCollection extends IlluminateRouteCollection
|
||||
{
|
||||
protected array $reverse = [];
|
||||
protected DataGenerator $dataGenerator;
|
||||
protected RouteParser $routeParser;
|
||||
protected array $routes = [];
|
||||
protected array $pendingRoutes = [];
|
||||
|
||||
public function __construct()
|
||||
public function forgetNamedRoute($name): void
|
||||
{
|
||||
$this->dataGenerator = new DataGenerator\GroupCountBased;
|
||||
$this->routeParser = new RouteParser\Std;
|
||||
}
|
||||
$route = $this->getByName($name);
|
||||
|
||||
public function get(string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
return $this->addRoute('GET', $path, $name, $handler);
|
||||
}
|
||||
|
||||
public function post(string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
return $this->addRoute('POST', $path, $name, $handler);
|
||||
}
|
||||
|
||||
public function put(string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
return $this->addRoute('PUT', $path, $name, $handler);
|
||||
}
|
||||
|
||||
public function patch(string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
return $this->addRoute('PATCH', $path, $name, $handler);
|
||||
}
|
||||
|
||||
public function delete(string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
return $this->addRoute('DELETE', $path, $name, $handler);
|
||||
}
|
||||
|
||||
public function addRoute(string $method, string $path, string $name, callable|string $handler): self
|
||||
{
|
||||
if (isset($this->routes[$name])) {
|
||||
throw new \RuntimeException("Route $name already exists");
|
||||
if (! $route) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->routes[$name] = $this->pendingRoutes[$name] = compact('method', 'path', 'handler');
|
||||
// Remove the quick lookup.
|
||||
unset($this->nameList[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeRoute(string $name): self
|
||||
{
|
||||
unset($this->routes[$name], $this->pendingRoutes[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function applyRoutes(): void
|
||||
{
|
||||
foreach ($this->pendingRoutes as $name => $route) {
|
||||
$routeDatas = $this->routeParser->parse($route['path']);
|
||||
|
||||
foreach ($routeDatas as $routeData) {
|
||||
$this->dataGenerator->addRoute($route['method'], $routeData, ['name' => $name, 'handler' => $route['handler']]);
|
||||
}
|
||||
|
||||
$this->reverse[$name] = $routeDatas;
|
||||
// Remove from the routes.
|
||||
foreach ($route->methods() as $method) {
|
||||
unset($this->routes[$method][$route->getDomain().$route->uri()]);
|
||||
unset($this->allRoutes[$method.$route->getDomain().$route->uri()]);
|
||||
}
|
||||
|
||||
$this->pendingRoutes = [];
|
||||
}
|
||||
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
public function getRouteData(): array
|
||||
{
|
||||
if (! empty($this->pendingRoutes)) {
|
||||
$this->applyRoutes();
|
||||
}
|
||||
|
||||
return $this->dataGenerator->getData();
|
||||
}
|
||||
|
||||
protected function fixPathPart(mixed $part, array $parameters, string $routeName): string
|
||||
{
|
||||
if (! is_array($part)) {
|
||||
return $part;
|
||||
}
|
||||
|
||||
if (! array_key_exists($part[0], $parameters)) {
|
||||
throw new \InvalidArgumentException("Could not generate URL for route '$routeName': no value provided for required part '$part[0]'.");
|
||||
}
|
||||
|
||||
return $parameters[$part[0]];
|
||||
}
|
||||
|
||||
public function getPath(string $name, array $parameters = []): string
|
||||
{
|
||||
if (! empty($this->pendingRoutes)) {
|
||||
$this->applyRoutes();
|
||||
}
|
||||
|
||||
if (isset($this->reverse[$name])) {
|
||||
$maxMatches = 0;
|
||||
$matchingParts = $this->reverse[$name][0];
|
||||
|
||||
// For a given route name, we want to choose the option that best matches the given parameters.
|
||||
// Each routing option is an array of parts. Each part is either a constant string
|
||||
// (which we don't care about here), or an array where the first element is the parameter name
|
||||
// and the second element is a regex into which the parameter value is inserted, if the parameter matches.
|
||||
foreach ($this->reverse[$name] as $parts) {
|
||||
foreach ($parts as $i => $part) {
|
||||
if (is_array($part) && Arr::exists($parameters, $part[0]) && $i > $maxMatches) {
|
||||
$maxMatches = $i;
|
||||
$matchingParts = $parts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fixedParts = array_map(function ($part) use ($parameters, $name) {
|
||||
return $this->fixPathPart($part, $parameters, $name);
|
||||
}, $matchingParts);
|
||||
|
||||
return '/'.ltrim(implode('', $fixedParts), '/');
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Route $name not found");
|
||||
}
|
||||
}
|
||||
|
@@ -12,9 +12,7 @@ namespace Flarum\Http;
|
||||
use Closure;
|
||||
use Flarum\Frontend\Controller as FrontendController;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -26,28 +24,27 @@ class RouteHandlerFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function toController(callable|string $controller): Closure
|
||||
public function toController(callable|string $controller): callable|string|array
|
||||
{
|
||||
return function (Request $request, array $routeParams) use ($controller) {
|
||||
$controller = $this->resolveController($controller);
|
||||
// If it's a class and it implements the RequestHandlerInterface, we'll
|
||||
// assume it's a PSR-7 request handler and we'll return [controller, 'handle']
|
||||
// as the callable.
|
||||
if (is_string($controller) && class_exists($controller) && in_array(RequestHandlerInterface::class, class_implements($controller))) {
|
||||
return [$controller, 'handle'];
|
||||
}
|
||||
|
||||
$request = $request->withQueryParams(array_merge($request->getQueryParams(), $routeParams));
|
||||
|
||||
return $controller->handle($request);
|
||||
};
|
||||
return $controller;
|
||||
}
|
||||
|
||||
public function toFrontend(string $frontend, callable|string|null $content = null): Closure
|
||||
public function toFrontend(string $frontend, callable|string|null $content = null): callable
|
||||
{
|
||||
return $this->toController(function (Container $container) use ($frontend, $content) {
|
||||
$frontend = $container->make("flarum.frontend.$frontend");
|
||||
$frontend = $this->container->make("flarum.frontend.$frontend");
|
||||
|
||||
if ($content) {
|
||||
$frontend->content(is_callable($content) ? $content : $container->make($content));
|
||||
}
|
||||
if ($content) {
|
||||
$frontend->content(is_callable($content) ? $content : $this->container->make($content));
|
||||
}
|
||||
|
||||
return new FrontendController($frontend);
|
||||
});
|
||||
return new FrontendController($frontend);
|
||||
}
|
||||
|
||||
public function toForum(string $content = null): Closure
|
||||
@@ -59,17 +56,4 @@ class RouteHandlerFactory
|
||||
{
|
||||
return $this->toFrontend('admin', $content);
|
||||
}
|
||||
|
||||
private function resolveController(callable|string $controller): Handler
|
||||
{
|
||||
$controller = is_callable($controller)
|
||||
? $this->container->call($controller)
|
||||
: $this->container->make($controller);
|
||||
|
||||
if (! $controller instanceof Handler) {
|
||||
throw new InvalidArgumentException('Controller must be an instance of '.Handler::class);
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
}
|
||||
|
20
framework/core/src/Http/Router.php
Normal file
20
framework/core/src/Http/Router.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Illuminate\Routing\Router as IlluminateRouter;
|
||||
|
||||
class Router extends IlluminateRouter
|
||||
{
|
||||
public function __construct(...$args)
|
||||
{
|
||||
parent::__construct(...$args);
|
||||
|
||||
$this->routes = new RouteCollection();
|
||||
}
|
||||
|
||||
public function forgetRoute(string $name): void
|
||||
{
|
||||
$this->routes->forgetNamedRoute($name);
|
||||
}
|
||||
}
|
16
framework/core/src/Http/RoutingServiceProvider.php
Normal file
16
framework/core/src/Http/RoutingServiceProvider.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Routing\RoutingServiceProvider as IlluminateRoutingServiceProvider;
|
||||
|
||||
class RoutingServiceProvider extends IlluminateRoutingServiceProvider
|
||||
{
|
||||
protected function registerRouter(): void
|
||||
{
|
||||
$this->app->singleton('router', function (Container $container) {
|
||||
return new Router($container['events'], $container);
|
||||
});
|
||||
}
|
||||
}
|
@@ -9,15 +9,12 @@
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Flarum\Foundation\AppInterface;
|
||||
use Flarum\Foundation\ErrorHandling\LogReporter;
|
||||
use Flarum\Foundation\SiteInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequest;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
|
||||
use Laminas\Stratigility\Middleware\ErrorResponseGenerator;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Pipeline;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
@@ -30,18 +27,17 @@ class Server
|
||||
|
||||
public function listen(): void
|
||||
{
|
||||
$runner = new RequestHandlerRunner(
|
||||
$this->safelyBootAndGetHandler(),
|
||||
new SapiEmitter,
|
||||
[ServerRequestFactory::class, 'fromGlobals'],
|
||||
function (Throwable $e) {
|
||||
$generator = new ErrorResponseGenerator;
|
||||
$request = Request::capture();
|
||||
$siteApp = $this->safelyBoot();
|
||||
$container = $siteApp->getContainer();
|
||||
$globalMiddleware = $siteApp->getMiddlewareStack();
|
||||
|
||||
return $generator($e, new ServerRequest, new Response);
|
||||
}
|
||||
);
|
||||
|
||||
$runner->run();
|
||||
(new Pipeline($container))
|
||||
->send($request)
|
||||
->through($globalMiddleware)
|
||||
->then(function (Request $request) use ($container) {
|
||||
return $container->make(Router::class)->dispatch($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,10 +48,10 @@ class Server
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function safelyBootAndGetHandler() // @phpstan-ignore-line
|
||||
private function safelyBoot(): AppInterface
|
||||
{
|
||||
try {
|
||||
return $this->site->bootApp()->getRequestHandler();
|
||||
return $this->site->bootApp();
|
||||
} catch (Throwable $e) {
|
||||
// Apply response code first so whatever happens, it's set before anything is printed
|
||||
http_response_code(500);
|
||||
|
Reference in New Issue
Block a user