<?php
/**
 * @file api/v1/emailTemplates/PKPEmailTemplateHandler.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class PKPEmailTemplateHandler
 *
 * @ingroup api_v1_email_templates
 *
 * @brief Base class to handle API requests for email templates.
 */

namespace PKP\API\v1\emailTemplates;

use APP\core\Application;
use PKP\core\APIResponse;
use PKP\facades\Repo;
use PKP\handler\APIHandler;
use PKP\plugins\Hook;
use PKP\security\authorization\ContextRequiredPolicy;
use PKP\security\authorization\PolicySet;
use PKP\security\authorization\RoleBasedHandlerOperationPolicy;
use PKP\security\authorization\UserRolesRequiredPolicy;
use PKP\security\Role;
use PKP\services\PKPSchemaService;
use Slim\Http\Request as SlimRequest;
use Slim\Http\Response;

class PKPEmailTemplateHandler extends APIHandler
{
    public const MAX_PER_PAGE = 100;

    /**
     * @copydoc APIHandler::__construct()
     */
    public function __construct()
    {
        $this->_handlerPath = 'emailTemplates';
        $roles = [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER];
        $this->_endpoints = [
            'GET' => [
                [
                    'pattern' => $this->getEndpointPattern(),
                    'handler' => [$this, 'getMany'],
                    'roles' => array_merge($roles, [Role::ROLE_ID_SUB_EDITOR, ROLE::ROLE_ID_ASSISTANT]),
                ],
                [
                    'pattern' => $this->getEndpointPattern() . '/{key}',
                    'handler' => [$this, 'get'],
                    'roles' => array_merge($roles, [Role::ROLE_ID_SUB_EDITOR, ROLE::ROLE_ID_ASSISTANT]),
                ],
            ],
            'POST' => [
                [
                    'pattern' => $this->getEndpointPattern(),
                    'handler' => [$this, 'add'],
                    'roles' => $roles,
                ],
            ],
            'PUT' => [
                [
                    'pattern' => $this->getEndpointPattern() . '/{key}',
                    'handler' => [$this, 'edit'],
                    'roles' => $roles,
                ],
            ],
            'DELETE' => [
                [
                    'pattern' => $this->getEndpointPattern() . '/restoreDefaults',
                    'handler' => [$this, 'restoreDefaults'],
                    'roles' => $roles,
                ],
                [
                    'pattern' => $this->getEndpointPattern() . '/{key}',
                    'handler' => [$this, 'delete'],
                    'roles' => $roles,
                ],
            ],
        ];
        parent::__construct();
    }

    /**
     * @copydoc PKPHandler::authorize
     */
    public function authorize($request, &$args, $roleAssignments)
    {
        $this->addPolicy(new UserRolesRequiredPolicy($request), true);

        $rolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);

        // This endpoint is not available at the site-wide level
        $this->addPolicy(new ContextRequiredPolicy($request));

        foreach ($roleAssignments as $role => $operations) {
            $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations));
        }
        $this->addPolicy($rolePolicy);

        return parent::authorize($request, $args, $roleAssignments);
    }

    /**
     * Get a collection of email templates
     */
    public function getMany(SlimRequest $slimRequest, Response $response, array $args): Response
    {
        $request = $this->getRequest();

        $collector = Repo::emailTemplate()->getCollector($request->getContext()->getId());

        // Process query params to format incoming data as needed
        foreach ($slimRequest->getQueryParams() as $param => $val) {
            switch ($param) {
                case 'alternateTo':
                    $collector->alternateTo($this->paramToArray($val));
                    break;
                case 'isModified':
                    $collector->isModified((bool) $val);
                    break;
                case 'searchPhrase':
                    $collector->searchPhrase(trim($val));
                    break;
                case 'count':
                    $collector->limit(min((int) $val, self::MAX_PER_PAGE));
                    break;
                case 'offset':
                    $collector->offset((int) $val);
                    break;
            }
        }

        Hook::call('API::emailTemplates::params', [$collector, $slimRequest]);

        $emailTemplates = $collector->getMany();

        return $response->withJson([
            'itemsMax' => $collector->getCount(),
            'items' => Repo::emailTemplate()->getSchemaMap()->summarizeMany($emailTemplates),
        ], 200);
    }

    /**
     * Get a single email template
     *
     * @param SlimRequest $slimRequest Slim request object
     * @param APIResponse $response object
     * @param array $args arguments
     *
     * @return APIResponse
     */
    public function get($slimRequest, $response, $args)
    {
        $request = $this->getRequest();

        $emailTemplate = Repo::emailTemplate()->getByKey($request->getContext()->getId(), $args['key']);

        if (!$emailTemplate) {
            return $response->withStatus(404)->withJsonError('api.emailTemplates.404.templateNotFound');
        }

        return $response->withJson(Repo::emailTemplate()->getSchemaMap()->map($emailTemplate), 200);
    }

    /**
     * Add an email template
     *
     * @param SlimRequest $slimRequest Slim request object
     * @param Response $response object
     * @param array $args arguments
     *
     * @return Response
     */
    public function add($slimRequest, $response, $args)
    {
        $request = $this->getRequest();
        $requestContext = $request->getContext();

        $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE, $slimRequest->getParsedBody());
        $params['contextId'] = $requestContext->getId();

        $errors = Repo::emailTemplate()->validate(null, $params, $requestContext);

        if (!empty($errors)) {
            return $response->withStatus(400)->withJson($errors);
        }

        $emailTemplate = Repo::emailTemplate()->newDataObject($params);
        Repo::emailTemplate()->add($emailTemplate);
        $emailTemplate = Repo::emailTemplate()->getByKey($emailTemplate->getData('contextId'), $emailTemplate->getData('key'));

        return $response->withJson(Repo::emailTemplate()->getSchemaMap()->map($emailTemplate), 200);
    }

    /**
     * Edit an email template
     *
     * @param SlimRequest $slimRequest Slim request object
     * @param APIResponse $response object
     * @param array $args arguments
     *
     * @return APIResponse
     */
    public function edit($slimRequest, $response, $args)
    {
        $request = $this->getRequest();
        $requestContext = $request->getContext();

        $emailTemplate = Repo::emailTemplate()->getByKey($requestContext->getId(), $args['key']);

        if (!$emailTemplate) {
            return $response->withStatus(404)->withJsonError('api.emailTemplates.404.templateNotFound');
        }

        $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE, $slimRequest->getParsedBody());
        $params['key'] = $args['key'];

        // Only allow admins to change the context an email template is attached to.
        // Set the contextId if it has not been passed or the user is not an admin
        $userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
        if (isset($params['contextId'])
                && !in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles)
                && $params['contextId'] !== $requestContext->getId()) {
            return $response->withStatus(403)->withJsonError('api.emailTemplates.403.notAllowedChangeContext');
        } elseif (!isset($params['contextId'])) {
            $params['contextId'] = $requestContext->getId();
        }

        $errors = Repo::emailTemplate()->validate(
            $emailTemplate,
            $params,
            $requestContext
        );

        if (!empty($errors)) {
            return $response->withStatus(400)->withJson($errors);
        }

        Repo::emailTemplate()->edit($emailTemplate, $params);

        $emailTemplate = Repo::emailTemplate()->getByKey(
            // context ID is null if edited for the first time
            $emailTemplate->getData('contextId') ?? $params['contextId'],
            $emailTemplate->getData('key')
        );

        return $response->withJson(Repo::emailTemplate()->getSchemaMap()->map($emailTemplate), 200);
    }

    /**
     * Delete an email template
     *
     * @param SlimRequest $slimRequest Slim request object
     * @param APIResponse $response object
     * @param array $args arguments
     *
     * @return APIResponse
     */
    public function delete($slimRequest, $response, $args)
    {
        $request = $this->getRequest();
        $requestContext = $request->getContext();

        $emailTemplate = Repo::emailTemplate()->getByKey($requestContext->getId(), $args['key']);

        // Only custom email templates can be deleted, so return 404 if no id exists
        if (!$emailTemplate || !$emailTemplate->getData('id')) {
            return $response->withStatus(404)->withJsonError('api.emailTemplates.404.templateNotFound');
        }

        $props = Repo::emailTemplate()->getSchemaMap()->map($emailTemplate);
        Repo::emailTemplate()->delete($emailTemplate);

        return $response->withJson($props, 200);
    }

    /**
     * Restore defaults in the email template settings
     *
     * @param SlimRequest $slimRequest Slim request object
     * @param Response $response object
     * @param array $args arguments
     *
     * @return Response
     */
    public function restoreDefaults($slimRequest, $response, $args)
    {
        $contextId = $this->getRequest()->getContext()->getId();
        $deletedKeys = Repo::emailTemplate()->restoreDefaults($contextId);
        return $response->withJson($deletedKeys, 200);
    }
}
