* @license http://www.opensource.org/licenses/mit-license.html MIT License * @version 5.2 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient */ namespace Aviat\AnimeClient; use Aura\Router\Generator; use Aura\Session\Segment; use Aviat\AnimeClient\API\Kitsu\Auth; use Aviat\AnimeClient\Enum\EventType; use Aviat\Ion\Di\{ ContainerAware, ContainerInterface, Exception\ContainerException, Exception\NotFoundException }; use Aviat\Ion\Exception\DoubleRenderException; use Aviat\Ion\View\{HtmlView, HttpView, JsonView}; use Aviat\Ion\{ConfigInterface, Event}; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; use Psr\SimpleCache\CacheInterface; use function Aviat\Ion\_dir; use function is_array; /** * Controller base, defines output methods */ class Controller { use ContainerAware; /** * The global configuration object */ public ConfigInterface $config; /** * The authentication object */ protected Auth $auth; /** * Cache manager */ protected CacheInterface $cache; /** * Request object */ protected ServerRequestInterface $request; /** * Url generation class */ protected UrlGenerator $urlGenerator; /** * Aura url generator */ protected Generator $url; /** * Session segment */ protected Segment $session; /** * Common data to be sent to views */ protected array $baseData = []; /** * The data bag for rendering */ protected RenderHelper $renderHelper; /** * Controller constructor. * * @throws ContainerException * @throws NotFoundException */ public function __construct(ContainerInterface $container) { $this->setContainer($container); $auraUrlGenerator = $container->get('aura-router')->getGenerator(); $session = $container->get('session'); $urlGenerator = $container->get('url-generator'); $this->auth = $container->get('auth'); $this->cache = $container->get('cache'); $this->config = $container->get('config'); $this->renderHelper = $container->get('render-helper'); $this->request = $container->get('request'); $this->session = $session->getSegment(SESSION_SEGMENT); $this->url = $auraUrlGenerator; $this->urlGenerator = $urlGenerator; $helper = $container->get('html-helper'); $this->baseData = [ '_' => $this->renderHelper, 'auth' => $container->get('auth'), 'component' => $container->get('component-helper'), 'container' => $container, 'config' => $this->config, 'escape' => $helper->escape(), 'helper' => $helper, 'menu_name' => '', 'message' => $this->session->getFlash('message'), // Get message box data if it exists 'other_type' => 'manga', 'url' => $auraUrlGenerator, 'url_type' => 'anime', 'urlGenerator' => $urlGenerator, ]; // Set up 'global' events Event::on(EventType::CLEAR_CACHE, fn () => clearCache($this->cache)); Event::on(EventType::RESET_CACHE_KEY, fn (string $key) => $this->cache->delete($key)); } /** * Set the current url in the session as the target of a future redirect * * @throws ContainerException * @throws NotFoundException */ public function setSessionRedirect(?string $url = NULL): void { $serverParams = $this->request->getServerParams(); if ( ! array_key_exists('HTTP_REFERER', $serverParams)) { return; } $util = $this->container->get('util'); $doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri(); $isLoginPage = str_contains($serverParams['HTTP_REFERER'], 'login'); // Don't attempt to set the redirect url if // the page is one of the form type pages, // and the previous page is also a form type if ($doubleFormPage || $isLoginPage) { return; } if (NULL === $url) { $url = $util->isViewPage() ? (string) $this->request->getUri() : $serverParams['HTTP_REFERER']; } $this->session->set('redirect_url', $url); } /** * Redirect to the url previously set in the session * * If one is not set, redirect to default url * * @throws InvalidArgumentException */ public function sessionRedirect(): void { $target = $this->session->get('redirect_url') ?? '/'; $this->redirect($target, 303); $this->session->set('redirect_url', NULL); } /** * Check if the current user is authenticated, else error and exit */ protected function checkAuth(): void { if ( ! $this->auth->isAuthenticated()) { $this->errorPage( 403, 'Forbidden', 'You must log in to perform this action.' ); } } /** * Get the string output of a partial template */ protected function loadPartial(HtmlView $view, string $template, array $data = []): string { $router = $this->container->get('dispatcher'); $data = array_merge($this->baseData ?? [], $data); $route = $router->getRoute(); $data['route_path'] = $route !== FALSE ? $route->path : ''; $templatePath = _dir($this->config->get('view_path'), "{$template}.php"); if ( ! is_file($templatePath)) { throw new InvalidArgumentException("Invalid template : {$template}"); } return $view->renderTemplate($templatePath, $data); } /** * Render a template with header and footer */ protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView { $csp = [ "default-src 'self' media.kitsu.io kitsu-production-media.s3.us-west-002.backblazeb2.com", "object-src 'none'", "child-src 'self' *.youtube.com polyfill.io", ]; $data = array_merge($this->baseData ?? [], $data); $view->addHeader('Content-Security-Policy', implode('; ', $csp)); $view->appendOutput($this->loadPartial($view, 'header', $data)); if (array_key_exists('message', $data) && is_array($data['message'])) { $view->appendOutput($this->loadPartial($view, 'message', $data['message'])); } $view->appendOutput($this->loadPartial($view, $template, $data)); $view->appendOutput($this->loadPartial($view, 'footer', $data)); return $view; } /** * 404 action * * @throws InvalidArgumentException */ public function notFound( string $title = 'Sorry, page not found', string $message = 'Page Not Found' ): never { $this->outputHTML('404', [ 'title' => $title, 'message' => $message, ], NULL, 404); exit(); } /** * Display a generic error page * * @throws InvalidArgumentException */ public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void { $this->outputHTML('error', [ 'title' => $title, 'message' => $message, 'long_message' => $longMessage, ], NULL, $httpCode); } /** * Redirect to the default controller/url from an empty path * * @throws InvalidArgumentException */ public function redirectToDefaultRoute(): void { $defaultType = $this->config->get('default_list'); $this->redirect($this->urlGenerator->defaultUrl($defaultType), 303); } /** * Set a session flash variable to display a message on * next page load */ public function setFlashMessage(string $message, string $type = 'info'): void { static $messages; if ( ! $messages) { $messages = []; } $messages[] = [ 'message_type' => $type, 'message' => $message, ]; $this->session->setFlash('message', $messages); } /** * Helper for consistent page titles * * @param string ...$parts Title segments */ public function formatTitle(string ...$parts): string { return implode(' · ', $parts); } /** * Add a message box to the page * * @throws InvalidArgumentException */ protected function showMessage(HtmlView $view, string $type, string $message): string { return $this->loadPartial($view, 'message', [ 'message_type' => $type, 'message' => $message, ]); } /** * Output a template to HTML, using the provided data * * @throws InvalidArgumentException */ protected function outputHTML(string $template, array $data = [], ?HtmlView $view = NULL, int $code = 200): void { if (NULL === $view) { $view = new HtmlView($this->container); } $view->setStatusCode($code); $this->renderFullPage($view, $template, $data)->send(); } /** * Output a JSON Response * * @param int $code - the http status code * @throws DoubleRenderException */ protected function outputJSON(mixed $data, int $code): void { JsonView::new() ->setOutput($data) ->setStatusCode($code) ->send(); } /** * Redirect to the selected page */ protected function redirect(string $url, int $code): void { HttpView::new() ->redirect($url, $code) ->send(); } } // End of BaseController.php