diff --git a/public/index.php b/public/index.php index 8a43411f..f11fdb5d 100644 --- a/public/index.php +++ b/public/index.php @@ -3,7 +3,9 @@ ini_set('display_errors', '0'); ini_set('display_startup_errors', '0'); -require __DIR__ . '/../vendor/autoload.php'; +$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); + +require $projectRoot . '/vendor/autoload.php'; /** * Rergardless of what happens next, we'll need a basline markown converter. @@ -15,34 +17,12 @@ ->smartPunctuation(); // Inject environment variables to global $_SERVER array -Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); +Dotenv\Dotenv::createImmutable($projectRoot)->load(); -$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +$server = JoshBruce\Site\Server::init($_SERVER); -/** - * Verifying setup is valid. - */ -$requestRequiredServerGlobals = [ - 'APP_ENV', - 'CONTENT_UP', - 'CONTENT_FOLDER', - 'REQUEST_SCHEME', - 'HTTP_HOST', - 'REQUEST_URI' -]; - -// TESTING -// unset($_SERVER['APP_ENV']); -$requestHasRequiredServerGlobals = true; -foreach ($requestRequiredServerGlobals as $key) { - if (! array_key_exists($key, $_SERVER)) { - $requestHasRequiredServerGlobals = false; - break; - } -} - -if (! $requestHasRequiredServerGlobals) { - $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') +if ($server->isMissingRequiredValues()) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') ->for('/500.md'); JoshBruce\Site\Emitter::emitWithResponse( @@ -62,26 +42,36 @@ exit; } -if ($_SERVER['APP_ENV'] !== 'production') { - $erroHandler = new Whoops\Run; - $erroHandler->pushHandler(new Whoops\Handler\PrettyPageHandler); - $erroHandler->register(); -} +if ($server->isUsingUnsupportedMethod()) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') + ->for('/405.md'); -/** - * Verifying specified content area exists. - */ + JoshBruce\Site\Emitter::emitWithResponse( + 405, + [ + 'Cache-Control' => [ + 'no-cache', + 'must-revalidate' + ], + 'Allow' => $server->supportedMethods() + ], + Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + $markdownConverter->convert($content->markdown()) + )->build() + ); + exit; +} -// TESTING -// $_SERVER['CONTENT_FOLDER'] = '/does/not/exist'; $content = JoshBruce\Site\Content::init( $projectRoot, - $_SERVER['CONTENT_UP'], - $_SERVER['CONTENT_FOLDER'] + $server->contentUp(), + $server->contentFolder() ); -if (! $content->folderDoesExist()) { - $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') +if ($content->folderIsMissing()) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/setup-errors') ->for('/502.md'); JoshBruce\Site\Emitter::emitWithResponse( @@ -101,50 +91,14 @@ exit; } -/** - * Bootsrap is complete: local response time 19ms - * - * Does the requested content exist? - */ -$requestUri = $_SERVER['REQUEST_URI']; -if ($requestUri === '/') { - $requestUri = ''; -} - -// TESTING -// $requestUri = '/does/not/exist'; // 404 -// -// $requestUri = '/assets/favicons/favicon-16x16.png'; // file -// +// TESTING: Redirection // Check browser address becomes /design-your-life -// if ($requestUri !== '/design-your-life') { // redirecting -// $requestUri = '/self-improvement'; // redirecting -// } // redirecting - -$requestIsForFile = strpos($requestUri, '.') > 0; - -$localFilePath = $requestUri . '/content.md'; -if ($requestIsForFile) { - $folderMap = [ - '/css' => '/.assets/styles', - '/js' => '/.assets/scripts', - '/assets' => '/.assets' - ]; +// if ($server->requestUri() !== '/design-your-life') { +// $_SERVER['REQUEST_URI'] = '/self-improvement'; +// $server = JoshBruce\Site\Server::init($_SERVER); +// } - $parts = explode('/', $requestUri); - $parts = array_filter($parts); - $first = array_shift($parts); - - $folderMapKey = '/' . $first; - - if (array_key_exists($folderMapKey, $folderMap)) { - $replace = $folderMap[$folderMapKey]; - - $localFilePath = str_replace($folderMapKey, $replace, $requestUri); - } -} - -$content = $content->for($localFilePath); +$content = $content->for($server->filePathForRequest()); if ($content->notFound()) { $content = $content->for(path: '/.errors/404.md'); JoshBruce\Site\Emitter::emitWithResponse( @@ -164,12 +118,7 @@ exit; } -/** - * Target file exists: local response time 27ms - * - * Handle file - */ -if ($requestIsForFile) { +if ($server->isRequestingFile()) { JoshBruce\Site\Emitter::emitWithResponseFile( 200, [ @@ -181,18 +130,14 @@ exit; } -/** - * Target file wants to redirect us: local response time 40ms - */ -$redirectPath = $content->redirectPath(); -if (strlen($redirectPath) > 0) { +if ($content->hasMoved()) { $scheme = $_SERVER['REQUEST_SCHEME']; $serverName = $_SERVER['HTTP_HOST']; $requestDomain = $scheme . '://' . $serverName; JoshBruce\Site\Emitter::emitWithResponse( 301, [ - 'Location' => $requestDomain . $redirectPath + 'Location' => $requestDomain . $content->redirectPath() ] ); exit; diff --git a/setup-errors/405.md b/setup-errors/405.md new file mode 100644 index 00000000..94b07c38 --- /dev/null +++ b/setup-errors/405.md @@ -0,0 +1,12 @@ +--- +title: Unsupported method +usage: The request is using an Unsupported method. +--- + +# 405: Unsupported method + +We cannot support that type of request. + +Please try again later. + +If this error persists, please contact [Josh Bruce](https://github.com/joshbruce). diff --git a/500-errors/500.md b/setup-errors/500.md similarity index 100% rename from 500-errors/500.md rename to setup-errors/500.md diff --git a/500-errors/502.md b/setup-errors/502.md similarity index 100% rename from 500-errors/502.md rename to setup-errors/502.md diff --git a/src/Content.php b/src/Content.php index 6e3ed209..9a6386cf 100644 --- a/src/Content.php +++ b/src/Content.php @@ -40,9 +40,9 @@ public function __construct( ) { } - public function folderDoesExist(): bool + public function folderIsMissing(): bool { - return file_exists($this->root()) and is_dir($this->root()); + return ! $this->folderExists(); } public function for(string $path): Content @@ -61,6 +61,11 @@ public function notFound(): bool return ! $this->exists(); } + public function hasMoved(): bool + { + return strlen($this->redirectPath()) > 0; + } + public function filePath(): string { return $this->root() . $this->path; @@ -131,6 +136,11 @@ public function redirectPath(): string return ''; } + private function folderExists(): bool + { + return file_exists($this->root()) and is_dir($this->root()); + } + private function exists(): bool { return file_exists($this->filePath()); diff --git a/src/Server.php b/src/Server.php new file mode 100644 index 00000000..dac4e345 --- /dev/null +++ b/src/Server.php @@ -0,0 +1,119 @@ + $serverGlobals + */ + public static function init(array $serverGlobals): Server + { + return new Server(serverGlobals: $serverGlobals); + } + + /** + * @param array $serverGlobals + */ + public function __construct(private array $serverGlobals) + { + } + + public function isMissingRequiredValues(): bool + { + $required = [ + 'APP_ENV', + 'CONTENT_UP', + 'CONTENT_FOLDER', + 'REQUEST_SCHEME', + 'HTTP_HOST', + 'REQUEST_URI' + ]; + + $hasRequired = true; + foreach ($required as $key) { + if (! array_key_exists($key, $this->serverGlobals)) { + return true; + } + } + + if ($this->serverGlobals['APP_ENV'] !== 'production') { + $erroHandler = new Run(); + $erroHandler->pushHandler(new PrettyPageHandler()); + $erroHandler->register(); + } + + return false; + } + + public function isUsingUnsupportedMethod(): bool + { + $requestMethod = strtoupper($this->serverGlobals['REQUEST_METHOD']); + return ! in_array($requestMethod, $this->supportedMethods()); + } + + public function isRequestingFile(): bool + { + return strpos($this->requestUri(), '.') > 0; + } + + /** + * @return string[] [description] + */ + public function supportedMethods(): array + { + return ['GET']; + } + + public function contentUp(): int + { + return intval($this->serverGlobals['CONTENT_UP']); + } + + public function contentFolder(): string + { + return strval($this->serverGlobals['CONTENT_FOLDER']); + } + + public function filePathForRequest(): string + { + if ($this->isRequestingFile()) { + $folderMap = [ + '/css' => '/.assets/styles', + '/js' => '/.assets/scripts', + '/assets' => '/.assets' + ]; + + $parts = explode('/', $this->requestUri()); + $parts = array_filter($parts); + $first = array_shift($parts); + + $folderMapKey = '/' . $first; + + if (array_key_exists($folderMapKey, $folderMap)) { + $replace = $folderMap[$folderMapKey]; + + return str_replace( + $folderMapKey, + $replace, + $this->requestUri() + ); + } + return $this->requestUri(); + } + return $this->requestUri() . '/content.md'; + } + + public function requestUri(): string + { + if ($this->serverGlobals['REQUEST_URI'] === '/') { + return ''; + } + return $this->serverGlobals['REQUEST_URI']; + } +} diff --git a/tests/ContentTest.php b/tests/ContentTest.php index 685f70b3..df2bf33e 100644 --- a/tests/ContentTest.php +++ b/tests/ContentTest.php @@ -35,5 +35,5 @@ 'text/html' ); - expect($this->baseContent->folderDoesExist())->toBeTrue(); + expect($this->baseContent->folderIsMissing())->toBeFalse(); })->group('content'); diff --git a/tests/Pest.php b/tests/Pest.php index de4dd446..48e59de3 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -57,6 +57,8 @@ function serverGlobals(string $requestUri = '/'): array $_SERVER['APP_ENV'] = 'test'; $_SERVER['CONTENT_UP'] = 0; $_SERVER['CONTENT_FOLDER'] = '/tests/test-content'; + $_SERVER['REQUEST_SCHEME'] = 'http'; + $_SERVER['HTTP_HOST'] = 'testing.com'; $_SERVER['REQUEST_URI'] = $requestUri; $_SERVER['REQUEST_METHOD'] = 'get'; diff --git a/tests/ServerTest.php b/tests/ServerTest.php new file mode 100644 index 00000000..66f41015 --- /dev/null +++ b/tests/ServerTest.php @@ -0,0 +1,85 @@ +filePathForRequest() + )->toBe( + '/content.md' + ); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_URI'] = '/some.file'; + + expect( + Server::init($serverGlobals)->filePathForRequest() + )->toBe( + '/some.file' + ); +})->group('server'); + +it('can determine if request is for a file', function() { + expect( + Server::init(serverGlobals())->isRequestingFile() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_URI'] = '/some.file'; + + expect( + Server::init($serverGlobals)->isRequestingFile() + )->toBeTrue(); +})->group('server'); + +it('limits request methods', function() { + expect( + Server::init(serverGlobals())->isUsingUnsupportedMethod() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + $serverGlobals['REQUEST_METHOD'] = 'INVALID'; + + expect( + Server::init($serverGlobals)->isUsingUnsupportedMethod() + )->toBeTrue(); +})->group('server'); + +it('has required variables', function() { + expect( + Server::init(serverGlobals())->isMissingRequiredValues() + )->toBeFalse(); + + $serverGlobals = serverGlobals(); + unset($serverGlobals['CONTENT_UP']); + + expect( + Server::init($serverGlobals)->isMissingRequiredValues() + )->toBeTrue(); +})->group('server'); + +// it('is immutable', function() { +// $server = Server::init(serverGlobals()); + +// $server->offsetSet('CONTENT_UP', 2); +// expect( +// $server->offsetGet('CONTENT_UP') +// )->toBe( +// 0 +// ); + +// $server->offsetUnset('CONTENT_UP'); +// expect( +// $server->offsetGet('CONTENT_UP') +// )->toBe( +// 0 +// ); +// })->group('server'); + +// test('Server exists', function() { +// expect( +// Server::init(serverGlobals()) +// )->toBeInstanceOf( +// Server::class +// ); +// })->group('server'); diff --git a/tests/Server_.php b/tests/Server_.php deleted file mode 100644 index 5c622f90..00000000 --- a/tests/Server_.php +++ /dev/null @@ -1,48 +0,0 @@ -response()->isOk() - )->toBeTrue(); - - $serverGlobals = serverGlobals(); - unset($serverGlobals['CONTENT_UP']); - unset($serverGlobals['CONTENT_FOLDER']); - - $response = Server::init($serverGlobals)->response(); - expect( - $response->isOk() - )->toBeFalse(); - - expect( - $response->getBody() - )->toBeString()->not->toBeEmpty(); -})->group('server'); - -it('is immutable', function() { - $server = Server::init(serverGlobals()); - - $server->offsetSet('CONTENT_UP', 2); - expect( - $server->offsetGet('CONTENT_UP') - )->toBe( - 0 - ); - - $server->offsetUnset('CONTENT_UP'); - expect( - $server->offsetGet('CONTENT_UP') - )->toBe( - 0 - ); -})->group('server'); - -test('Server exists', function() { - expect( - Server::init(serverGlobals()) - )->toBeInstanceOf( - Server::class - ); -})->group('server');