...
 
Commits (12)
......@@ -181,7 +181,10 @@ class Factory
'status' => 'success', //should success be assumed?
], $data);
ob_end_clean();
if (ob_get_level() > 1) {
// New PSR-7 Router has an OB started all the time
ob_end_clean();
}
static::setCORSHeader();
......
......@@ -337,6 +337,15 @@ class blog implements Interfaces\Api
]);
}
// This is a first create blog that should have a banner
// We are trying to stop spam with this check
if ($blog->isPublished() && !$editing && !is_uploaded_file($_FILES['file']['tmp_name'])) {
return Factory::response([
'status' => 'error',
'message' => 'You must upload a banner'
]);
}
try {
if ($editing) {
$saved = $manager->update($blog);
......
......@@ -28,11 +28,13 @@ class channel implements Interfaces\Api
*/
public function get($pages)
{
$currentUser = Session::getLoggedinUser();
$channel = new User(strtolower($pages[0]));
$channel->fullExport = true; //get counts
$channel->exportCounts = true;
if (!$channel->isPro()) {
if (!$channel->isPro() && $channel->getGuid() !== $currentUser->getGuid()) {
return Factory::response([
'status' => 'error',
'message' => 'E_NOT_PRO'
......
......@@ -75,14 +75,14 @@ class settings implements Interfaces\Api
->setUser($user)
->setActor(Session::getLoggedinUser());
if (!$manager->isActive()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not Pro',
]);
}
if (isset($_POST['domain'])) {
// if (!$manager->isActive()) {
// return Factory::response([
// 'status' => 'error',
// 'message' => 'You are not Pro',
// ]);
// }
if (isset($_POST['domain']) && $manager->isActive()) {
/** @var ProDomain $proDomain */
$proDomain = Di::_()->get('Pro\Domain');
......
......@@ -85,12 +85,12 @@ class assets implements Interfaces\Api
->setUser($user)
->setActor(Session::getLoggedinUser());
if (!$manager->isActive()) {
return Factory::response([
'status' => 'error',
'message' => 'You are not Pro',
]);
}
// if (!$manager->isActive()) {
// return Factory::response([
// 'status' => 'error',
// 'message' => 'You are not Pro',
// ]);
// }
/** @var AssetsManager $assetsManager */
$assetsManager = Di::_()->get('Pro\Assets\Manager');
......
......@@ -104,7 +104,7 @@ class account implements Interfaces\Api
Factory::isLoggedIn();
$response = [];
$vars = Core\Router::getPutVars();
$vars = Core\Router\PrePsr7\Router::getPutVars();
$user = Core\Session::getLoggedInUser();
......
......@@ -102,7 +102,7 @@ class Repository
->setRating($data['rating'])
->setTags($data['tags'])
->setNsfw($data['nsfw'])
->setRejectReason($data['rejection_reason'])
->setRejectedReason($data['rejection_reason'])
->setChecksum($data['checksum']);
$response[] = $boost;
......
<?php
/**
* Ref.
* Holds a forward reference to a provider and a method.
* Used by PSR-7 Router to resolve DI bindings.
*
* @author edgebal
*/
namespace Minds\Core\Di;
use Minds\Traits\MagicAttributes;
/**
* Class Ref
* @package Minds\Core\Di
* @method string getProvider()
* @method Ref setProvider(string $provider)
* @method string getMethod()
* @method Ref setMethod(string $method)
*/
class Ref
{
use MagicAttributes;
/** @var string */
protected $provider;
/** @var string */
protected $method;
/**
* Ref constructor.
* @param string $provider
* @param string $method
*/
public function __construct(string $provider, string $method)
{
$this->setProvider($provider);
$this->setMethod($method);
}
/**
* @param string $provider
* @param string $method
* @return Ref
*/
public static function _(string $provider, string $method): Ref
{
return new static($provider, $method);
}
}
Great news! Your channel is now eligible for a free preview of [Minds Pro](https://minds.com/pro?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>).
This gives you a chance to test out our new features and decide if the product is right for you. Pro provides you with the tools to launch your own website and monetize your content without fear of unfair algorithms, censorship or demonetization.
| |
|:--:|
| [![Try Pro](https://cdn-assets.minds.com/emails/try-pro.png){=150x}](https://www.minds.com/pro/<?= $vars['user']->username ?>/settings?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>) |
| |
Specifically, be sure to check out your new [Analytics](https://www.minds.com/analytics/dashboard) console in the right-hand menu once you log in. Please remember that purchasing our [products](https://minds.com/upgrade?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>) is an invaluable donation to the financial sustainability of our platform and community.
Thank you for your support!
......@@ -51,7 +51,7 @@ class Manager
$features = $this->config->get('features') ?: [];
if (!isset($features[$feature])) {
error_log("[Features\Manager] Feature '{$feature}' is not declared. Assuming false.");
// error_log("[Features\Manager] Feature '{$feature}' is not declared. Assuming false.");
return false;
}
......
......@@ -6,14 +6,21 @@
namespace Minds\Core\Feeds\Activity;
use Zend\Diactoros\ServerRequest;
class Manager
{
public function add()
public function add(ServerRequest $request)
{
throw new \NotImplementedException();
}
public function update(ServerRequest $request)
{
throw new \NotImplementedException();
}
public function update()
public function delete(ServerRequest $request)
{
throw new \NotImplementedException();
}
......
......@@ -12,6 +12,10 @@ class FeedsProvider extends Provider
return new Elastic\Manager();
});
$this->di->bind('Feeds\Activity\Manager', function ($di) {
return new Activity\Manager();
});
$this->di->bind('Feeds\Firehose\Manager', function ($di) {
return new Firehose\Manager();
});
......
<?php
/**
* Module
* @author edgebal
*/
namespace Minds\Core\Feeds;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
/**
* Executed onInit
* @return void
*/
public function onInit(): void
{
(new FeedsProvider())->register();
(new Routes())->register();
}
}
<?php
/**
* Routes
* @author edgebal
*/
namespace Minds\Core\Feeds;
use Minds\Core\Di\Ref;
use Minds\Core\Router\Middleware\LoggedInMiddleware;
use Minds\Core\Router\ModuleRoutes;
use Minds\Core\Router\Route;
class Routes extends ModuleRoutes
{
/**
* Registers all module routes
*/
public function register(): void
{
$this->route
->withPrefix('api/v3/newsfeed')
->withMiddleware([
LoggedInMiddleware::class,
])
->do(function (Route $route) {
$route->post(
'',
Ref::_('Feeds\Activity\Manager', 'add')
);
$route->post(
':guid',
Ref::_('Feeds\Activity\Manager', 'update')
);
$route->delete(
':guid',
Ref::_('Feeds\Activity\Manager', 'delete')
);
});
}
}
......@@ -45,8 +45,8 @@ class Repository
'must' => [
[
'range' => [
'votes:up:24h:synced' => [
'gte' => $opts['from'],
'@timestamp' => [
'gte' => $opts['from'] * 1000,
],
],
],
......@@ -81,7 +81,7 @@ class Repository
'aggs' => [
'counts' => [
'max' => [
'field' => 'votes:up:24h',
'field' => 'votes:up',
],
],
'owners' => [
......
......@@ -4,6 +4,7 @@ namespace Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Events\Dispatcher;
use Minds\Interfaces\ModuleInterface;
/**
* Core Minds Engine.
......@@ -28,6 +29,7 @@ class Minds extends base
Referrals\Module::class,
Reports\Module::class,
VideoChat\Module::class,
Feeds\Module::class,
Front\Module::class,
];
......@@ -60,6 +62,7 @@ class Minds extends base
/*
* Initialise the modules
*/
/** @var ModuleInterface $module */
foreach ($modules as $module) {
$module->onInit();
}
......@@ -111,7 +114,6 @@ class Minds extends base
(new Plus\PlusProvider())->register();
(new Pro\ProProvider())->register();
(new Hashtags\HashtagsProvider())->register();
(new Feeds\FeedsProvider())->register();
(new Analytics\AnalyticsProvider())->register();
(new Channels\ChannelsProvider())->register();
(new Blogs\BlogsProvider())->register();
......
......@@ -157,6 +157,13 @@ class Manager
'user_guid' => $this->user->guid,
])->first();
// If requested by an inactive user, this is preview mode
if (!$settings && !$this->isActive()) {
$settings = new Settings();
$settings->setUserGuid($this->user->guid);
$settings->setTitle($this->user->name ?: $this->user->username);
}
if (!$settings) {
return null;
}
......@@ -333,8 +340,11 @@ class Manager
$settings->setTimeUpdated(time());
$this->setupRoutingDelegate
->onUpdate($settings);
// Only update routing if we are active
if ($this->isActive()) {
$this->setupRoutingDelegate
->onUpdate($settings);
}
return $this->repository->update($settings);
}
......
......@@ -116,6 +116,6 @@ class ChannelDeferredOps implements Interfaces\QueueRunner
default:
echo "ERROR! Invalid type {$type} passed\n\n";
}
});
}, [ 'max_messages' => 1 ]);
}
}
......@@ -242,7 +242,9 @@ class Repository
->setAppeal(isset($row['appeal_note']) ? true : false)
->setAppealNote(isset($row['appeal_note']) ? (string) $row['appeal_note'] : '')
->setReports(
isset($row['reports']) ?
$this->buildReports($row['reports']->values())
: null
)
->setInitialJuryDecisions(
isset($row['initial_jury']) ?
......
This diff is collapsed.
<?php declare(strict_types=1);
/**
* Dispatcher
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Router\Middleware\Kernel\EmptyResponseMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class Dispatcher implements RequestHandlerInterface
{
/** @var MiddlewareInterface */
protected $emptyResponseMiddleware;
/**
* Dispatcher constructor.
* @param MiddlewareInterface $emptyResponseMiddleware
*/
public function __construct(
$emptyResponseMiddleware = null
) {
$this->emptyResponseMiddleware = $emptyResponseMiddleware ?: new EmptyResponseMiddleware();
}
/** @var MiddlewareInterface[] */
protected $middleware = [];
/**
* @param MiddlewareInterface $middleware
* @return $this
*/
public function pipe(MiddlewareInterface $middleware): self
{
$this->middleware[] = $middleware;
return $this;
}
/**
* Handles a request and produces a response.
*
* May call other collaborating code to generate the response.
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
if (count($this->middleware) === 0) {
return $this->emptyResponseMiddleware->process($request, $this);
}
$middleware = array_shift($this->middleware);
return $middleware->process($request, $this);
}
}
<?php declare(strict_types=1);
/**
* ForbiddenException
* @author edgebal
*/
namespace Minds\Core\Router\Exceptions;
use Exception;
class ForbiddenException extends Exception
{
}
<?php
/**
* UnauthorizedException
* @author edgebal
*/
namespace Minds\Core\Router\Exceptions;
use Exception;
class UnauthorizedException extends Exception
{
}
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Router\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class Manager
{
/** @var Middleware\RouterMiddleware[] */
protected $middleware;
/**
* Manager constructor.
* @param Middleware\RouterMiddleware[] $middleware
*/
public function __construct(
$middleware = null
) {
$this->middleware = $middleware ?: [
new Middleware\SEOMiddleware(),
new Middleware\ProMiddleware(), // this needs to always be the last element in this array
];
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return bool|null
*/
public function handle(ServerRequest &$request, JsonResponse &$response): ?bool
{
$result = null;
foreach ($this->middleware as $middleware) {
$result = $middleware->onRequest($request, $response);
if ($result === false) {
break;
}
}
return $result;
}
}
<?php declare(strict_types=1);
/**
* AdminMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\ForbiddenException;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Security\XSRF;
use Minds\Entities\User;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AdminMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_user';
/** @var callable */
private $xsrfValidateRequest;
public function __construct(
$xsrfValidateRequest = null
) {
$this->xsrfValidateRequest = $xsrfValidateRequest ?: [XSRF::class, 'validateRequest'];
}
/**
* @param string $attributeName
* @return AdminMiddleware
*/
public function setAttributeName(string $attributeName): AdminMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws ForbiddenException
* @throws UnauthorizedException
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (
!$request->getAttribute($this->attributeName) ||
!call_user_func($this->xsrfValidateRequest)
) {
throw new UnauthorizedException();
}
/** @var User $currentUser */
$currentUser = $request->getAttribute($this->attributeName);
if (!$currentUser->isAdmin()) {
throw new ForbiddenException();
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* ContentNegotiationMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ContentNegotiationMiddleware implements MiddlewareInterface
{
/** @var string[] */
const JSON_MIME_TYPES = ['application/json', 'text/json', 'application/x-json'];
/** @var string[] */
const HTML_MIME_TYPES = ['text/html', 'application/xhtml+xml'];
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$accept = array_map([$this, '_normalizeAcceptEntries'], explode(',', implode(',', $request->getHeader('Accept'))));
if (array_intersect($accept, static::JSON_MIME_TYPES)) {
$request = $request
->withAttribute('accept', 'json');
} elseif (array_intersect($accept, static::HTML_MIME_TYPES)) {
$request = $request
->withAttribute('accept', 'html');
}
return $handler
->handle($request);
}
/**
* @param $value
* @return mixed
*/
protected function _normalizeAcceptEntries(string $value): string
{
$fragments = explode(';', $value);
return $fragments[0];
}
}
<?php declare(strict_types=1);
/**
* CorsMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\EmptyResponse;
class CorsMiddleware implements MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->getMethod() === 'OPTIONS') {
return new EmptyResponse(204, [
'Access-Control-Allow-Origin' => $request->getHeaderLine('Origin'),
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Methods' => implode(',', [
'GET',
'POST',
'PUT',
'DELETE',
'OPTIONS',
]),
'Access-Control-Allow-Headers' => implode(',', [
'Accept',
'Authorization',
'Cache-Control',
'Content-Type',
'DNT',
'If-Modified-Since',
'Keep-Alive',
'Origin',
'User-Agent',
'X-Mx-ReqToken',
'X-Requested-With',
'X-No-Cache',
]),
]);
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* EmptyResponseMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
class EmptyResponseMiddleware implements MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$message = 'Endpoint Not Found';
$status = 404;
switch ($request->getAttribute('accept')) {
case 'html':
return new HtmlResponse(sprintf('<h1>%s</h1>', $message), $status);
case 'json':
default:
return new JsonResponse([
'status' => 'error',
'message' => $message,
], $status);
}
}
}
<?php declare(strict_types=1);
/**
* ErrorHandlerMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Exception;
use Minds\Core\Router\Exceptions\ForbiddenException;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use function Sentry\captureException;
class ErrorHandlerMiddleware implements MiddlewareInterface
{
/** @var bool */
protected $sentryEnabled = true;
/**
* @param bool $sentryEnabled
* @return ErrorHandlerMiddleware
*/
public function setSentryEnabled(bool $sentryEnabled): ErrorHandlerMiddleware
{
$this->sentryEnabled = $sentryEnabled;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$message = 'Internal Server Error';
$status = 500;
try {
return $handler
->handle($request);
} catch (UnauthorizedException $e) {
$message = 'Unauthorized';
$status = 401;
} catch (ForbiddenException $e) {
$message = 'Forbidden';
$status = 403;
} catch (Exception $e) {
// Log
// TODO: Monolog
error_log((string) $e);
// Sentry
if ($this->sentryEnabled) {
captureException($e);
}
}
switch ($request->getAttribute('accept')) {
case 'html':
return new HtmlResponse(sprintf('<h1>%s</h1>', $message), $status);
case 'json':
default:
return new JsonResponse([
'status' => 'error',
'message' => $message,
], $status);
}
}
}
<?php declare(strict_types=1);
/**
* FrameSecurityMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class FrameSecurityMiddleware implements MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
return $handler
->handle($request)
->withHeader('X-Frame-Options', 'DENY');
}
}
<?php declare(strict_types=1);
/**
* JsonPayloadMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class JsonPayloadMiddleware implements MiddlewareInterface
{
/** @var string[] */
const JSON_MIME_TYPES = ['application/json', 'text/json', 'application/x-json'];
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$contentType = $this->_normalizeContentTypeEntry($request->getHeader('Content-Type'));
if (in_array($contentType, static::JSON_MIME_TYPES, true)) {
$request = $request
->withParsedBody(json_decode($request->getBody(), true));
}
return $handler
->handle($request);
}
/**
* @param array $values
* @return mixed
*/
protected function _normalizeContentTypeEntry(array $values): string
{
$fragments = explode(';', $values[0] ?? '');
return $fragments[0];
}
}
<?php
/**
* OauthMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Session;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response;
class OauthMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_user';
/**
* @param string $attributeName
* @return OauthMiddleware
*/
public function setAttributeName(string $attributeName): OauthMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$request->getAttribute($this->attributeName)) {
Session::withRouterRequest($request, new Response());
return $handler->handle(
$request
->withAttribute($this->attributeName, Session::getLoggedinUser() ?: null)
);
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* RegistryEntryMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Di\Ref as DiRef;
use Minds\Core\Router\RegistryEntry;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RegistryEntryMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_router-registry-entry';
/**
* @param string $attributeName
* @return RegistryEntryMiddleware
*/
public function setAttributeName(string $attributeName): RegistryEntryMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws Exception
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var RegistryEntry $registryEntry */
$registryEntry = $request->getAttribute($this->attributeName);
if ($registryEntry) {
$binding = $registryEntry->getBinding();
$parameters = $registryEntry->extract($request->getUri()->getPath());
if ($binding instanceof DiRef) {
return call_user_func(
[
Di::_()->get($binding->getProvider()),
$binding->getMethod()
],
$request
->withAttribute('parameters', $parameters)
);
} elseif (is_callable($binding)) {
return call_user_func(
$binding,
$request
->withAttribute('parameters', $parameters)
);
} else {
throw new Exception("Invalid router binding");
}
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* RequestHandlerMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Exception;
use Minds\Core\Router\Dispatcher;
use Minds\Core\Router\RegistryEntry;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequestHandlerMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_request-handler';
/**
* @param string $attributeName
* @return RequestHandlerMiddleware
*/
public function setAttributeName(string $attributeName): RequestHandlerMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws Exception
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$requestHandler = $request->getAttribute($this->attributeName);
if ($requestHandler) {
if ($requestHandler instanceof RegistryEntry) {
// Setup sub-router
$dispatcher = new Dispatcher();
// Pipe route-specific middleware
foreach ($requestHandler->getMiddleware() as $middleware) {
if (is_string($middleware)) {
if (!class_exists($middleware)) {
throw new Exception("{$middleware} does not exist");
}
$middlewareInstance = new $middleware;
} else {
$middlewareInstance = $middleware;
}
if (!($middlewareInstance instanceof MiddlewareInterface)) {
throw new Exception("{$middleware} is not a middleware");
}
$dispatcher->pipe($middlewareInstance);
}
// Dispatch with middleware
return $dispatcher
->pipe(
(new RegistryEntryMiddleware())
->setAttributeName('_router-registry-entry')
)
->handle(
$request
->withAttribute('_router-registry-entry', $requestHandler)
);
} elseif (is_callable($requestHandler)) {
$response = call_user_func($requestHandler, $request);
if ($response) {
return $response;
}
}
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* RouteResolverMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Di\Di;
use Minds\Core\Router\PrePsr7;
use Minds\Core\Router\Registry;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RouteResolverMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_request-handler';
/** @var Registry */
protected $registry;
/**
* RouteResolverMiddleware constructor.
* @param Registry $registry
*/
public function __construct(
$registry = null
) {
$this->registry = $registry ?: Di::_()->get('Router\Registry');
}
/**
* @param string $attributeName
* @return RouteResolverMiddleware
*/
public function setAttributeName(string $attributeName): RouteResolverMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Module Router
$registryEntry = $this->registry->getBestMatch(
strtolower($request->getMethod()),
$request->getUri()->getPath()
);
if ($registryEntry) {
return $handler
->handle(
$request
->withAttribute($this->attributeName, $registryEntry)
)
->withHeader('X-Route-Resolver', 'router-registry');
}
// Pre PSR-7 Fallback Handlers
$prePsr7Fallback = new PrePsr7\Fallback();
// Pre PSR-7 Controllers
if ($prePsr7Fallback->shouldRoute($request->getUri()->getPath())) {
return $prePsr7Fallback
->handle($request)
->withHeader('X-Route-Resolver', 'pre-psr7');
}
// Static HTML
if ($request->getAttribute('accept') === 'html') {
return $prePsr7Fallback
->handleStatic($request)
->withHeader('X-Route-Resolver', 'pre-psr7-static');
}
// No route handler
return $handler
->handle($request);
}
}
<?php
/**
* SessionMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\Sessions\Manager;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SessionMiddleware implements MiddlewareInterface
{
/** @var Manager */
protected $session;
/** @var string */
protected $attributeName = '_user';
/**
* SessionMiddleware constructor.
* @param Manager $session
*/
public function __construct(
$session = null
) {
$this->session = $session ?: Di::_()->get('Sessions\Manager');
}
/**
* @param string $attributeName
* @return SessionMiddleware
*/
public function setAttributeName(string $attributeName): SessionMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$request->getAttribute($this->attributeName)) {
$this->session
->withRouterRequest($request);
return $handler->handle(
$request
->withAttribute($this->attributeName, Session::getLoggedinUser() ?: null)
);
}
return $handler
->handle($request);
}
}
<?php
/**
* XsrfCookieMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Security\XSRF;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class XsrfCookieMiddleware implements MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
XSRF::setCookie();
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* LoggedInMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Security\XSRF;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LoggedInMiddleware implements MiddlewareInterface
{
/** @var string */
protected $attributeName = '_user';
/** @var callable */
private $xsrfValidateRequest;
public function __construct(
$xsrfValidateRequest = null
) {
$this->xsrfValidateRequest = $xsrfValidateRequest ?: [XSRF::class, 'validateRequest'];
}
/**
* @param string $attributeName
* @return LoggedInMiddleware
*/
public function setAttributeName(string $attributeName): LoggedInMiddleware
{
$this->attributeName = $attributeName;
return $this;
}
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
* @throws UnauthorizedException
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (
!$request->getAttribute($this->attributeName) ||
!call_user_func($this->xsrfValidateRequest)
) {
throw new UnauthorizedException();
}
return $handler
->handle($request);
}
}
<?php declare(strict_types=1);
/**
* ModuleRoutes
* @author edgebal
*/
namespace Minds\Core\Router;
abstract class ModuleRoutes
{
/** @var Route */
protected $route;
/**
* ModuleRoutes constructor.
* @param Route $route
*/
public function __construct(
$route = null
) {
$this->route = $route ?: new Route();
}
/**
* Registers all module routes
*/
abstract public function register(): void;
}
<?php
/**
* Fallback
* @author edgebal
*/
namespace Minds\Core\Router\PrePsr7;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\I18n\I18n;
use Minds\Core\SEO\Defaults as SEODefaults;
use Psr\Http\Message\RequestInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Stream;
class Fallback
{
/** @var string[] */
const ALLOWED = [
'/api/v1/',
'/api/v2/',
'/emails/',
'/fs/v1',
'/oauth2/',
'/checkout',
'/deeplinks',
'/icon',
'/sitemap',
'/sitemaps',
'/thumbProxy',
'/archive',
'/wall',
'/not-supported',
'/apple-app-site-association',
];
/** @var Config */
protected $config;
/**
* Fallback constructor.
* @param Config $config
*/
public function __construct(
$config = null
) {
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param string $route
* @return bool
*/
public function shouldRoute(string $route): bool
{
$route = sprintf("/%s", ltrim($route, '/'));
$shouldFallback = false;
foreach (static::ALLOWED as $allowedRoute) {
if (stripos($route, $allowedRoute) === 0) {
$shouldFallback = true;
break;
}
}
return $shouldFallback;
}
/**
* @param RequestInterface $request
* @return Response
*/
public function handle(RequestInterface $request)
{
ob_clean();
ob_start();
(new Router())
->route($request->getUri()->getPath(), strtolower($request->getMethod()));
$response = ob_get_contents();
ob_end_clean();
$stream = fopen('php://memory', 'r+');
fwrite($stream, $response);
rewind($stream);
return new Response(new Stream($stream), http_response_code());
}
/**
* @param RequestInterface $request
* @return HtmlResponse
*/
public function handleStatic(RequestInterface $request)
{
ob_clean();
ob_start();
(new Router())
->route($request->getUri()->getPath(), strtolower($request->getMethod()));
$html = ob_get_contents();
ob_end_clean();
return new HtmlResponse($html, 200);
}
/**
* Complete routing fallback
*/
public function route()
{
(new Router())->route();
}
}
......@@ -4,7 +4,7 @@
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
namespace Minds\Core\Router\PrePsr7\Middleware;
use Exception;
use Minds\Core\EntitiesBuilder;
......
......@@ -4,7 +4,7 @@
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
namespace Minds\Core\Router\PrePsr7\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
......
<?php
namespace Minds\Core\Router\Middleware;
namespace Minds\Core\Router\PrePsr7\Middleware;
use Minds\Core\Config;
use Minds\Core\Di\Di;
......
<?php
namespace Minds\Core\Router\PrePsr7;
use Minds\Core\Di\Di;
use Minds\Core\I18n\I18n;
use Minds\Core\Router\PrePsr7\Middleware\ProMiddleware;
use Minds\Core\Router\PrePsr7\Middleware\RouterMiddleware;
use Minds\Core\Router\PrePsr7\Middleware\SEOMiddleware;
use Minds\Core\Session;
use Minds\Helpers;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequestFactory;
use Minds\Core\Security;
use Minds\Core\page;
/**
* Minds Core Router.
*/
class Router
{
// these are core pages, other pages are registered by plugins
public static $routes = [
'/archive/thumbnail' => 'Minds\\Controllers\\fs\\v1\\thumbnail',
'/api/v1/archive/thumbnails' => 'Minds\\Controllers\\api\\v1\\media\\thumbnails',
'/oauth2/token' => 'Minds\\Controllers\\oauth2\\token',
'/oauth2/implicit' => 'Minds\\Controllers\\oauth2\\implicit',
'/icon' => 'Minds\\Controllers\\icon',
'//icon' => 'Minds\\Controllers\\icon',
'/api' => 'Minds\\Controllers\\api\\api',
'/fs' => 'Minds\\Controllers\\fs\\fs',
'/thumbProxy' => 'Minds\\Controllers\\thumbProxy',
'/wall' => 'Minds\\Controllers\\Legacy\\wall',
'/not-supported' => "Minds\Controllers\\notSupported",
// "/app" => "minds\\pages\\app",
'/emails/unsubscribe' => 'Minds\\Controllers\\emails\\unsubscribe',
'/sitemap' => 'Minds\\Controllers\\sitemap',
'/apple-app-site-association' => '\\Minds\\Controllers\\deeplinks',
'/sitemaps' => '\\Minds\\Controllers\\sitemaps',
'/checkout' => '\\Minds\\Controllers\\checkout',
];
/**
* Route the pages
* (fallback to elgg page handler if we fail).
*
* @param string $uri
* @param string $method
*
* @return null|mixed
*/
public function route($uri = null, $method = null)
{
if ((!$uri) && (isset($_SERVER['REDIRECT_ORIG_URI']))) {
$uri = strtok($_SERVER['REDIRECT_ORIG_URI'], '?');
}
if (!$uri) {
$uri = strtok($_SERVER['REQUEST_URI'], '?');
}
$this->detectContentType();
header('X-Frame-Options: DENY');
$route = rtrim($uri, '/');
$segments = explode('/', $route);
$method = $method ? $method : strtolower($_SERVER['REQUEST_METHOD']);
if ($method == 'post') {
$this->postDataFix();
}
$request = ServerRequestFactory::fromGlobals();
$response = new JsonResponse([]);
/** @var RouterMiddleware[] $prePsr7Middleware */
$prePsr7Middleware = [
new ProMiddleware(),
new SEOMiddleware(),
];
foreach ($prePsr7Middleware as $middleware) {
$result = $middleware->onRequest($request, $response);
if ($result === false) {
return null;
}
}
if ($request->getMethod() === 'OPTIONS') {
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With,X-No-Cache');
return null;
}
// Sessions
// TODO: Support middleware
$session = Di::_()->get('Sessions\Manager');
$session->withRouterRequest($request);
// OAuth Middleware
// TODO: allow interface to bypass
// TODO: Support middleware
if (!Session::isLoggedIn()) { // Middleware will resolve this
Session::withRouterRequest($request, $response);
}
// XSRF Cookie - may be able to remove now with OAuth flow
Security\XSRF::setCookie();
if (Session::isLoggedin()) {
Helpers\Analytics::increment('active');
}
if (isset($_GET['__e_ct_guid']) && is_numeric($_GET['__e_ct_guid'])) {
Helpers\Analytics::increment('active', $_GET['__e_ct_guid']);
Helpers\Campaigns\EmailRewards::reward($_GET['campaign'], $_GET['__e_ct_guid']);
}
Di::_()->get('Email\RouterHooks')
->withRouterRequest($request);
Di::_()->get('Referrals\Cookie')
->withRouterRequest($request)
->create();
$loop = count($segments);
while ($loop >= 0) {
$offset = $loop - 1;
if ($loop < count($segments)) {
$slug_length = strlen($segments[$offset + 1].'/');
$route_length = strlen($route);
$route = substr($route, 0, $route_length - $slug_length);
}
if (isset(self::$routes[$route])) {
$handler = new self::$routes[$route]();
$pages = array_splice($segments, $loop) ?: [];
if (method_exists($handler, $method)) {
// Set the request
if (method_exists($handler, 'setRequest')) {
$handler->setRequest($request);
}
// Set the response
if (method_exists($handler, 'setResponse')) {
$handler->setResponse($response);
}
return $handler->$method($pages);
} else {
return null;
}
}
--$loop;
}
if (!$this->legacyRoute($uri)) {
(new I18n())->serveIndex();
}
return null;
}
/**
* Legacy Router fallback.
*
* @param string $uri
*
* @return bool
*/
public function legacyRoute($uri)
{
$path = explode('/', substr($uri, 1));
$handler = array_shift($path);
$page = implode('/', $path);
new page(false); //just to load init etc
return false;
}
/**
* Detects request content type and apply the corresponding polyfills.
*/
public function detectContentType()
{
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] == 'application/json') {
//\elgg_set_viewtype('json');
if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
$this->postDataFix();
}
}
}
/**
* Populates $_POST and $_REQUEST with request's JSON payload.
*/
public function postDataFix()
{
$postdata = file_get_contents('php://input');
$request = json_decode($postdata, true);
if ($request) {
foreach ($request as $k => $v) {
$_POST[$k] = $v;
$_REQUEST[$k] = $v;
}
}
}
/**
* Return vars for request
* @return array
*/
public static function getPutVars()
{
$postdata = file_get_contents('php://input');
$request = json_decode($postdata, true);
return $request;
}
/**
* Register routes.
*
* @param array $routes - an array of routes to handlers
*
* @return array - the array of all your routes
*/
public static function registerRoutes($routes = [])
{
return self::$routes = array_merge(self::$routes, $routes);
}
}
<?php declare(strict_types=1);
/**
* Registry
* @author edgebal
*/
namespace Minds\Core\Router;
class Registry
{
/** @var Registry */
protected static $instance;
/** @var array */
protected $registry = [];
/**
* @return Registry
*/
public static function _(): Registry
{
if (!static::$instance) {
static::$instance = new static();
}
return static::$instance;
}
/**
* @param string $method
* @param string $route
* @param mixed $binding
* @param string[] $middleware
* @return Registry
*/
public function register(string $method, string $route, $binding, array $middleware): Registry
{
$method = strtolower($method);
if (!isset($this->registry[$method])) {
$this->registry[$method] = [];
}
$registryEntry = new RegistryEntry();
$registryEntry
->setRoute($route)
->setBinding($binding)
->setMiddleware($middleware);
$this->registry[$method][] = $registryEntry;
return $this;
}
/**
* @param string $method
* @param string $route
* @return RegistryEntry|null
*/
public function getBestMatch(string $method, string $route):? RegistryEntry
{
if (!isset($this->registry[$method]) || !$this->registry[$method]) {
return null;
}
$route = trim($route, '/');
/** @var RegistryEntry[] $sortedRegistryEntries */
$sortedRegistryEntries = $this->registry[$method];
usort($sortedRegistryEntries, [$this, '_registryEntrySort']);
foreach ($sortedRegistryEntries as $registryEntry) {
if ($registryEntry->matches($route)) {
return $registryEntry;
}
}
return null;
}
/**
* @param RegistryEntry $a
* @param RegistryEntry $b
* @return int
*/
protected function _registryEntrySort(RegistryEntry $a, RegistryEntry $b): int
{
if ($a->getDepth() !== $b->getDepth()) {
return $b->getDepth() - $a->getDepth();
}
return $b->getSpecificity() - $a->getSpecificity();
}
}
<?php declare(strict_types=1);
/**
* RegistryEntry
* @author edgebal
*/
namespace Minds\Core\Router;
use Exception;
use Minds\Traits\MagicAttributes;
/**
* Class RegistryEntry
* @package Minds\Core\Router
* @method string getRoute()
* @method mixed getBinding()
* @method RegistryEntry setBinding(mixed $binding)
* @method string[] getMiddleware[]
* @method RegistryEntry setMiddleware(string[] $middleware)
*/
class RegistryEntry
{
use MagicAttributes;
/** @var string */
protected $route;
/** @var mixed */
protected $binding;
/** @var string[] */
protected $middleware;
/**
* @param string $route
* @return RegistryEntry
*/
public function setRoute(string $route): RegistryEntry
{
$this->route = trim($route, '/');
return $this;
}
/**
* @return string
*/
public function getWildcardRoute(): string
{
return preg_replace('#/:[^/]+#', '/*', $this->route);
}
/**
* @return int
*/
public function getDepth(): int
{
if (!$this->route) {
return -1;
}
return substr_count($this->route, '/');
}
/**
* @return int
*/
public function getSpecificity(): int
{
if (!$this->route) {
return 1;
}
$fragments = explode('/', $this->getWildcardRoute());
$count = count($fragments);
$specificity = 0;
for ($i = 0; $i < $count; $i++) {
if ($fragments[$i] !== '*') {
$specificity += 2 ** ($count - 1 - $i);
}
}
return $specificity;
}
/**
* @param string $route
* @return bool
*/
public function matches(string $route): bool
{
$route = trim($route, '/');
$pattern = sprintf("#^%s$#i", strtr(preg_quote($this->getWildcardRoute(), '#'), ['\*' => '[^/]+']));
return (bool) preg_match($pattern, $route);
}
/**
* @param string $route
* @return array
*/
public function extract(string $route): array
{
$route = trim($route, '/');
$pattern = sprintf(
'#^%s$#i',
preg_replace_callback('#/\\\:([^/]+)#', [$this, '_regexNamedCapture'], preg_quote($this->route, '#'))
);
$matches = [];
preg_match($pattern, $route, $matches);
$parameters = [];
foreach ($matches as $key => $value) {
if (is_numeric($key)) {
continue;
}
$parameters[$key] = $value;
}
return $parameters;
}
/**
* @param array $matches
* @return string
* @throws Exception
*/
protected function _regexNamedCapture(array $matches): string
{
$name = $matches[1] ?? '_';
if (is_numeric($name) || !ctype_alnum($name)) {
throw new Exception('Invalid route parameter name');
}
return sprintf('/(?<%s>[^/]+)', $name);
}
}
<?php declare(strict_types=1);
/**
* Route
* @author edgebal
*/
namespace Minds\Core\Router;
use Exception;
use Minds\Traits\MagicAttributes;
/**
* Class Route
* @package Minds\Core\Router
* @method string getPrefix()
* @method Route setPrefix(string $prefix)
* @method string[] getMiddleware()
* @method Route setMiddleware(string[] $prefix)
*/
class Route
{
use MagicAttributes;
/** @var string */
protected $prefix = '/';
/** @var string[] */
protected $middleware = [];
/** @var Registry */
protected $registry;
/** @var string[] */
const ALLOWED_METHODS = ['get','post','put','delete'];
/**
* Route constructor.
* @param Registry|null $registry
*/
public function __construct(
Registry $registry = null
) {
$this->registry = $registry ?: Registry::_();
}
/**
* @param string $prefix
* @return Route
*/
public function withPrefix(string $prefix): Route
{
$instance = clone($this);
$instance->setPrefix(sprintf("/%s/%s", trim($instance->getPrefix(), '/'), trim($prefix, '/')));
return $instance;
}
/**
* @param string[] $middleware
* @return Route
*/
public function withMiddleware(array $middleware): Route
{
$instance = clone($this);
$instance->setMiddleware(array_merge($instance->getMiddleware(), $middleware));
return $instance;
}
/**
* @param callable $fn
* @return Route
*/
public function do(callable $fn): Route
{
call_user_func($fn, $this);
return $this;
}
/**
* @param string[] $methods
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function register(array $methods, string $route, $binding): bool
{
if (array_diff($methods, static::ALLOWED_METHODS)) {
throw new Exception('Invalid method');
}
$route = sprintf("/%s/%s", trim($this->getPrefix(), '/'), trim($route, '/'));
$route = trim($route, '/');
foreach ($methods as $method) {
$this->registry->register($method, $route, $binding, $this->middleware);
}
return true;
}
/**
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function all(string $route, $binding): bool
{
return $this->register(static::ALLOWED_METHODS, $route, $binding);
}
/**
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function get(string $route, $binding): bool
{
return $this->register(['get'], $route, $binding);
}
/**
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function post(string $route, $binding): bool
{
return $this->register(['post'], $route, $binding);
}
/**
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function put(string $route, $binding): bool
{
return $this->register(['put'], $route, $binding);
}
/**
* @param string $route
* @param $binding
* @return bool
* @throws Exception
*/
public function delete(string $route, $binding): bool
{
return $this->register(['delete'], $route, $binding);
}
}
......@@ -12,8 +12,12 @@ class RouterProvider extends Provider
{
public function register()
{
$this->di->bind('Router\Manager', function ($di) {
return new Manager();
}, [ 'useFactory' => true ]);
$this->di->bind('Router', function ($di) {
return new Dispatcher();
}, ['useFactory' => true]);
$this->di->bind('Router\Registry', function ($di) {
return Registry::_();
}, ['useFactory' => true]);
}
}
......@@ -294,5 +294,7 @@ class ProhibitedDomains
'/lineupnow.com',
'/amusecandy.com',
'/360mate.com',
'rebrand.ly',
'fiverr.com', // temporarily until better defence is built
];
}
<?php
namespace Spec\Minds\Core\Router;
use Minds\Core\Router\Dispatcher;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
class DispatcherSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(Dispatcher::class);
}
public function it_should_pipe(
MiddlewareInterface $middleware
) {
$this
->pipe($middleware)
->shouldReturn($this);
}
public function it_should_handle(
MiddlewareInterface $middleware1,
MiddlewareInterface $middleware2,
ServerRequestInterface $request,
ResponseInterface $response
) {
$middleware1->process($request, $this)
->shouldBeCalled()
->willReturn($response);
$middleware2->process(Argument::cetera())
->shouldNotBeCalled();
$this
->pipe($middleware1)
->pipe($middleware2)
->handle($request)
->shouldReturn($response);
}
public function it_should_handle_an_empty_stack(
MiddlewareInterface $fallbackMiddleware,
ServerRequestInterface $request,
ResponseInterface $response
) {
$this->beConstructedWith($fallbackMiddleware);
$fallbackMiddleware->process($request, $this)
->shouldBeCalled()
->willReturn($response);
$this
->handle($request)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\ForbiddenException;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Router\Middleware\AdminMiddleware;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AdminMiddlewareSpec extends ObjectBehavior
{
public function let()
{
$xsrfValidateRequest = function () {
/** XSRF::validateRequest() */
return true;
};
$this->beConstructedWith($xsrfValidateRequest);
}
public function it_is_initializable()
{
$this->shouldHaveType(AdminMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$user->isAdmin()
->shouldBeCalled()
->willReturn(true);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_user')
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_throw_unauthorized_if_no_user_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn(null);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_unauthorized_if_xsrf_check_fail_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$this->beConstructedWith(function () {
/** XSRF::validateRequest() */
return false;
});
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_forbidden_if_not_an_admin_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$user->isAdmin()
->shouldBeCalled()
->willReturn(false);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(ForbiddenException::class)
->duringProcess($request, $handler);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\ContentNegotiationMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ContentNegotiationMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(ContentNegotiationMiddleware::class);
}
public function it_should_process_and_mark_as_json(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['text/json; utf=8,application/json']);
$request
->withAttribute('accept', 'json')
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_process_and_mark_as_html(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['text/html; utf=8', 'application/xhtml+xml']);
$request
->withAttribute('accept', 'html')
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_process_and_leave_unmarked(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['image/png']);
$request
->withAttribute('accept', Argument::cetera())
->shouldNotBeCalled();
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\CorsMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\EmptyResponse;
class CorsMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(CorsMiddleware::class);
}
public function it_should_process_and_return_empty_response_if_options(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getMethod()
->shouldBeCalled()
->willReturn('OPTIONS');
$request->getHeaderLine('Origin')
->shouldBeCalled()
->willReturn('https://phpspec.test');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(EmptyResponse::class);
}
public function it_should_process_and_passthru_if_not_options(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getMethod()
->shouldBeCalled()
->willReturn('GET');
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\EmptyResponseMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
class EmptyResponseMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(EmptyResponseMiddleware::class);
}
public function it_should_process_and_return_an_html_response(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('html');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(HtmlResponse::class);
}
public function it_should_process_and_return_a_json_response(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('json');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(JsonResponse::class);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Exception;
use Minds\Core\Router\Middleware\Kernel\ErrorHandlerMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
class ErrorHandlerMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(ErrorHandlerMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_catch_during_process_and_output_html(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$handler->handle($request)
->shouldBeCalled()
->willThrow(new Exception('PHPSpec'));
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('html');
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldBeAnInstanceOf(HtmlResponse::class);
}
public function it_should_catch_during_process_and_output_json(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$handler->handle($request)
->shouldBeCalled()
->willThrow(new Exception('PHPSpec'));
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('json');
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldBeAnInstanceOf(JsonResponse::class);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\FrameSecurityMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class FrameSecurityMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(FrameSecurityMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$response->withHeader('X-Frame-Options', 'DENY')
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\JsonPayloadMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class JsonPayloadMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(JsonPayloadMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Content-Type')
->shouldBeCalled()
->willReturn(['text/json']);
$request->getBody()
->shouldBeCalled()
->willReturn(json_encode(['phpspec' => 1]));
$request->withParsedBody(['phpspec' => 1])
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\OauthMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class OauthMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(OauthMiddleware::class);
}
/**
* Untestable due to the use of Session as static class
*/
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Di\Di;
use Minds\Core\Di\Ref as DiRef;
use Minds\Core\Router\Middleware\Kernel\RegistryEntryMiddleware;
use Minds\Core\Router\RegistryEntry;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RegistryEntryMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(RegistryEntryMiddleware::class);
}
public function it_should_process_and_passthru_if_no_entry(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$request->getAttribute('_phpspec_router-registry-entry')
->shouldBeCalled()
->willReturn(null);
$this
->setAttributeName('_phpspec_router-registry-entry')
->process($request, $handler);
}
public function it_should_process_di_bindings(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
RegistryEntry $registryEntry,
DiRef $diRef,
UriInterface $uri
) {
$request->getAttribute('_phpspec_router-registry-entry')
->shouldBeCalled()
->willReturn($registryEntry);
$registryEntry->getBinding()
->shouldBeCalled()
->willReturn($diRef);
$request->getUri()
->shouldBeCalled()
->willReturn($uri);
$uri->getPath()
->shouldBeCalled()
->willReturn('/phpspec/1000/edit');
$registryEntry->extract('/phpspec/1000/edit')
->shouldBeCalled()
->willReturn(['id' => '1000']);
$providerId = static::class . 'Provider';
$diRef->getProvider()
->shouldBeCalled()
->willReturn($providerId);
$diRef->getMethod()
->shouldBeCalled()
->willReturn('test');
Di::_()->bind($providerId, function () use ($response) {
return (new class {
protected $response;
public function setResponse($response)
{
$this->response = $response;
return $this;
}
public function test()
{
return $this->response;
}
})->setResponse($response->getWrappedObject());
});
$request->withAttribute('parameters', ['id' => '1000'])
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_router-registry-entry')
->process($request, $handler)
->shouldReturn($response);
Di::_()->bind($providerId, function () {
return false; // Release closure bindings
});
}
public function it_should_process_callable(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
RegistryEntry $registryEntry,
UriInterface $uri
) {
$request->getAttribute('_phpspec_router-registry-entry')
->shouldBeCalled()
->willReturn($registryEntry);
$binding = function () use ($response) {
return $response->getWrappedObject();
};
$registryEntry->getBinding()
->shouldBeCalled()
->willReturn($binding);
$request->getUri()
->shouldBeCalled()
->willReturn($uri);
$uri->getPath()
->shouldBeCalled()
->willReturn('/phpspec/1000/edit');
$registryEntry->extract('/phpspec/1000/edit')
->shouldBeCalled()
->willReturn(['id' => '1000']);
$request->withAttribute('parameters', ['id' => '1000'])
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_router-registry-entry')
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Dispatcher;
use Minds\Core\Router\Middleware\Kernel\RequestHandlerMiddleware;
use Minds\Core\Router\RegistryEntry;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequestHandlerMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(RequestHandlerMiddleware::class);
}
public function it_should_process_and_passthru_if_no_handler(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getAttribute('_phpspec_request-handler')
->shouldBeCalled()
->willReturn(null);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_request-handler')
->process($request, $handler);
}
public function it_should_process_and_dispatch_using_registry_entry(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
RegistryEntry $registryEntry,
MiddlewareInterface $middleware
) {
$request->getAttribute('_phpspec_request-handler')
->shouldBeCalled()
->willReturn($registryEntry);
$registryEntry->getMiddleware()
->shouldBeCalled()
->willReturn([$middleware]);
$request->withAttribute('_router-registry-entry', $registryEntry)
->shouldBeCalled()
->willReturn($request);
$middleware->process($request, Argument::type(Dispatcher::class))
->shouldBeCalled()
->willReturn($response);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_request-handler')
->process($request, $handler);
}
public function it_should_process_and_dispatch_using_closure(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
RegistryEntry $registryEntry,
MiddlewareInterface $middleware
) {
$closure = function () use ($response) {
return $response->getWrappedObject();
};
$request->getAttribute('_phpspec_request-handler')
->shouldBeCalled()
->willReturn($closure);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_request-handler')
->process($request, $handler);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\RouteResolverMiddleware;
use Minds\Core\Router\Registry;
use Minds\Core\Router\RegistryEntry;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RouteResolverMiddlewareSpec extends ObjectBehavior
{
/** @var Registry */
protected $registry;
public function let(
Registry $registry
) {
$this->registry = $registry;
$this->beConstructedWith($registry);
}
public function it_is_initializable()
{
$this->shouldHaveType(RouteResolverMiddleware::class);
}
public function it_should_process_using_registry_entry(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
RegistryEntry $registryEntry,
UriInterface $uri
) {
$request->getMethod()
->shouldBeCalled()
->willReturn('GET');
$request->getUri()
->shouldBeCalled()
->willReturn($uri);
$uri->getPath()
->shouldBeCalled()
->willReturn('/phpspec/test');
$this->registry->getBestMatch('get', '/phpspec/test')
->shouldBeCalled()
->willReturn($registryEntry);
$request->withAttribute('_phpspec_request-handler', $registryEntry)
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$response->withHeader('X-Route-Resolver', 'router-registry')
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_request-handler')
->process($request, $handler)
->shouldReturn($response);
}
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\SessionMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SessionMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(SessionMiddleware::class);
}
/**
* Untestable due to the use of Session as static class
*/
}
<?php
namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\XsrfCookieMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class XsrfCookieMiddlewareSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(XsrfCookieMiddleware::class);
}
/**
* Untestable due to the use of XSRF as static class
*/
}
<?php
namespace Spec\Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Router\Middleware\LoggedInMiddleware;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LoggedInMiddlewareSpec extends ObjectBehavior
{
public function let()
{
$xsrfValidateRequest = function () {
/** XSRF::validateRequest() */
return true;
};
$this->beConstructedWith($xsrfValidateRequest);
}
public function it_is_initializable()
{
$this->shouldHaveType(LoggedInMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_user')
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_throw_unauthorized_if_no_user_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn(null);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_unauthorized_if_xsrf_check_fail_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$this->beConstructedWith(function () {
/** XSRF::validateRequest() */
return false;
});
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
}
<?php
namespace Spec\Minds\Core\Router\PrePsr7;
use Minds\Core\Config;
use Minds\Core\Router\PrePsr7\Fallback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FallbackSpec extends ObjectBehavior
{
/** @var Config */
protected $config;
public function let(
Config $config
) {
$this->config = $config;
$this->beConstructedWith($config);
}
public function it_is_initializable()
{
$this->shouldHaveType(Fallback::class);
}
public function it_should_check_if_should_route()
{
$this
->callOnWrappedObject('shouldRoute', [
Fallback::ALLOWED[0]
])
->shouldReturn(true);
$this
->callOnWrappedObject('shouldRoute', [
Fallback::ALLOWED[count(Fallback::ALLOWED) - 1]
])
->shouldReturn(true);
$this
->callOnWrappedObject('shouldRoute', [
'~**INVALID FALLBACK ROUTE**~?'
])
->shouldReturn(false);
}
/**
* The rest of this class is untestable due to the need of
* instantiating PrePsr7\Router class on-the-fly.
*
* That's all, folks!
*/
}
<?php
namespace Spec\Minds\Core\Router;
use Minds\Core\Router\RegistryEntry;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RegistryEntrySpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(RegistryEntry::class);
}
public function it_should_set_route()
{
$this
->setRoute('/phpspec/:id/edit/')
->getRoute()
->shouldReturn('phpspec/:id/edit');
}
public function it_should_get_wildcard_route()
{
$this
->setRoute('/phpspec/:id/edit/')
->getWildcardRoute()
->shouldReturn('phpspec/*/edit');
}
public function it_should_get_depth()
{
$this
->setRoute('/')
->getDepth()
->shouldReturn(-1);
$this
->setRoute('/phpspec')
->getDepth()
->shouldReturn(0);
$this
->setRoute('/phpspec/:id/edit/')
->getDepth()
->shouldReturn(2);
}
public function it_should_get_specificity()
{
$this
->setRoute('/')
->getSpecificity()
->shouldReturn(1);
$this
->setRoute('/phpspec')
->getSpecificity()
->shouldReturn(1);
$this
->setRoute('/phpspec/:id/edit/')
->getSpecificity()
->shouldReturn(5);
$this
->setRoute('/phpspec/random/edit')
->getSpecificity()
->shouldReturn(7);
}
public function it_should_match()
{
$this
->setRoute('/')
->matches('/')
->shouldReturn(true);
$this
->setRoute('/')
->matches('/test')
->shouldReturn(false);
$this
->setRoute('/phpspec/:id')
->matches('/phpspec/1000')
->shouldReturn(true);
$this
->setRoute('/phpspec/:id')
->matches('/phpspec')
->shouldReturn(false);
$this
->setRoute('/phpspec/:id')
->matches('/phpspec/9999/1000')
->shouldReturn(false);
$this
->setRoute('/phpspec/:id/edit')
->matches('/phpspec/1000/edit')
->shouldReturn(true);
$this
->setRoute('/phpspec/:id/edit')
->matches('/phpspec/1000')
->shouldReturn(false);
$this
->setRoute('/phpspec/:id/edit')
->matches('/phpspec/9999/1000')
->shouldReturn(false);
}
public function it_should_extract()
{
$this
->setRoute('/phpspec/:id/edit')
->extract('/phpspec/9999/edit')
->shouldReturn(['id' => '9999']);
$this
->setRoute('/phpspec/:id/edit/:timestamp')
->extract('/phpspec/9999/edit/1000000')
->shouldReturn(['id' => '9999', 'timestamp' => '1000000']);
}
}
<?php
namespace Spec\Minds\Core\Router;
use Minds\Core\Router\Registry;
use Minds\Core\Router\RegistryEntry;
use PhpSpec\Exception\Example\FailureException;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Server\MiddlewareInterface;
class RegistrySpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(Registry::class);
}
public function it_should_register(
MiddlewareInterface $middleware
) {
$this
->register('post', '/phpspec', function () {
}, [$middleware])
->shouldReturn($this);
}
public function it_should_get_best_match(
MiddlewareInterface $middleware
) {
$this
->register('get', '/phpspec/:id', null, [$middleware])
->register('get', '/phpspec/new', null, [$middleware])
->register('get', '/phpspec/', null, [$middleware])
->getBestMatch('get', '/phpspec/new')
->shouldBeARegistryEntryWithRoute('phpspec/new');
}
public function getMatchers(): array
{
return [
'beARegistryEntryWithRoute' => function ($subject, $route) {
if (!($subject instanceof RegistryEntry)) {
throw new FailureException(sprintf("%s is a RegistryEntry instance", get_class($subject)));
}
if ($subject->getRoute() !== $route) {
throw new FailureException(sprintf("[RegistryEntry:%s] routes to %s", $subject->getRoute(), $route));
}
return true;
}
];
}
}
<?php
namespace Spec\Minds\Core\Router;
use Closure;
use Exception;
use Minds\Core\Di\Ref;
use Minds\Core\Router\Registry;
use Minds\Core\Router\Route;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Server\MiddlewareInterface;
class RouteSpec extends ObjectBehavior
{
/** @var Registry */
protected $registry;
public function let(
Registry $registry
) {
$this->registry = $registry;
$this->beConstructedWith($registry);
}
public function it_is_initializable()
{
$this->shouldHaveType(Route::class);
}
public function it_should_register(
Ref $ref
) {
$method = Route::ALLOWED_METHODS[0];
$this->registry->register($method, 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register([$method], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_with_prefix_and_middleware(
Ref $ref,
MiddlewareInterface $middleware
) {
$method = Route::ALLOWED_METHODS[0];
$this->registry->register($method, 'prefix/0/phpspec/test/2', $ref, [
$middleware
])
->shouldBeCalled();
$this
->withPrefix('/prefix/0/')
->withMiddleware([$middleware])
->register([$method], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_throw_if_no_known_method_during_register(
Ref $ref
) {
$method = '~**INVALID HTTP METHOD**~';
$this->registry->register($method, Argument::cetera())
->shouldNotBeCalled();
$this
->shouldThrow(new Exception('Invalid method'))
->duringRegister([$method], '/phpspec/test/2/', $ref);
}
public function it_should_register_all(
Ref $ref
) {
$this->registry->register(Argument::type('string'), 'phpspec/test/2', $ref, [])
->shouldBeCalledTimes(count(Route::ALLOWED_METHODS));
$this
->all('/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_get(
Ref $ref
) {
$this->registry->register('get', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['get'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_post(
Ref $ref
) {
$this->registry->register('post', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['post'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_put(
Ref $ref
) {
$this->registry->register('put', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['put'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_delete(
Ref $ref
) {
$this->registry->register('delete', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['delete'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
}
<?php
namespace Spec\Minds\Core;
use Minds\Core\Features\Manager as Features;
use Minds\Core\Router;
use Minds\Core\Router\Dispatcher;
use Minds\Core\Router\PrePsr7\Fallback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RouterSpec extends ObjectBehavior
{
/** @var Dispatcher */
protected $dispatcher;
/** @var Features */
protected $features;
/** @var Fallback */
protected $fallback;
public function let(
Dispatcher $dispatcher,
Features $features,
Fallback $fallback
) {
$this->dispatcher = $dispatcher;
$this->features = $features;
$this->fallback = $fallback;
$this->beConstructedWith($dispatcher, $features, $fallback);
}
public function it_is_initializable()
{
$this->shouldHaveType(Router::class);
}
}
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b15c56ced8933ff505e81b7fa7b1eac0",
"content-hash": "6c23473ac0e2036a05166b78c51a6080",
"packages": [
{
"name": "Minds/Surge",
......@@ -3117,6 +3117,112 @@
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/http-server-handler",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
"reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
"reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
"keywords": [
"handler",
"http",
"http-interop",
"psr",
"psr-15",
"psr-7",
"request",
"response",
"server"
],
"time": "2018-10-30T16:46:14+00:00"
},
{
"name": "psr/http-server-middleware",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-middleware.git",
"reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
"reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0",
"psr/http-server-handler": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side middleware",
"keywords": [
"http",
"http-interop",
"middleware",
"psr",
"psr-15",
"psr-7",
"request",
"response"
],
"time": "2018-10-30T17:12:04+00:00"
},
{
"name": "psr/log",
"version": "1.1.1",
......
......@@ -472,6 +472,7 @@ $CONFIG->set('max_video_length', 900);
$CONFIG->set('max_video_length_plus', 1860);
$CONFIG->set('features', [
'psr7-router' => true,
'es-feeds' => false,
'helpdesk' => true,
'top-feeds' => true,
......