<?php
/**
 * Webservice controller listener
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2015 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\EventListener;

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\HeaderBag;
use Sso\WebserviceBundle\Error\Storage as ErrorStorage;
use Sso\WebserviceBundle\Error\Type\External as ExternalError;
use Sso\WebserviceBundle\Exception\InvalidHeaderException;
use Sso\WebserviceBundle\Exception\InvalidContentException;
use Sso\WebserviceBundle\Response\Builder as ResponseBuilder;

/**
 * Webservice controller listener
 *
 * Loads xml request into request attribute "XmlRequest" and
 * verifies controller/action parameters
 */
class WsControllerListener implements EventSubscriberInterface
{

    /**
     * The error storage
     *
     * @var ErrorStorage
     */
    protected $errors;

    /**
     * Response builder
     *
     * @var ResponseBuilder
     */
    protected $responseBuilder;

    /**
     * Constructor
     *
     * @param ErrorStorage $errorStorage
     * @param ResponseBuilder $responseBuilder
     */
    public function __construct(ErrorStorage $errorStorage, ResponseBuilder $responseBuilder)
    {
        $this->errors = $errorStorage;
        $this->responseBuilder = $responseBuilder;
    }

    /**
     * Received controller event
     *
     * Verify content and load xml
     *
     * @param FilterControllerEvent $event
     * @throws InvalidContentException
     */
    public function onKernelController(FilterControllerEvent $event)
    {
        $request = $event->getRequest();
        if (!$configuration = $request->attributes->get('_wsRoute')) {
            return;
        }

        if ('xml' !== $request->getContentType()) {
            throw new InvalidContentException('Invalid Content-Type. Expected xml.');
        }

        if (false === ($xml = $this->loadXml($request->getContent()))) {
            throw new InvalidContentException('Invalid Content. Could not load xml.');
        }

        $request->attributes->set('XmlRequest', $xml);

        $this->verifyControllerAction($xml, $request->headers);
    }

    /**
     * Validate and load xml
     *
     * @param string $xml
     * @return \SimpleXMLElement|false
     */
    private function loadXml($xml)
    {
        libxml_clear_errors();
        libxml_disable_entity_loader(true);
        libxml_use_internal_errors(true);

        $simpleXML = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_COMPACT);

        if (!$simpleXML) {
            $errors = libxml_get_errors();
            foreach ($errors as $error) {
                $this->errors->addError(new ExternalError('xh10', 'XmlInvalid', $error->message));
            }
            libxml_clear_errors();
            libxml_disable_entity_loader(false);
        } else {
            libxml_clear_errors();
            libxml_disable_entity_loader(false);
        }

        if ($this->errors->hasErrors()) {
            throw new InvalidContentException('Invalid Content. Expected well formed xml.');
        }

        return $simpleXML;
    }

    /**
     * Verify controller and action against xml
     *
     * @param \SimpleXMLElement $xml
     * @param HeaderBag $headers
     * @return bool
     */
    private function verifyControllerAction(\SimpleXMLElement $xml, HeaderBag $headers)
    {
        $controller = $headers->get('api-controller');
        if (!isset($xml->$controller)) {
            $this->errors->addError(new ExternalError('xh20', 'ControllerInvalid', 'Controller in header does not match controller in xml'));
        } else {
            $action = $headers->get('api-action');
            if (!isset($xml->$controller->$action)) {
                $this->errors->addError(new ExternalError('xh22', 'ActionInvalid', 'Action in header does not match action in xml'));
            }
        }

        // Controller or action missing
        if ($this->errors->hasErrors()) {
            throw new InvalidHeaderException();
        }

        // Set controller and action for xml response
        $this->responseBuilder->setController($headers->get('api-controller'));
        $this->responseBuilder->setAction($headers->get('api-action'));
    }

    /**
     * Returns list of subscribed events
     *
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(KernelEvents::CONTROLLER => ['onKernelController', -1]);
    }

}
