<?php

/**
 * @package KantPHP
 * @author  Zhenqiang Zhang <zhenqiang.zhang@hotmail.com>
 * @copyright (c) KantPHP Studio, All rights reserved.
 * @license http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
 */
namespace Kant\Filter;

use Kant\Kant;
use Kant\Action\ActionEvent;
use Kant\Foundation\Behavior;
use Kant\Controller\Controller;
use Kant\Exception\MethodNotAllowedHttpException;

/**
 * VerbFilter is an action filter that filters by HTTP request methods.
 *
 * It allows to define allowed HTTP request methods for each action and will throw
 * an HTTP 405 error when the method is not allowed.
 *
 * To use VerbFilter, declare it in the `behaviors()` method of your controller class.
 * For example, the following declarations will define a typical set of allowed
 * request methods for REST CRUD actions.
 *
 * ```php
 * public function behaviors()
 * {
 * return [
 * 'verbs' => [
 * 'class' => \Kant\Filter\VerbFilter::className(),
 * 'actions' => [
 * 'index' => ['get'],
 * 'view' => ['get'],
 * 'create' => ['get', 'post'],
 * 'update' => ['get', 'put', 'post'],
 * 'delete' => ['post', 'delete'],
 * ],
 * ],
 * ];
 * }
 * ```
 *
 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
 * @author Carsten Brandt <mail@cebe.cc>
 * @since 2.0
 */
class VerbFilter extends Behavior
{

    /**
     *
     * @var array this property defines the allowed request methods for each action.
     *      For each action that should only support limited set of request methods
     *      you add an entry with the action id as array key and an array of
     *      allowed methods (e.g. GET, HEAD, PUT) as the value.
     *      If an action is not listed all request methods are considered allowed.
     *     
     *      You can use `'*'` to stand for all actions. When an action is explicitly
     *      specified, it takes precedence over the specification given by `'*'`.
     *     
     *      For example,
     *     
     *      ```php
     *      [
     *      'create' => ['get', 'post'],
     *      'update' => ['get', 'put', 'post'],
     *      'delete' => ['post', 'delete'],
     *      '*' => ['get'],
     *      ]
     *      ```
     */
    public $actions = [];

    /**
     * Declares event handlers for the [[owner]]'s events.
     * 
     * @return array events (array keys) and the corresponding event handler methods (array values).
     */
    public function events()
    {
        return [
            Controller::EVENT_BEFORE_ACTION => 'beforeActions'
        ];
    }

    /**
     *
     * @param ActionEvent $event            
     * @return bool
     * @throws MethodNotAllowedHttpException when the request method is not allowed.
     */
    public function beforeAction($event)
    {
        $action = $event->action->id;
        if (isset($this->actions[$action])) {
            $verbs = $this->actions[$action];
        } elseif (isset($this->actions['*'])) {
            $verbs = $this->actions['*'];
        } else {
            return $event->isValid;
        }
        
        $verb = Kant::$app->getRequest()->getMethod();
        $allowed = array_map('strtoupper', $verbs);
        if (!in_array($verb, $allowed)) {
            $event->isValid = false;
            // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
            Kant::$app->getResponse()
                ->getHeaders()
                ->set('Allow', implode(', ', $allowed));
            throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.');
        }
        
        return $event->isValid;
    }
}
