From d8d395978973f91db0d4a9720006c9edb6e010cf Mon Sep 17 00:00:00 2001
From: Mario Trojan <mario.trojan@uni-tuebingen.de>
Date: Wed, 15 Jan 2020 16:12:33 +0100
Subject: [PATCH] Metadata Vocabularies for Google Scholar. (#1529)

- Resolves VUFIND-951.
---
 config/vufind/metadata.ini                    |  14 ++
 module/VuFind/config/module.config.php        |   2 +
 .../MetadataVocabulary/AbstractBase.php       | 114 +++++++++++++++++
 .../src/VuFind/MetadataVocabulary/BEPress.php |  61 +++++++++
 .../VuFind/MetadataVocabulary/DublinCore.php  |  59 +++++++++
 .../src/VuFind/MetadataVocabulary/Eprints.php |  81 ++++++++++++
 .../MetadataVocabulary/HighwirePress.php      |  88 +++++++++++++
 .../MetadataVocabularyInterface.php           |  53 ++++++++
 .../src/VuFind/MetadataVocabulary/PRISM.php   |  55 ++++++++
 .../MetadataVocabulary/PluginFactory.php      |  48 +++++++
 .../MetadataVocabulary/PluginManager.php      |  67 ++++++++++
 .../src/VuFind/View/Helper/Root/Metadata.php  | 121 ++++++++++++++++++
 .../View/Helper/Root/MetadataFactory.php      |  68 ++++++++++
 .../RecordDriver/DefaultRecord/core.phtml     |   1 +
 themes/root/theme.config.php                  |   2 +
 15 files changed, 834 insertions(+)
 create mode 100644 config/vufind/metadata.ini
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/AbstractBase.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/BEPress.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/DublinCore.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/Eprints.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/HighwirePress.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/MetadataVocabularyInterface.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/PRISM.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/PluginFactory.php
 create mode 100644 module/VuFind/src/VuFind/MetadataVocabulary/PluginManager.php
 create mode 100644 module/VuFind/src/VuFind/View/Helper/Root/Metadata.php
 create mode 100644 module/VuFind/src/VuFind/View/Helper/Root/MetadataFactory.php

diff --git a/config/vufind/metadata.ini b/config/vufind/metadata.ini
new file mode 100644
index 00000000000..df8d4cec422
--- /dev/null
+++ b/config/vufind/metadata.ini
@@ -0,0 +1,14 @@
+; This file controls the export of HTML meta headers on the Record page.
+;
+; The amount of exported metadata depends on field availability
+; as well as the implementation of the chosen RecordDriver.
+; (e.g. \VuFind\RecordDriver\DefaultRecord implements getContainerTitle(),
+; but in case of SolrMarc the referenced "container_title" field
+; is not indexed by default).
+;
+[Vocabularies]
+;VuFind\RecordDriver\SolrMarc[] = BEPress
+;VuFind\RecordDriver\SolrMarc[] = DublinCore
+;VuFind\RecordDriver\SolrMarc[] = Eprints
+;VuFind\RecordDriver\SolrMarc[] = HighwirePress       ; recommended for Google Scholar
+;VuFind\RecordDriver\SolrMarc[] = PRISM
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index b0e499d1418..cd3d16423ee 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -391,6 +391,7 @@ $config = [
             'VuFind\ILS\HoldSettings' => 'VuFind\ILS\HoldSettingsFactory',
             'VuFind\Log\Logger' => 'VuFind\Log\LoggerFactory',
             'VuFind\Mailer\Mailer' => 'VuFind\Mailer\Factory',
+            'VuFind\MetadataVocabulary\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
             'VuFind\Net\IpAddressUtils' => 'Zend\ServiceManager\Factory\InvokableFactory',
             'VuFind\OAI\Server' => 'VuFind\OAI\ServerFactory',
             'VuFind\OAI\Server\Auth' => 'VuFind\OAI\ServerFactory',
@@ -567,6 +568,7 @@ $config = [
             'hierarchy_treedatasource' => [ /* see VuFind\Hierarchy\TreeDataSource\PluginManager for defaults */ ],
             'hierarchy_treerenderer' => [ /* see VuFind\Hierarchy\TreeRenderer\PluginManager for defaults */ ],
             'ils_driver' => [ /* See VuFind\ILS\Driver\PluginManager for defaults */ ],
+            'metadatavocabulary' => [ /* See VuFind\MetadataVocabulary\PluginManager for defaults */],
             'recommend' => [ /* See VuFind\Recommend\PluginManager for defaults */ ],
             'record_fallbackloader' => [ /* See VuFind\Record\FallbackLoader\PluginManager for defaults */ ],
             'recorddriver' => [ /* See VuFind\RecordDriver\PluginManager for defaults */ ],
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/AbstractBase.php b/module/VuFind/src/VuFind/MetadataVocabulary/AbstractBase.php
new file mode 100644
index 00000000000..796039a346b
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/AbstractBase.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Metadata vocabulary base class
+ * (provides results from available RecordDriver methods in a standardized form)
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary base class
+ * (provides results from available RecordDriver methods in a standardized form)
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+abstract class AbstractBase implements MetadataVocabularyInterface
+{
+    /**
+     * This varriable can be overwritten by child classes
+     * to define which custom field is filled by which generic fields.
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = [];
+
+    /**
+     * Generate standardized data from available RecordDriver methods
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    protected function getGenericData(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        return [
+            'author' => array_unique(
+                array_merge(
+                    $driver->tryMethod('getPrimaryAuthors'),
+                    $driver->tryMethod('getSecondaryAuthors'),
+                    $driver->tryMethod('getCorporateAuthors')
+                )
+            ),
+            'container_title' => $driver->tryMethod('getContainerTitle'),
+            'date' => $driver->tryMethod('getPublicationDates'),
+            'doi' => $driver->tryMethod('getCleanDOI'),
+            'endpage' => $driver->tryMethod('getContainerEndPage'),
+            'isbn' => $driver->tryMethod('getCleanISBN'),
+            'issn' => $driver->tryMethod('getCleanISSN'),
+            'issue' => $driver->tryMethod('getContainerIssue'),
+            'language' => $driver->tryMethod('getLanguages'),
+            'publisher' => $driver->tryMethod('getPublishers'),
+            'startpage' => $driver->tryMethod('getContainerStartPage'),
+            'title' => $driver->tryMethod('getTitle'),
+            'volume' => $driver->tryMethod('getContainerVolume'),
+        ];
+    }
+
+    /**
+     * Perform mapping from generic data to vocabulary data
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    public function getMappedData(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        $genericData = $this->getGenericData($driver);
+        $mappedData = [];
+
+        foreach ($this->vocabFieldToGenericFieldsMap as $vocabField => $genFields) {
+            foreach ((array)$genFields as $genericField) {
+                $genericValues = $genericData[$genericField] ?? [];
+                if ($genericValues) {
+                    if (!is_array($genericValues)) {
+                        $genericValues = [$genericValues];
+                    }
+                    foreach ($genericValues as $genericValue) {
+                        if (!isset($mappedData[$vocabField])) {
+                            $mappedData[$vocabField] = [];
+                        }
+                        $mappedData[$vocabField][] = $genericValue;
+                    }
+                }
+            }
+        }
+
+        return $mappedData;
+    }
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/BEPress.php b/module/VuFind/src/VuFind/MetadataVocabulary/BEPress.php
new file mode 100644
index 00000000000..0a467a596b4
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/BEPress.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Metadata vocabulary implementation for BEPress
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary implementation for BEPress
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class BEPress extends AbstractBase
+{
+    /**
+     * Mapping from BEPress to VuFind fields; see http://
+     * div.div1.com.au/div-thoughts/div-commentaries/66-div-commentary-metadata
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = [
+        'bepress_citation_author' => 'author',
+        'bepress_citation_date' => 'date',
+        'bepress_citation_doi' => 'doi',
+        'bepress_citation_firstpage' => 'startpage',
+        'bepress_citation_isbn' => 'isbn',
+        'bepress_citation_issn' => 'issn',
+        'bepress_citation_issue' => 'issue',
+        'bepress_citation_journal_title' => 'container_title',
+        'bepress_citation_lastpage' => 'endpage',
+        'bepress_citation_publisher' => 'publisher',
+        'bepress_citation_title' => 'title',
+        'bepress_citation_volume' => 'volume',
+    ];
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/DublinCore.php b/module/VuFind/src/VuFind/MetadataVocabulary/DublinCore.php
new file mode 100644
index 00000000000..ee03d4c397c
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/DublinCore.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Metadata vocabulary implementation for Dublin Core
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary implementation for Dublin Core
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class DublinCore extends AbstractBase
+{
+    /**
+     * Mapping from Dublin Core to VuFind fields
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = [
+        'DC.citation.epage' => 'endpage',
+        'DC.citation.issue' => 'issue',
+        'DC.citation.spage' => 'startpage',
+        'DC.citation.volume' => 'volume',
+        'DC.creator' => 'author',
+        'DC.identifier' => ['doi', 'isbn', 'issn'],
+        'DC.issued' => 'date',
+        'DC.language' => 'language',
+        'DC.publisher' => 'publisher',
+        'DC.relation.ispartof' => 'container_title',
+        'DC.title' => 'title',
+    ];
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/Eprints.php b/module/VuFind/src/VuFind/MetadataVocabulary/Eprints.php
new file mode 100644
index 00000000000..8858bd1e777
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/Eprints.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Metadata vocabulary implementation for Eprints
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary implementation for Eprints
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class Eprints extends AbstractBase
+{
+    /**
+     * Mapping from Eprints to VuFind fields
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = [
+        'eprints.creators_name' => 'author',
+        'eprints.date' => 'date',
+        'eprints.issn' => 'issn',
+        'eprints.number' => 'volume',
+        'eprints.publication' => 'container_title',
+        'eprints.publisher' => 'publisher',
+        'eprints.title' => 'title',
+    ];
+
+    /**
+     * Special implementation to combine start / end page in eprints.pagerange
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    public function getMappedData(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        $mappedData = parent::getMappedData($driver);
+
+        // special handling for pagerange
+        $startpage = $driver->tryMethod('getContainerStartPage');
+
+        if ($startpage) {
+            $pagerange = $startpage;
+            $endpage = $driver->tryMethod('getContainerEndPage');
+            if ($endpage != '' && $endpage != $startpage) {
+                $pagerange = $startpage . '-' . $endpage;
+            }
+            $mappedData['eprints.pagerange'] = [$pagerange];
+        }
+
+        return $mappedData;
+    }
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/HighwirePress.php b/module/VuFind/src/VuFind/MetadataVocabulary/HighwirePress.php
new file mode 100644
index 00000000000..22bb4acc41d
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/HighwirePress.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Metadata vocabulary implementation for Highwire Press
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary implementation for Highwire Press
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class HighwirePress extends AbstractBase
+{
+    /**
+     * Mapping from Highwire Press to VuFind fields; see
+     * https://jira.duraspace.org/secure/attachment/13020/Invisible_institutional.pdf
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = [
+        'citation_author' => 'author',
+        'citation_date' => 'date',
+        'citation_doi' => 'doi',
+        'citation_firstpage' => 'startpage',
+        'citation_isbn' => 'isbn',
+        'citation_issn' => 'issn',
+        'citation_issue' => 'issue',
+        'citation_journal_title' => 'container_title',
+        'citation_language' => 'language',
+        'citation_lastpage' => 'endpage',
+        'citation_publisher' => 'publisher',
+        'citation_title' => 'title',
+        'citation_volume' => 'volume',
+    ];
+
+    /**
+     * Special implementation for date formats
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    public function getMappedData(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        $mappedData = parent::getMappedData($driver);
+
+        // special handling for dates
+        if (isset($mappedData['citation_date'])) {
+            foreach ($mappedData['citation_date'] as $key => $date) {
+                // If we only have a year, leave it as-is
+                // If we have a date, we need to convert to MM-DD-YYYY or MM/DD/YYYY
+                if (!preg_match('"^\d+$"', $date)) {
+                    $mappedData['citation_date'][$key]
+                        = date('m/d/Y', strtotime($date));
+                }
+            }
+        }
+
+        return $mappedData;
+    }
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/MetadataVocabularyInterface.php b/module/VuFind/src/VuFind/MetadataVocabulary/MetadataVocabularyInterface.php
new file mode 100644
index 00000000000..edb0d94c0d6
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/MetadataVocabularyInterface.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Metadata vocabulary interface
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary interface
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+interface MetadataVocabularyInterface
+{
+    /**
+     * Map data from RecordDriver to this vocabulary.
+     *
+     * Note that AbstractBase instead of DefaultRecord is used
+     * for higher flexibility. That's why all implementations must use
+     * "tryMethod" instead of calling the methods directly.
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    public function getMappedData(\VuFind\RecordDriver\AbstractBase $driver);
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/PRISM.php b/module/VuFind/src/VuFind/MetadataVocabulary/PRISM.php
new file mode 100644
index 00000000000..3c4a9bef6f0
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/PRISM.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Metadata vocabulary implementation for PRISM
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary implementation for PRISM
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class PRISM extends AbstractBase
+{
+    /**
+     * Mapping from Highwire Press to VuFind fields
+     * see https://www.idealliance.org/prism-metadata
+     *
+     * @var array
+     */
+    protected $vocabFieldToGenericFieldsMap = ['prism.doi' => 'doi',
+                                               'prism.endingPage' => 'endpage',
+                                               'prism.isbn' => 'isbn',
+                                               'prism.issn' => 'issn',
+                                               'prism.startingPage' => 'startpage',
+                                               'prism.title' => 'title',
+                                               'prism.volume' => 'volume',
+                                            ];
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/PluginFactory.php b/module/VuFind/src/VuFind/MetadataVocabulary/PluginFactory.php
new file mode 100644
index 00000000000..aacf5c59968
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/PluginFactory.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Metadata vocabulary plugin factory
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary plugin factory
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class PluginFactory extends \VuFind\ServiceManager\AbstractPluginFactory
+{
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->defaultNamespace = 'VuFind\MetadataVocabulary';
+    }
+}
diff --git a/module/VuFind/src/VuFind/MetadataVocabulary/PluginManager.php b/module/VuFind/src/VuFind/MetadataVocabulary/PluginManager.php
new file mode 100644
index 00000000000..6bb581a8460
--- /dev/null
+++ b/module/VuFind/src/VuFind/MetadataVocabulary/PluginManager.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Metadata vocabulary plugin manager
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\MetadataVocabulary;
+
+/**
+ * Metadata vocabulary plugin manager
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
+{
+    /**
+     * Constructor
+     *
+     * Make sure plugins are properly initialized.
+     *
+     * @param mixed $configOrContainerInstance Configuration or container instance
+     * @param array $v3config                  If $configOrContainerInstance is a
+     *                                         container, this value will be passed
+     *                                         to the parent constructor.
+     */
+    public function __construct($configOrContainerInstance = null,
+        array $v3config = []
+    ) {
+        $this->addAbstractFactory(PluginFactory::class);
+        parent::__construct($configOrContainerInstance, $v3config);
+    }
+
+    /**
+     * Return the base class or interface that plug-ins must conform to.
+     *
+     * @return class
+     */
+    protected function getExpectedInterface()
+    {
+        return MetadataVocabularyInterface::class;
+    }
+}
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php b/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php
new file mode 100644
index 00000000000..5aadeb2a6aa
--- /dev/null
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Metadata view helper
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\View\Helper\Root;
+
+/**
+ * Metadata view helper
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class Metadata extends \Zend\View\Helper\AbstractHelper
+{
+    /**
+     * Metadata configuration entries
+     *
+     * @var \Zend\Config\Config
+     */
+    protected $config;
+
+    /**
+     * Zend meta helper, used to embed html tags in the generated page
+     *
+     * @var \Zend\View\Helper\HeadMeta
+     */
+    protected $metaHelper;
+
+    /**
+     * Plugin Manager for vocabularies
+     *
+     * @var \VuFind\MetadataVocabulary\PluginManager
+     */
+    protected $pluginManager;
+
+    /**
+     * Constructor
+     *
+     * @param \VuFind\MetadataVocabulary\PluginManager $pm         Plugin manager
+     * @param \Zend\Config\Config                      $config     Configuration
+     * @param \Zend\View\Helper\HeadMeta               $metaHelper Head meta helper
+     */
+    public function __construct(\VuFind\MetadataVocabulary\PluginManager $pm,
+        \Zend\Config\Config $config,
+        \Zend\View\Helper\HeadMeta $metaHelper
+    ) {
+        $this->pluginManager = $pm;
+        $this->config = $config;
+        $this->metaHelper = $metaHelper;
+    }
+
+    /**
+     * Get all active vocabularies for the current record.
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return array
+     */
+    protected function getVocabularies(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        $recordDriverConfigs = isset($this->config->Vocabularies)
+            ? $this->config->Vocabularies->toArray() : [];
+        $retVal = [];
+        foreach ($recordDriverConfigs as $className => $vocabs) {
+            if ($driver instanceof $className) {
+                $retVal = array_merge($retVal, $vocabs);
+            }
+        }
+        return array_unique($retVal);
+    }
+
+    /**
+     * Generate all metatags for RecordDriver and add to page
+     *
+     * Decide which Plugins to load for the given RecordDriver
+     * dependant on configuration. (only by class name,
+     * namespace will not be considered)
+     *
+     * @param \VuFind\RecordDriver\AbstractBase $driver Record driver
+     *
+     * @return void
+     */
+    public function generateMetatags(\VuFind\RecordDriver\AbstractBase $driver)
+    {
+        foreach ($this->getVocabularies($driver) as $metatagType) {
+            $vocabulary = $this->pluginManager->get($metatagType);
+            $mappedFields = $vocabulary->getMappedData($driver);
+            foreach ($mappedFields as $field => $values) {
+                foreach ($values as $value) {
+                    $this->metaHelper->appendName($field, $value);
+                }
+            }
+        }
+    }
+}
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/MetadataFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/MetadataFactory.php
new file mode 100644
index 00000000000..6c2d2260a49
--- /dev/null
+++ b/module/VuFind/src/VuFind/View/Helper/Root/MetadataFactory.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Metadata helper factory
+ *
+ * PHP version 7
+ *
+ * Copyright (C) University of Tübingen 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\View\Helper\Root;
+
+use Interop\Container\ContainerInterface;
+use Zend\ServiceManager\Factory\FactoryInterface;
+
+/**
+ * Metadata helper factory
+ *
+ * @category VuFind
+ * @package  Metadata_Vocabularies
+ * @author   Mario Trojan <mario.trojan@uni-tuebingen.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class MetadataFactory implements FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service Manager
+     * @param type               $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws \Exception (options not allowed in this implementation)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options sent to factory.');
+        }
+
+        return new Metadata(
+            $container->get(\VuFind\MetadataVocabulary\PluginManager::class),
+            $container->get(\VuFind\Config\PluginManager::class)->get('metadata'),
+            $container->get('ViewHelperManager')->get('HeadMeta')
+        );
+    }
+}
diff --git a/themes/bootstrap3/templates/RecordDriver/DefaultRecord/core.phtml b/themes/bootstrap3/templates/RecordDriver/DefaultRecord/core.phtml
index 2aed4297dd6..b115d2d0193 100644
--- a/themes/bootstrap3/templates/RecordDriver/DefaultRecord/core.phtml
+++ b/themes/bootstrap3/templates/RecordDriver/DefaultRecord/core.phtml
@@ -1,3 +1,4 @@
+<?php $this->metadata()->generateMetatags($this->driver);?>
 <div class="media" vocab="http://schema.org/" resource="#record" typeof="<?=$this->driver->getSchemaOrgFormats()?> Product">
   <?php
     $QRCode = $this->record($this->driver)->getQRCode("core");
diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php
index f7c837606d6..1818208fc95 100644
--- a/themes/root/theme.config.php
+++ b/themes/root/theme.config.php
@@ -32,6 +32,7 @@ return [
             'VuFind\View\Helper\Root\KeepAlive' => 'VuFind\View\Helper\Root\KeepAliveFactory',
             'VuFind\View\Helper\Root\Linkify' => 'Zend\ServiceManager\Factory\InvokableFactory',
             'VuFind\View\Helper\Root\LocalizedNumber' => 'Zend\ServiceManager\Factory\InvokableFactory',
+            'VuFind\View\Helper\Root\Metadata' => 'VuFind\View\Helper\Root\MetadataFactory',
             'VuFind\View\Helper\Root\OpenUrl' => 'VuFind\View\Helper\Root\OpenUrlFactory',
             'VuFind\View\Helper\Root\Overdrive' => 'VuFind\View\Helper\Root\OverdriveFactory',
             'VuFind\View\Helper\Root\Permission' => 'VuFind\View\Helper\Root\PermissionFactory',
@@ -98,6 +99,7 @@ return [
             'keepAlive' => 'VuFind\View\Helper\Root\KeepAlive',
             'linkify' => 'VuFind\View\Helper\Root\Linkify',
             'localizedNumber' => 'VuFind\View\Helper\Root\LocalizedNumber',
+            'metadata' => 'VuFind\View\Helper\Root\Metadata',
             'openUrl' => 'VuFind\View\Helper\Root\OpenUrl',
             'overdrive' => 'VuFind\View\Helper\Root\Overdrive',
             'permission' => 'VuFind\View\Helper\Root\Permission',
-- 
GitLab