File List
<?php
/**
* Contains the definition of the Dispatch class
*
* @author Lindsay Marshall <lindsay.marshall@ncl.ac.uk>
* @copyright 2017-2021 Newcastle University
* @package Framework\Framework
*/
namespace Framework;
use \Config\Config;
use \Config\Framework as FW;
use \Framework\Exception\BadValue;
use \Framework\Web\StatusCodes;
use \Support\Context;
/**
* This class dispatches pages to the appropriate places
*
* @todo use an enum for constants when 8.1 arrives
*/
class Dispatch
{
/*
* Indicates that there is an Object that handles the call
*/
public const OBJECT = 1;
/*
* Indicates that there is only a template for this URL.
*/
public const TEMPLATE = 2;
/*
* Indicates that the URL should be temporarily redirected - 302
*/
public const REDIRECT = 3;
/*
* Indicates that the URL should be permanent redirected - 301
*/
public const REHOME = 4;
/*
* Indicates that the URL should be permanently redirected - 302
*/
public const XREDIRECT = 5;
/*
* Indicates that the URL should be temporarily redirected -301
*/
public const XREHOME = 6;
/*
* Indicates that the URL should be temporarily redirected - 303
*/
public const REDIRECT3 = 7;
/*
* Indicates that the URL should be temporarily redirected - 303
*/
public const XREDIRECT3 = 8;
/*
* Indicates that the URL should be temporarily redirected - 307
*/
public const REDIRECT7 = 9;
/*
* Indicates that the URL should be temporarily redirected - 307
*/
public const XREDIRECT7 = 10;
/*
* Indicates that the URL should be permanently redirected - 308
*/
public const REHOME8 = 11;
/*
* Indicates that the URL should be permanently redirected - 308
*/
public const XREHOME8 = 12;
/**
* @var array<array> Values for determining handling of above codes
*/
private static array $actions = [
self::REDIRECT => [TRUE, [TRUE, '', FALSE, FALSE]],
self::REHOME => [TRUE, [FALSE, '', FALSE, FALSE]],
self::XREDIRECT => [FALSE, [TRUE, '', FALSE, FALSE]],
self::XREHOME => [FALSE, [FALSE, '', FALSE, FALSE]],
self::REDIRECT3 => [TRUE, [TRUE, '', FALSE, TRUE]],
self::XREDIRECT3 => [FALSE, [TRUE, '', FALSE, TRUE]],
self::REDIRECT7 => [TRUE, [TRUE, '', TRUE, FALSE]],
self::XREDIRECT7 => [FALSE, [TRUE, '', TRUE, FALSE]],
self::REHOME8 => [TRUE, [FALSE, '', TRUE, FALSE]],
self::XREHOME8 => [FALSE, [FALSE, '', TRUE, FALSE]],
];
/**
* @var array<string>
*/
private static array $checks = [
self::OBJECT => 'checkObject',
self::TEMPLATE => 'checkTemplate',
self::REDIRECT => 'checkRedirect',
self::REDIRECT3 => 'checkRedirect',
self::REDIRECT7 => 'checkRedirect',
self::REHOME => 'checkRedirect',
self::REHOME8 => 'checkRedirect',
self::XREDIRECT => 'checkXRedirect',
self::XREDIRECT3 => 'checkXRedirect',
self::XREDIRECT7 => 'checkXRedirect',
self::XREHOME => 'checkXRedirect',
self::XREHOME8 => 'checkXRedirect',
];
/**
* @var array<string> Constants that might be defined in the configuration that
* need to be passed into templates.
*/
private static array $configs = ['lang', 'keywords', 'description'];
/**
* Setup basic values for templates
*/
public static function basicSetup(Context $context, string $action) : void
{
$basicvals = [
'context' => $context,
'action' => $action,
'siteinfo' => \Support\SiteInfo::getinstance(), // make sure we get the derived version not the Framework version
'ajax' => FALSE, // Mark pages as not using AJAX by default
'security' => \Framework\Support\Security::getinstance(),
'usejquery' => TRUE,
'usebootstrapcss' => TRUE,
'usebootstrapjs' => TRUE,
'usebootbox' => TRUE,
'usevue' => FALSE,
];
foreach (self::$configs as $cf)
{
try
{
$constant_reflex = new \ReflectionClassConstant('\\Config\\Config', strtoupper($cf));
$basicvals[$cf] = $constant_reflex->getValue();
}
catch (\ReflectionException)
{
NULL; // void
}
}
$context->local()->addval($basicvals, '', TRUE);
$context->web()->initCSP(); // prepare the CSP values
\Framework\Support\Security::getInstance()->sslCheck($context);
}
/**
* Handle dispatch of a page.
*
* @psalm-suppress PossiblyUndefinedMethod
*/
public static function handle(Context $context, string $action) : void
{
$local = $context->local();
$mime = \Framework\Web\Web::HTMLMIME;
/*
* Look in the database for what to do based on the first part of the URL. DBRX means do a regexp match (not yet implemented)
*/
try
{
/**
* @psalm-suppress TypeDoesNotContainType
* @psalm-suppress RedundantCondition
* @phan-suppress-next-line PhanUndeclaredClassConstant
*/
$page = \R::findOne(FW::PAGE, 'name'.(Config::DBRX ? ' regexp ' : '=').'? and active=?', [$action, 1]);
}
catch (\Throwable)
{ // You catch DB errors from pages that are not active or don't explicitly exist.
$page = NULL;
}
if (!is_object($page))
{ // No such page or it is marked as inactive so pass it to NoPage to handle it
$page = new \stdClass();
$page->kind = self::OBJECT;
$page->source = '\Pages\NoPage';
}
else
{
$page->check($context);
}
self::basicSetup($context, $action);
$code = StatusCodes::HTTP_OK;
switch ($page->kind)
{
case self::OBJECT: // fire up the object to handle the request
$pageObj = new $page->source();
$csp = $pageObj;
try
{
$pageObj->ifmodcheck($context); // check for any If- headers
\Support\Setup::preliminary($context, $page); // any user setup code
$tpl = $pageObj->handle($context);
$pageObj->setCache($context); // set up cache-control headers.
}
catch(\Framework\Exception\Forbidden $e)
{
$context->web()->noaccess($e->getMessage());
/* NOT REACHED */
}
catch(BadValue |
\Framework\Exception\BadOperation |
\Framework\Exception\MissingBean |
\Framework\Exception\ParameterCount $e)
{
$context->web()->bad($e->getMessage());
/* NOT REACHED */
}
if (\is_array($tpl)) // @phan-suppress-current-line PhanPossiblyUndeclaredVariable
{ // page is returning more than just a template filename
[$tpl, $mime, $code] = $tpl;
}
break;
case self::TEMPLATE: // render a template
\Support\Setup::preliminary($context, $page); // any user setup code
$csp = $context->web();
$tpl = $page->source;
break;
default:
if (!isset(self::$actions[$page->kind]))
{ // check value is OK
$context->web()->internal('Bad page kind');
/* NOT REACHED */
}
if (self::$actions[$page->kind][0])
{ // local diversion
$context->divert($page->source, ...self::$actions[$page->kind][1]);
/* NOT REACHED */
}
$context->web()->relocate($page->source, ...self::$actions[$page->kind][1]); // off site relocation
/* NOT REACHED */
}
/** @psalm-suppress PossiblyUndefinedVariable - if we get here it is defined */
if ($tpl !== '') // @phan-suppress-current-line PhanPossiblyUndeclaredVariable
{ // an empty template string means generate no output here...
$html = $local->getrender($tpl); // @phan-suppress-current-line PhanPossiblyUndeclaredVariable
// Now set up CSP Header in use : rendering the page may have generated new hashcodes.
/** @psalm-suppress PossiblyUndefinedVariable - if we get here it is defined */
$csp->setCSP(); // @phan-suppress-current-line PhanPossiblyUndeclaredVariable
$context->web()->sendstring($html, $mime, $code);
}
//else if ($code != StatusCodes::HTTP_OK);
//{
// header(StatusCodes::httpHeaderFor($code));
//}
}
/**
* Check OBJECT
*
* @param string $source This should be a class name possibly namespaced
*
* @throws BadValue
* @psalm-suppress UnusedMethod
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private static function checkObject(string $source) : void
{
if (!\preg_match('/^(\\\\?[a-z][a-z0-9]*)+$/i', $source))
{
throw new BadValue('Invalid source for page type (class name) "'.$source.'"');
}
}
/**
* Check TEMPLATE
*
* @param string $source This should be a twig file name, possibly namespaced
*
* @throws BadValue
* @psalm-suppress UnusedMethod
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private static function checkTemplate(string $source) : void
{
if (!\preg_match('#^@?(\w+/)?\w+\.twig$#i', $source))
{
throw new BadValue('Invalid source for page type (twig) "'.$source.'"');
}
}
/**
* Check REDIRECT - internal so no http
*
* @param string $source This should be a local url path
*
* @throws BadValue
* @psalm-suppress UnusedMethod
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private static function checkRedirect(string $source) : void
{
if (!\preg_match('#^(/.*?)+#i', $source))
{
throw new BadValue('Invalid source for page type (local url path)');
}
}
/**
* Check XREDIRECT - external so must be a url
*
* @param string $source This should be a URL
*
* @throws BadValue
* @psalm-suppress UnusedMethod
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private static function checkXRedirect(string $source) : void
{
if (\filter_var($source, \FILTER_VALIDATE_URL) === FALSE)
{
throw new BadValue('Invalid source for page type (URL)');
}
}
/**
* Check if a value is appropriate for the dispatch kind
*
* @throws BadValue
*/
public static function check(int $kind, string $source) : void
{
if (!isset(self::$checks[$kind]))
{
throw new BadValue('Invalid page type');
}
self::{self::$checks[$kind]}($source);
}
}
?>