<?php

/**
 * @file api/v1/users/PKPUserHandler.inc.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 PKPUserHandler
 * @ingroup api_v1_users
 *
 * @brief Base class to handle API requests for user operations.
 *
 */

import('lib.pkp.classes.handler.APIHandler');
import('classes.core.Services');

class PKPUserHandler extends APIHandler {

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->_handlerPath = 'users';
		$roles = [ROLE_ID_SITE_ADMIN, ROLE_ID_MANAGER, ROLE_ID_SUB_EDITOR];
		$this->_endpoints = [
			'GET' => [
				[
					'pattern' => $this->getEndpointPattern(),
					'handler' => [$this, 'getMany'],
					'roles' => $roles
				],
				[
					'pattern' => $this->getEndpointPattern() . '/reviewers',
					'handler' => [$this, 'getReviewers'],
					'roles' => $roles
				],
				[
					'pattern' => $this->getEndpointPattern() . '/{userId:\d+}',
					'handler' => [$this, 'get'],
					'roles' => $roles
				],
				[
					'pattern' => $this->getEndpointPattern() . '/report',
					'handler' => [$this, 'getReport'],
					'roles' => $roles
				],
			],
		];
		parent::__construct();
	}

	/**
	 * @copydoc PKPHandler::authorize()
	 */
	function authorize($request, &$args, $roleAssignments) {
		import('lib.pkp.classes.security.authorization.ContextAccessPolicy');
		$this->addPolicy(new ContextAccessPolicy($request, $roleAssignments));
		return parent::authorize($request, $args, $roleAssignments);
	}

	/**
	 * Get a collection of users
	 * @param $slimRequest Request Slim request object
	 * @param $response Response object
	 * @param $args array arguments
	 *
	 * @return Response
	 */
	public function getMany($slimRequest, $response, $args) {
		$request = $this->getRequest();
		$context = $request->getContext();

		if (!$context) {
			return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
		}

		$params = $this->_processAllowedParams($slimRequest->getQueryParams(), [
			'assignedToCategory',
			'assignedToSection',
			'assignedToSubmission',
			'assignedToSubmissionStage',
			'count',
			'offset',
			'orderBy',
			'orderDirection',
			'roleIds',
			'searchPhrase',
			'status',
		]);

		$params['contextId'] = $context->getId();

		\HookRegistry::call('API::users::params', [&$params, $slimRequest]);

		$items = [];
		$usersItereator = Services::get('user')->getMany($params);
		$propertyArgs = [
			'request' => $request,
			'slimRequest' => $slimRequest,
		];
		foreach ($usersItereator as $user) {
			$items[] = Services::get('user')->getSummaryProperties($user, $propertyArgs);
		}

		return $response->withJson([
			'itemsMax' => Services::get('user')->getMax($params),
			'items' => $items,
		], 200);
	}

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

		if (!empty($args['userId'])) {
			$user = Services::get('user')->get((int) $args['userId']);
		}

		if (!$user) {
			return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
		}

		$data = Services::get('user')->getFullProperties($user, array(
			'request' => $request,
			'slimRequest' 	=> $slimRequest
		));

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

	/**
	 * Get a collection of reviewers
	 * @param $slimRequest Request Slim request object
	 * @param $response Response object
	 * @param $args array arguments
	 *
	 * @return Response
	 */
	public function getReviewers($slimRequest, $response, $args) {
		$request = $this->getRequest();
		$context = $request->getContext();

		if (!$context) {
			return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
		}

		$params = $this->_processAllowedParams($slimRequest->getQueryParams(), [
			'averageCompletion',
			'count',
			'daysSinceLastAssignment',
			'offset',
			'orderBy',
			'orderDirection',
			'reviewerRating',
			'reviewsActive',
			'reviewsCompleted',
			'reviewStage',
			'searchPhrase',
			'status',
		]);

		$params['contextId'] = $context->getId();

		\HookRegistry::call('API::users::reviewers::params', array(&$params, $slimRequest));

		$items = [];
		$usersIterator = Services::get('user')->getReviewers($params);
		$propertyArgs = [
			'request' => $request,
			'slimRequest' => $slimRequest,
		];
		foreach ($usersIterator as $user) {
			$items[] = Services::get('user')->getReviewerSummaryProperties($user, $propertyArgs);
		}

		return $response->withJson([
			'itemsMax' => Services::get('user')->getReviewersMax($params),
			'items' => $items,
		], 200);
	}

	/**
	 * Convert the query params passed to the end point. Exclude unsupported
	 * params and coerce the type of those passed.
	 *
	 * @param array $params Key/value of request params
	 * @param array $allowedKeys The param keys which should be processed and returned
	 * @return array
	 */
	private function _processAllowedparams($params, $allowedKeys) {

		// Merge query params over default params
		$defaultParams = [
			'count' => 20,
			'offset' => 0,
		];

		$requestParams = array_merge($defaultParams, $params);

		// Process query params to format incoming data as needed
		$returnParams = [];
		foreach ($requestParams as $param => $val) {
			if (!in_array($param, $allowedKeys)) {
				continue;
			}
			switch ($param) {
				case 'orderBy':
					if (in_array($val, ['id', 'familyName', 'givenName'])) {
						$returnParams[$param] = $val;
					}
					break;

				case 'orderDirection':
					$returnParams[$param] = $val === 'ASC' ? $val : 'DESC';
					break;

				case 'status':
					if (in_array($val, ['all', 'active', 'disabled'])) {
						$returnParams[$param] = $val;
					}
					break;

				// Always convert roleIds to array
				case 'roleIds':
					if (is_string($val)) {
						$val = explode(',', $val);
					} elseif (!is_array($val)) {
						$val = [$val];
					}
					$returnParams[$param] = array_map('intval', $val);
					break;
				case 'assignedToCategory':
				case 'assignedToSection':
				case 'assignedToSubmissionStage':
				case 'assignedToSubmission':
				case 'reviewerRating':
				case 'reviewStage':
				case 'offset':
					$returnParams[$param] = (int) $val;
					break;

				case 'searchPhrase':
					$returnParams[$param] = trim($val);
					break;

				case 'reviewsCompleted':
				case 'reviewsActive':
				case 'daysSinceLastAssignment':
				case 'averageCompletion':
					if (is_array($val)) {
						$val = array_map('intval', $val);
					} elseif (strpos($val, '-') !== false) {
						$val = array_map('intval', explode('-', $val));
					} else {
						$val = [(int) $val];
					}
					$returnParams[$param] = $val;
					break;

				// Enforce a maximum count per request
				case 'count':
					$returnParams[$param] = min(100, (int) $val);
					break;
			}
		}

		return $returnParams;
	}

	/**
	 * Retrieve the user report
	 *
	 * @param Slim\Http\Request $slimRequest Slim request object
	 * @param \APIResponse $response Response
	 * @param array $args
	 * @return ?\APIResponse Response
	 */
	public function getReport(\Slim\Http\Request $slimRequest, \APIResponse $response, array $args) : ?\APIResponse {
		$request = $this->getRequest();

		$context = $request->getContext();
		if (!$context) {
			return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
		}

		$params = ['contextId' => $context->getId()];
		foreach ($slimRequest->getQueryParams() as $param => $value) {
			switch ($param) {
				case 'userGroupIds':
					if (is_string($value) && strpos($value, ',') > -1) {
						$value = explode(',', $value);
					} elseif (!is_array($value)) {
						$value = [$value];
					}
					$params[$param] = array_map('intval', $value);
					break;
				case 'mappings':
					if (is_string($value) && strpos($value, ',') > -1) {
						$value = explode(',', $value);
					} elseif (!is_array($value)) {
						$value = [$value];
					}
					$params[$param] = $value;
					break;
			}
		}

		\HookRegistry::call('API::users::user::report::params', [&$params, $slimRequest]);

		$this->getApp()->getContainer()->get('settings')->replace(['outputBuffering' => false]);

		$report = \Services::get('user')->getReport($params);
		header('content-type: text/comma-separated-values');
		header('content-disposition: attachment; filename=user-report-' . date('Y-m-d') . '.csv');
		$report->serialize(fopen('php://output', 'w+'));
		exit;
	}
}
