372 lines
16 KiB
PHP
372 lines
16 KiB
PHP
<?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 '';
|
|
}
|
|
}
|
|
}
|