<?php
/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Enterprise License (PEL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 * @license    http://www.pimcore.org/license     GPLv3 and PEL
 */

namespace Pimcore\Bundle\CoreBundle\EventListener;

use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
use Pimcore\Config;
use Pimcore\Db\ConnectionInterface;
use Pimcore\Document\Renderer\DocumentRenderer;
use Pimcore\FeatureToggles\Features\DebugMode;
use Pimcore\Http\Exception\ResponseException;
use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
use Pimcore\Model\Document;
use Pimcore\Model\Site;
use Pimcore\Templating\Renderer\ActionRenderer;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class ResponseExceptionListener implements EventSubscriberInterface
{
    use LoggerAwareTrait;
    use PimcoreContextAwareTrait;

    /**
     * @var ActionRenderer
     */
    protected $documentRenderer;

    /**
     * @var bool
     */
    protected $renderErrorPage = true;

    /**
     * @var ConnectionInterface
     */
    protected $db;

    /**
     * @param DocumentRenderer $documentRenderer
     * @param ConnectionInterface $db
     * @param bool $renderErrorPage
     */
    public function __construct(DocumentRenderer $documentRenderer, ConnectionInterface $db, $renderErrorPage = true)
    {
        $this->documentRenderer = $documentRenderer;
        $this->renderErrorPage = (bool)$renderErrorPage;
        $this->db = $db;
    }

    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::EXCEPTION => 'onKernelException'
        ];
    }

    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        $exception = $event->getException();

        // handle ResponseException (can be used from any context)
        if ($exception instanceof ResponseException) {
            $event->setResponse($exception->getResponse());

            // a response was explicitely set -> do not continue to error page
            return;
        }

        // further checks are only valid for default context
        $request = $event->getRequest();
        if ($this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT)) {
            if ($this->renderErrorPage) {
                $this->handleErrorPage($event);
            }
        }
    }

    protected function handleErrorPage(GetResponseForExceptionEvent $event)
    {
        if (\Pimcore::inDebugMode(DebugMode::NO_ERROR_PAGE)) {
            return;
        }

        $exception = $event->getException();

        $statusCode = 500;
        $headers = [];

        if ($exception instanceof HttpExceptionInterface) {
            $statusCode = $exception->getStatusCode();
            $headers = $exception->getHeaders();
        }

        $errorPath = Config::getSystemConfig()->documents->error_pages->default;

        if (Site::isSiteRequest()) {
            $site = Site::getCurrentSite();
            $errorPath = $site->getErrorDocument();
        }

        // HTTP Error Log
        $uri = $event->getRequest()->getUri();
        $exists = $this->db->fetchOne('SELECT date FROM http_error_log WHERE uri = ?', $uri);
        if ($exists) {
            $this->db->query('UPDATE http_error_log SET `count` = `count` + 1, date = ? WHERE uri = ?', [time(), $uri]);
        } else {
            $this->db->insert('http_error_log', [
                'uri' => $uri,
                'code' => (int) $statusCode,
                'parametersGet' => serialize($_GET),
                'parametersPost' => serialize($_POST),
                'cookies' => serialize($_COOKIE),
                'serverVars' => serialize($_SERVER),
                'date' => time(),
                'count' => 1
            ]);
        }

        // Error page rendering
        if (empty($errorPath)) {
            $errorPath = '/';
        }

        $document = Document::getByPath($errorPath);

        if (!$document instanceof Document\Page) {
            // default is home
            $document = Document::getById(1);
        }

        try {
            $response = $this->documentRenderer->render($document, [
                'exception' => $exception
            ]);
        } catch (\Exception $e) {
            // we are even not able to render the error page, so we send the client a unicorn
            $response = 'Page not found. 🦄';
            $this->logger->emergency('Unable to render error page, exception thrown');
            $this->logger->emergency($e);
        }

        $event->setResponse(new Response($response, $statusCode, $headers));
    }
}
