diff --git a/module/VuFind/src/VuFind/RecordTab/AbstractContentFactory.php b/module/VuFind/src/VuFind/RecordTab/AbstractContentFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..645c2f38e239d90bbc815852c4473b1e607c7454
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/AbstractContentFactory.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Abstract factory for building AbstractContent tabs.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+use VuFind\Config\PluginManager as ConfigManager;
+use VuFind\Content\PluginManager as ContentManager;
+
+/**
+ * Abstract factory for building AbstractContent tabs.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+abstract class AbstractContentFactory
+    implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * The name of the tab being constructed.
+     *
+     * @var string
+     */
+    protected $tabName;
+
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        $config = $container->get(ConfigManager::class)->get('config');
+        // Only instantiate the loader if the feature is enabled:
+        $loader = isset($config->Content->{$this->tabName})
+            ? $container->get(ContentManager::class)->get($this->tabName)
+            : null;
+        return new $requestedName($loader, $this->getHideSetting($config));
+    }
+
+    /**
+     * Support method for construction of AbstractContent objects -- should we
+     * hide this tab if it is empty?
+     *
+     * @param \Zend\Config\Config $config VuFind configuration
+     *
+     * @return bool
+     */
+    protected function getHideSetting(\Zend\Config\Config $config)
+    {
+        $setting = $config->Content->hide_if_empty ?? false;
+        if ($setting === true || $setting === false
+            || $setting === 1 || $setting === 0
+        ) {
+            return (bool)$setting;
+        }
+        if ($setting === 'true' || $setting === '1') {
+            return true;
+        }
+        $hide = array_map('trim', array_map('strtolower', explode(',', $setting)));
+        return in_array(strtolower($this->tabName), $hide);
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/CollectionHierarchyTreeFactory.php b/module/VuFind/src/VuFind/RecordTab/CollectionHierarchyTreeFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a03cabe156d8a507336fdd1d70686944cd34dce
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/CollectionHierarchyTreeFactory.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Factory for building the CollectionHierarchyTree tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the CollectionHierarchyTree tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class CollectionHierarchyTreeFactory
+    implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName(
+            $container->get(\VuFind\Config\PluginManager::class)->get('config'),
+            $container->get(\VuFind\Record\Loader::class)
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/CollectionListFactory.php b/module/VuFind/src/VuFind/RecordTab/CollectionListFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..a2f5df7b98ac22dc9a47f0c093f4cc7ee821a314
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/CollectionListFactory.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Factory for building the CollectionList tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the CollectionList tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class CollectionListFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName(
+            $container->get(\VuFind\Search\SearchRunner::class),
+            $container->get(\VuFind\Recommend\PluginManager::class)
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/ExcerptFactory.php b/module/VuFind/src/VuFind/RecordTab/ExcerptFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..da6a01ba6c7e34f8ff6a9598923a28797b827452
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/ExcerptFactory.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Factory for building Excerpt tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+/**
+ * Factory for building Excerpt tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class ExcerptFactory extends AbstractContentFactory
+{
+    /**
+     * The name of the tab being constructed.
+     *
+     * @var string
+     */
+    protected $tabName = 'excerpts';
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/Factory.php b/module/VuFind/src/VuFind/RecordTab/Factory.php
deleted file mode 100644
index 2725d068153a057dcb3f4d8809fe1c7c83af2260..0000000000000000000000000000000000000000
--- a/module/VuFind/src/VuFind/RecordTab/Factory.php
+++ /dev/null
@@ -1,288 +0,0 @@
-<?php
-/**
- * Record Tab Factory Class
- *
- * PHP version 7
- *
- * Copyright (C) Villanova University 2014.
- *
- * 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  RecordDrivers
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     https://vufind.org/wiki/development:plugins:hierarchy_components Wiki
- */
-namespace VuFind\RecordTab;
-
-use Zend\ServiceManager\ServiceManager;
-
-/**
- * Record Tab Factory Class
- *
- * @category VuFind
- * @package  RecordDrivers
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     https://vufind.org/wiki/development:plugins:hierarchy_components Wiki
- *
- * @codeCoverageIgnore
- */
-class Factory
-{
-    /**
-     * Factory for CollectionHierarchyTree tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return CollectionHierarchyTree
-     */
-    public static function getCollectionHierarchyTree(ServiceManager $sm)
-    {
-        return new CollectionHierarchyTree(
-            $sm->get('VuFind\Config\PluginManager')->get('config'),
-            $sm->get('VuFind\Record\Loader')
-        );
-    }
-
-    /**
-     * Factory for CollectionList tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return CollectionList
-     */
-    public static function getCollectionList(ServiceManager $sm)
-    {
-        return new CollectionList(
-            $sm->get('VuFind\Search\SearchRunner'),
-            $sm->get('VuFind\Recommend\PluginManager')
-        );
-    }
-
-    /**
-     * Factory for Excerpt tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return Excerpt
-     */
-    public static function getExcerpt(ServiceManager $sm)
-    {
-        $config = $sm->get('VuFind\Config\PluginManager')->get('config');
-        // Only instantiate the loader if the feature is enabled:
-        if (isset($config->Content->excerpts)) {
-            $loader = $sm->get('VuFind\Content\PluginManager')
-                ->get('excerpts');
-        } else {
-            $loader = null;
-        }
-        return new Excerpt($loader, static::getHideSetting($config, 'excerpts'));
-    }
-
-    /**
-     * Support method for construction of AbstractContent objects -- should we
-     * hide this tab if it is empty?
-     *
-     * @param \Zend\Config\Config $config VuFind configuration
-     * @param string              $tab    Name of tab to check config for
-     *
-     * @return bool
-     */
-    protected static function getHideSetting(\Zend\Config\Config $config, $tab)
-    {
-        // TODO: can we move this code out of the factory so it's more easily reused?
-        $setting = isset($config->Content->hide_if_empty)
-            ? $config->Content->hide_if_empty : false;
-        if ($setting === true || $setting === false
-            || $setting === 1 || $setting === 0
-        ) {
-            return (bool)$setting;
-        }
-        if ($setting === 'true' || $setting === '1') {
-            return true;
-        }
-        $hide = array_map('trim', array_map('strtolower', explode(',', $setting)));
-        return in_array(strtolower($tab), $hide);
-    }
-
-    /**
-     * Factory for HierarchyTree tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return HierarchyTree
-     */
-    public static function getHierarchyTree(ServiceManager $sm)
-    {
-        return new HierarchyTree(
-            $sm->get('VuFind\Config\PluginManager')->get('config')
-        );
-    }
-
-    /**
-     * Factory for HoldingsILS tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return HoldingsILS
-     */
-    public static function getHoldingsILS(ServiceManager $sm)
-    {
-        // If VuFind is configured to suppress the holdings tab when the
-        // ILS driver specifies no holdings, we need to pass in a connection
-        // object:
-        $config = $sm->get('VuFind\Config\PluginManager')->get('config');
-        $catalog = ($config->Site->hideHoldingsTabWhenEmpty ?? false)
-            ? $sm->get('VuFind\ILS\Connection') : null;
-        return new HoldingsILS(
-            $catalog,
-            (string)($config->Site->holdingsTemplate ?? 'standard')
-        );
-    }
-
-    /**
-     * Factory for HoldingsWorldCat tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return HoldingsWorldCat
-     */
-    public static function getHoldingsWorldCat(ServiceManager $sm)
-    {
-        $bm = $sm->get('VuFind\Search\BackendManager');
-        return new HoldingsWorldCat($bm->get('WorldCat')->getConnector());
-    }
-
-    /**
-     * Factory for Map tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return Map
-     */
-    public static function getMap(ServiceManager $sm)
-    {
-        // get Map Tab config options
-        $mapTabConfig = $sm->get('VuFind\GeoFeatures\MapTabConfig');
-        $mapTabOptions = $mapTabConfig->getMapTabOptions();
-        $mapTabDisplay = $mapTabOptions['recordMap'];
-
-        // add basemap options
-        $basemapConfig = $sm->get('VuFind\GeoFeatures\BasemapConfig');
-        $basemapOptions = $basemapConfig->getBasemap('MapTab');
-
-        return new Map($mapTabDisplay, $basemapOptions, $mapTabOptions);
-    }
-
-    /**
-     * Factory for Preview tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return Preview
-     */
-    public static function getPreview(ServiceManager $sm)
-    {
-        $cfg = $sm->get('VuFind\Config\PluginManager')->get('config');
-        // currently only active if config [content] [previews] contains google
-        // and googleoptions[tab] is not empty.
-        $active = false;
-        if (isset($cfg->Content->previews)) {
-            $previews = array_map(
-                'trim', explode(',', strtolower($cfg->Content->previews))
-            );
-            if (in_array('google', $previews)
-                && isset($cfg->Content->GoogleOptions['tab'])
-                && strlen(trim($cfg->Content->GoogleOptions['tab'])) > 0
-            ) {
-                $active = true;
-            }
-        }
-        return new Preview($active);
-    }
-
-    /**
-     * Factory for SimilarItems tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return SimilarItemsCarousel
-     */
-    public static function getSimilarItemsCarousel(ServiceManager $sm)
-    {
-        return new SimilarItemsCarousel($sm->get('VuFindSearch\Service'));
-    }
-
-    /**
-     * Factory for Reviews tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return Reviews
-     */
-    public static function getReviews(ServiceManager $sm)
-    {
-        $config = $sm->get('VuFind\Config\PluginManager')->get('config');
-        // Only instantiate the loader if the feature is enabled:
-        if (isset($config->Content->reviews)) {
-            $loader = $sm->get('VuFind\Content\PluginManager')
-                ->get('reviews');
-        } else {
-            $loader = null;
-        }
-        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->get('VuFind\Config\PluginManager')->get('config');
-        // Only instantiate the loader if the feature is enabled:
-        if (isset($config->Content->toc)) {
-            $loader = $sm->get('VuFind\Content\PluginManager')
-                ->get('toc');
-        } else {
-            $loader = null;
-        }
-        return new TOC($loader, static::getHideSetting($config, 'toc'));
-    }
-
-    /**
-     * Factory for UserComments tab plugin.
-     *
-     * @param ServiceManager $sm Service manager.
-     *
-     * @return UserComments
-     */
-    public static function getUserComments(ServiceManager $sm)
-    {
-        $capabilities = $sm->get('VuFind\Config\AccountCapabilities');
-        $config = $sm->get('VuFind\Config\PluginManager')->get('config');
-        $useRecaptcha = isset($config->Captcha) && isset($config->Captcha->forms)
-            && (trim($config->Captcha->forms) === '*'
-            || strpos($config->Captcha->forms, 'userComments') !== false);
-        return new UserComments(
-            'enabled' === $capabilities->getCommentSetting(),
-            $useRecaptcha
-        );
-    }
-}
diff --git a/module/VuFind/src/VuFind/RecordTab/HierarchyTreeFactory.php b/module/VuFind/src/VuFind/RecordTab/HierarchyTreeFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..998bfbac2297753bc2af9420f813cd06394705d4
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/HierarchyTreeFactory.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Factory for building the HierarchyTree tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the HierarchyTree tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class HierarchyTreeFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName(
+            $container->get(\VuFind\Config\PluginManager::class)->get('config')
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/HoldingsILSFactory.php b/module/VuFind/src/VuFind/RecordTab/HoldingsILSFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..cacf087ada9db6bd7a4c01f6883bafb0ef48611e
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/HoldingsILSFactory.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Factory for building the HoldingsILS tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the HoldingsILS tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class HoldingsILSFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        // If VuFind is configured to suppress the holdings tab when the
+        // ILS driver specifies no holdings, we need to pass in a connection
+        // object:
+        $config = $container->get(\VuFind\Config\PluginManager::class)
+            ->get('config');
+        $catalog = ($config->Site->hideHoldingsTabWhenEmpty ?? false)
+            ? $container->get(\VuFind\ILS\Connection::class) : null;
+        return new $requestedName(
+            $catalog,
+            (string)($config->Site->holdingsTemplate ?? 'standard')
+        );
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..2552e4bc70d37ee8a5aefdd955a49827ece007a8
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Factory for building the HoldingsWorldCat tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the HoldingsWorldCat tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class HoldingsWorldCatFactory
+    implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        $bm = $container->get(\VuFind\Search\BackendManager::class);
+        return new $requestedName($bm->get('WorldCat')->getConnector());
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/MapFactory.php b/module/VuFind/src/VuFind/RecordTab/MapFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3b5a5175f2cc56ac9b7e6d10c925467e0edcc22
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/MapFactory.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Factory for building the Map tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the Map tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class MapFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        // get Map Tab config options
+        $mapTabConfig = $container->get(\VuFind\GeoFeatures\MapTabConfig::class);
+        $mapTabOptions = $mapTabConfig->getMapTabOptions();
+        $mapTabDisplay = $mapTabOptions['recordMap'];
+
+        // add basemap options
+        $basemapConfig = $container->get(\VuFind\GeoFeatures\BasemapConfig::class);
+        $basemapOptions = $basemapConfig->getBasemap('MapTab');
+
+        return new $requestedName($mapTabDisplay, $basemapOptions, $mapTabOptions);
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/PluginManager.php b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
index 99e674a4e0c3b3f88418bd2f2d21a32a37e52baf..215f77130a389cfcfbc51af118c6b303d6c980dc 100644
--- a/module/VuFind/src/VuFind/RecordTab/PluginManager.php
+++ b/module/VuFind/src/VuFind/RecordTab/PluginManager.php
@@ -28,6 +28,7 @@
 namespace VuFind\RecordTab;
 
 use VuFind\RecordDriver\AbstractBase as AbstractRecordDriver;
+use Zend\ServiceManager\Factory\InvokableFactory;
 
 /**
  * Record tab plugin manager
@@ -46,21 +47,21 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
      * @var array
      */
     protected $aliases = [
-        'collectionhierarchytree' => 'VuFind\RecordTab\CollectionHierarchyTree',
-        'collectionlist' => 'VuFind\RecordTab\CollectionList',
-        'description' => 'VuFind\RecordTab\Description',
-        'excerpt' => 'VuFind\RecordTab\Excerpt',
-        'hierarchytree' => 'VuFind\RecordTab\HierarchyTree',
-        'holdingsils' => 'VuFind\RecordTab\HoldingsILS',
-        'holdingsworldcat' => 'VuFind\RecordTab\HoldingsWorldCat',
-        'map' => 'VuFind\RecordTab\Map',
-        'preview' => 'VuFind\RecordTab\Preview',
-        'reviews' => 'VuFind\RecordTab\Reviews',
-        'similaritemscarousel' => 'VuFind\RecordTab\SimilarItemsCarousel',
-        'staffviewarray' => 'VuFind\RecordTab\StaffViewArray',
-        'staffviewmarc' => 'VuFind\RecordTab\StaffViewMARC',
-        'toc' => 'VuFind\RecordTab\TOC',
-        'usercomments' => 'VuFind\RecordTab\UserComments',
+        'collectionhierarchytree' => CollectionHierarchyTree::class,
+        'collectionlist' => CollectionList::class,
+        'description' => Description::class,
+        'excerpt' => Excerpt::class,
+        'hierarchytree' => HierarchyTree::class,
+        'holdingsils' => HoldingsILS::class,
+        'holdingsworldcat' => HoldingsWorldCat::class,
+        'map' => Map::class,
+        'preview' => Preview::class,
+        'reviews' => Reviews::class,
+        'similaritemscarousel' => SimilarItemsCarousel::class,
+        'staffviewarray' => StaffViewArray::class,
+        'staffviewmarc' => StaffViewMARC::class,
+        'toc' => TOC::class,
+        'usercomments' => UserComments::class,
     ];
 
     /**
@@ -69,30 +70,21 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
      * @var array
      */
     protected $factories = [
-        'VuFind\RecordTab\CollectionHierarchyTree' =>
-            'VuFind\RecordTab\Factory::getCollectionHierarchyTree',
-        'VuFind\RecordTab\CollectionList' =>
-            'VuFind\RecordTab\Factory::getCollectionList',
-        'VuFind\RecordTab\Description' =>
-            'Zend\ServiceManager\Factory\InvokableFactory',
-        'VuFind\RecordTab\Excerpt' => 'VuFind\RecordTab\Factory::getExcerpt',
-        'VuFind\RecordTab\HierarchyTree' =>
-            'VuFind\RecordTab\Factory::getHierarchyTree',
-        'VuFind\RecordTab\HoldingsILS' => 'VuFind\RecordTab\Factory::getHoldingsILS',
-        'VuFind\RecordTab\HoldingsWorldCat' =>
-            'VuFind\RecordTab\Factory::getHoldingsWorldCat',
-        'VuFind\RecordTab\Map' => 'VuFind\RecordTab\Factory::getMap',
-        'VuFind\RecordTab\Preview' => 'VuFind\RecordTab\Factory::getPreview',
-        'VuFind\RecordTab\Reviews' => 'VuFind\RecordTab\Factory::getReviews',
-        'VuFind\RecordTab\SimilarItemsCarousel' =>
-            'VuFind\RecordTab\Factory::getSimilarItemsCarousel',
-        'VuFind\RecordTab\StaffViewArray' =>
-            'Zend\ServiceManager\Factory\InvokableFactory',
-        'VuFind\RecordTab\StaffViewMARC' =>
-            'Zend\ServiceManager\Factory\InvokableFactory',
-        'VuFind\RecordTab\TOC' => 'VuFind\RecordTab\Factory::getTOC',
-        'VuFind\RecordTab\UserComments' =>
-            'VuFind\RecordTab\Factory::getUserComments',
+        CollectionHierarchyTree::class => CollectionHierarchyTreeFactory::class,
+        CollectionList::class => CollectionListFactory::class,
+        Description::class => InvokableFactory::class,
+        Excerpt::class => ExcerptFactory::class,
+        HierarchyTree::class => HierarchyTreeFactory::class,
+        HoldingsILS::class => HoldingsILSFactory::class,
+        HoldingsWorldCat::class => HoldingsWorldCatFactory::class,
+        Map::class => MapFactory::class,
+        Preview::class => PreviewFactory::class,
+        Reviews::class => ReviewsFactory::class,
+        SimilarItemsCarousel::class => SimilarItemsCarouselFactory::class,
+        StaffViewArray::class => InvokableFactory::class,
+        StaffViewMARC::class => InvokableFactory::class,
+        TOC::class => TOCFactory::class,
+        UserComments::class => UserCommentsFactory::class,
     ];
 
     /**
@@ -107,7 +99,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
     public function __construct($configOrContainerInstance = null,
         array $v3config = []
     ) {
-        $this->addAbstractFactory('VuFind\RecordTab\PluginFactory');
+        $this->addAbstractFactory(PluginFactory::class);
         $this->addInitializer('ZfcRbac\Initializer\AuthorizationServiceInitializer');
         parent::__construct($configOrContainerInstance, $v3config);
     }
@@ -149,7 +141,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
      */
     protected function getExpectedInterface()
     {
-        return 'VuFind\RecordTab\TabInterface';
+        return TabInterface::class;
     }
 
     /**
diff --git a/module/VuFind/src/VuFind/RecordTab/PreviewFactory.php b/module/VuFind/src/VuFind/RecordTab/PreviewFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a4905a9c3119801dbffd4909c7132e8805ee5cc
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/PreviewFactory.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Factory for building the Preview tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the Preview tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class PreviewFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        $cfg = $container->get('VuFind\Config\PluginManager')->get('config');
+        // currently only active if config [content] [previews] contains google
+        // and googleoptions[tab] is not empty.
+        $active = false;
+        if (isset($cfg->Content->previews)) {
+            $previews = array_map(
+                'trim', explode(',', strtolower($cfg->Content->previews))
+            );
+            if (in_array('google', $previews)
+                && strlen(trim($cfg->Content->GoogleOptions['tab'] ?? '')) > 0
+            ) {
+                $active = true;
+            }
+        }
+        return new $requestedName($active);
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/ReviewsFactory.php b/module/VuFind/src/VuFind/RecordTab/ReviewsFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..baeb44580bbf6ab76cd54759a53955b133520a0c
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/ReviewsFactory.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Factory for building Reviews tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+/**
+ * Factory for building Reviews tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class ReviewsFactory extends AbstractContentFactory
+{
+    /**
+     * The name of the tab being constructed.
+     *
+     * @var string
+     */
+    protected $tabName = 'reviews';
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/SimilarItemsCarouselFactory.php b/module/VuFind/src/VuFind/RecordTab/SimilarItemsCarouselFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea0e50c1fb34bd404864e3791798b2b6dd92a060
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/SimilarItemsCarouselFactory.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Factory for building the SimilarItemsCarousel tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the SimilarItemsCarousel tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class SimilarItemsCarouselFactory
+    implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        return new $requestedName($container->get(\VuFindSearch\Service::class));
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/TOCFactory.php b/module/VuFind/src/VuFind/RecordTab/TOCFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..19d3e94e8166588b109196c2593d13cea72ca776
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/TOCFactory.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Factory for building TOC tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+/**
+ * Factory for building TOC tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class TOCFactory extends AbstractContentFactory
+{
+    /**
+     * The name of the tab being constructed.
+     *
+     * @var string
+     */
+    protected $tabName = 'toc';
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/UserCommentsFactory.php b/module/VuFind/src/VuFind/RecordTab/UserCommentsFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..0863f8aff112fc8146b34aec652e5d99044e5c99
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/UserCommentsFactory.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Factory for building the UserComments tab.
+ *
+ * PHP version 7
+ *
+ * Copyright (C) Villanova University 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  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+namespace VuFind\RecordTab;
+
+use Interop\Container\ContainerInterface;
+
+/**
+ * Factory for building the UserComments tab.
+ *
+ * @category VuFind
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org/wiki/development Wiki
+ */
+class UserCommentsFactory implements \Zend\ServiceManager\Factory\FactoryInterface
+{
+    /**
+     * Create an object
+     *
+     * @param ContainerInterface $container     Service manager
+     * @param string             $requestedName Service being created
+     * @param null|array         $options       Extra options (optional)
+     *
+     * @return object
+     *
+     * @throws ServiceNotFoundException if unable to resolve the service.
+     * @throws ServiceNotCreatedException if an exception is raised when
+     * creating a service.
+     * @throws ContainerException if any other error occurs
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(ContainerInterface $container, $requestedName,
+        array $options = null
+    ) {
+        if (!empty($options)) {
+            throw new \Exception('Unexpected options passed to factory.');
+        }
+        $capabilities = $container->get(\VuFind\Config\AccountCapabilities::class);
+        $config = $container->get(\VuFind\Config\PluginManager::class)
+            ->get('config');
+        $captchaConfig = $config->Captcha->forms ?? '';
+        $useRecaptcha = trim($captchaConfig) === '*'
+            || strpos($captchaConfig, 'userComments') !== false;
+        return new $requestedName(
+            'enabled' === $capabilities->getCommentSetting(),
+            $useRecaptcha
+        );
+    }
+}