[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
v1
/
lib
/
pkp
/
classes
/
handler
/
[
Home
]
File: APIHandler.php
<?php /** * @file lib/pkp/classes/handler/APIHandler.php * * Copyright (c) 2014-2021 Simon Fraser University * Copyright (c) 2003-2021 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class APIHandler * * @ingroup handler * * @brief Base request API handler */ namespace PKP\handler; use APP\core\Application; use APP\core\Request; use APP\core\Services; use PKP\core\APIResponse; use PKP\plugins\Hook; use PKP\security\authorization\internal\ApiAuthorizationMiddleware; use PKP\security\authorization\internal\ApiCsrfMiddleware; use PKP\security\authorization\internal\ApiTokenDecodingMiddleware; use PKP\statistics\PKPStatisticsHelper; use PKP\validation\ValidatorFactory; use Slim\App; use Slim\Http\Request as SlimRequest; class APIHandler extends PKPHandler { /** @var App */ protected $_app; /** @var Request */ protected $_request; protected $_endpoints = []; /** @var SlimRequest */ protected $_slimRequest = null; /** @var string The endpoint pattern for this handler */ protected $_pathPattern; /** @var string The unique endpoint string for this handler */ protected $_handlerPath = null; /** @var bool Define if all the path building for admin api */ protected $_apiForAdmin = false; /** * Constructor */ public function __construct() { parent::__construct(); $this->_app = new App([ // Load custom response handler 'response' => function ($c) { return new APIResponse(); }, 'settings' => [ // we need access to route within middleware 'determineRouteBeforeAppMiddleware' => true, ] ]); $this->_app->add(new ApiAuthorizationMiddleware($this)); $this->_app->add(new ApiCsrfMiddleware($this)); $this->_app->add(new ApiTokenDecodingMiddleware($this)); // remove trailing slashes $this->_app->add(function ($request, $response, $next) { $uri = $request->getUri(); $path = $uri->getPath(); if ($path != '/' && substr($path, -1) == '/') { // path with trailing slashes to non-trailing counterpart $uri = $uri->withPath(substr($path, 0, -1)); if ($request->getMethod() == 'GET') { return $response->withRedirect((string)$uri, 301); } else { return $next($request->withUri($uri), $response); } } return $next($request, $response); }); // if pathinfo is disabled, rewrite URI to match Slim's expectation $app = $this->getApp(); $handler = $this; $this->_app->add(function ($request, $response, $next) use ($app, $handler) { $uri = $request->getUri(); $endpoint = trim($request->getQueryParam('endpoint') ?? ''); $path = $uri->getPath(); // pkp/pkp-lib#4919: PKP software routes with PATH_INFO (unaffected by // mod_rewrite) but Slim relies on REQUEST_URI. Inject PATH_INFO into // Slim for consistent behavior in URL rewriting scenarios. $newUri = $uri->withPath($_SERVER['PATH_INFO']); if ($uri != $newUri) { $handler->_slimRequest = $request->withUri($newUri); return $app->process($handler->_slimRequest, $response); } return $next($request, $response); }); // Allow remote requests to the API $this->_app->add(function ($request, $response, $next) { $response = $response->withHeader('Access-Control-Allow-Origin', '*'); return $next($request, $response); }); $this->_request = Application::get()->getRequest(); $this->setupEndpoints(); } /** * Return PKP request object * * @return Request */ public function getRequest() { return $this->_request; } /** * Return Slim request object * * @return SlimRequest|null */ public function getSlimRequest() { return $this->_slimRequest; } /** * Set Slim request object * */ public function setSlimRequest($slimRequest) { return $this->_slimRequest = $slimRequest; } /** * Get the Slim application. * * @return App */ public function getApp() { return $this->_app; } /** * Get the endpoint pattern for this handler * * Compiles the URI path pattern from the context, api version and the * unique string for the this handler. * * @return string */ public function getEndpointPattern() { if (isset($this->_pathPattern)) { return $this->_pathPattern; } if ($this->_apiForAdmin) { $this->_pathPattern = '/index/api/{version}/' . $this->_handlerPath; return $this->_pathPattern; } $this->_pathPattern = '/{contextPath}/api/{version}/' . $this->_handlerPath; return $this->_pathPattern; } /** * Get the entity ID for a specified parameter name. * (Parameter names are generally defined in authorization policies * * @return int|string|null */ public function getEntityId($parameterName) { assert(false); return null; } /** * setup endpoints */ public function setupEndpoints() { $app = $this->getApp(); $endpoints = $this->getEndpoints(); Hook::call('APIHandler::endpoints', [&$endpoints, $this]); foreach ($endpoints as $method => $definitions) { foreach ($definitions as $parameters) { $method = strtolower($method); $pattern = $parameters['pattern']; $handler = $parameters['handler']; $roles = $parameters['roles'] ?? null; $app->$method($pattern, $handler)->setName($handler[1]); if (!is_null($roles) && is_array($roles)) { $this->addRoleAssignment($roles, $handler[1]); } } } } /** * Returns the list of endpoints * * @return array */ public function getEndpoints() { return $this->_endpoints; } /** * Fetches parameter value * * @param string $parameterName * @param null|mixed $default * */ public function getParameter($parameterName, $default = null) { $slimRequest = $this->getSlimRequest(); if ($slimRequest == null) { return $default; } $route = $slimRequest->getAttribute('route'); // we probably have an invalid url if route is null if (!is_null($route)) { $arguments = $route->getArguments(); if (isset($arguments[$parameterName])) { return $arguments[$parameterName]; } $queryParams = $slimRequest->getQueryParams(); if (isset($queryParams[$parameterName])) { return $queryParams[$parameterName]; } } return $default; } /** * Convert a query parameter to an array * * This method will convert a query parameter to an array, and * supports a comma-separated list of values */ protected function paramToArray($value): array { if (is_array($value)) { return $value; } elseif (is_string($value)) { return explode(',', $value); } return [$value]; } /** * Convert string values in boolean, integer and number parameters to their * appropriate type when the string is in a recognizable format. * * Converted booleans: False: "0", "false". True: "true", "1" * Converted integers: Anything that passes ctype_digit() * Converted floats: Anything that passes is_numeric() * * Empty strings will be converted to null. * * @param string $schema One of the SCHEMA_... constants * @param array $params Key/value parameters to be validated * * @return array Converted parameters */ public function convertStringsToSchema($schema, $params) { $schema = Services::get('schema')->get($schema); foreach ($params as $paramName => $paramValue) { if (!property_exists($schema->properties, $paramName)) { continue; } if (!empty($schema->properties->{$paramName}->multilingual)) { foreach ($paramValue as $localeKey => $localeValue) { $params[$paramName][$localeKey] = $this->_convertStringsToSchema( $localeValue, $schema->properties->{$paramName}->type, $schema->properties->{$paramName} ); } } else { $params[$paramName] = $this->_convertStringsToSchema( $paramValue, $schema->properties->{$paramName}->type, $schema->properties->{$paramName} ); } } return $params; } /** * Helper function to convert a string to a specified type if it meets * certain conditions. * * This function can be called recursively on nested objects and arrays. * * @see self::convertStringsToTypes * * @param string $type One of boolean, integer or number */ private function _convertStringsToSchema($value, $type, $schema) { // Convert all empty strings to null except arrays (see note below) if (is_string($value) && !strlen($value) && $type !== 'array') { return null; } switch ($type) { case 'boolean': if (is_string($value)) { if ($value === 'true' || $value === '1') { return true; } elseif ($value === 'false' || $value === '0') { return false; } } break; case 'integer': if (is_string($value) && ctype_digit($value)) { return (int) $value; } break; case 'number': if (is_string($value) && is_numeric($value)) { return floatval($value); } break; case 'array': if (is_array($value)) { $newArray = []; if (is_array($schema->items)) { foreach ($schema->items as $i => $itemSchema) { $newArray[$i] = $this->_convertStringsToSchema($value[$i], $itemSchema->type, $itemSchema); } } else { foreach ($value as $i => $v) { $newArray[$i] = $this->_convertStringsToSchema($v, $schema->items->type, $schema->items); } } return $newArray; // An empty string is accepted as an empty array. This addresses the // issue where browsers strip empty arrays from post data before sending. // See: https://bugs.jquery.com/ticket/6481 } elseif (is_string($value) && !strlen($value)) { return []; } break; case 'object': if (is_array($value)) { // In some cases a property may be defined as an object but it may not // contain specific details about that object's properties. In these cases, // leave the properties alone. if (!property_exists($schema, 'properties')) { return $value; } $newObject = []; foreach ($schema->properties as $propName => $propSchema) { if (!isset($value[$propName])) { continue; } $newObject[$propName] = $this->_convertStringsToSchema($value[$propName], $propSchema->type, $propSchema); } return $newObject; } break; } return $value; } /** * A helper method to validate start and end date params for stats * API handlers * * 1. Checks the date formats * 2. Ensures a start date is not earlier than PKPStatisticsHelper::STATISTICS_EARLIEST_DATE * 3. Ensures an end date is no later than yesterday * 4. Ensures the start date is not later than the end date * * @param array $params The params to validate * @param string $dateStartParam Where the find the start date in the array of params * @param string $dateEndParam Where to find the end date in the array of params * * @return bool|string True if they validate, or a string which * contains the locale key of an error message. */ protected function _validateStatDates($params, $dateStartParam = 'dateStart', $dateEndParam = 'dateEnd') { $validator = ValidatorFactory::make( $params, [ $dateStartParam => [ 'date_format:Y-m-d', 'after_or_equal:' . PKPStatisticsHelper::STATISTICS_EARLIEST_DATE, 'before_or_equal:' . $dateEndParam, ], $dateEndParam => [ 'date_format:Y-m-d', 'before_or_equal:yesterday', 'after_or_equal:' . $dateStartParam, ], ], [ '*.date_format' => 'invalidFormat', $dateStartParam . '.after_or_equal' => 'tooEarly', $dateEndParam . '.before_or_equal' => 'tooLate', $dateStartParam . '.before_or_equal' => 'invalidRange', $dateEndParam . '.after_or_equal' => 'invalidRange', ] ); if ($validator->fails()) { $errors = $validator->errors()->getMessages(); if ((!empty($errors[$dateStartParam]) && in_array('invalidFormat', $errors[$dateStartParam])) || (!empty($errors[$dateEndParam]) && in_array('invalidFormat', $errors[$dateEndParam]))) { return 'api.stats.400.wrongDateFormat'; } if (!empty($errors[$dateStartParam]) && in_array('tooEarly', $errors[$dateStartParam])) { return 'api.stats.400.earlyDateRange'; } if (!empty($errors[$dateEndParam]) && in_array('tooLate', $errors[$dateEndParam])) { return 'api.stats.400.lateDateRange'; } if ((!empty($errors[$dateStartParam]) && in_array('invalidRange', $errors[$dateStartParam])) || (!empty($errors[$dateEndParam]) && in_array('invalidRange', $errors[$dateEndParam]))) { return 'api.stats.400.wrongDateRange'; } } return true; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\handler\APIHandler', '\APIHandler'); }