[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
baru
/
lib
/
pkp
/
classes
/
sushi
/
[
Home
]
File: CounterR5Report.php
<?php /** * @file classes/sushi/CounterR5Report.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 CounterR5Report * * @ingroup sushi * * @brief Base class for COUNTER R5 reports * */ namespace PKP\sushi; use APP\core\Application; use APP\core\Services; use APP\facades\Repo; use DateInterval; use DatePeriod; use DateTime; use Exception; use PKP\components\forms\FieldSelect; use PKP\components\forms\FieldText; use PKP\context\Context; abstract class CounterR5Report { /** The access method */ public const ACCESS_METHOD = 'Regular'; /** Access type */ public const ACCESS_TYPE = 'OA_Gold'; /** ID of the context the report is for. */ public Context $context; /** Platform name, either context or site name (if configured so in the site settings). */ public string $platformName; /** The platform ID is configured in the site settings. Uses the context path if no platform ID is configured. */ public string $platformId; /** The customer ID is the ID of the institutional record in this context. */ public int $customerId; /** Name of the institutional record in this context. */ public string $institutionName; /** Available institution IDs (the ID of the institutional record and ROR). */ public ?array $institutionIds; /** The requested begin date */ public string $beginDate; /** The requested end date */ public string $endDate; /** Requested metric types */ public array $metricTypes = [ 'Total_Item_Investigations', 'Unique_Item_Investigations', 'Total_Item_Requests', 'Unique_Item_Requests' ]; /** Requested Year of Publication (YOP) */ public array $yearsOfPublication = []; /** Warnings displayed in the report header. */ public array $warnings = []; /** List of all filters requested and applied that will be displayed in the report header. */ protected array $filters = []; /** List of all attributes requested and applied that will be displayed in the report header. */ protected array $attributes = []; /** Additional columns/elements to include in the report. */ protected array $attributesToShow = []; /** The granularity of the usage data to include in the report. */ protected string $granularity = 'Month'; /** * Get report name defined by COUNTER. */ abstract public function getName(): string; /** * Get report ID defined by COUNTER. */ abstract public function getID(): string; /** * Get report release. */ public function getRelease(): string { return '5'; } /** * Get report description. */ abstract public function getDescription(): string; /** * Get API path defined by COUNTER for this report. */ abstract public function getAPIPath(): string; /** * Get request parameters supported by this report. */ abstract public function getSupportedParams(): array; /** * Get filters supported by this report. */ abstract public function getSupportedFilters(): array; /** * Get attributes supported by this report. */ abstract public function getSupportedAttributes(): array; /** * Get used filters that will be displayed in the report header. */ public function getFilters(): array { return $this->filters; } /** * Get used attributes that will be displayed in the report header. */ public function getAttributes(): array { return $this->attributes; } /** * Set filters based on the requested parameters. */ public function setFilters(array $filters): void { $this->filters = $filters; foreach ($filters as $filter) { switch ($filter['Name']) { case 'Metric_Type': $this->metricTypes = explode('|', $filter['Value']); break; } } } /** * Set attributes based on the requested parameters. */ public function setAttributes(array $attributes): void { $this->attributes = $attributes; foreach ($attributes as $attribute) { switch ($attribute['Name']) { case 'Attributes_To_Show': $this->attributesToShow = explode('|', $attribute['Value']); break; case 'granularity': $this->granularity = $attribute['Value']; break; } } } /** Get report items */ abstract public function getReportItems(): array; /** Get report items prepared for TSV report */ abstract public function getTSVReportItems(): array; /** Get TSV report column names */ abstract public function getTSVColumnNames(): array; /** Add a warning */ protected function addWarning(array $exception): void { $this->warnings[] = $exception; } /** * Process report parameters * * @throws SushiException */ public function processReportParams($request, $params): void { $this->context = $request->getContext(); $this->setPlatform($request->getSite()); $this->checkRequiredParams($params); $this->checkCustomerId($params); $this->checkDate($params); $this->checkSupportedParams($params); $this->checkFilters($params); $this->checkAttributes($params); } /** * Set the platform name and ID */ protected function setPlatform($site): void { $platformName = $this->context->getName($this->context->getPrimaryLocale()); $platformId = $this->context->getPath(); if ($site->getData('isSiteSushiPlatform')) { if ($site->getData('title')) { $platformName = $site->getTitle($site->getPrimaryLocale()); } $platformId = $site->getData('sushiPlatformID'); } $this->platformName = $platformName; $this->platformId = $platformId; } /** * Check if the required parameter are provided * * @throws SushiException */ protected function checkRequiredParams($params): void { $missingRequiredParams = []; if (!isset($params['customer_id'])) { $missingRequiredParams[] = 'customer_id'; } if (!isset($params['begin_date'])) { $missingRequiredParams[] = 'begin_date'; } if (!isset($params['end_date'])) { $missingRequiredParams[] = 'end_date'; } if (!empty($missingRequiredParams)) { throw new SushiException( 'Insufficient Information to Process Request', 1030, 'Fatal', __('sushi.exception.1030.missing', ['params' => implode(__('common.commaListSeparator'), $missingRequiredParams)]), 400 ); } } /** * Check if the customer ID is valid * * @throws SushiException */ protected function checkCustomerId($params): void { $institutionName = $institutionId = null; $customerId = $params['customer_id']; if (is_numeric($customerId)) { if ($customerId == 0) { $institutionName = 'The World'; } else { $institution = Repo::institution()->get($customerId); if (isset($institution) && $institution->getContextId() == $this->context->getId()) { $institutionId = []; $institutionName = $institution->getLocalizedName(); $ror = $institution->getROR(); if (isset($ror)) { $institutionId[] = ['Type' => 'ROR', 'Value' => $ror]; } $institutionId[] = ['Type' => 'Proprietary', 'Value' => $this->platformId . ':' . $customerId]; } } } if (!isset($institutionName)) { throw new SushiException( 'Insufficient Information to Process Request', 1030, 'Fatal', __('sushi.exception.1030.invalid', ['params' => 'customer_id']), 400 ); } $this->customerId = $customerId; $this->institutionName = $institutionName; if (isset($institutionId)) { $this->institutionIds = $institutionId; } } /** * Get the first month the usage data is available for COUNTER R5 reports. * It is either: * the next month of the COUNTER R5 start, or * this journal's first publication date. */ public static function getEarliestDate(): string { $context = Application::get()->getRequest()->getContext(); $statsService = Services::get('sushiStats'); $counterR5StartDate = $statsService->getEarliestDate(); $firstDatePublished = Repo::publication()->getDateBoundaries( Repo::publication() ->getCollector() ->filterByContextIds([$context->getId()]) )->min_date_published; $earliestDate = strtotime($firstDatePublished) > strtotime($counterR5StartDate) ? $firstDatePublished : $counterR5StartDate; $earliestDate = date('Y-m-01', strtotime($earliestDate . ' + 1 months')); return $earliestDate; } /** * Get the last possible date COUNTER R5 reports could exist for. * This is the last day of the previous month, * because the all stats for the previous month should be already compiled. */ public static function getLastDate(): string { return date('Y-m-d', strtotime('last day of previous month')); } /** * Validate the date parameters (begin_date, end_date) * * @throws SushiException */ protected function checkDate($params): void { $earliestDate = self::getEarliestDate(); $lastDate = self::getLastDate(); $beginDate = $params['begin_date']; $endDate = $params['end_date']; $invalidDateErrorMessages = []; // validate if begin_date and end_date in the format Y-m-d or Y-m if ((!$this->validateDate($beginDate) && !$this->validateDate($beginDate, 'Y-m')) || (!$this->validateDate($endDate) && !$this->validateDate($endDate, 'Y-m'))) { $invalidDateErrorMessages[] = __('sushi.exception.3020.dateFormat'); } // validate if begin_date is after the end_date, or // if it is the current of future month i.e. later than the lastDate if (strtotime($beginDate) >= strtotime($endDate) || strtotime($beginDate) > strtotime($lastDate)) { $invalidDateErrorMessages[] = __('sushi.exception.3020.dateRange'); } if (!empty($invalidDateErrorMessages)) { throw new SushiException( 'Invalid Date Arguments', 3020, 'Error', implode('. ', $invalidDateErrorMessages), 400 ); } // check for warnings if (strtotime($endDate) > strtotime($lastDate)) { $this->addWarning([ 'Code' => 3031, 'Severity' => 'Warning', 'Message' => 'Usage Not Ready for Requested Dates', 'Data' => __('sushi.exception.3031', ['beginDate' => $beginDate, 'endDate' => $endDate, 'lastDate' => $lastDate]) ]); $endDate = $lastDate; } if (strtotime($beginDate) < strtotime($earliestDate)) { $this->addWarning([ 'Code' => 3032, 'Severity' => 'Warning', 'Message' => 'Usage No Longer Available for Requested Dates', 'Data' => __('sushi.exception.3032', ['beginDate' => $beginDate, 'endDate' => $endDate, 'earliestDate' => $earliestDate]) ]); $beginDate = $earliestDate; } // check if requested dates are in the middle of a month, if their format is YYYY-MM-DD if ($this->validateDate($beginDate) || $this->validateDate($endDate)) { $beginDay = date('d', strtotime($beginDate)); $endDay = date('d', strtotime($endDate)); $lastDayOfEndMonth = date('t', strtotime($endDate)); if ($beginDay != '01' || $endDay != $lastDayOfEndMonth) { $this->addWarning([ 'Code' => 1, 'Severity' => 'Warning', 'Message' => 'Wrong Requested Dates', 'Data' => __('sushi.exception.1', ['beginDate' => $beginDate, 'endDate' => $endDate]) ]); } } $this->beginDate = date_format(date_create($beginDate), 'Y-m-01'); $this->endDate = date_format(date_create($endDate), 'Y-m-t'); } /** * Check if there are other, not recognized parameters in this context/for this report */ protected function checkSupportedParams($params): void { $supportedParameters = $this->getSupportedParams(); $unsupportedParameters = array_diff(array_keys($params), $supportedParameters); if (!empty($unsupportedParameters)) { $this->addWarning([ 'Code' => 3050, 'Severity' => 'Warning', 'Message' => 'Parameter Not Recognized in this Context', 'Data' => __('sushi.exception.3050', ['params' => implode(__('common.commaListSeparator'), $unsupportedParameters)]) ]); } } /** * Check required filters */ protected function checkFilters($params): void { $filters = [ ['Name' => 'Begin_Date', 'Value' => $this->beginDate], ['Name' => 'End_Date', 'Value' => $this->endDate], ]; $unsupportedFilterParams = []; $supportedFilters = $this->getSupportedFilters(); foreach ($supportedFilters as $supportedFilter) { if (isset($params[$supportedFilter['param']])) { $requestedFilterValues = explode('|', $params[$supportedFilter['param']]); $validFilters = array_intersect($requestedFilterValues, $supportedFilter['supportedValues']); if (!empty($validFilters)) { $filters[] = ['Name' => $supportedFilter['name'], 'Value' => implode('|', $validFilters)]; } if ($supportedFilter['name'] == 'YOP') { $unsupportedYOP = $validYOP = []; foreach ($requestedFilterValues as $yopValue) { if (!preg_match('/\d{4}|\d{4}-\d{4}/', $yopValue)) { $unsupportedYOP[] = $yopValue; } else { $validYOP[] = $yopValue; } } if (!empty($unsupportedYOP)) { $unsupportedFilterParams[] = $supportedFilter['param'] . '=' . implode('|', $unsupportedYOP); } if (!empty($validYOP)) { $filters[] = ['Name' => $supportedFilter['name'], 'Value' => implode('|', $validYOP)]; } } elseif ($supportedFilter['name'] == 'Item_Id') { $itemId = array_shift($requestedFilterValues); if (!is_numeric($itemId)) { $this->addWarning([ 'Code' => 2, 'Severity' => 'Warning', 'Message' => 'Invalid Item_Id', 'Data' => __('sushi.exception.2', ['itemId' => $itemId]) ]); } else { $filters[] = ['Name' => $supportedFilter['name'], 'Value' => $itemId]; if (!empty($requestedFilterValues)) { $this->addWarning([ 'Code' => 3, 'Severity' => 'Warning', 'Message' => 'Wrong Item_Id Value', 'Data' => __('sushi.exception.3', ['itemIdValues' => implode('|', $requestedFilterValues)]) ]); } } } else { $unsupportedFilterValues = array_diff($requestedFilterValues, $supportedFilter['supportedValues']); if (!empty($unsupportedFilterValues)) { $unsupportedFilterParams[] = $supportedFilter['param'] . '=' . implode('|', $unsupportedFilterValues); } } } } $this->setFilters($filters); // The Platform filter is only intended in cases where there is a single endpoint for multiple platforms. // This can be omitted if the service provides report data for only one platform. // Thus we will not consider it in the filter list we provide in the response, but will use an exception // if it is provided in the request and different that this platform name. if (isset($params['platform']) && $params['platform'] != $this->platformName) { $unsupportedFilterParams[] = 'platform=' . $params['platform']; } if (!empty($unsupportedFilterParams)) { $this->addWarning([ 'Code' => 3060, 'Severity' => 'Warning', 'Message' => 'Invalid ReportFilter Value', 'Data' => __('sushi.exception.3060', ['filterValues' => implode(__('common.commaListSeparator'), $unsupportedFilterParams)]) ]); } } /** * Check required attributes */ protected function checkAttributes($params): void { $attributes = $unsupportedAttributeParams = []; $supportedAttributes = $this->getSupportedAttributes(); foreach ($supportedAttributes as $supportedAttribute) { if (isset($params[$supportedAttribute['param']])) { $requestedAttributeValues = explode('|', $params[$supportedAttribute['param']]); $unsupportedAttributeValues = array_diff($requestedAttributeValues, $supportedAttribute['supportedValues']); if (!empty($unsupportedAttributeValues)) { $unsupportedAttributeParams[] = $supportedAttribute['param'] . '=' . implode('|', $unsupportedAttributeValues); } $validAttributes = array_intersect($requestedAttributeValues, $supportedAttribute['supportedValues']); if (!empty($validAttributes)) { $attributes[] = ['Name' => $supportedAttribute['name'], 'Value' => implode('|', $validAttributes)]; } } } if (!empty($unsupportedAttributeParams)) { $this->addWarning([ 'Code' => 3062, 'Severity' => 'Warning', 'Message' => 'Invalid ReportAttribute Value', 'Data' => __('sushi.exception.3062', ['attributeValues' => implode(__('common.commaListSeparator'), $unsupportedAttributeParams)]) ]); } // even if attributes are empty (e.g. for standard views), call setAttribute so that the predefined attributes can be set $this->setAttributes($attributes); } /** * Get report header */ public function getReportHeader(): array { $reportHeader = [ 'Created' => date('Y-m-d\TH:i:s\Z', time()), 'Created_By' => $this->platformName, 'Customer_ID' => (string) $this->customerId, 'Report_ID' => $this->getID(), 'Release' => $this->getRelease(), 'Report_Name' => $this->getName(), 'Institution_Name' => $this->institutionName, ]; if (!empty($this->institutionIds)) { $reportHeader['Institution_ID'] = $this->institutionIds; } $reportHeader['Report_Filters'] = $this->getFilters(); if (!empty($this->getAttributes())) { $reportHeader['Report_Attributes'] = $this->getAttributes(); } if (!empty($this->warnings)) { $reportHeader['Exceptions'] = $this->warnings; } return $reportHeader; } /** Get report header for TSV reports */ public function getTSVReportHeader(): array { $institutionIds = []; if (isset($this->institutionIds)) { foreach ($this->institutionIds as $institutionId) { if ($institutionId['Type'] == 'Proprietary') { $institutionIds[] = $institutionId['Value']; } else { $institutionIds[] = $institutionId['Type'] . ':' . $institutionId['Value']; } } } $reportHeaderInstitutionId = !empty($institutionIds) ? implode(';', $institutionIds) : ''; $reportHeaderMetricTypes = $beginDate = $endDate = ''; $reportHeaderFilters = $reportHeaderAttributes = []; foreach ($this->filters as $filter) { switch ($filter['Name']) { case ('Metric_Type'): $reportHeaderMetricTypes = implode(';', explode('|', $filter['Value'])); break; case ('Begin_Date'): $beginDate = $filter['Name'] . '=' . $filter['Value']; break; case ('End_Date'): $endDate = $filter['Name'] . '=' . $filter['Value']; break; default: $reportHeaderFilters[] = $filter['Name'] . '=' . $filter['Value']; } } foreach ($this->attributes as $attribute) { if ($attribute['Name'] == 'granularity') { $excludeMonthlyDetails = $attribute['Value'] == 'Month' ? 'False' : 'True'; $reportHeaderAttributes[] = 'Exclude_Monthly_Details' . '=' . $excludeMonthlyDetails; } else { $reportHeaderAttributes[] = $attribute['Name'] . '=' . $attribute['Value']; } } $exceptions = []; foreach ($this->warnings as $warning) { $exceptions[] = $warning['Code'] . ':' . $warning['Message'] . '(' . $warning['Data'] . ')'; } $reportHeader = [ ['Report_Name', $this->getName()], ['Report_ID', $this->getID()], ['Release', $this->getRelease()], ['Institution_Name', $this->institutionName], ['Institution_ID', $reportHeaderInstitutionId], ['Metric_Types', $reportHeaderMetricTypes], ['Report_Filters', implode(';', $reportHeaderFilters)], ['Report_Attributes', implode(';', $reportHeaderAttributes)], ['Exceptions', implode(';', $exceptions)], ['Reporting_Period', $beginDate . ';' . $endDate], ['Created', date('Y-m-d\TH:i:s\Z', time())], ['Created_By', $this->platformName], ]; return $reportHeader; } /** Get monthly period */ protected function getMonthlyDatePeriod(): DatePeriod { // every month for the given period needs to be considered $start = new DateTime($this->beginDate); $end = new DateTime($this->endDate); $interval = DateInterval::createFromDateString('1 month'); return new DatePeriod($start, $interval, $end); } /** * Validate date, check if the date is a valid date and in requested format */ protected function validateDate(string $date, string $format = 'Y-m-d'): bool { $d = DateTime::createFromFormat($format, $date); return $d && $d->format($format) === $date; } /** * Get report form fields common to all reports */ public static function getCommonReportSettingsFormFields(): array { $context = Application::get()->getRequest()->getContext(); $institutions = Repo::institution()->getCollector() ->filterByContextIds([$context->getId()]) ->getMany(); $institutionOptions = [['value' => '0', 'label' => 'The World']]; foreach ($institutions as $institution) { $institutionOptions[] = ['value' => $institution->getId(), 'label' => $institution->getLocalizedName()]; } $earliestDate = self::getEarliestDate(); $lastDate = self::getLastDate(); return [ new FieldText('begin_date', [ 'label' => __('manager.statistics.counterR5Report.settings.startDate'), 'description' => __('manager.statistics.counterR5Report.settings.date.startDate.description', ['earliestDate' => $earliestDate]), 'size' => 'small', 'isMultilingual' => false, 'isRequired' => true, 'value' => $earliestDate, 'groupId' => 'default', ]), new FieldText('end_date', [ 'label' => __('manager.statistics.counterR5Report.settings.endDate'), 'description' => __('manager.statistics.counterR5Report.settings.date.endDate.description', ['lastDate' => $lastDate]), 'size' => 'small', 'isMultilingual' => false, 'isRequired' => true, 'value' => $lastDate, 'groupId' => 'default', ]), new FieldSelect('customer_id', [ 'label' => __('manager.statistics.counterR5Report.settings.customerId'), 'options' => $institutionOptions, 'value' => '0', 'isRequired' => true, 'groupId' => 'default', ]), ]; } }