1003 lines
39 KiB
PHP
1003 lines
39 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file plugins/generic/citationStyleLanguage/CitationStyleLanguagePlugin.php
|
|
*
|
|
* Copyright (c) 2017-2020 Simon Fraser University
|
|
* Copyright (c) 2017-2020 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class CitationStyleLanguagePlugin
|
|
*
|
|
* @ingroup plugins_generic_citationStyleLanguage
|
|
*
|
|
* @brief Citation Style Language plugin class.
|
|
*/
|
|
|
|
namespace APP\plugins\generic\citationStyleLanguage;
|
|
|
|
require_once(__DIR__ . '/lib/vendor/autoload.php');
|
|
|
|
use APP\author\Author;
|
|
use APP\core\Application;
|
|
use APP\facades\Repo;
|
|
use APP\issue\Issue;
|
|
use APP\monograph\Chapter;
|
|
use APP\monograph\ChapterDAO;
|
|
use APP\plugins\generic\citationStyleLanguage\pages\CitationStyleLanguageHandler;
|
|
use APP\publication\Publication;
|
|
use APP\publicationFormat\PublicationFormat;
|
|
use APP\submission\Submission;
|
|
use APP\template\TemplateManager;
|
|
use Exception;
|
|
use PKP\core\JSONMessage;
|
|
use PKP\core\PKPApplication;
|
|
use PKP\core\PKPRequest;
|
|
use PKP\db\DAORegistry;
|
|
use PKP\facades\Locale;
|
|
use PKP\linkAction\LinkAction;
|
|
use PKP\linkAction\request\AjaxModal;
|
|
use PKP\plugins\GenericPlugin;
|
|
use PKP\plugins\Hook;
|
|
use PKP\submission\SubmissionKeywordDAO;
|
|
use PKP\submission\SubmissionLanguageDAO;
|
|
use Seboettg\CiteProc\CiteProc;
|
|
use stdClass;
|
|
|
|
class CitationStyleLanguagePlugin extends GenericPlugin
|
|
{
|
|
/** @var array List of citation styles available */
|
|
public array $_citationStyles = [];
|
|
|
|
/** @var array List of citation download formats available */
|
|
public array $_citationDownloads = [];
|
|
|
|
/** @var string Name of the application */
|
|
public string $application;
|
|
|
|
protected bool $isBook = false;
|
|
protected bool $isChapter = false;
|
|
protected bool $isArticle = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->application = Application::get()->getName();
|
|
}
|
|
|
|
/**
|
|
* @copydoc Plugin::getDisplayName()
|
|
*/
|
|
public function getDisplayName()
|
|
{
|
|
return __('plugins.generic.citationStyleLanguage.displayName');
|
|
}
|
|
|
|
/**
|
|
* @copydoc Plugin::getDescription()
|
|
*/
|
|
public function getDescription()
|
|
{
|
|
return __('plugins.generic.citationStyleLanguage.description');
|
|
}
|
|
|
|
/**
|
|
* @copydoc Plugin::register()
|
|
*
|
|
* @param null|mixed $mainContextId
|
|
*/
|
|
public function register($category, $path, $mainContextId = null)
|
|
{
|
|
$success = parent::register($category, $path, $mainContextId);
|
|
if (Application::isUnderMaintenance()) {
|
|
return $success;
|
|
}
|
|
if ($success && $this->getEnabled($mainContextId)) {
|
|
Hook::add('PreprintHandler::view', [$this, 'getTemplateData']);
|
|
Hook::add('CatalogBookHandler::book', [$this, 'getTemplateData']);
|
|
Hook::add('ArticleHandler::view', [$this, 'getTemplateData']);
|
|
Hook::add('Templates::Article::Details', [$this, 'addCitationMarkup']);
|
|
Hook::add('Templates::Catalog::Book::Details', [$this, 'addCitationMarkup']);
|
|
Hook::add('Templates::Catalog::Chapter::Details', [$this, 'addCitationMarkup']);
|
|
Hook::add('LoadHandler', [$this, 'setPageHandler']);
|
|
}
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* Get list of citation styles available
|
|
*/
|
|
public function getCitationStyles(): array
|
|
{
|
|
if (!empty($this->_citationStyles)) {
|
|
return $this->_citationStyles;
|
|
}
|
|
|
|
$defaults = [
|
|
[
|
|
'id' => 'acm-sig-proceedings',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.acm-sig-proceedings'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'acs-nano',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.acs-nano'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'apa',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.apa'),
|
|
'isEnabled' => true,
|
|
'isPrimary' => true,
|
|
],
|
|
[
|
|
'id' => 'associacao-brasileira-de-normas-tecnicas',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.associacao-brasileira-de-normas-tecnicas'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'chicago-author-date',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.chicago-author-date'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'harvard-cite-them-right',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.harvard-cite-them-right'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'ieee',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.ieee'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'modern-language-association',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.modern-language-association'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'turabian-fullnote-bibliography',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.turabian-fullnote-bibliography'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'vancouver',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.vancouver'),
|
|
'isEnabled' => true,
|
|
],
|
|
[
|
|
'id' => 'ama',
|
|
'title' => __('plugins.generic.citationStyleLanguage.style.ama'),
|
|
'isEnabled' => true,
|
|
],
|
|
];
|
|
|
|
// If hooking in to add a custom .csl file, add a `useCsl` key to your
|
|
// style definition with the path to the file.
|
|
Hook::call('CitationStyleLanguage::citationStyleDefaults', [&$defaults, $this]);
|
|
$this->_citationStyles = $defaults;
|
|
|
|
return $this->_citationStyles;
|
|
}
|
|
|
|
/**
|
|
* Get the primary style name or default to the first available style
|
|
*/
|
|
public function getPrimaryStyleName(int $contextId): string
|
|
{
|
|
$primaryStyleName = $this->getSetting($contextId, 'primaryCitationStyle');
|
|
if ($primaryStyleName) {
|
|
return $primaryStyleName;
|
|
}
|
|
|
|
$styles = $this->getCitationStyles();
|
|
$primaryStyles = array_filter($styles, function ($style) {
|
|
return !empty($style['isPrimary']);
|
|
});
|
|
|
|
$primaryStyle = count($primaryStyles) ? array_shift($primaryStyles) : array_shift($styles);
|
|
|
|
return $primaryStyle['id'];
|
|
}
|
|
|
|
/**
|
|
* Get enabled citation styles
|
|
*/
|
|
public function getEnabledCitationStyles(int $contextId): array
|
|
{
|
|
$styles = $this->getCitationStyles();
|
|
$enabled = $this->getSetting($contextId, 'enabledCitationStyles');
|
|
if (!is_array($enabled)) {
|
|
return array_filter($styles, function ($style) {
|
|
return !empty($style['isEnabled']);
|
|
});
|
|
}
|
|
return array_filter($styles, function ($style) use ($enabled) {
|
|
return in_array($style['id'], $enabled);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get list of citation download formats available
|
|
*/
|
|
public function getCitationDownloads(): array
|
|
{
|
|
if (!empty($this->_citationDownloads)) {
|
|
return $this->_citationDownloads;
|
|
}
|
|
|
|
$defaults = [
|
|
[
|
|
'id' => 'ris',
|
|
'title' => __('plugins.generic.citationStyleLanguage.download.ris'),
|
|
'isEnabled' => true,
|
|
'useTemplate' => $this->getTemplateResource('citation-styles/ris.tpl'),
|
|
'fileExtension' => 'ris',
|
|
'contentType' => 'application/x-Research-Info-Systems',
|
|
],
|
|
[
|
|
'id' => 'bibtex',
|
|
'title' => __('plugins.generic.citationStyleLanguage.download.bibtex'),
|
|
'isEnabled' => true,
|
|
'fileExtension' => 'bib',
|
|
'contentType' => 'application/x-bibtex',
|
|
],
|
|
];
|
|
|
|
// If hooking in to add a custom .csl file, add a `useCsl` key to your
|
|
// style definition with the path to the file.
|
|
Hook::call('CitationStyleLanguage::citationDownloadDefaults', [&$defaults, $this]);
|
|
$this->_citationDownloads = $defaults;
|
|
|
|
return $this->_citationDownloads;
|
|
}
|
|
|
|
/**
|
|
* Get enabled citation styles
|
|
*/
|
|
public function getEnabledCitationDownloads(int $contextId): array
|
|
{
|
|
$downloads = $this->getCitationDownloads();
|
|
$enabled = $this->getSetting($contextId, 'enabledCitationDownloads');
|
|
if (!is_array($enabled)) {
|
|
return array_filter($downloads, function ($style) {
|
|
return !empty($style['isEnabled']);
|
|
});
|
|
}
|
|
return array_filter($downloads, function ($style) use ($enabled) {
|
|
return in_array($style['id'], $enabled);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pluck citation IDs from array of citations
|
|
*/
|
|
public function mapCitationIds(array $citations): array
|
|
{
|
|
return array_values(array_map(function ($citation) {
|
|
return $citation['id'];
|
|
}, $citations));
|
|
}
|
|
|
|
/**
|
|
* Get citation config for a citation ID (example: 'apa')
|
|
*/
|
|
public function getCitationStyleConfig(string $styleId): array
|
|
{
|
|
$styleConfigs = array_merge($this->getCitationStyles(), $this->getCitationDownloads());
|
|
$styleConfig = array_filter($styleConfigs, function ($styleConfig) use ($styleId) {
|
|
return $styleConfig['id'] === $styleId;
|
|
});
|
|
return array_shift($styleConfig);
|
|
}
|
|
|
|
protected function getPublicationTypeUrlPath(): string
|
|
{
|
|
return match ($this->application) {
|
|
'ojs2' => 'article',
|
|
'ops' => 'preprint',
|
|
'omp' => 'catalog',
|
|
default => ''
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Retrieve citation information for the article details template. This
|
|
* method is hooked in before a template displays.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getTemplateData(string $hookName, array $args): bool
|
|
{
|
|
$request = $args[0];
|
|
$templateMgr = TemplateManager::getManager($request);
|
|
|
|
$context = $request->getContext();
|
|
$contextId = $context->getId();
|
|
|
|
switch ($this->application) {
|
|
case 'ops':
|
|
$preprint = $args[1];
|
|
$publication = $args[2];
|
|
$citation = $this->getCitation($request, $preprint, $this->getPrimaryStyleName($contextId), null, $publication);
|
|
break;
|
|
case 'ojs2':
|
|
$issue = $args[1];
|
|
$submission = $args[2];
|
|
$publication = $args[3];
|
|
$chapter = null;
|
|
$citation = $this->getCitation($request, $submission, $this->getPrimaryStyleName($contextId), $issue, $publication);
|
|
break;
|
|
case 'omp':
|
|
/** @var Submission $submission */
|
|
$submission = & $args[1];
|
|
$publication = & $args[2];
|
|
$chapter = & $args[3];
|
|
$issue = null;
|
|
$citation = $this->getCitation($request, $submission, $this->getPrimaryStyleName($contextId), $issue, $publication);
|
|
break;
|
|
default:
|
|
throw new Exception('Unknown application!');
|
|
}
|
|
|
|
$citationArgs = [
|
|
'submissionId' => $submission->getId(),
|
|
'publicationId' => $publication->getId(),
|
|
];
|
|
if ($issue) {
|
|
$citationArgs['issueId'] = $issue->getId();
|
|
}
|
|
if ($chapter) {
|
|
$citationArgs['chapterId'] = $chapter->getId();
|
|
}
|
|
$citationArgsJson = $citationArgs;
|
|
$citationArgsJson['return'] = 'json';
|
|
|
|
$templateMgr->assign([
|
|
'citation' => $citation,
|
|
'citationArgs' => $citationArgs,
|
|
'citationArgsJson' => $citationArgsJson,
|
|
'citationStyles' => $this->getEnabledCitationStyles($contextId),
|
|
'citationDownloads' => $this->getEnabledCitationDownloads($contextId),
|
|
]);
|
|
|
|
$templateMgr->addStyleSheet(
|
|
'cslPluginStyles',
|
|
$request->getBaseUrl() . '/' . $this->getPluginPath() . '/css/citationStyleLanguagePlugin.css',
|
|
[
|
|
'priority' => TemplateManager::STYLE_SEQUENCE_LAST,
|
|
'contexts' => ['frontend'],
|
|
'inline' => false,
|
|
]
|
|
);
|
|
|
|
$templateMgr->addJavaScript(
|
|
'citationStyleLanguage',
|
|
$request->getBaseUrl() . '/' . $this->getPluginPath() . '/js/articleCitation.js'
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
public function addCitationMarkup(string $hookName, array $args): bool
|
|
{
|
|
$smarty = & $args[1];
|
|
$output = & $args[2];
|
|
$output .= $smarty->fetch($this->getTemplateResource('citationblock.tpl'));
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a specified citation for a given article, book or chapter
|
|
*
|
|
* This citation format follows the csl-json schema and takes some direction
|
|
* from existing CSL mappings documented by Zotero and Mendeley.
|
|
*
|
|
* @see CSL-json schema https://github.com/citation-style-language/schema#csl-json-schema
|
|
* @see Zotero's mappings https://aurimasv.github.io/z2csl/typeMap.xml#map-journalArticle
|
|
* @see Mendeley's mappings http://support.mendeley.com/customer/portal/articles/364144-csl-type-mapping
|
|
*
|
|
* @param string $citationStyle Name of the citation style to use.
|
|
* @param Issue $issue Optional. Will fetch from db if not passed.
|
|
* @param Publication $publication Optional. A particular version
|
|
* @param Chapter $chapter Optional. OMP chapter pages only.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getCitation(PKPRequest $request, Submission $submission, string $citationStyle = 'apa', ?Issue $issue = null, ?Publication $publication = null, ?Chapter $chapter = null): string
|
|
{
|
|
$publication ??= $submission->getCurrentPublication();
|
|
$context = $request->getContext();
|
|
if (!$chapter) {
|
|
$chapter = $this->application === 'omp' ? $this->getChapter($request, $publication) : null;
|
|
}
|
|
$this->setDocumentType($chapter);
|
|
/** @var SubmissionKeywordDAO $submissionKeywordDao */
|
|
$submissionKeywordDao = DAORegistry::getDAO('SubmissionKeywordDAO');
|
|
$keywords = $submissionKeywordDao->getKeywords($publication->getId(), [Locale::getSupportedLocales()]);
|
|
|
|
$citationData = new stdClass();
|
|
|
|
if ($this->isArticle) {
|
|
$citationData->type = ($this->application === 'ojs2' ? 'article-journal' : 'article');
|
|
$citationData->risType = 'JOUR';
|
|
$citationData->id = $submission->getId();
|
|
$citationData->title = $publication->getLocalizedFullTitle();
|
|
$citationData->{'container-title'} = $context->getLocalizedName();
|
|
$issueId = $publication->getData('issueId');
|
|
$issue ??= $issueId ? Repo::issue()->get($issueId) : null;
|
|
if ($issue) {
|
|
$citationData->volume = htmlspecialchars($issue->getData('volume'));
|
|
// Zotero prefers issue and Mendeley uses `number` to store revisions
|
|
$citationData->issue = htmlspecialchars($issue->getData('number'));
|
|
}
|
|
|
|
if ($sectionId = $publication->getData('sectionId')) {
|
|
$section = Repo::section()->get($sectionId);
|
|
if ($section && !$section->getHideTitle()) {
|
|
$citationData->section = htmlspecialchars($section->getTitle($context->getPrimaryLocale()));
|
|
}
|
|
}
|
|
$citationData->keywords = $keywords[Locale::getLocale()] ?? [];
|
|
if ($publication->getData('pages')) {
|
|
$citationData->page = htmlspecialchars($publication->getData('pages'));
|
|
}
|
|
$citationData->abstract = htmlspecialchars(strip_tags($publication->getLocalizedData('abstract')));
|
|
$citationData = $this->setArticleAuthors($citationData, $publication, $context);
|
|
$citationData->URL = $request->getDispatcher()->url($request, PKPApplication::ROUTE_PAGE, null, $this->getPublicationTypeUrlPath(), 'view', $submission->getBestId());
|
|
if ($publication->getDoi()) {
|
|
$citationData->DOI = $publication->getDoi();
|
|
}
|
|
} elseif ($this->isBook) {
|
|
$citationData->type = 'book';
|
|
$citationData->risType = 'BOOK';
|
|
$citationData->id = $submission->getId();
|
|
$citationData->title = $publication->getLocalizedFullTitle();
|
|
$citationData = $this->addSeriesInformation($citationData, $publication);
|
|
$citationData->publisher = htmlspecialchars($context->getLocalizedName());
|
|
$citationData->keywords = $keywords[Locale::getLocale()] ?? [];
|
|
if ($publication->getData('pages')) {
|
|
$citationData->page = htmlspecialchars($publication->getData('pages'));
|
|
}
|
|
$citationData->abstract = htmlspecialchars(strip_tags($publication->getLocalizedData('abstract')));
|
|
$citationData->serialNumber = $this->getSerialNumber($publication);
|
|
$citationData = $this->setBookAuthors($citationData, $publication, $context);
|
|
$citationData->URL = $request->getDispatcher()->url($request, PKPApplication::ROUTE_PAGE, null, $this->getPublicationTypeUrlPath(), 'book', $submission->getBestId());
|
|
if ($publication->getDoi()) {
|
|
$citationData->DOI = $publication->getDoi();
|
|
}
|
|
} elseif ($this->isChapter) {
|
|
$citationData->type = 'chapter';
|
|
$citationData->risType = 'CHAP';
|
|
$citationData->id = $chapter->getSourceChapterId();
|
|
$citationData->title = $chapter->getLocalizedFullTitle();
|
|
$citationData->{'container-title'} = $publication->getLocalizedFullTitle();
|
|
$citationData = $this->addSeriesInformation($citationData, $publication);
|
|
$citationData->publisher = htmlspecialchars($context->getLocalizedName());
|
|
if ($chapter->getPages()) {
|
|
$citationData->page = htmlspecialchars($chapter->getPages());
|
|
}
|
|
$citationData->abstract = htmlspecialchars(strip_tags($chapter->getLocalizedData('abstract')));
|
|
$citationData->serialNumber = $this->getSerialNumber($publication);
|
|
$citationData = $this->setBookChapterAuthors($citationData, $publication, $context, $chapter);
|
|
$citationData->URL = $request->getDispatcher()->url($request, PKPApplication::ROUTE_PAGE, null, $this->getPublicationTypeUrlPath(), 'book', [$submission->getBestId(), 'chapter', $chapter->getSourceChapterId()]);
|
|
|
|
if ($chapter->getDoi()) {
|
|
$citationData->DOI = $chapter->getDoi();
|
|
}
|
|
} else {
|
|
throw new Exception('Unknown submission content type!');
|
|
}
|
|
|
|
/** @var SubmissionLanguageDAO $submissionLanguageDao */
|
|
$submissionLanguageDao = DAORegistry::getDAO('SubmissionLanguageDAO');
|
|
$languages = $submissionLanguageDao->getLanguages($publication->getId(), [Locale::getLocale()]);
|
|
if (array_key_exists(Locale::getLocale(), $languages)) {
|
|
$citationData->languages = $languages[Locale::getLocale()];
|
|
}
|
|
|
|
$citationData->{'publisher-place'} = $this->getSetting($context->getId(), 'publisherLocation');
|
|
$abbreviation = $context->getData('abbreviation', $context->getPrimaryLocale()) ?? $context->getData('acronym', $context->getPrimaryLocale());
|
|
if ($abbreviation) {
|
|
$citationData->{'container-title-short'} = htmlspecialchars($abbreviation);
|
|
}
|
|
|
|
$citationData->accessed = new stdClass();
|
|
$citationData->accessed->raw = date('Y-m-d');
|
|
|
|
if ($publication->getData('datePublished')) {
|
|
$citationData->issued = new stdClass();
|
|
$citationData->issued->raw = htmlspecialchars($publication->getData('datePublished'));
|
|
$publishedPublications = $submission->getPublishedPublications();
|
|
if (count($publishedPublications) > 1) {
|
|
$originalPublication = array_reduce($publishedPublications, function ($a, $b) {
|
|
return $a && $a->getId() < $b->getId() ? $a : $b;
|
|
});
|
|
$originalDate = $originalPublication->getData('datePublished');
|
|
if ($originalDate && $originalDate !== $publication->getData('datePublished')) {
|
|
$citationData->{'original-date'} = new stdClass();
|
|
$citationData->{'original-date'}->raw = htmlspecialchars($originalPublication->getData('datePublished'));
|
|
}
|
|
}
|
|
} elseif ($this->isArticle && $issue?->getPublished()) {
|
|
$citationData->issued = new stdClass();
|
|
$citationData->issued->raw = htmlspecialchars($issue->getDatePublished());
|
|
}
|
|
|
|
Hook::call('CitationStyleLanguage::citation', [&$citationData, &$citationStyle, $submission, $issue, $context, $publication]);
|
|
|
|
$citation = '';
|
|
|
|
// Determine whether to use citeproc-php or a custom template to render
|
|
// the citation
|
|
$styleConfig = $this->getCitationStyleConfig($citationStyle);
|
|
if (!empty($styleConfig)) {
|
|
if (!empty($styleConfig['useTemplate'])) {
|
|
$templateMgr = TemplateManager::getManager($request);
|
|
$templateMgr->assign([
|
|
'citationData' => $citationData,
|
|
'citationStyle' => $citationStyle,
|
|
'publication' => $publication,
|
|
]);
|
|
switch ($this->application) {
|
|
case 'ops':
|
|
case 'ojs2':
|
|
$templateMgr->assign([
|
|
'article' => $submission,
|
|
'issue' => $issue,
|
|
'journal' => $context,
|
|
]);
|
|
break;
|
|
case 'omp':
|
|
$templateMgr->assign([
|
|
'book' => $submission,
|
|
'press' => $context,
|
|
]);
|
|
break;
|
|
default: throw new Exception('Unknown application!');
|
|
}
|
|
|
|
$citation = $templateMgr->fetch($styleConfig['useTemplate']);
|
|
} else {
|
|
$style = $this->loadStyle($styleConfig);
|
|
if ($style) {
|
|
// Determine what locale to use. Try in order:
|
|
// - xx_YY
|
|
// - xx
|
|
// Fall back English if none found.
|
|
foreach ([
|
|
str_replace('_', '-', substr(Locale::getLocale(), 0, 5)),
|
|
substr(Locale::getLocale(), 0, 2),
|
|
'en-US',
|
|
] as $tryLocale) {
|
|
if (file_exists(dirname(__FILE__) . '/lib/vendor/citation-style-language/locales/locales-' . $tryLocale . '.xml')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Clickable URL and DOI including affixes
|
|
$additionalMarkup = [
|
|
'DOI' => [
|
|
'function' => function ($item, $renderedValue) {
|
|
return '<a href="https://doi.org/' . $item->DOI . '">' . $renderedValue . '</a>';
|
|
},
|
|
'affixes' => true
|
|
],
|
|
'URL' => [
|
|
'function' => function ($item, $renderedValue) {
|
|
return '<a href="' . $item->URL . '">' . $renderedValue . '</a>';
|
|
},
|
|
'affixes' => true
|
|
],
|
|
];
|
|
|
|
$citeProc = new CiteProc($style, $tryLocale, $additionalMarkup);
|
|
$citation = $citeProc->render([$citationData], 'bibliography');
|
|
}
|
|
}
|
|
}
|
|
|
|
return $citation;
|
|
}
|
|
|
|
/**
|
|
* Load a CSL style and return the contents as a string
|
|
*/
|
|
public function loadStyle(array $styleConfig): false|string
|
|
{
|
|
$path = empty($styleConfig['useCsl'])
|
|
? $this->getPluginPath() . '/citation-styles/' . $styleConfig['id'] . '.csl'
|
|
: $styleConfig['useCsl'];
|
|
return file_get_contents($path);
|
|
}
|
|
|
|
/**
|
|
* Download a citation format
|
|
*
|
|
* Downloadable citation formats can be used to import into third-party
|
|
* software.
|
|
*
|
|
* @param string $citationStyle Name of the citation style to use.
|
|
* @param Issue $issue Optional. Will fetch from db if not passed.
|
|
* @param $publication Publication Optional.
|
|
* @param $chapter Chapter Optional. OMP chapter pages only.
|
|
*/
|
|
public function downloadCitation(PKPRequest $request, Submission $submission, string $citationStyle = 'ris', ?Issue $issue = null, ?Publication $publication = null, ?Chapter $chapter = null)
|
|
{
|
|
if ($this->isArticle) {
|
|
$issueId = $submission->getCurrentPublication()->getData('issueId');
|
|
$issue ??= $issueId ? Repo::issue()->get($issueId) : null;
|
|
}
|
|
|
|
$styleConfig = $this->getCitationStyleConfig($citationStyle);
|
|
if (empty($styleConfig)) {
|
|
return false;
|
|
}
|
|
|
|
$citation = trim(strip_tags($this->getCitation($request, $submission, $citationStyle, $issue, $publication, $chapter)));
|
|
|
|
// TODO this is likely going to cause an error in a citation some day,
|
|
// but is necessary to get the .ris downloadable format working. The
|
|
// CSL language doesn't seem to offer a way to indicate a line break.
|
|
// See: https://github.com/citation-style-language/styles/issues/2831
|
|
$citation = str_replace('\n', "\n", $citation);
|
|
|
|
$encodedFilename = urlencode(substr(($publication ? $publication->getLocalizedTitle() : ''), 0, 60)) . '.' . $styleConfig['fileExtension'];
|
|
|
|
header("Content-Disposition: attachment; filename*=UTF-8''\"{$encodedFilename}\"");
|
|
header('Content-Type: ' . $styleConfig['contentType']);
|
|
echo $citation;
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* @see Plugin::getActions()
|
|
*/
|
|
public function getActions($request, $actionArgs)
|
|
{
|
|
$actions = parent::getActions($request, $actionArgs);
|
|
|
|
if (!$this->getEnabled()) {
|
|
return $actions;
|
|
}
|
|
|
|
$router = $request->getRouter();
|
|
$linkAction = new LinkAction(
|
|
'settings',
|
|
new AjaxModal(
|
|
$router->url(
|
|
$request,
|
|
null,
|
|
null,
|
|
'manage',
|
|
null,
|
|
[
|
|
'verb' => 'settings',
|
|
'plugin' => $this->getName(),
|
|
'category' => 'generic',
|
|
]
|
|
),
|
|
$this->getDisplayName()
|
|
),
|
|
__('manager.plugins.settings'),
|
|
null
|
|
);
|
|
|
|
array_unshift($actions, $linkAction);
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* @see Plugin::manage()
|
|
*/
|
|
public function manage($args, $request)
|
|
{
|
|
switch ($request->getUserVar('verb')) {
|
|
case 'settings':
|
|
$form = new CitationStyleLanguageSettingsForm($this);
|
|
|
|
if ($request->getUserVar('save')) {
|
|
$form->readInputData();
|
|
if ($form->validate()) {
|
|
$form->execute();
|
|
return new JSONMessage(true);
|
|
}
|
|
}
|
|
|
|
$form->initData();
|
|
return new JSONMessage(true, $form->fetch($request));
|
|
}
|
|
return parent::manage($args, $request);
|
|
}
|
|
|
|
/**
|
|
* Route requests for the citation styles to custom page handler
|
|
*/
|
|
public function setPageHandler(string $hookName, array $params): bool
|
|
{
|
|
$page = & $params[0];
|
|
$handler = & $params[3];
|
|
if ($this->getEnabled() && $page === 'citationstylelanguage') {
|
|
$handler = new CitationStyleLanguageHandler($this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getEditorGroups(int $contextId): array
|
|
{
|
|
return $this->getSetting($contextId, 'groupEditor') ?? [];
|
|
}
|
|
|
|
public function getTranslatorGroups(int $contextId): array
|
|
{
|
|
return $this->getSetting($contextId, 'groupTranslator') ?? [];
|
|
}
|
|
|
|
public function getAuthorGroups(int $contextId): array
|
|
{
|
|
return $this->getSetting($contextId, 'groupAuthor') ?? [];
|
|
}
|
|
|
|
public function getChapterAuthorGroups(int $contextId): array
|
|
{
|
|
return $this->getSetting($contextId, 'groupChapterAuthor') ?? [];
|
|
}
|
|
|
|
public function getSerialNumber(Publication $publication): array
|
|
{
|
|
$serialNumber = [];
|
|
$publicationFormats = $publication->getData('publicationFormats');
|
|
/** @var PublicationFormat $publicationFormat */
|
|
foreach ($publicationFormats as $publicationFormat) {
|
|
if ($publicationFormat->getIsApproved()) {
|
|
$identificationCodes = $publicationFormat->getIdentificationCodes();
|
|
$identificationCodes = $identificationCodes->toArray();
|
|
foreach ($identificationCodes as $identificationCode) {
|
|
$serialNumber[] = htmlspecialchars($identificationCode->getValue());
|
|
}
|
|
}
|
|
}
|
|
return $serialNumber;
|
|
}
|
|
|
|
protected function addSeriesInformation(stdClass $citationData, Publication $publication): stdClass
|
|
{
|
|
$seriesId = $publication->getData('seriesId');
|
|
$series = $seriesId ? Repo::section()->get($seriesId) : null;
|
|
if (!$series) {
|
|
return $citationData;
|
|
}
|
|
$citationData->{'collection-title'} = htmlspecialchars(trim($series->getLocalizedFullTitle()));
|
|
$citationData->volume = htmlspecialchars($publication->getData('seriesPosition'));
|
|
$citationData->{'collection-editor'} = htmlspecialchars($series->getEditorsString());
|
|
$onlineISSN = $series->getOnlineISSN();
|
|
if (!empty($onlineISSN)) {
|
|
$citationData->serialNumber[] = htmlspecialchars($onlineISSN);
|
|
}
|
|
$printISSN = $series->getPrintISSN();
|
|
if (!empty($printISSN)) {
|
|
$citationData->serialNumber[] = htmlspecialchars($printISSN);
|
|
}
|
|
return $citationData;
|
|
}
|
|
|
|
protected function getChapter(PKPRequest $request, Publication $publication): ?Chapter
|
|
{
|
|
$args = $request->getRequestedArgs();
|
|
|
|
if (isset($args[1], $args[2]) && $args[1] === 'chapter') {
|
|
$key = 2;
|
|
} elseif (isset($args[1], $args[3], $args[4]) && $args[1] === 'version' && $args[3] === 'chapter') {
|
|
$key = 4;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
$chapterId = (int) $args[$key];
|
|
if ($chapterId > 0) {
|
|
/** @var ChapterDAO */
|
|
$chapterDao = DAORegistry::getDAO('ChapterDAO');
|
|
return $chapterDao->getBySourceChapterAndPublication($chapterId, $publication->getId());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
protected function setDocumentType($chapter): void
|
|
{
|
|
switch ($this->application) {
|
|
case 'ops':
|
|
case 'ojs2':
|
|
$this->isArticle = true;
|
|
break;
|
|
case 'omp':
|
|
if ($chapter) {
|
|
$this->isChapter = true;
|
|
} else {
|
|
$this->isBook = true;
|
|
}
|
|
break;
|
|
default: throw new Exception('Unknown application!');
|
|
}
|
|
}
|
|
|
|
protected function setArticleAuthors($citationData, $publication, $context): stdClass
|
|
{
|
|
$authors = $publication->getData('authors');
|
|
$authorsGroups = $this->getAuthorGroups($context->getId());
|
|
$translatorsGroups = $this->getTranslatorGroups($context->getId());
|
|
if (count($authors)) {
|
|
/** @var Author $author */
|
|
foreach ($authors as $author) {
|
|
$currentAuthor = new stdClass();
|
|
if (empty($author->getLocalizedFamilyName())) {
|
|
$currentAuthor->family = htmlspecialchars($author->getLocalizedGivenName());
|
|
} else {
|
|
$currentAuthor->family = htmlspecialchars($author->getLocalizedFamilyName());
|
|
$currentAuthor->given = htmlspecialchars($author->getLocalizedGivenName());
|
|
}
|
|
|
|
$userGroupId = $author->getUserGroupId();
|
|
switch (true) {
|
|
case in_array($userGroupId, $translatorsGroups):
|
|
if (!isset($citationData->translator)) {
|
|
$citationData->translator = [];
|
|
}
|
|
$citationData->translator[] = $currentAuthor;
|
|
break;
|
|
case in_array($userGroupId, $authorsGroups):
|
|
if (!isset($citationData->author)) {
|
|
$citationData->author = [];
|
|
}
|
|
$citationData->author[] = $currentAuthor;
|
|
break;
|
|
default:
|
|
if (!isset($citationData->author)) {
|
|
$citationData->author = [];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $citationData;
|
|
}
|
|
|
|
protected function setBookAuthors($citationData, $publication, $context): stdClass
|
|
{
|
|
$authors = $publication->getData('authors');
|
|
$authorsGroups = $this->getAuthorGroups($context->getId());
|
|
$editorsGroups = $this->getEditorGroups($context->getId());
|
|
$translatorsGroups = $this->getTranslatorGroups($context->getId());
|
|
if (count($authors)) {
|
|
/** @var Author $author */
|
|
foreach ($authors as $author) {
|
|
$currentAuthor = new stdClass();
|
|
if (empty($author->getLocalizedFamilyName())) {
|
|
$currentAuthor->family = htmlspecialchars($author->getLocalizedGivenName());
|
|
} else {
|
|
$currentAuthor->family = htmlspecialchars($author->getLocalizedFamilyName());
|
|
$currentAuthor->given = htmlspecialchars($author->getLocalizedGivenName());
|
|
}
|
|
|
|
$userGroupId = $author->getUserGroupId();
|
|
switch (true) {
|
|
case in_array($userGroupId, $editorsGroups):
|
|
if (!isset($citationData->editor)) {
|
|
$citationData->editor = [];
|
|
}
|
|
$citationData->editor[] = $currentAuthor;
|
|
break;
|
|
case in_array($userGroupId, $translatorsGroups):
|
|
if (!isset($citationData->translator)) {
|
|
$citationData->translator = [];
|
|
}
|
|
$citationData->translator[] = $currentAuthor;
|
|
break;
|
|
case in_array($userGroupId, $authorsGroups):
|
|
if (!isset($citationData->author)) {
|
|
$citationData->author = [];
|
|
}
|
|
$citationData->author[] = $currentAuthor;
|
|
break;
|
|
default:
|
|
if (!isset($citationData->author)) {
|
|
$citationData->author = [];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $citationData;
|
|
}
|
|
|
|
protected function setBookChapterAuthors($citationData, $publication, $context, $chapter): stdClass
|
|
{
|
|
$chapterAuthorGroups = $this->getChapterAuthorGroups($context->getId());
|
|
$chapterAuthors = $chapter->getAuthors();
|
|
|
|
/** @var Author $chapterAuthor */
|
|
foreach ($chapterAuthors as $chapterAuthor) {
|
|
$currentAuthor = new stdClass();
|
|
if (empty($chapterAuthor->getLocalizedFamilyName())) {
|
|
$currentAuthor->family = htmlspecialchars($chapterAuthor->getLocalizedGivenName());
|
|
} else {
|
|
$currentAuthor->family = htmlspecialchars($chapterAuthor->getLocalizedFamilyName());
|
|
$currentAuthor->given = htmlspecialchars($chapterAuthor->getLocalizedGivenName());
|
|
}
|
|
|
|
$userGroupId = $chapterAuthor->getUserGroupId();
|
|
if (in_array($userGroupId, $chapterAuthorGroups)) {
|
|
$citationData->author[] = $currentAuthor;
|
|
}
|
|
}
|
|
|
|
$bookAuthors = $publication->getData('authors');
|
|
$authorsGroups = $this->getAuthorGroups($context->getId());
|
|
$editorsGroups = $this->getEditorGroups($context->getId());
|
|
$translatorsGroups = $this->getTranslatorGroups($context->getId());
|
|
if (count($bookAuthors)) {
|
|
/** @var Author $bookAuthor */
|
|
foreach ($bookAuthors as $bookAuthor) {
|
|
$currentAuthor = new stdClass();
|
|
if (empty($bookAuthor->getLocalizedFamilyName())) {
|
|
$currentAuthor->family = htmlspecialchars($bookAuthor->getLocalizedGivenName());
|
|
} else {
|
|
$currentAuthor->family = htmlspecialchars($bookAuthor->getLocalizedFamilyName());
|
|
$currentAuthor->given = htmlspecialchars($bookAuthor->getLocalizedGivenName());
|
|
}
|
|
|
|
$userGroupId = $bookAuthor->getUserGroupId();
|
|
switch (true) {
|
|
case in_array($userGroupId, $editorsGroups):
|
|
if (!isset($citationData->editor)) {
|
|
$citationData->editor = [];
|
|
}
|
|
$citationData->editor[] = $currentAuthor;
|
|
break;
|
|
case in_array($userGroupId, $translatorsGroups):
|
|
if (!isset($citationData->translator)) {
|
|
$citationData->translator = [];
|
|
}
|
|
$citationData->translator[] = $currentAuthor;
|
|
break;
|
|
case in_array($userGroupId, $authorsGroups):
|
|
if (!isset($citationData->{'container-author'})) {
|
|
$citationData->{'container-author'} = [];
|
|
}
|
|
$citationData->{'container-author'}[] = $currentAuthor;
|
|
break;
|
|
default:
|
|
if (!isset($citationData->author)) {
|
|
$citationData->author = [];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($citationData->{'container-author'})) {
|
|
$diffChapterAuthorsAuthors = array_udiff($citationData->{'container-author'}, $citationData->author, [$this, 'compareAuthors']);
|
|
if (count($diffChapterAuthorsAuthors) === 0) {
|
|
$citationData->{'container-author'} = [];
|
|
}
|
|
}
|
|
|
|
return $citationData;
|
|
}
|
|
|
|
protected function compareAuthors($a, $b): int
|
|
{
|
|
return 0 === strcmp($a->family, $b->family) && 0 === strcmp($a->given, $b->given) ? 0 : 1;
|
|
}
|
|
}
|