Files
OSJ/lib/pkp/pages/stats/PKPStatsHandler.php
CHIEFSOFT\ameye df3a033196 first commit
2024-06-08 17:09:23 -04:00

611 lines
21 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @file pages/stats/PKPStatsHandler.php
*
* Copyright (c) 2013-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 PKPStatsHandler
*
* @ingroup pages_stats
*
* @brief Handle requests for statistics pages.
*/
namespace PKP\pages\stats;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use APP\handler\Handler;
use APP\template\TemplateManager;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\plugins\PluginRegistry;
use PKP\security\authorization\ContextAccessPolicy;
use PKP\security\Role;
use PKP\statistics\PKPStatisticsHelper;
use PKP\sushi\CounterR5Report;
class PKPStatsHandler extends Handler
{
/** @copydoc PKPHandler::_isBackendPage */
public $_isBackendPage = true;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->addRoleAssignment(
[Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
['editorial', 'publications', 'context', 'users', 'reports', 'counterR5']
);
}
/**
* @see PKPHandler::authorize()
*
* @param PKPRequest $request
* @param array $args
* @param array $roleAssignments
*/
public function authorize($request, &$args, $roleAssignments)
{
$this->addPolicy(new ContextAccessPolicy($request, $roleAssignments));
return parent::authorize($request, $args, $roleAssignments);
}
//
// Public handler methods.
//
/**
* Display editorial stats about the submission workflow process
*
* @param array $args
* @param Request $request
*/
public function editorial($args, $request)
{
$dispatcher = $request->getDispatcher();
$context = $request->getContext();
if (!$context) {
$dispatcher->handle404();
}
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$dateStart = date('Y-m-d', strtotime('-91 days'));
$dateEnd = date('Y-m-d', strtotime('yesterday'));
$args = [
'contextIds' => [$context->getId()],
];
$totals = Services::get('editorialStats')->getOverview($args);
$averages = Services::get('editorialStats')->getAverages($args);
$dateRangeTotals = Services::get('editorialStats')->getOverview(
array_merge(
$args,
[
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
]
)
);
// Stats that should be converted to percentages
$percentageStats = [
'acceptanceRate',
'declineRate',
'declinedDeskRate',
'declinedReviewRate',
];
// Stats that should be indented in the table
$indentStats = [
'submissionsDeclinedDeskReject',
'submissionsDeclinedPostReview',
'daysToAccept',
'daysToReject',
'declinedDeskRate',
'declinedReviewRate',
'submissionsInProgress',
'submissionsImported',
];
// Compile table rows
$tableRows = [];
foreach ($totals as $i => $stat) {
$row = [
'key' => $stat['key'],
'name' => __($stat['name']),
'total' => $stat['value'],
'dateRange' => $dateRangeTotals[$i]['value'],
];
if (in_array($stat['key'], $indentStats)) {
$row['name'] = '' . $row['name'];
}
if (in_array($stat['key'], $percentageStats)) {
$row['total'] = ($stat['value'] * 100) . '%';
$row['dateRange'] = ($dateRangeTotals[$i]['value'] * 100) . '%';
}
$description = $this->_getStatDescription($stat['key']);
if ($description) {
$row['description'] = $description;
}
if (array_key_exists($stat['key'], $averages)
&& $averages[$stat['key']] !== -1
&& $row['total'] > 0) {
$row['total'] = __('stats.countWithYearlyAverage', [
'count' => $stat['value'],
'average' => $averages[$stat['key']],
]);
}
$tableRows[] = $row;
}
// Get the workflow stage counts
$activeByStage = [];
foreach (Application::getApplicationStages() as $stageId) {
$activeByStage[] = [
'name' => __(Application::getWorkflowStageName($stageId)),
'count' => Services::get('editorialStats')->countActiveByStages($stageId, $args),
'color' => Application::getWorkflowStageColor($stageId),
];
}
$statsComponent = new \PKP\components\PKPStatsEditorialPage(
$dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), 'stats/editorial'),
[
'activeByStage' => $activeByStage,
'averagesApiUrl' => $dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), 'stats/editorial/averages'),
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'dateRangeOptions' => [
[
'dateStart' => date('Y-m-d', strtotime('-91 days')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last90Days'),
],
[
'dateStart' => date('Y-m-d', strtotime(date('Y') . '-01-01')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.thisYear'),
],
[
'dateStart' => date('Y-m-d', strtotime((date('Y') - 1) . '-01-01')),
'dateEnd' => date('Y-m-d', strtotime((date('Y') - 1) . '-12-31')),
'label' => __('stats.dateRange.lastYear'),
],
[
'dateStart' => date('Y-m-d', strtotime((date('Y') - 2) . '-01-01')),
'dateEnd' => date('Y-m-d', strtotime((date('Y') - 1) . '-12-31')),
'label' => __('stats.dateRange.lastTwoYears'),
],
],
'percentageStats' => $percentageStats,
'tableColumns' => [
[
'name' => 'name',
'label' => __('common.name'),
'value' => 'name',
],
[
'name' => 'dateRange',
'label' => $dateStart . ' — ' . $dateEnd,
'value' => 'dateRange',
],
[
'name' => 'total',
'label' => __('stats.total'),
'value' => 'total',
],
],
'tableRows' => $tableRows,
]
);
$templateMgr->setLocaleKeys([
'stats.descriptionForStat',
'stats.countWithYearlyAverage',
]);
$templateMgr->setState($statsComponent->getConfig());
$templateMgr->assign([
'pageComponent' => 'StatsEditorialPage',
'pageTitle' => __('stats.editorialActivity'),
]);
$templateMgr->display('stats/editorial.tpl');
}
/**
* Display published submissions statistics page
*
* @param PKPRequest $request
* @param array $args
*/
public function publications($args, $request)
{
$dispatcher = $request->getDispatcher();
$context = $request->getContext();
if (!$context) {
$dispatcher->handle404();
}
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$dateStart = date('Y-m-d', strtotime('-31 days'));
$dateEnd = date('Y-m-d', strtotime('yesterday'));
$count = 30;
$timeline = Services::get('publicationStats')->getTimeline(PKPStatisticsHelper::STATISTICS_DIMENSION_DAY, [
'assocTypes' => [Application::ASSOC_TYPE_SUBMISSION],
'contextIds' => [$context->getId()],
'count' => $count,
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
]);
$statsComponent = new \PKP\components\PKPStatsPublicationPage(
$dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), 'stats/publications'),
[
'timeline' => $timeline,
'timelineInterval' => PKPStatisticsHelper::STATISTICS_DIMENSION_DAY,
'timelineType' => 'abstract',
'tableColumns' => [
[
'name' => 'title',
'label' => __('common.title'),
],
[
'name' => 'abstractViews',
'label' => __('submission.abstractViews'),
'value' => 'abstractViews',
],
[
'name' => 'galleyViews',
'label' => __('stats.fileViews'),
'value' => 'galleyViews',
],
[
'name' => 'pdf',
'label' => __('stats.pdf'),
'value' => 'pdfViews',
],
[
'name' => 'html',
'label' => __('stats.html'),
'value' => 'htmlViews',
],
[
'name' => 'other',
'label' => __('common.other'),
'value' => 'otherViews',
],
[
'name' => 'total',
'label' => __('stats.total'),
'value' => 'total',
'orderBy' => 'total',
'initialOrderDirection' => true,
],
],
'count' => $count,
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'dateRangeOptions' => [
[
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last30Days'),
],
[
'dateStart' => date('Y-m-d', strtotime('-91 days')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last90Days'),
],
[
'dateStart' => date('Y-m-d', strtotime('-12 months')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last12Months'),
],
[
'dateStart' => '',
'dateEnd' => '',
'label' => __('stats.dateRange.allDates'),
],
],
'orderBy' => 'total',
'orderDirection' => true,
]
);
$geoAPIEndPoint = null;
$geoStatsSetting = $context->getEnableGeoUsageStats($request->getSite());
switch ($geoStatsSetting) {
case PKPStatisticsHelper::STATISTICS_SETTING_COUNTRY:
$geoAPIEndPoint = 'countries';
break;
case PKPStatisticsHelper::STATISTICS_SETTING_REGION:
$geoAPIEndPoint = 'regions';
break;
case PKPStatisticsHelper::STATISTICS_SETTING_CITY:
$geoAPIEndPoint = 'cities';
break;
}
$templateMgr->setState($statsComponent->getConfig());
$templateMgr->assign([
'pageComponent' => 'StatsPublicationsPage',
'pageTitle' => __('stats.publicationStats'),
'pageWidth' => TemplateManager::PAGE_WIDTH_WIDE,
'geoReportType' => $geoAPIEndPoint
]);
$templateMgr->display('stats/publications.tpl');
}
/**
* Display published submissions statistics page
*
* @param PKPRequest $request
* @param array $args
*/
public function context($args, $request)
{
$dispatcher = $request->getDispatcher();
$context = $request->getContext();
if (!$context) {
$dispatcher->handle404();
}
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$dateStart = date('Y-m-d', strtotime('-31 days'));
$dateEnd = date('Y-m-d', strtotime('yesterday'));
$timeline = Services::get('contextStats')->getTimeline(PKPStatisticsHelper::STATISTICS_DIMENSION_DAY, [
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'contextIds' => [$context->getId()]
]);
$statsComponent = new \PKP\components\PKPStatsContextPage(
$dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), 'stats/contexts/' . $context->getId()),
[
'timeline' => $timeline,
'timelineInterval' => PKPStatisticsHelper::STATISTICS_DIMENSION_DAY,
'tableColumns' => [
[
'name' => 'title',
'label' => __('common.title'),
],
[
'name' => 'total',
'label' => __('stats.total'),
'value' => 'total',
],
],
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'dateRangeOptions' => [
[
'dateStart' => $dateStart,
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last30Days'),
],
[
'dateStart' => date('Y-m-d', strtotime('-91 days')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last90Days'),
],
[
'dateStart' => date('Y-m-d', strtotime('-12 months')),
'dateEnd' => $dateEnd,
'label' => __('stats.dateRange.last12Months'),
],
[
'dateStart' => '',
'dateEnd' => '',
'label' => __('stats.dateRange.allDates'),
],
]
]
);
$templateMgr->setState($statsComponent->getConfig());
$templateMgr->assign([
'pageComponent' => 'StatsContextPage',
'pageTitle' => __('stats.contextStats'),
'pageWidth' => TemplateManager::PAGE_WIDTH_WIDE,
]);
$templateMgr->display('stats/context.tpl');
}
/**
* Display list of available COUNTER R5 reports
*/
public function counterR5(array $args, Request $request): void
{
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$apiUrl = $request->getDispatcher()->url($request, PKPApplication::ROUTE_API, $request->getContext()->getPath(), 'stats/sushi');
$context = $request->getContext();
$locales = $context->getSupportedFormLocaleNames();
$locales = array_map(fn (string $locale, string $name) => ['key' => $locale, 'label' => $name], array_keys($locales), $locales);
$counterReportForm = new \APP\components\forms\counter\CounterReportForm($apiUrl, $locales);
$counterReportsListPanel = new \PKP\components\listPanels\PKPCounterReportsListPanel(
'counterReportsListPanel',
__('manager.statistics.counterR5Reports'),
[
'apiUrl' => $apiUrl,
'form' => $counterReportForm,
]
);
$earliestDate = CounterR5Report::getEarliestDate();
$lastDate = CounterR5Report::getLastDate();
$templateMgr->setState([
'components' => [
$counterReportsListPanel->id => $counterReportsListPanel->getConfig(),
],
]);
$templateMgr->assign([
'pageComponent' => 'CounterReportsPage',
'usagePossible' => $lastDate > $earliestDate,
]);
$templateMgr->display('stats/counterReports.tpl');
}
/**
* Display users stats
*
*/
public function users(array $args, Request $request): void
{
$dispatcher = $request->getDispatcher();
$context = $request->getContext();
if (!$context) {
$dispatcher->handle404();
}
// The POST handler is here merely to serve a redirection URL to the Vue component
if ($request->isPost()) {
echo $dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), 'users/report', null, null, $request->getUserVars());
exit;
}
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$context = $request->getContext();
$selfUrl = $dispatcher->url($request, PKPApplication::ROUTE_PAGE, $context->getPath(), 'stats', 'users');
$reportForm = new \PKP\components\forms\statistics\users\ReportForm($selfUrl, $context);
$templateMgr->setState([
'components' => [
'usersReportForm' => $reportForm->getConfig()
]
]);
$templateMgr->assign([
'pageTitle' => __('stats.userStatistics'),
'pageComponent' => 'StatsUsersPage',
'userStats' => array_map(
function ($item) {
$item['name'] = __($item['name']);
return $item;
},
Repo::user()->getRolesOverview(Repo::user()->getCollector()->filterByContextIds(['contextId' => $context->getId()]))
),
]);
$templateMgr->display('stats/users.tpl');
}
/**
* Route to other Reports operations
*
* @param array $args
* @param PKPRequest $request
*/
public function reports($args, $request)
{
$path = array_shift($args);
switch ($path) {
case '':
case 'reports':
$this->displayReports($args, $request);
break;
case 'report':
$this->report($args, $request);
break;
default: assert(false);
}
}
/**
* Display report possibilities (report plugins)
*
* @param array $args
* @param PKPRequest $request
*/
public function displayReports($args, $request)
{
$dispatcher = $request->getDispatcher();
$context = $request->getContext();
if (!$context) {
$dispatcher->handle404();
}
$templateMgr = TemplateManager::getManager($request);
$this->setupTemplate($request);
$reportPlugins = PluginRegistry::loadCategory('reports');
$templateMgr->assign('reportPlugins', $reportPlugins);
$templateMgr->assign([
'pageTitle' => __('manager.statistics.reports'),
]);
$templateMgr->display('stats/reports.tpl');
}
/**
* Delegates to plugins operations
* related to report generation.
*
* @param array $args
* @param Request $request
*/
public function report($args, $request)
{
$this->setupTemplate($request);
$pluginName = $request->getUserVar('pluginName');
$reportPlugins = PluginRegistry::loadCategory('reports');
if ($pluginName == '' || !isset($reportPlugins[$pluginName])) {
$request->redirect(null, null, 'stats', 'reports');
}
$plugin = $reportPlugins[$pluginName];
$plugin->display($args, $request);
}
//
// Protected methods.
//
/**
* Get a description for stats that require one
*
* @param string $key
*/
protected function _getStatDescription($key)
{
switch ($key) {
case 'daysToDecision': return __('stats.description.daysToDecision');
case 'acceptanceRate': return __('stats.description.acceptRejectRate');
case 'declineRate': return __('stats.description.acceptRejectRate');
case 'submissionsSkipped': return __('stats.description.submissionsSkipped');
}
return '';
}
}