[ 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
/
api
/
v1
/
stats
/
sushi
/
[
Home
]
File: PKPStatsSushiHandler.php
<?php /** * @file api/v1/stats/sushi/PKPStatsSushiHandler.php * * Copyright (c) 2022 Simon Fraser University * Copyright (c) 2022 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class PKPStatsSushiHandler * * @ingroup api_v1_stats * * @brief Handle API requests for COUNTER R5 SUSHI statistics. * */ namespace PKP\API\v1\stats\sushi; use APP\core\Application; use APP\facades\Repo; use APP\sushi\PR; use APP\sushi\PR_P1; use PKP\core\APIResponse; use PKP\handler\APIHandler; 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\sushi\CounterR5Report; use PKP\sushi\SushiException; use PKP\validation\ValidatorFactory; use Slim\Http\Request as SlimHttpRequest; class PKPStatsSushiHandler extends APIHandler { /** @var bool Whether the API is public */ public $isPublic = true; /** * Constructor */ public function __construct() { $site = Application::get()->getRequest()->getSite(); $context = Application::get()->getRequest()->getContext(); if (($site->getData('isSushiApiPublic') !== null && !$site->getData('isSushiApiPublic')) || ($context->getData('isSushiApiPublic') !== null && !$context->getData('isSushiApiPublic'))) { $this->isPublic = false; } $this->_handlerPath = 'stats/sushi'; $roles = $this->isPublic ? null : [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER]; $this->_endpoints = [ 'GET' => $this->getGETDefinitions($roles) ]; parent::__construct(); } /** * Get this API's endpoints definitions */ protected function getGETDefinitions(array $roles = null): array { return [ [ 'pattern' => $this->getEndpointPattern() . '/status', 'handler' => [$this, 'getStatus'], 'roles' => $roles ], [ 'pattern' => $this->getEndpointPattern() . '/members', 'handler' => [$this, 'getMembers'], 'roles' => $roles ], [ 'pattern' => $this->getEndpointPattern() . '/reports', 'handler' => [$this, 'getReports'], 'roles' => $roles ], [ 'pattern' => $this->getEndpointPattern() . '/reports/pr', 'handler' => [$this, 'getReportsPR'], 'roles' => $roles ], [ 'pattern' => $this->getEndpointPattern() . '/reports/pr_p1', 'handler' => [$this, 'getReportsPR1'], 'roles' => $roles ], ]; } /** * @copydoc PKPHandler::authorize() */ public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextRequiredPolicy($request)); if (!$this->isPublic) { $this->addPolicy(new UserRolesRequiredPolicy($request), true); $rolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES); foreach ($roleAssignments as $role => $operations) { $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); } return parent::authorize($request, $args, $roleAssignments); } /** * Get the current status of the reporting service */ public function getStatus(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { $request = $this->getRequest(); $context = $request->getContext(); // use only the name in the context primary locale to be consistent $contextName = $context->getName($context->getPrimaryLocale()); return $response->withJson([ 'Description' => __('sushi.status.description', ['contextName' => $contextName]), 'Service_Active' => true, ], 200); } /** * Get the list of consortium members related to a Customer_ID */ public function getMembers(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { $request = $this->getRequest(); $context = $request->getContext(); $site = $request->getSite(); $params = $slimRequest->getQueryParams(); if (!isset($params['customer_id'])) { // error: missing required customer_id return $response->withJson([ 'Code' => 1030, 'Severity' => 'Fatal', 'Message' => 'Insufficient Information to Process Request', 'Data' => __('sushi.exception.1030.missing', ['params' => 'customer_id']) ], 400); } $platformId = $context->getPath(); if ($site->getData('isSiteSushiPlatform')) { $platformId = $site->getData('sushiPlatformID'); } $institutionName = $institutionId = null; $customerId = $params['customer_id']; if (is_numeric($customerId)) { $customerId = (int) $customerId; if ($customerId == 0) { $institutionName = 'The World'; } else { $institution = Repo::institution()->get($customerId); if (isset($institution) && $institution->getContextId() == $context->getId()) { $institutionId = []; $institutionName = $institution->getLocalizedName(); if (!empty($institution->getROR())) { $institutionId[] = ['Type' => 'ROR', 'Value' => $institution->getROR()]; } $institutionId[] = ['Type' => 'Proprietary', 'Value' => $platformId . ':' . $customerId]; } } } if (!isset($institutionName)) { // error: invalid customer_id return $response->withJson([ 'Code' => 1030, 'Severity' => 'Fatal', 'Message' => 'Insufficient Information to Process Request', 'Data' => __('sushi.exception.1030.invalid', ['params' => 'customer_id']) ], 400); } $item = [ 'Customer_ID' => $customerId, 'Name' => $institutionName, ]; if (isset($institutionId)) { $item['Institution_ID'] = $institutionId; } return $response->withJson([$item], 200); } /** * Get list of reports supported by the API */ public function getReports(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { $items = $this->getReportList(); return $response->withJson($items, 200); } /** * Get the application specific list of reports supported by the API */ protected function getReportList(): array { return [ [ 'Report_Name' => 'Platform Master Report', 'Report_ID' => 'PR', 'Release' => '5', 'Report_Description' => __('sushi.reports.pr.description'), 'Path' => 'reports/pr' ], [ 'Report_Name' => 'Platform Usage', 'Report_ID' => 'PR_P1', 'Release' => '5', 'Report_Description' => __('sushi.reports.pr_p1.description'), 'Path' => 'reports/pr_p1' ], ]; } /** * COUNTER 'Platform Usage' [PR_P1]. * A customizable report summarizing activity across the Platform (journal, press, or server). */ public function getReportsPR(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { return $this->getReportResponse(new PR(), $slimRequest, $response, $args); } /** * COUNTER 'Platform Master Report' [PR]. * This is a Standard View of the Platform Master Report that presents usage for the overall Platform broken down by Metric_Type */ public function getReportsPR1(SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { return $this->getReportResponse(new PR_P1(), $slimRequest, $response, $args); } /** Validate user input for TSV reports */ protected function _validateUserInput(CounterR5Report $report, array $params): array { $request = $this->getRequest(); $context = $request->getContext(); $earliestDate = CounterR5Report::getEarliestDate(); $lastDate = CounterR5Report::getLastDate(); $submissionIds = Repo::submission()->getCollector()->filterByContextIds([$context->getId()])->getIds()->implode(','); $rules = [ 'begin_date' => [ 'regex:/^\d{4}-\d{2}(-\d{2})?$/', 'after_or_equal:' . $earliestDate, 'before_or_equal:end_date', ], 'end_date' => [ 'regex:/^\d{4}-\d{2}(-\d{2})?$/', 'before_or_equal:' . $lastDate, 'after_or_equal:begin_date', ], 'item_id' => [ // TO-ASK: shell this rather be just validation for positive integer? 'in:' . $submissionIds, ], 'yop' => [ 'regex:/^\d{4}((\||-)\d{4})*$/', ], ]; $reportId = $report->getID(); if (in_array($reportId, ['PR', 'TR', 'IR'])) { $rules['metric_type'] = ['required']; } $errors = []; $validator = ValidatorFactory::make( $params, $rules, [ 'begin_date.regex' => __( 'manager.statistics.counterR5Report.settings.wrongDateFormat' ), 'end_date.regex' => __( 'manager.statistics.counterR5Report.settings.wrongDateFormat' ), 'begin_date.after_or_equal' => __( 'stats.dateRange.invalidStartDateMin' ), 'end_date.before_or_equal' => __( 'stats.dateRange.invalidEndDateMax' ), 'begin_date.before_or_equal' => __( 'stats.dateRange.invalidDateRange' ), 'end_date.after_or_equal' => __( 'stats.dateRange.invalidDateRange' ), 'item_id.*' => __( 'manager.statistics.counterR5Report.settings.wrongItemId' ), 'yop.regex' => __( 'manager.statistics.counterR5Report.settings.wrongYOPFormat' ), ] ); if ($validator->fails()) { $errors = $validator->errors()->getMessages(); } return $errors; } /** * Get the requested report */ protected function getReportResponse(CounterR5Report $report, SlimHttpRequest $slimRequest, APIResponse $response, array $args): APIResponse { $responseTSV = str_contains($slimRequest->getHeaderLine('Accept'), APIResponse::RESPONSE_TSV) ? true : false; $params = $slimRequest->getQueryParams(); if ($responseTSV) { $errors = $this->_validateUserInput($report, $params); if (!empty($errors)) { return $response->withJson($errors, 400); } } try { $report->processReportParams($this->getRequest(), $params); } catch (SushiException $e) { return $response->withJson($e->getResponseData(), $e->getHttpStatusCode()); } if ($responseTSV) { $reportHeader = $report->getTSVReportHeader(); $reportColumnNames = $report->getTSVColumnNames(); $reportItems = $report->getTSVReportItems(); // consider 3030 error (no usage available) $key = array_search('3030', array_column($report->warnings, 'Code')); if ($key !== false) { $error = $report->warnings[$key]['Code'] . ':' . $report->warnings[$key]['Message'] . '(' . $report->warnings[$key]['Data'] . ')'; foreach ($reportHeader as &$headerRow) { if (in_array('Exceptions', $headerRow)) { $headerRow[1] = $headerRow[1] == '' ? $error : $headerRow[1] . ';' . $error; } } } $report = array_merge($reportHeader, [['']], $reportColumnNames, $reportItems); return $response->withCSV($report, [], count($reportItems), APIResponse::RESPONSE_TSV); } $reportHeader = $report->getReportHeader(); $reportItems = $report->getReportItems(); return $response->withJson([ 'Report_Header' => $reportHeader, 'Report_Items' => $reportItems, ], 200); } }