File: /var/www/ojs/plugins/reports/articles/ArticleReportPlugin.inc.php
<?php
/**
* @file plugins/reports/articles/ArticleReportPlugin.inc.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
*/
import('lib.pkp.classes.plugins.ReportPlugin');
class ArticleReportPlugin extends ReportPlugin {
/**
* @copydoc Plugin::register()
*/
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
*/
function getName() {
return 'ArticleReportPlugin';
}
/**
* @copydoc Plugin::getDisplayName()
*/
function getDisplayName() {
return __('plugins.reports.articles.displayName');
}
/**
* @copydoc Plugin::getDescriptionName()
*/
function getDescription() {
return __('plugins.reports.articles.description');
}
/**
* @copydoc ReportPlugin::display()
*/
function display($args, $request) {
$journal = $request->getJournal();
$acronym = PKPString::regexp_replace("/[^A-Za-z0-9 ]/", '', $journal->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));
$submissionDao = DAORegistry::getDAO('SubmissionDAO'); /* @var $submissionDao SubmissionDAO */
$editDecisionDao = DAORegistry::getDAO('EditDecisionDAO'); /* @var $editDecisionDao EditDecisionDAO */
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /* @var $stageAssignmentDao StageAssignmentDAO */
$userGroupDao = DAORegistry::getDAO('UserGroupDAO'); /* @var $userGroupDao UserGroupDAO */
$userDao = DAORegistry::getDAO('UserDAO'); /* @var $userDao UserDAO */
$sectionDao = DAORegistry::getDAO('SectionDAO'); /* @var $sectionDao SectionDAO */
$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 */
$editorUserGroupIds = array_map(function($userGroup) {
return $userGroup->getId();
}, array_filter($userGroupDao->getByContextId($journal->getId())->toArray(), function($userGroup) {
return in_array($userGroup->getRoleId(), [ROLE_ID_MANAGER, ROLE_ID_SUB_EDITOR]);
}));
AppLocale::requireComponents(LOCALE_COMPONENT_APP_EDITOR, LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_PKP_READER);
// 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 = [];
$submissions = $submissionDao->getByContextId($journal->getId());
$maxAuthors = $maxEditors = $maxDecisions = 0;
while ($submission = $submissions->next()) {
$publication = $submission->getCurrentPublication();
$maxAuthors = max($maxAuthors, count($publication->getData('authors')));
$editDecisions = $editDecisionDao->getEditorDecisions($submission->getId());
$statusMap = $submission->getStatusMap();
// Count the highest number of decisions per editor.
$editDecisionsPerEditor = [];
foreach ($editDecisions as $editDecision) {
$editorId = $editDecision['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 = $userDao->getById($userId);
$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 (!isset($sectionTitles[$sectionId])) {
$section = $sectionDao->getById($sectionId);
$sectionTitles[$sectionId] = $section->getLocalizedTitle();
}
// Get the controlled vocabulary data
$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' => $publication->getLocalizedFullTitle(),
'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')),
'sectionTitle' => $sectionTitles[$sectionId],
'language' => $publication->getData('locale'),
'coverage' => $publication->getLocalizedData('coverage'),
'rights' => $publication->getLocalizedData('rights'),
'source' => $publication->getLocalizedData('source'),
'subjects' => join(', ', $subjects[AppLocale::getLocale()] ?? $subjects[$submission->getLocale()] ?? []),
'type' => $publication->getLocalizedData('type'),
'disciplines' => join(', ', $disciplines[AppLocale::getLocale()] ?? $disciplines[$submission->getLocale()] ?? []),
'keywords' => join(', ', $keywords[AppLocale::getLocale()] ?? $keywords[$submission->getLocale()] ?? []),
'agencies' => join(', ', $agencies[AppLocale::getLocale()] ?? $agencies[$submission->getLocale()] ?? []),
'status' => $submission->getStatus() == 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(),
'editors' => $editors,
'decisions' => $editDecisions,
];
}
// 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'),
]);
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['editorId'] != $editorId) continue;
if ($j != $decisionCounter++) continue;
$latestDecision = $this->getDecisionMessage($decision['decision']);
$latestDecisionDate = $decision['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 $stageId int WORKFLOW_STAGE_ID_...
* @return string
*/
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 $decision int SUBMISSION_EDITOR_DECISION_... or SUBMISSION_EDITOR_RECOMMEND_...
* @return string
*/
function getDecisionMessage($decision) {
import('classes.workflow.EditorDecisionActionsManager'); // SUBMISSION_EDITOR_...
switch ($decision) {
case SUBMISSION_EDITOR_DECISION_ACCEPT:
return __('editor.submission.decision.accept');
case SUBMISSION_EDITOR_DECISION_PENDING_REVISIONS:
return __('editor.submission.decision.requestRevisions');
case SUBMISSION_EDITOR_DECISION_RESUBMIT:
return __('editor.submission.decision.resubmit');
case SUBMISSION_EDITOR_DECISION_DECLINE:
return __('editor.submission.decision.decline');
case SUBMISSION_EDITOR_DECISION_SEND_TO_PRODUCTION:
return __('editor.submission.decision.sendToProduction');
case SUBMISSION_EDITOR_DECISION_EXTERNAL_REVIEW:
return __('editor.submission.decision.sendExternalReview');
case SUBMISSION_EDITOR_DECISION_INITIAL_DECLINE:
return __('editor.submission.decision.decline');
case SUBMISSION_EDITOR_RECOMMEND_ACCEPT:
return __('editor.submission.recommendation.display', array('recommendation' => __('editor.submission.decision.accept')));
case SUBMISSION_EDITOR_RECOMMEND_DECLINE:
return __('editor.submission.recommendation.display', array('recommendation' => __('editor.submission.decision.decline')));
case SUBMISSION_EDITOR_RECOMMEND_PENDING_REVISIONS:
return __('editor.submission.recommendation.display', array('recommendation' => __('editor.submission.decision.requestRevisions')));
case SUBMISSION_EDITOR_RECOMMEND_RESUBMIT:
return __('editor.submission.recommendation.display', array('recommendation' => __('editor.submission.decision.resubmit')));
default:
return '';
}
}
}