From ae7d8c02071454682a3cd07a785575b62b6ce93e Mon Sep 17 00:00:00 2001
From: John Jung <john@johnjung.us>
Date: Fri, 27 Oct 2017 07:27:25 -0500
Subject: [PATCH] Syndetics tables of contents and summaries. (#456)

---
 config/vufind/config.ini                      |   7 +
 module/VuFind/config/module.config.php        |  18 ++-
 module/VuFind/src/VuFind/Content/Factory.php  |  34 +++++
 .../src/VuFind/Content/Summaries/Factory.php  |  86 +++++++++++
 .../Content/Summaries/PluginManager.php       |  51 +++++++
 .../VuFind/Content/Summaries/Syndetics.php    | 127 ++++++++++++++++
 .../VuFind/src/VuFind/Content/TOC/Factory.php |  86 +++++++++++
 .../src/VuFind/Content/TOC/PluginManager.php  |  51 +++++++
 .../src/VuFind/Content/TOC/Syndetics.php      | 142 ++++++++++++++++++
 .../VuFind/src/VuFind/RecordTab/Factory.php   |  20 +++
 module/VuFind/src/VuFind/RecordTab/TOC.php    |   4 +-
 module/VuFind/src/VuFind/Service/Factory.php  |  24 +++
 .../src/VuFind/View/Helper/Root/Factory.php   |  14 ++
 .../Root/RecordDataFormatterFactory.php       |   2 +-
 .../SolrDefault/data-summary.phtml            |  10 ++
 .../bootstrap3/templates/RecordTab/toc.phtml  |  26 +++-
 themes/bootstrap3/theme.config.php            |   1 +
 17 files changed, 692 insertions(+), 11 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Content/Summaries/Factory.php
 create mode 100644 module/VuFind/src/VuFind/Content/Summaries/PluginManager.php
 create mode 100644 module/VuFind/src/VuFind/Content/Summaries/Syndetics.php
 create mode 100644 module/VuFind/src/VuFind/Content/TOC/Factory.php
 create mode 100644 module/VuFind/src/VuFind/Content/TOC/PluginManager.php
 create mode 100644 module/VuFind/src/VuFind/Content/TOC/Syndetics.php
 create mode 100644 themes/bootstrap3/templates/RecordDriver/SolrDefault/data-summary.phtml

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index f185d63568f..a9bb6eb8e9f 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -728,6 +728,13 @@ noCoverAvailableImage = images/noCover2.gif
 ; content providers.
 ;hide_if_empty = reviews,excerpts
 
+; You can select from Syndetics or SyndeticsPlus to add summary information to
+; the description tab.
+;summaries = Syndetics:MySyndeticsId,SyndeticsPlus:MySyndeticsId
+
+; You can select from Syndetics or SyndeticsPlus to load Tables of Contents
+;toc = Syndetics:MySyndeticsId,SyndeticsPlus:MySyndeticsId
+
 ; You can select from Syndetics or SyndeticsPlus
 ;authorNotes = Syndetics:MySyndeticsId,SyndeticsPlus:MySyndeticsId
 
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 90bbd7bdfc9..3fb4db62d2b 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -187,6 +187,8 @@ $config = [
             'VuFind\ContentCoversPluginManager' => 'VuFind\Service\Factory::getContentCoversPluginManager',
             'VuFind\ContentExcerptsPluginManager' => 'VuFind\Service\Factory::getContentExcerptsPluginManager',
             'VuFind\ContentReviewsPluginManager' => 'VuFind\Service\Factory::getContentReviewsPluginManager',
+            'VuFind\ContentSummariesPluginManager' => 'VuFind\Service\Factory::getContentSummariesPluginManager',
+            'VuFind\ContentTOCPluginManager' => 'VuFind\Service\Factory::getContentTOCPluginManager',
             'VuFind\CookieManager' => 'VuFind\Service\Factory::getCookieManager',
             'VuFind\Cover\Router' => 'VuFind\Service\Factory::getCoverRouter',
             'VuFind\DateConverter' => 'VuFind\Service\Factory::getDateConverter',
@@ -356,6 +358,8 @@ $config = [
                     'authornotes' => 'VuFind\Content\Factory::getAuthorNotes',
                     'excerpts' => 'VuFind\Content\Factory::getExcerpts',
                     'reviews' => 'VuFind\Content\Factory::getReviews',
+                    'summaries' => 'VuFind\Content\Factory::getSummaries',
+                    'toc' => 'VuFind\Content\Factory::getTOC',
                 ],
             ],
             'content_authornotes' => [
@@ -370,6 +374,18 @@ $config = [
                     'syndeticsplus' => 'VuFind\Content\Excerpts\Factory::getSyndeticsPlus',
                 ],
             ],
+            'content_summaries' => [
+                'factories' => [
+                    'syndetics' => 'VuFind\Content\Summaries\Factory::getSyndetics',
+                    'syndeticsplus' => 'VuFind\Content\Summaries\Factory::getSyndeticsPlus',
+                ],
+            ],
+            'content_toc' => [
+                'factories' => [
+                    'syndetics' => 'VuFind\Content\TOC\Factory::getSyndetics',
+                    'syndeticsplus' => 'VuFind\Content\TOC\Factory::getSyndeticsPlus',
+                ],
+            ],
             'content_covers' => [
                 'factories' => [
                     'amazon' => 'VuFind\Content\Covers\Factory::getAmazon',
@@ -573,13 +589,13 @@ $config = [
                     'preview' => 'VuFind\RecordTab\Factory::getPreview',
                     'reviews' => 'VuFind\RecordTab\Factory::getReviews',
                     'similaritemscarousel' => 'VuFind\RecordTab\Factory::getSimilarItemsCarousel',
+                    'toc' => 'VuFind\RecordTab\Factory::getTOC',
                     'usercomments' => 'VuFind\RecordTab\Factory::getUserComments',
                 ],
                 'invokables' => [
                     'description' => 'VuFind\RecordTab\Description',
                     'staffviewarray' => 'VuFind\RecordTab\StaffViewArray',
                     'staffviewmarc' => 'VuFind\RecordTab\StaffViewMARC',
-                    'toc' => 'VuFind\RecordTab\TOC',
                 ],
                 'initializers' => [
                     'ZfcRbac\Initializer\AuthorizationServiceInitializer'
diff --git a/module/VuFind/src/VuFind/Content/Factory.php b/module/VuFind/src/VuFind/Content/Factory.php
index 56f1821434d..6c769de06a9 100644
--- a/module/VuFind/src/VuFind/Content/Factory.php
+++ b/module/VuFind/src/VuFind/Content/Factory.php
@@ -92,4 +92,38 @@ class Factory
             ? $config->Content->reviews : '';
         return new Loader($loader, $providers);
     }
+
+    /**
+     * Create Summaries loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getSummaries(ServiceManager $sm)
+    {
+        $loader = $sm->getServiceLocator()
+            ->get('VuFind\ContentSummariesPluginManager');
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        $providers = isset($config->Content->summaries)
+            ? $config->Content->summaries : '';
+        return new Loader($loader, $providers);
+    }
+
+    /**
+     * Create TOC loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getTOC(ServiceManager $sm)
+    {
+        $loader = $sm->getServiceLocator()
+            ->get('VuFind\ContentTOCPluginManager');
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        $providers = isset($config->Content->toc)
+            ? $config->Content->toc : '';
+        return new Loader($loader, $providers);
+    }
 }
diff --git a/module/VuFind/src/VuFind/Content/Summaries/Factory.php b/module/VuFind/src/VuFind/Content/Summaries/Factory.php
new file mode 100644
index 00000000000..b28f6b7d842
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/Summaries/Factory.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Factory for instantiating content loaders
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace VuFind\Content\Summaries;
+
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Factory for instantiating content loaders
+ *
+ * @category           VuFind2
+ * @package            Content
+ * @author             John Jung <jej@uchicago.edu>
+ * @license            http://opensource.org/licenses/gpl-2.0.php GNU General
+ *                     Public License
+ * @link               http://vufind.org/wiki/vufind2:developer_manual Wiki
+ * @codeCoverageIgnore
+ */
+class Factory
+{
+    /**
+     * Create either a Syndetics or SyndeticsPlus loader
+     *
+     * @param ServiceManager $sm   Service manager
+     * @param bool           $plus Instantiate in Syndetics Plus mode?
+     *
+     * @return mixed
+     */
+    public static function getAbstractSyndetics(ServiceManager $sm, $plus)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        return new Syndetics(
+            isset($config->Syndetics->use_ssl) && $config->Syndetics->use_ssl,
+            $plus,
+            isset($config->Syndetics->timeout) ? $config->Syndetics->timeout : 10
+        );
+    }
+
+    /**
+     * Create Syndetics loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getSyndetics(ServiceManager $sm)
+    {
+        return static::getAbstractSyndetics($sm, false);
+    }
+
+    /**
+     * Create SyndeticsPlus loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getSyndeticsPlus(ServiceManager $sm)
+    {
+        return static::getAbstractSyndetics($sm, true);
+    }
+}
diff --git a/module/VuFind/src/VuFind/Content/Summaries/PluginManager.php b/module/VuFind/src/VuFind/Content/Summaries/PluginManager.php
new file mode 100644
index 00000000000..d8ba2a673f1
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/Summaries/PluginManager.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Summaries content loader plugin manager
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Content\Summaries;
+
+/**
+ * Summaries content loader plugin manager
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
+{
+    /**
+     * Return the name of the base class or interface that plug-ins must conform
+     * to.
+     *
+     * @return string
+     */
+    protected function getExpectedInterface()
+    {
+        return 'VuFind\Content\AbstractBase';
+    }
+}
diff --git a/module/VuFind/src/VuFind/Content/Summaries/Syndetics.php b/module/VuFind/src/VuFind/Content/Summaries/Syndetics.php
new file mode 100644
index 00000000000..7a2f1a906d3
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/Summaries/Syndetics.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Syndetics Summaries content loader.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace VuFind\Content\Summaries;
+
+/**
+ * Syndetics Summaries content loader.
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class Syndetics extends \VuFind\Content\AbstractSyndetics
+{
+    /**
+     * List of data sources for author notes.
+     *
+     * @var array
+     */
+    protected $sourceList = [
+        'AVSUMMARY' => [
+            'title' => 'Summaries',
+            'file' => 'AVSUMMARY.XML',
+            'div' => '<div id="syn_avsummary"></div>'
+        ],
+        'SUMMARY' => [
+            'title' => 'Summaries',
+            'file' => 'SUMMARY.XML',
+            'div' => '<div id="syn_summary"></div>'
+        ],
+    ];
+
+    /**
+     * This method is responsible for connecting to Syndetics for summaries.
+     *
+     * It first queries the master url for the ISBN entry seeking a summary URL.
+     * If a summary URL is found, the script will then use HTTP request to
+     * retrieve summaries.
+     * Configuration:  Sources are processed in order - refer to $sourceList above.
+     *
+     * @param string           $key     API key
+     * @param \VuFindCode\ISBN $isbnObj ISBN object
+     *
+     * @throws \Exception
+     * @return array     Returns array with summary data.
+     * @author John Jung <jej@uchicago.edu>
+     */
+    public function loadByIsbn($key, \VuFindCode\ISBN $isbnObj)
+    {
+        // Initialize return value:
+        $summaries = [];
+
+        // Find out if there are any summaries
+        $isbn = $this->getIsbn10($isbnObj);
+        $url = $this->getIsbnUrl($isbn, $key);
+        $result = $this->getHttpClient($url)->send();
+        if (!$result->isSuccess()) {
+            return $excerpt;
+        }
+
+        // Test XML Response
+        if (!($xmldoc = $this->xmlToDOMDocument($result->getBody()))) {
+            throw new \Exception('Invalid XML');
+        }
+
+        foreach ($this->sourceList as $source => $sourceInfo) {
+            $nodes = $xmldoc->getElementsByTagName($source);
+            if ($nodes->length) {
+                // Load summary
+                $url = $this->getIsbnUrl($isbn, $key, $sourceInfo['file']);
+                $result2 = $this->getHttpClient($url)->send();
+                if (!$result2->isSuccess()) {
+                    continue;
+                }
+
+                // Test XML Response
+                $xmldoc2 = $this->xmlToDOMDocument($result2->getBody());
+                if (!$xmldoc2) {
+                    throw new \Exception('Invalid XML');
+                }
+
+                // If we have syndetics plus, we don't actually want the content
+                // we'll just stick in the relevant div
+                if ($this->usePlus) {
+                    $summaries[] = $sourceInfo['div'];
+                } else {
+                    // Get the marc field for summaries. (520)
+                    $nodes = $xmldoc2->GetElementsbyTagName("Fld520");
+                    foreach ($nodes as $node) {
+                        $summaries[] = preg_replace(
+                            '/<a>|<a [^>]*>|<\/a>/', '',
+                            html_entity_decode($node->nodeValue)
+                        );
+                    }
+                }
+            }
+        }
+
+        return $summaries;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Content/TOC/Factory.php b/module/VuFind/src/VuFind/Content/TOC/Factory.php
new file mode 100644
index 00000000000..cfe89c2e324
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/TOC/Factory.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Factory for instantiating content loaders
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace VuFind\Content\TOC;
+
+use Zend\ServiceManager\ServiceManager;
+
+/**
+ * Factory for instantiating content loaders
+ *
+ * @category           VuFind2
+ * @package            Content
+ * @author             John Jung <jej@uchicago.edu>
+ * @license            http://opensource.org/licenses/gpl-2.0.php GNU General
+ *                     Public License
+ * @link               http://vufind.org/wiki/vufind2:developer_manual Wiki
+ * @codeCoverageIgnore
+ */
+class Factory
+{
+    /**
+     * Create either a Syndetics or SyndeticsPlus loader
+     *
+     * @param ServiceManager $sm   Service manager
+     * @param bool           $plus Instantiate in Syndetics Plus mode?
+     *
+     * @return mixed
+     */
+    public static function getAbstractSyndetics(ServiceManager $sm, $plus)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        return new Syndetics(
+            isset($config->Syndetics->use_ssl) && $config->Syndetics->use_ssl,
+            $plus,
+            isset($config->Syndetics->timeout) ? $config->Syndetics->timeout : 10
+        );
+    }
+
+    /**
+     * Create Syndetics loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getSyndetics(ServiceManager $sm)
+    {
+        return static::getAbstractSyndetics($sm, false);
+    }
+
+    /**
+     * Create SyndeticsPlus loader
+     *
+     * @param ServiceManager $sm Service manager
+     *
+     * @return mixed
+     */
+    public static function getSyndeticsPlus(ServiceManager $sm)
+    {
+        return static::getAbstractSyndetics($sm, true);
+    }
+}
diff --git a/module/VuFind/src/VuFind/Content/TOC/PluginManager.php b/module/VuFind/src/VuFind/Content/TOC/PluginManager.php
new file mode 100644
index 00000000000..614c127a248
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/TOC/PluginManager.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * TOC content loader plugin manager
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Content\TOC;
+
+/**
+ * TOC content loader plugin manager
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
+{
+    /**
+     * Return the name of the base class or interface that plug-ins must conform
+     * to.
+     *
+     * @return string
+     */
+    protected function getExpectedInterface()
+    {
+        return 'VuFind\Content\AbstractBase';
+    }
+}
diff --git a/module/VuFind/src/VuFind/Content/TOC/Syndetics.php b/module/VuFind/src/VuFind/Content/TOC/Syndetics.php
new file mode 100644
index 00000000000..bfe330b1fd9
--- /dev/null
+++ b/module/VuFind/src/VuFind/Content/TOC/Syndetics.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Syndetics TOC content loader.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The University of Chicago 2017.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+namespace VuFind\Content\TOC;
+
+/**
+ * Syndetics TOC content loader.
+ *
+ * @category VuFind2
+ * @package  Content
+ * @author   John Jung <jej@uchicago.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class Syndetics extends \VuFind\Content\AbstractSyndetics
+{
+    /**
+     * List of data sources for author notes.
+     *
+     * @var array
+     */
+    protected $sourceList = [
+        'TOC' => [
+            'title' => 'TOC',
+            'file' => 'TOC.XML',
+            'div' => '<div id="syn_toc"></div>'
+        ]
+    ];
+
+    /**
+     * This method is responsible for connecting to Syndetics for tables
+     * of contents.
+     *
+     * It first queries the master url for the ISBN entry seeking an excerpt URL.
+     * If an excerpt URL is found, the script will then use HTTP request to
+     * retrieve the script. The script will then parse the excerpt according to
+     * US MARC (I believe). It will provide a link to the URL master HTML page
+     * for more information.
+     * Configuration:  Sources are processed in order - refer to $sourceList above.
+     *
+     * @param string           $key     API key
+     * @param \VuFindCode\ISBN $isbnObj ISBN object
+     *
+     * @throws \Exception
+     * @return array     Returns array with table of contents data.
+     * @author John Jung <jej@uchicago.edu>
+     */
+    public function loadByIsbn($key, \VuFindCode\ISBN $isbnObj)
+    {
+        // Initialize return value:
+        $toc = [];
+
+        // Find out if there are any tables of contents
+        $isbn = $this->getIsbn10($isbnObj);
+        $url = $this->getIsbnUrl($isbn, $key);
+        $result = $this->getHttpClient($url)->send();
+        if (!$result->isSuccess()) {
+            return $excerpt;
+        }
+
+        // Test XML Response
+        if (!($xmldoc = $this->xmlToDOMDocument($result->getBody()))) {
+            throw new \Exception('Invalid XML');
+        }
+
+        $i = 0;
+        foreach ($this->sourceList as $source => $sourceInfo) {
+            $nodes = $xmldoc->getElementsByTagName($source);
+            if ($nodes->length) {
+                // Load toc
+                $url = $this->getIsbnUrl($isbn, $key, $sourceInfo['file']);
+                $result2 = $this->getHttpClient($url)->send();
+                if (!$result2->isSuccess()) {
+                    continue;
+                }
+
+                // Test XML Response
+                $xmldoc2 = $this->xmlToDOMDocument($result2->getBody());
+                if (!$xmldoc2) {
+                    throw new \Exception('Invalid XML');
+                }
+
+                // If we have syndetics plus, we don't actually want the content
+                // we'll just stick in the relevant div
+                if ($this->usePlus) {
+                    $toc = $sourceInfo['div'];
+                } else {
+                    // Get the marc field for toc (970)
+                    $nodes = $xmldoc2->GetElementsbyTagName("Fld970");
+
+                    foreach ($nodes as $node) {
+                        $li = '';
+
+                        // Chapter labels.
+                        $nodeList = $node->getElementsByTagName('l');
+                        if ($nodeList->length > 0) {
+                            $li .= sprintf("%s. ", $nodeList->item(0)->nodeValue);
+                        }
+
+                        // Chapter title.
+                        $nodeList = $node->getElementsByTagName('t');
+                        if ($nodeList->length > 0) {
+                            $li .= $nodeList->item(0)->nodeValue;
+                        }
+
+                        $toc[] = preg_replace(
+                            '/<a>|<a [^>]*>|<\/a>/', '',
+                            html_entity_decode($li)
+                        );
+                    }
+                }
+                $i++;
+            }
+        }
+
+        return $toc;
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/Factory.php b/module/VuFind/src/VuFind/RecordTab/Factory.php
index d61ecf33ac3..fa63b618735 100644
--- a/module/VuFind/src/VuFind/RecordTab/Factory.php
+++ b/module/VuFind/src/VuFind/RecordTab/Factory.php
@@ -251,6 +251,26 @@ class Factory
         return new Reviews($loader, static::getHideSetting($config, 'reviews'));
     }
 
+    /**
+     * Factory for TOC tab plugin.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return TablesOfContents
+     */
+    public static function getTOC(ServiceManager $sm)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        // Only instantiate the loader if the feature is enabled:
+        if (isset($config->Content->toc)) {
+            $loader = $sm->getServiceLocator()->get('VuFind\ContentPluginManager')
+                ->get('toc');
+        } else {
+            $loader = null;
+        }
+        return new TOC($loader, static::getHideSetting($config, 'toc'));
+    }
+
     /**
      * Factory for UserComments tab plugin.
      *
diff --git a/module/VuFind/src/VuFind/RecordTab/TOC.php b/module/VuFind/src/VuFind/RecordTab/TOC.php
index 76bc7621b31..6b5f250a533 100644
--- a/module/VuFind/src/VuFind/RecordTab/TOC.php
+++ b/module/VuFind/src/VuFind/RecordTab/TOC.php
@@ -36,7 +36,7 @@ namespace VuFind\RecordTab;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org/wiki/development:plugins:record_tabs Wiki
  */
-class TOC extends AbstractBase
+class TOC extends AbstractContent
 {
     /**
      * Get the on-screen description for this tab.
@@ -56,6 +56,6 @@ class TOC extends AbstractBase
     public function isActive()
     {
         $toc = $this->getRecordDriver()->tryMethod('getTOC');
-        return !empty($toc);
+        return !empty($toc) || parent::isActive();
     }
 }
diff --git a/module/VuFind/src/VuFind/Service/Factory.php b/module/VuFind/src/VuFind/Service/Factory.php
index 1780a0dbf7f..94314f22282 100644
--- a/module/VuFind/src/VuFind/Service/Factory.php
+++ b/module/VuFind/src/VuFind/Service/Factory.php
@@ -205,6 +205,30 @@ class Factory
         return static::getGenericPluginManager($sm, 'Content\Reviews');
     }
 
+    /**
+     * Construct the Content\Summaries Plugin Manager.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return \VuFind\Content\Summaries\PluginManager
+     */
+    public static function getContentSummariesPluginManager(ServiceManager $sm)
+    {
+        return static::getGenericPluginManager($sm, 'Content\Summaries');
+    }
+
+    /**
+     * Construct the Content\TOC Plugin Manager.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return \VuFind\Content\TOC\PluginManager
+     */
+    public static function getContentTOCPluginManager(ServiceManager $sm)
+    {
+        return static::getGenericPluginManager($sm, 'Content\TOC');
+    }
+
     /**
      * Construct the cookie manager.
      *
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
index 406e4fc94db..1051a910c37 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
@@ -543,6 +543,20 @@ class Factory
         );
     }
 
+    /**
+     * Construct the Summary helper.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return Summaries
+     */
+    public static function getSummaries(ServiceManager $sm)
+    {
+        $loader = $sm->getServiceLocator()->get('VuFind\ContentPluginManager')
+            ->get('summaries');
+        return new ContentLoader($loader);
+    }
+
     /**
      * Construct the SyndeticsPlus helper.
      *
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php
index f2da01a8434..18916b07561 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php
@@ -276,7 +276,7 @@ class RecordDataFormatterFactory
     public function getDefaultDescriptionSpecs()
     {
         $spec = new RecordDataFormatter\SpecBuilder();
-        $spec->setLine('Summary', 'getSummary');
+        $spec->setTemplateLine('Summary', true, 'data-summary.phtml');
         $spec->setLine('Published', 'getDateSpan');
         $spec->setLine('Item Description', 'getGeneralNotes');
         $spec->setLine('Physical Description', 'getPhysicalDescriptions');
diff --git a/themes/bootstrap3/templates/RecordDriver/SolrDefault/data-summary.phtml b/themes/bootstrap3/templates/RecordDriver/SolrDefault/data-summary.phtml
new file mode 100644
index 00000000000..ccc42e787fa
--- /dev/null
+++ b/themes/bootstrap3/templates/RecordDriver/SolrDefault/data-summary.phtml
@@ -0,0 +1,10 @@
+<? foreach ($this->driver->getSummary() as $summary): ?>
+  <?=$this->escapeHtml($summary) ?><br />
+<? endforeach; ?>
+<? $isbn = $this->driver->getCleanISBN(); ?>
+<? foreach ($this->summaries($isbn) as $provider => $content): ?>
+  <? foreach ($content as $summary): ?>
+    <? $htmlContent = substr($summary, 0, 1) == '<' && substr($summary, -1) == '>'; ?>
+    <?=$htmlContent ? $summary : ($this->escapeHtml($summary) . '<br />')?>
+  <? endforeach; ?>
+<? endforeach; ?>
diff --git a/themes/bootstrap3/templates/RecordTab/toc.phtml b/themes/bootstrap3/templates/RecordTab/toc.phtml
index e0fa6787a9b..1ba50de723f 100644
--- a/themes/bootstrap3/templates/RecordTab/toc.phtml
+++ b/themes/bootstrap3/templates/RecordTab/toc.phtml
@@ -2,15 +2,27 @@
     // Set page title.
     $this->headTitle($this->translate('Table of Contents') . ': ' . $this->driver->getBreadcrumb());
 
-    $toc = $this->driver->getTOC();
+    $toc = $this->tab->getContent();
+    if (empty($toc)) {
+        $driverToc = $this->driver->getTOC();
+        if (!empty($driverToc)) {
+            $toc['RecordDriver'] = $driverToc;
+        }
+    }
 ?>
 <? if (!empty($toc)): ?>
   <strong><?=$this->transEsc('Table of Contents')?>: </strong>
-  <ul class="toc">
-    <? foreach ($toc as $line): ?>
-      <li><?=$this->escapeHtml($line)?></li>
-    <? endforeach; ?>
-  </ul>
+  <? foreach ($toc as $provider => $content): ?>
+    <? if (!is_array($content)): // treat non-array content as raw HTML ?>
+      <?=$content?>
+    <? else: ?>
+      <ul class="toc">
+      <? foreach ($content as $line): ?>
+        <li><?=$this->escapeHtml($line)?></li>
+      <? endforeach; ?>
+      </ul>
+    <? endif; ?>
+  <? endforeach; ?>
 <? else: ?>
-  <?=$this->transEsc("Table of Contents unavailable")?>.
+  <?=$this->transEsc('Table of Contents unavailable')?>.
 <? endif; ?>
diff --git a/themes/bootstrap3/theme.config.php b/themes/bootstrap3/theme.config.php
index c2a266cbba5..9abcde9b89d 100644
--- a/themes/bootstrap3/theme.config.php
+++ b/themes/bootstrap3/theme.config.php
@@ -31,6 +31,7 @@ return [
             'flashmessages' => 'VuFind\View\Helper\Bootstrap3\Factory::getFlashmessages',
             'layoutclass' => 'VuFind\View\Helper\Bootstrap3\Factory::getLayoutClass',
             'recaptcha' => 'VuFind\View\Helper\Bootstrap3\Factory::getRecaptcha',
+            'summaries' => 'VuFind\View\Helper\Root\Factory::getSummaries',
         ],
         'invokables' => [
             'highlight' => 'VuFind\View\Helper\Bootstrap3\Highlight',
-- 
GitLab