<?php
/**
* @file plugins/reports/articles/ArticleReportPlugin.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 ArticleReportPlugin
*
* @ingroup plugins_reports_article
*
* @brief Article report plugin
*/
namespace APP\plugins\reports\articles;
use APP\decision\Decision;
use APP\facades\Repo;
use PKP\core\PKPString;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\plugins\ReportPlugin;
use PKP\security\Role;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\PKPSubmission;
use PKP\submission\SubmissionAgencyDAO;
use PKP\submission\SubmissionDisciplineDAO;
use PKP\submission\SubmissionKeywordDAO;
use PKP\submission\SubmissionSubjectDAO;
class ArticleReportPlugin extends ReportPlugin
{
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
$success = parent::register($category, $path, $mainContextId);
$this->addLocaleData();
return $success;
}
/**
* Get the name of this plugin. The name must be unique within
* its category.
*
* @return string name of plugin
*/
public function getName()
{
return 'ArticleReportPlugin';
}
/**
* @copydoc Plugin::getDisplayName()
*/
public function getDisplayName()
{
return __('plugins.reports.articles.displayName');
}
/**
* @copydoc Plugin::getDescriptionName()
*/
public function getDescription()
{
return __('plugins.reports.articles.description');
}
/**
* @copydoc ReportPlugin::display()
*/
public function display($args, $request)
{
$context = $request->getContext();
$acronym = PKPString::regexp_replace('/[^A-Za-z0-9 ]/', '', $context->getLocalizedAcronym());
// Prepare for UTF8-encoded CSV output.
header('content-type: text/comma-separated-values');
header('content-disposition: attachment; filename=articles-' . $acronym . '-' . date('Ymd') . '.csv');
$fp = fopen('php://output', 'wt');
// Add BOM (byte order mark) to fix UTF-8 in Excel
fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$submissionKeywordDao = DAORegistry::getDAO('SubmissionKeywordDAO'); /** @var SubmissionKeywordDAO $submissionKeywordDao */
$submissionSubjectDao = DAORegistry::getDAO('SubmissionSubjectDAO'); /** @var SubmissionSubjectDAO $submissionSubjectDao */
$submissionDisciplineDao = DAORegistry::getDAO('SubmissionDisciplineDAO'); /** @var SubmissionDisciplineDAO $submissionDisciplineDao */
$submissionAgencyDao = DAORegistry::getDAO('SubmissionAgencyDAO'); /** @var SubmissionAgencyDAO $submissionAgencyDao */
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany()
->toArray();
$editorUserGroupIds = array_map(function ($userGroup) {
return $userGroup->getId();
}, array_filter($userGroups, function ($userGroup) {
return in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]);
}));
// Load the data from the database and store it in an array.
// (This must be stored before display because we won't know the data
// dimensions until it has all been loaded.)
$results = $sectionTitles = [];
$collector = Repo::submission()->getCollector()->filterByContextIds([$context->getId()]);
$submissions = $collector->getMany();
$maxAuthors = $maxEditors = $maxDecisions = 0;
foreach ($submissions as $submission) {
$publication = $submission->getCurrentPublication();
$maxAuthors = max($maxAuthors, count($publication->getData('authors')));
$editDecisions = Repo::decision()->getCollector()
->filterBySubmissionIds([$submission->getId()])
->getMany();
$statusMap = $submission->getStatusMap();
// Count the highest number of decisions per editor.
$editDecisionsPerEditor = [];
foreach ($editDecisions as $editDecision) {
$editorId = $editDecision->getData('editorId');
$editDecisionsPerEditor[$editorId] = ($editDecisionsPerEditor[$editorId] ?? 0) + 1;
$maxDecisions = max($maxDecisions, $editDecisionsPerEditor[$editorId]);
}
// Load editor and decision information
$stageAssignmentsFactory = $stageAssignmentDao->getBySubmissionAndStageId($submission->getId());
$editors = $editorsById = [];
while ($stageAssignment = $stageAssignmentsFactory->next()) {
$userId = $stageAssignment->getUserId();
if (!in_array($stageAssignment->getUserGroupId(), $editorUserGroupIds)) {
continue;
}
if (isset($editors[$userId])) {
continue;
}
if (!isset($editorsById[$userId])) {
$editor = Repo::user()->get($userId, true);
$editorsById[$userId] = [
$editor->getLocalizedGivenName(),
$editor->getLocalizedFamilyName(),
$editor->getData('orcid'),
$editor->getEmail(),
];
}
$editors[$userId] = $editorsById[$userId];
$maxEditors = max($maxEditors, count($editors));
}
// Load section title information
$sectionId = $publication->getData('sectionId');
if ($sectionId && !isset($sectionTitles[$sectionId])) {
$section = Repo::section()->get($sectionId);
$sectionTitles[$sectionId] = $section->getLocalizedTitle();
}
$subjects = $submissionSubjectDao->getSubjects($submission->getCurrentPublication()->getId());
$disciplines = $submissionDisciplineDao->getDisciplines($submission->getCurrentPublication()->getId());
$keywords = $submissionKeywordDao->getKeywords($submission->getCurrentPublication()->getId());
$agencies = $submissionAgencyDao->getAgencies($submission->getCurrentPublication()->getId());
// Store the submission results
$results[] = [
'submissionId' => $submission->getId(),
'title' => htmlspecialchars($publication->getLocalizedFullTitle(null, 'html')),
'abstract' => html_entity_decode(strip_tags($publication->getLocalizedData('abstract'))),
'authors' => array_map(function ($author) {
return [
$author->getLocalizedGivenName(),
$author->getLocalizedFamilyName(),
$author->getData('orcid'),
$author->getData('country'),
$author->getLocalizedData('affiliation'),
$author->getData('email'),
$author->getData('url'),
html_entity_decode(strip_tags($author->getLocalizedData('biography'))),
];
}, $publication->getData('authors')->values()->toArray()),
'sectionTitle' => $sectionTitles[$sectionId] ?? '',
'language' => $publication->getData('locale'),
'coverage' => $publication->getLocalizedData('coverage'),
'rights' => $publication->getLocalizedData('rights'),
'source' => $publication->getLocalizedData('source'),
'subjects' => join(', ', $subjects[Locale::getLocale()] ?? $subjects[$submission->getLocale()] ?? []),
'type' => $publication->getLocalizedData('type'),
'disciplines' => join(', ', $disciplines[Locale::getLocale()] ?? $disciplines[$submission->getLocale()] ?? []),
'keywords' => join(', ', $keywords[Locale::getLocale()] ?? $keywords[$submission->getLocale()] ?? []),
'agencies' => join(', ', $agencies[Locale::getLocale()] ?? $agencies[$submission->getLocale()] ?? []),
'status' => $submission->getStatus() == PKPSubmission::STATUS_QUEUED ? $this->getStageLabel($submission->getStageId()) : __($statusMap[$submission->getStatus()]),
'url' => $request->url(null, 'workflow', 'access', $submission->getId()),
'doi' => $submission->getStoredPubId('doi'),
'dateSubmitted' => $submission->getDateSubmitted(),
'lastModified' => $submission->getLastModified(),
'firstPublished' => $submission->getOriginalPublication()?->getData('datePublished') ?? '',
'editors' => $editors,
'decisions' => $editDecisions->toArray(),
];
}
// Build and display the column headers.
$columns = [
__('article.submissionId'),
__('article.title'),
__('article.abstract')
];
$authorColumnCount = $editorColumnCount = $decisionColumnCount = 0;
for ($a = 1; $a <= $maxAuthors; $a++) {
$columns = array_merge($columns, $authorColumns = [
__('user.givenName') . ' (' . __('user.role.author') . " {$a})",
__('user.familyName') . ' (' . __('user.role.author') . " {$a})",
__('user.orcid') . ' (' . __('user.role.author') . " {$a})",
__('common.country') . ' (' . __('user.role.author') . " {$a})",
__('user.affiliation') . ' (' . __('user.role.author') . " {$a})",
__('user.email') . ' (' . __('user.role.author') . " {$a})",
__('user.url') . ' (' . __('user.role.author') . " {$a})",
__('user.biography') . ' (' . __('user.role.author') . " {$a})"
]);
$authorColumnCount = count($authorColumns);
}
$columns = array_merge($columns, [
__('section.title'),
__('common.language'),
__('article.coverage'),
__('submission.rights'),
__('submission.source'),
__('common.subjects'),
__('common.type'),
__('search.discipline'),
__('common.keywords'),
__('submission.supportingAgencies'),
__('common.status'),
__('common.url'),
__('metadata.property.displayName.doi'),
__('common.dateSubmitted'),
__('submission.lastModified'),
__('submission.firstPublished'),
]);
for ($e = 1; $e <= $maxEditors; $e++) {
$columns = array_merge($columns, $editorColumns = [
__('user.givenName') . ' (' . __('user.role.editor') . " {$e})",
__('user.familyName') . ' (' . __('user.role.editor') . " {$e})",
__('user.orcid') . ' (' . __('user.role.editor') . " {$e})",
__('user.email') . ' (' . __('user.role.editor') . " {$e})",
]);
$editorColumnCount = count($editorColumns);
for ($d = 1; $d <= $maxDecisions; $d++) {
$columns = array_merge($columns, $decisionColumns = [
__('submission.editorDecision') . " {$d} " . ' (' . __('user.role.editor') . " {$e})",
__('common.dateDecided') . " {$d} " . ' (' . __('user.role.editor') . " {$e})"
]);
$decisionColumnCount = count($decisionColumns);
}
}
fputcsv($fp, array_values($columns));
// Display the data rows.
foreach ($results as $result) {
$row = [];
foreach ($result as $column => $value) {
switch ($column) {
case 'authors':
for ($i = 0; $i < $maxAuthors; $i++) {
$row = array_merge($row, $value[$i] ?? array_fill(0, $authorColumnCount, ''));
}
break;
case 'editors':
$editorIds = array_keys($value);
$editorEntries = array_values($value);
for ($i = 0; $i < $maxEditors; $i++) {
$submissionHasThisEditor = isset($editorEntries[$i]);
$row = array_merge($row, $submissionHasThisEditor ? $editorEntries[$i] : array_fill(0, $editorColumnCount, ''));
for ($j = 0; $j < $maxDecisions; $j++) {
if (!$submissionHasThisEditor) {
$row = array_merge($row, array_fill(0, $decisionColumnCount, ''));
continue;
}
$editorId = $editorIds[$i];
$latestDecision = $latestDecisionDate = '';
$decisionCounter = 0;
foreach ($result['decisions'] as $decision) {
if ($decision->getData('editorId') != $editorId) {
continue;
}
if ($j != $decisionCounter++) {
continue;
}
$latestDecision = $this->getDecisionMessage($decision->getData('decision'));
$latestDecisionDate = $decision->getData('dateDecided');
}
$row = array_merge($row, [$latestDecision, $latestDecisionDate]);
}
}
break;
case 'decisions':
break; // Handled in the 'editors' case
default: $row[] = $value; // Other columns can be sent as they are.
}
}
fputcsv($fp, $row);
}
fclose($fp);
}
/**
* Get stage label
*
* @param int $stageId WORKFLOW_STAGE_ID_...
*
* @return string
*/
public function getStageLabel($stageId)
{
switch ($stageId) {
case WORKFLOW_STAGE_ID_SUBMISSION:
return __('submission.submission');
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
return __('submission.review');
case WORKFLOW_STAGE_ID_EDITING:
return __('submission.copyediting');
case WORKFLOW_STAGE_ID_PRODUCTION:
return __('submission.production');
}
return '';
}
/**
* Get decision message
*
* @param int $decision Decision::*...
*
* @return string
*/
public function getDecisionMessage($decision)
{
switch ($decision) {
case Decision::ACCEPT:
return __('editor.submission.decision.accept');
case Decision::PENDING_REVISIONS:
return __('editor.submission.decision.requestRevisions');
case Decision::RESUBMIT:
return __('editor.submission.decision.resubmit');
case Decision::DECLINE:
return __('editor.submission.decision.decline');
case Decision::SEND_TO_PRODUCTION:
return __('editor.submission.decision.sendToProduction');
case Decision::EXTERNAL_REVIEW:
return __('editor.submission.decision.sendExternalReview');
case Decision::INITIAL_DECLINE:
return __('editor.submission.decision.decline');
case Decision::RECOMMEND_ACCEPT:
return __('editor.submission.recommendation.display', ['recommendation' => __('editor.submission.decision.accept')]);
case Decision::RECOMMEND_DECLINE:
return __('editor.submission.recommendation.display', ['recommendation' => __('editor.submission.decision.decline')]);
case Decision::RECOMMEND_PENDING_REVISIONS:
return __('editor.submission.recommendation.display', ['recommendation' => __('editor.submission.decision.requestRevisions')]);
case Decision::RECOMMEND_RESUBMIT:
return __('editor.submission.recommendation.display', ['recommendation' => __('editor.submission.decision.resubmit')]);
default:
return '';
}
}
}
|