From e764da1d6402212b7be0ee4385018b326bd7552d Mon Sep 17 00:00:00 2001
From: Chris Hallberg <crhallberg@gmail.com>
Date: Wed, 18 Dec 2013 09:20:31 -0500
Subject: [PATCH] Moved admin code to its own module.

---
 config/application.config.php                 |   4 +-
 module/VuFind/config/module.config.php        |   3 -
 .../src/VuFind/Controller/AdminController.php | 311 ------------------
 module/VuFindAdmin/Module.php                 |  68 ++++
 module/VuFindAdmin/config/module.config.php   |  83 +++++
 .../VuFindAdmin/Controller/AbstractAdmin.php  | 124 +++++++
 .../Controller/AdminController.php            |  71 ++++
 .../Controller/ConfigController.php           |  86 +++++
 .../Controller/MaintenanceController.php      |  84 +++++
 .../Controller/SocialstatsController.php      |  57 ++++
 .../Controller/StatisticsController.php       | 118 +++++++
 .../admin/{config.phtml => config/home.phtml} |   2 +-
 .../home.phtml}                               |   2 +-
 themes/blueprint/templates/admin/menu.phtml   |  10 +-
 .../home.phtml}                               |   0
 .../home.phtml}                               |   0
 .../admin/{config.phtml => config/home.phtml} |  11 +-
 .../bootstrap/templates/admin/disabled.phtml  |   6 +-
 themes/bootstrap/templates/admin/home.phtml   |  21 +-
 .../templates/admin/maintenance.phtml         |  22 --
 .../templates/admin/maintenance/home.phtml    |  19 ++
 themes/bootstrap/templates/admin/menu.phtml   |  10 +-
 .../home.phtml}                               |  11 +-
 .../home.phtml}                               |  32 +-
 .../bootstrap/templates/layout/layout.phtml   |   5 +-
 25 files changed, 767 insertions(+), 393 deletions(-)
 delete mode 100644 module/VuFind/src/VuFind/Controller/AdminController.php
 create mode 100644 module/VuFindAdmin/Module.php
 create mode 100644 module/VuFindAdmin/config/module.config.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/AbstractAdmin.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/AdminController.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/ConfigController.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/MaintenanceController.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/SocialstatsController.php
 create mode 100644 module/VuFindAdmin/src/VuFindAdmin/Controller/StatisticsController.php
 rename themes/blueprint/templates/admin/{config.phtml => config/home.phtml} (85%)
 rename themes/blueprint/templates/admin/{maintenance.phtml => maintenance/home.phtml} (85%)
 rename themes/blueprint/templates/admin/{socialstats.phtml => socialstats/home.phtml} (100%)
 rename themes/blueprint/templates/admin/{statistics.phtml => statistics/home.phtml} (100%)
 rename themes/bootstrap/templates/admin/{config.phtml => config/home.phtml} (53%)
 delete mode 100644 themes/bootstrap/templates/admin/maintenance.phtml
 create mode 100644 themes/bootstrap/templates/admin/maintenance/home.phtml
 rename themes/bootstrap/templates/admin/{socialstats.phtml => socialstats/home.phtml} (78%)
 rename themes/bootstrap/templates/admin/{statistics.phtml => statistics/home.phtml} (78%)

diff --git a/config/application.config.php b/config/application.config.php
index 0a5657e6664..f12766dcdef 100644
--- a/config/application.config.php
+++ b/config/application.config.php
@@ -1,7 +1,7 @@
 <?php
 $config = array(
     'modules' => array(
-        'VuFindHttp', 'VuFindTheme', 'VuFindSearch', 'VuFind',
+        'VuFindHttp', 'VuFindTheme', 'VuFindSearch', 'VuFind', 'VuFindAdmin'
     ),
     'module_listener_options' => array(
         'config_glob_paths'    => array(
@@ -34,4 +34,4 @@ if ($localModules = getenv('VUFIND_LOCAL_MODULES')) {
         }
     }
 }
-return $config;
\ No newline at end of file
+return $config;
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 99164a6565e..4e4f942e1cb 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -84,7 +84,6 @@ $config = array(
             },
         ),
         'invokables' => array(
-            'admin' => 'VuFind\Controller\AdminController',
             'ajax' => 'VuFind\Controller\AjaxController',
             'alphabrowse' => 'VuFind\Controller\AlphabrowseController',
             'author' => 'VuFind\Controller\AuthorController',
@@ -1025,8 +1024,6 @@ $listRoutes = array('userList' => 'MyList', 'editList' => 'EditList');
 
 // Define static routes -- Controller/Action strings
 $staticRoutes = array(
-    'Admin/Config', 'Admin/DeleteExpiredSearches', 'Admin/EnableAutoConfig',
-    'Admin/Home', 'Admin/Maintenance', 'Admin/SocialStats', 'Admin/Statistics',
     'Alphabrowse/Home', 'Author/Home', 'Author/Search',
     'Authority/Home', 'Authority/Record', 'Authority/Search',
     'Browse/Author', 'Browse/Dewey', 'Browse/Era', 'Browse/Genre', 'Browse/Home',
diff --git a/module/VuFind/src/VuFind/Controller/AdminController.php b/module/VuFind/src/VuFind/Controller/AdminController.php
deleted file mode 100644
index 5192e09ee88..00000000000
--- a/module/VuFind/src/VuFind/Controller/AdminController.php
+++ /dev/null
@@ -1,311 +0,0 @@
-<?php
-/**
- * Admin Controller
- *
- * PHP version 5
- *
- * Copyright (C) Villanova University 2010.
- *
- * 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  Controller
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     http://vufind.org   Main Site
- */
-namespace VuFind\Controller;
-use VuFind\Exception\Forbidden as ForbiddenException, Zend\Mvc\MvcEvent;
-
-/**
- * Class controls VuFind administration.
- *
- * @category VuFind2
- * @package  Controller
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     http://vufind.org   Main Site
- */
-
-class AdminController extends AbstractBase
-{
-    /**
-     * preDispatch -- block access when appropriate.
-     *
-     * @param MvcEvent $e Event object
-     *
-     * @return void
-     */
-    public function preDispatch(MvcEvent $e)
-    {
-        // Disable search box in Admin module:
-        $this->layout()->searchbox = false;
-
-        // If we're using the "disabled" action, we don't need to do any further
-        // checking to see if we are disabled!!
-        $routeMatch = $e->getRouteMatch();
-        if (strtolower($routeMatch->getParam('action')) == 'disabled') {
-            return;
-        }
-
-        // Block access to everyone when module is disabled:
-        $config = $this->getConfig();
-        if (!isset($config->Site->admin_enabled) || !$config->Site->admin_enabled) {
-            $routeMatch->setParam('action', 'disabled');
-            return;
-        }
-
-        // Block access by IP when IP checking is enabled:
-        if (isset($config->AdminAuth->ipRegEx)) {
-            $ipMatch = preg_match(
-                $config->AdminAuth->ipRegEx,
-                $this->getRequest()->getServer()->get('REMOTE_ADDR')
-            );
-            if (!$ipMatch) {
-                throw new ForbiddenException('Access denied.');
-            }
-        }
-
-        // Block access by username when user whitelist is enabled:
-        if (isset($config->AdminAuth->userWhitelist)) {
-            $user = $this->getUser();
-            if ($user == false) {
-                $e->setResponse($this->forceLogin(null, array(), false));
-                return;
-            }
-            $matchFound = false;
-            foreach ($config->AdminAuth->userWhitelist as $check) {
-                if ($check == $user->username) {
-                    $matchFound = true;
-                    break;
-                }
-            }
-            if (!$matchFound) {
-                throw new ForbiddenException('Access denied.');
-            }
-        }
-    }
-
-    /**
-     * Register the default events for this controller
-     *
-     * @return void
-     */
-    protected function attachDefaultListeners()
-    {
-        parent::attachDefaultListeners();
-        $events = $this->getEventManager();
-        $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 1000);
-    }
-
-    /**
-     * Display disabled message.
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function disabledAction()
-    {
-        return $this->createViewModel();
-    }
-
-    /**
-     * Admin home.
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function homeAction()
-    {
-        $config = $this->getConfig();
-        $xml = false;
-        if (isset($config->Index->url)) {
-            $response = $this->getServiceLocator()->get('VuFind\Http')
-                ->get($config->Index->url . '/admin/multicore');
-            $xml = $response->isSuccess() ? $response->getBody() : false;
-        }
-        $view = $this->createViewModel();
-        $view->xml = $xml ? simplexml_load_string($xml) : false;
-        return $view;
-    }
-
-    /**
-     * Social statistics reporting
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function socialstatsAction()
-    {
-        $view = $this->createViewModel();
-        $view->comments = $this->getTable('comments')->getStatistics();
-        $view->favorites = $this->getTable('userresource')->getStatistics();
-        $view->tags = $this->getTable('resourcetags')->getStatistics();
-        return $view;
-    }
-
-    /**
-     * Statistics reporting
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function statisticsAction()
-    {
-        $view = $this->createViewModel();
-        $config = $this->getConfig();
-
-        // Search statistics
-        $search = $this->getServiceLocator()->get('VuFind\SearchStats');
-        $view->searchesBySource
-            = $config->Statistics->searchesBySource
-            ?: false;
-        $searchSummary = $search->getStatsSummary(
-            7, $config->Statistics->searchesBySource
-        );
-        $view->topSearches = isset($searchSummary['top'])
-            ? $searchSummary['top'] : null;
-        $view->emptySearches = isset($searchSummary['empty'])
-            ? $searchSummary['empty'] : null;
-        $view->totalSearches = isset($searchSummary['total'])
-            ? $searchSummary['total'] : null;
-
-        // Record statistics
-        $records = $this->getServiceLocator()->get('VuFind\RecordStats');
-        $view->recordsBySource = $config->Statistics->recordsBySource ?: false;
-        $recordSummary = $records->getStatsSummary(
-            5, $config->Statistics->recordsBySource
-        );
-        $view->topRecords = isset($recordSummary['top'])
-            ? $recordSummary['top'] : null;
-        $view->totalRecordViews = isset($recordSummary['total'])
-            ? $recordSummary['total'] : null;
-
-        // Browser statistics
-        $view->currentBrowser = $search->getBrowser(
-            $this->getRequest()->getServer('HTTP_USER_AGENT')
-        );
-
-        // Look for universal statistics recorder
-        $matchFound = false;
-        foreach ($search->getDriversForSource(null) as $currentDriver) {
-            $browserStats = $currentDriver->getBrowserStats(false, 5);
-            if (!empty($browserStats)) {
-                $matchFound = true;
-                break;
-            }
-        }
-
-        // If no full coverage mode found, take the first valid source
-        if (!$matchFound) {
-            $drivers = $search->getDriversForSource(null, true);
-            foreach ($drivers as $currentDriver) {
-                $browserStats = $currentDriver->getBrowserStats(false, 5);
-                if (!empty($browserStats)) {
-                    $matchFound = true;
-                    break;
-                }
-            }
-        }
-
-        // Initialize browser/version data in view based on what we found above:
-        if ($matchFound) {
-            $view->browserStats = $browserStats;
-            $view->topVersions = $currentDriver->getBrowserStats(true, 5);
-        } else {
-            $view->browserStats = $view->topVersions = null;
-        }
-        
-        return $view;
-    }
-
-    /**
-     * Configuration management
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function configAction()
-    {
-        $view = $this->createViewModel();
-        $view->baseConfigPath = \VuFind\Config\Locator::getBaseConfigPath('');
-        $conf = $this->getConfig();
-        $view->showInstallLink
-            = isset($conf->System->autoConfigure) && $conf->System->autoConfigure;
-        return $view;
-    }
-
-    /**
-     * Support action for config -- attempt to enable auto configuration.
-     *
-     * @return mixed
-     */
-    public function enableautoconfigAction()
-    {
-        $configFile = \VuFind\Config\Locator::getConfigPath('config.ini');
-        $writer = new \VuFind\Config\Writer($configFile);
-        $writer->set('System', 'autoConfigure', 1);
-        if ($writer->save()) {
-            $this->flashMessenger()->setNamespace('info')
-                ->addMessage('Auto-configuration enabled.');
-
-            // Reload config now that it has been edited (otherwise, old setting
-            // will persist in cache):
-            $this->getServiceLocator()->get('VuFind\Config')->reload('config');
-        } else {
-            $this->flashMessenger()->setNamespace('error')
-                ->addMessage(
-                    'Could not enable auto-configuration; check permissions on '
-                    . $configFile . '.'
-                );
-        }
-        return $this->forwardTo('Admin', 'Config');
-    }
-
-    /**
-     * System maintenance
-     *
-     * @return \Zend\View\Model\ViewModel
-     */
-    public function maintenanceAction()
-    {
-        return $this->createViewModel();
-    }
-
-    /**
-     * Support action for maintenance -- delete expired searches.
-     *
-     * @return mixed
-     */
-    public function deleteexpiredsearchesAction()
-    {
-        $daysOld = intval($this->params()->fromQuery('daysOld', 2));
-        if ($daysOld < 2) {
-            $this->flashMessenger()->setNamespace('error')
-                ->addMessage(
-                    'Expiration age must be at least two days.'
-                );
-        } else {
-            // Delete the expired searches--this cleans up any junk left in the
-            // database from old search histories that were not caught by the
-            // session garbage collector.
-            $search = $this->getTable('Search');
-            $query = $search->getExpiredQuery($daysOld);
-            if (($count = count($search->select($query))) == 0) {
-                $msg = "No expired searches to delete.";
-            } else {
-                $search->delete($query);
-                $msg = "{$count} expired searches deleted.";
-            }
-            $this->flashMessenger()->setNamespace('info')->addMessage($msg);
-        }
-        return $this->forwardTo('Admin', 'Maintenance');
-    }
-}
-
diff --git a/module/VuFindAdmin/Module.php b/module/VuFindAdmin/Module.php
new file mode 100644
index 00000000000..0b40322baae
--- /dev/null
+++ b/module/VuFindAdmin/Module.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * VuFind Admin Tools module.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Module
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://github.com/dmj/vf2-proxy
+ */
+namespace VuFindAdmin;
+use Zend\ModuleManager\ModuleManager,
+    Zend\Mvc\MvcEvent;
+
+/**
+ * VuFind Admin Tools module.
+ *
+ * @category VuFind2
+ * @package  Module
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://github.com/dmj/vf2-proxy
+ */
+class Module
+{
+    /**
+     * Get module configuration
+     *
+     * @return array
+     */
+    public function getConfig()
+    {
+        return include __DIR__ . '/config/module.config.php';
+    }
+
+    /**
+     * Get autoloader configuration
+     *
+     * @return array
+     */
+    public function getAutoloaderConfig()
+    {
+        return array(
+            'Zend\Loader\StandardAutoloader' => array(
+                'namespaces' => array(
+                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
+                ),
+            ),
+        );
+    }
+}
diff --git a/module/VuFindAdmin/config/module.config.php b/module/VuFindAdmin/config/module.config.php
new file mode 100644
index 00000000000..196344a9c0d
--- /dev/null
+++ b/module/VuFindAdmin/config/module.config.php
@@ -0,0 +1,83 @@
+<?php
+namespace VuFindAdmin\Module\Configuration;
+
+$config = array(
+    'controllers' => array(
+        'invokables' => array(
+            'admin' => 'VuFindAdmin\Controller\AdminController',
+            'adminconfig' => 'VuFindAdmin\Controller\ConfigController',
+            'adminsocial' => 'VuFindAdmin\Controller\SocialstatsController',
+            'adminmaintenance' => 'VuFindAdmin\Controller\MaintenanceController',
+            'adminstatistics' => 'VuFindAdmin\Controller\StatisticsController',
+        ),
+    ),
+    'router' => array(
+        'routes' => array(
+            'admin' => array(
+                'type' => 'Zend\Mvc\Router\Http\Literal',
+                'options' => array(
+                    'route'    => '/Admin',
+                    'defaults' => array(
+                        'controller' => 'Admin',
+                        'action'     => 'Home',
+                    )
+                ),
+                'may_terminate' => true,
+                'child_routes' => array(
+                    'disabled' => array(
+                        'type' => 'Zend\Mvc\Router\Http\Literal',
+                        'options' => array(
+                            'route'    => '/Disabled',
+                            'defaults' => array(
+                                'controller' => 'Admin',
+                                'action'     => 'Disabled',
+                            )
+                        )
+                    ),
+                    'config' => array(
+                        'type' => 'Zend\Mvc\Router\Http\Segment',
+                        'options' => array(
+                            'route'    => '/Config[/:action]',
+                            'defaults' => array(
+                                'controller' => 'AdminConfig',
+                                'action'     => 'Home',
+                            )
+                        )
+                    ),
+                    'maintenance' => array(
+                        'type' => 'Zend\Mvc\Router\Http\Segment',
+                        'options' => array(
+                            'route'    => '/Maintenance[/:action]',
+                            'defaults' => array(
+                                'controller' => 'AdminMaintenance',
+                                'action'     => 'Home',
+                            )
+                        )
+                    ),
+                    'social' => array(
+                        'type' => 'Zend\Mvc\Router\Http\Segment',
+                        'options' => array(
+                            'route'    => '/Social[/:action]',
+                            'defaults' => array(
+                                'controller' => 'AdminSocial',
+                                'action'     => 'Home',
+                            )
+                        )
+                    ),
+                    'statistics' => array(
+                        'type' => 'Zend\Mvc\Router\Http\Segment',
+                        'options' => array(
+                            'route'    => '/Statistics[/:action]',
+                            'defaults' => array(
+                                'controller' => 'AdminStatistics',
+                                'action'     => 'Home',
+                            )
+                        )
+                    ),
+                ),
+            ),
+        ),
+    ),
+);
+
+return $config;
\ No newline at end of file
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/AbstractAdmin.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/AbstractAdmin.php
new file mode 100644
index 00000000000..3b5bdaeb72c
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/AbstractAdmin.php
@@ -0,0 +1,124 @@
+<?php
+/**
+ * VuFind Admin Controller Base
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFindAdmin\Controller;
+use VuFind\Exception\Forbidden as ForbiddenException,
+    Zend\Mvc\MvcEvent,
+    Zend\Stdlib\Parameters;
+
+/**
+ * VuFind Admin Controller Base
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class AbstractAdmin extends \VuFind\Controller\AbstractBase
+{
+    /**
+     * preDispatch -- block access when appropriate.
+     *
+     * @param MvcEvent $e Event object
+     *
+     * @return void
+     */
+    public function preDispatch(MvcEvent $e)
+    {
+        // Disable search box in Admin module:
+        $this->layout()->searchbox = false;
+
+        // If we're using the "disabled" action, we don't need to do any further
+        // checking to see if we are disabled!!
+        $routeMatch = $e->getRouteMatch();
+        if (strtolower($routeMatch->getParam('action')) == 'disabled') {
+            return;
+        }
+
+        // Block access to everyone when module is disabled:
+        $config = $this->getConfig();
+        if (!isset($config->Site->admin_enabled) || !$config->Site->admin_enabled) {
+            $pluginManager  = $this->getServiceLocator()
+                ->get('Zend\Mvc\Controller\PluginManager');
+            $redirectPlugin = $pluginManager->get('redirect');
+            return $redirectPlugin->toRoute('admin/disabled');
+        }
+
+        // Block access by IP when IP checking is enabled:
+        if (isset($config->AdminAuth->ipRegEx)) {
+            $ipMatch = preg_match(
+                $config->AdminAuth->ipRegEx,
+                $this->getRequest()->getServer()->get('REMOTE_ADDR')
+            );
+            if (!$ipMatch) {
+                throw new ForbiddenException('Access denied.');
+            }
+        }
+
+        // Block access by username when user whitelist is enabled:
+        if (isset($config->AdminAuth->userWhitelist)) {
+            $user = $this->getUser();
+            if ($user == false) {
+                $e->setResponse($this->forceLogin(null, array(), false));
+                return;
+            }
+            $matchFound = false;
+            foreach ($config->AdminAuth->userWhitelist as $check) {
+                if ($check == $user->username) {
+                    $matchFound = true;
+                    break;
+                }
+            }
+            if (!$matchFound) {
+                throw new ForbiddenException('Access denied.');
+            }
+        }
+    }
+
+    /**
+     * Register the default events for this controller
+     *
+     * @return void
+     */
+    protected function attachDefaultListeners()
+    {
+        parent::attachDefaultListeners();
+        $events = $this->getEventManager();
+        $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 1000);
+    }
+    
+    /**
+     * Display disabled message.
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function disabledAction()
+    {
+        return $this->createViewModel();
+    }
+}
\ No newline at end of file
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/AdminController.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/AdminController.php
new file mode 100644
index 00000000000..f29f06ca72f
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/AdminController.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Admin Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFindAdmin\Controller;
+
+/**
+ * Class controls VuFind administration.
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class AdminController extends AbstractAdmin
+{
+    /**
+     * Display disabled message.
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function disabledAction()
+    {
+        return $this->createViewModel();
+    }
+
+    /**
+     * Admin home.
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+        $config = $this->getConfig();
+        $xml = false;
+        if (isset($config->Index->url)) {
+            $response = $this->getServiceLocator()->get('VuFind\Http')
+                ->get($config->Index->url . '/admin/multicore');
+            $xml = $response->isSuccess() ? $response->getBody() : false;
+        }
+        $view = $this->createViewModel();
+        $view->xml = $xml ? simplexml_load_string($xml) : false;
+        return $view;
+    }
+}
+
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/ConfigController.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/ConfigController.php
new file mode 100644
index 00000000000..17100761307
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/ConfigController.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Admin Configuration Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFindAdmin\Controller;
+
+/**
+ * Class controls VuFind administration.
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class ConfigController extends AbstractAdmin
+{
+    /**
+     * Configuration management
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+        $view = $this->createViewModel();
+        $view->setTemplate('admin/config/home');
+        $view->baseConfigPath = \VuFind\Config\Locator::getBaseConfigPath('');
+        $conf = $this->getConfig();
+        $view->showInstallLink
+            = isset($conf->System->autoConfigure) && $conf->System->autoConfigure;
+        return $view;
+    }
+
+    /**
+     * Support action for config -- attempt to enable auto configuration.
+     *
+     * @return mixed
+     */
+    public function enableautoconfigAction()
+    {
+        $configFile = \VuFind\Config\Locator::getConfigPath('config.ini');
+        $writer = new \VuFind\Config\Writer($configFile);
+        $writer->set('System', 'autoConfigure', 1);
+        if ($writer->save()) {
+            $this->flashMessenger()->setNamespace('info')
+                ->addMessage('Auto-configuration enabled.');
+
+            // Reload config now that it has been edited (otherwise, old setting
+            // will persist in cache):
+            $this->getServiceLocator()->get('VuFind\Config')->reload('config');
+        } else {
+            $this->flashMessenger()->setNamespace('error')
+                ->addMessage(
+                    'Could not enable auto-configuration; check permissions on '
+                    . $configFile . '.'
+                );
+        }
+        return $this->forwardTo('AdminConfig', 'Home');
+    }
+
+}
+
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/MaintenanceController.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/MaintenanceController.php
new file mode 100644
index 00000000000..5dbfd4a00fa
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/MaintenanceController.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Admin Maintenance Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFindAdmin\Controller;
+
+/**
+ * Class helps maintain database
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class MaintenanceController extends AbstractAdmin
+{
+    /**
+     * System Maintenance
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+        $view = $this->createViewModel();
+        $view->setTemplate('admin/maintenance/home');
+        return $view;
+    }
+
+    /**
+     * Delete expired searches.
+     *
+     * @return mixed
+     */
+    public function deleteexpiredsearchesAction()
+    {
+        $daysOld = intval($this->params()->fromQuery('daysOld', 2));
+        if ($daysOld < 2) {
+            $this->flashMessenger()->setNamespace('error')
+                ->addMessage(
+                    'Expiration age must be at least two days.'
+                );
+        } else {
+            // Delete the expired searches--this cleans up any junk left in the
+            // database from old search histories that were not caught by the
+            // session garbage collector.
+            $search = $this->getTable('Search');
+            $query = $search->getExpiredQuery($daysOld);
+            if (($count = count($search->select($query))) == 0) {
+                $msg = "No expired searches to delete.";
+            } else {
+                $search->delete($query);
+                $msg = "{$count} expired searches deleted.";
+            }
+            $this->flashMessenger()->setNamespace('info')->addMessage($msg);
+        }
+        return $this->forwardTo('AdminMaintenance', 'Home');
+    }
+}
+
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/SocialstatsController.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/SocialstatsController.php
new file mode 100644
index 00000000000..257b762c880
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/SocialstatsController.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Admin Social Statistics Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFindAdmin\Controller;
+
+/**
+ * Class controls VuFind social statistical data.
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class SocialstatsController extends AbstractAdmin
+{
+    /**
+     * Social statistics reporting
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+        $view = $this->createViewModel();
+        $view->setTemplate('admin/socialstats/home');
+        $view->comments = $this->getTable('comments')->getStatistics();
+        $view->favorites = $this->getTable('userresource')->getStatistics();
+        $view->tags = $this->getTable('resourcetags')->getStatistics();
+        return $view;
+    }
+}
+
diff --git a/module/VuFindAdmin/src/VuFindAdmin/Controller/StatisticsController.php b/module/VuFindAdmin/src/VuFindAdmin/Controller/StatisticsController.php
new file mode 100644
index 00000000000..5701803b597
--- /dev/null
+++ b/module/VuFindAdmin/src/VuFindAdmin/Controller/StatisticsController.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Admin Statistics Controller
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFindAdmin\Controller;
+
+/**
+ * Class controls VuFind statistical data.
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+class StatisticsController extends AbstractAdmin
+{
+    /**
+     * Statistics reporting
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+       
+        $view = $this->createViewModel();
+        $view->setTemplate('admin/statistics/home');
+        $config = $this->getConfig();
+
+        // Search statistics
+        $search = $this->getServiceLocator()->get('VuFind\SearchStats');
+        $view->searchesBySource
+            = $config->Statistics->searchesBySource
+            ?: false;
+        $searchSummary = $search->getStatsSummary(
+            7, $config->Statistics->searchesBySource
+        );
+        $view->topSearches = isset($searchSummary['top'])
+            ? $searchSummary['top'] : null;
+        $view->emptySearches = isset($searchSummary['empty'])
+            ? $searchSummary['empty'] : null;
+        $view->totalSearches = isset($searchSummary['total'])
+            ? $searchSummary['total'] : null;
+
+        // Record statistics
+        $records = $this->getServiceLocator()->get('VuFind\RecordStats');
+        $view->recordsBySource = $config->Statistics->recordsBySource ?: false;
+        $recordSummary = $records->getStatsSummary(
+            5, $config->Statistics->recordsBySource
+        );
+        $view->topRecords = isset($recordSummary['top'])
+            ? $recordSummary['top'] : null;
+        $view->totalRecordViews = isset($recordSummary['total'])
+            ? $recordSummary['total'] : null;
+
+        // Browser statistics
+        $view->currentBrowser = $search->getBrowser(
+            $this->getRequest()->getServer('HTTP_USER_AGENT')
+        );
+
+        // Look for universal statistics recorder
+        $matchFound = false;
+        foreach ($search->getDriversForSource(null) as $currentDriver) {
+            $browserStats = $currentDriver->getBrowserStats(false, 5);
+            if (!empty($browserStats)) {
+                $matchFound = true;
+                break;
+            }
+        }
+
+        // If no full coverage mode found, take the first valid source
+        if (!$matchFound) {
+            $drivers = $search->getDriversForSource(null, true);
+            foreach ($drivers as $currentDriver) {
+                $browserStats = $currentDriver->getBrowserStats(false, 5);
+                if (!empty($browserStats)) {
+                    $matchFound = true;
+                    break;
+                }
+            }
+        }
+
+        // Initialize browser/version data in view based on what we found above:
+        if ($matchFound) {
+            $view->browserStats = $browserStats;
+            $view->topVersions = $currentDriver->getBrowserStats(true, 5);
+        } else {
+            $view->browserStats = $view->topVersions = null;
+        }
+
+        return $view;
+    }
+}
+
diff --git a/themes/blueprint/templates/admin/config.phtml b/themes/blueprint/templates/admin/config/home.phtml
similarity index 85%
rename from themes/blueprint/templates/admin/config.phtml
rename to themes/blueprint/templates/admin/config/home.phtml
index a1b96041a24..ebf54a03660 100644
--- a/themes/blueprint/templates/admin/config.phtml
+++ b/themes/blueprint/templates/admin/config/home.phtml
@@ -13,7 +13,7 @@
   <p>Some basic settings can also be adjusted through the auto-configuration tool.</p>
   <? if (!$this->showInstallLink): ?>
     <p><?=$this->transEsc('Auto configuration is currently disabled') ?>.</p>
-    <p><a href="<?=$this->url('admin-enableautoconfig')?>"><?=$this->transEsc('Enable Auto Config')?></a></p>
+    <p><a href="<?=$this->url('admin/config', array('action' => 'EnableAutoConfig'))?>"><?=$this->transEsc('Enable Auto Config')?></a></p>
   <? else: ?>
     <p><a href="<?=$this->url('install-home')?>"><?=$this->transEsc('auto_configure_title')?></a></p>
   <? endif; ?>
diff --git a/themes/blueprint/templates/admin/maintenance.phtml b/themes/blueprint/templates/admin/maintenance/home.phtml
similarity index 85%
rename from themes/blueprint/templates/admin/maintenance.phtml
rename to themes/blueprint/templates/admin/maintenance/home.phtml
index 2f37fcd5219..92b27e06ddb 100644
--- a/themes/blueprint/templates/admin/maintenance.phtml
+++ b/themes/blueprint/templates/admin/maintenance/home.phtml
@@ -11,7 +11,7 @@
 
   <h2>Utilities</h2>
   <?=$this->flashmessages()?>
-  <form method="get" action="<?=$this->url('admin-deleteexpiredsearches')?>">
+  <form method="get" action="<?=$this->url('admin/maintenance', array('action' => 'DeleteExpiredSearches'))?>">
     <label for="del_daysOld" style="font-weight: normal;">Delete unsaved user search histories older than</label>
     <input id="del_daysOld" type="text" name="daysOld" size="5" value="2"/> days.
     <input type="submit" name="submit" value="<?=$this->transEsc('Submit')?>"/>
diff --git a/themes/blueprint/templates/admin/menu.phtml b/themes/blueprint/templates/admin/menu.phtml
index b3c51308725..14b5a7e9554 100644
--- a/themes/blueprint/templates/admin/menu.phtml
+++ b/themes/blueprint/templates/admin/menu.phtml
@@ -1,7 +1,7 @@
 <ul id="list1">
-  <li<?=strtolower($this->layout()->templateName) == "home" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-home')?>"><?=$this->transEsc('Home')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "socialstats" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-socialstats')?>"><?=$this->transEsc('Social Statistics')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "statistics" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-statistics')?>"><?=$this->transEsc('Statistics')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "config" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-config')?>"><?=$this->transEsc('Configuration')?></a>
-  <li<?=strtolower($this->layout()->templateName) == "maintenance" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-maintenance')?>"><?=$this->transEsc('System Maintenance')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "home" ? ' class="active"' : ''?>><a href="<?=$this->url('admin')?>"><?=$this->transEsc('Home')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "socialstats" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/social')?>"><?=$this->transEsc('Social Statistics')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "statistics" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/statistics')?>"><?=$this->transEsc('Statistics')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "config" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/config')?>"><?=$this->transEsc('Configuration')?></a>
+  <li<?=strtolower($this->layout()->templateName) == "maintenance" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/maintenance')?>"><?=$this->transEsc('System Maintenance')?></a></li>
 </ul>
diff --git a/themes/blueprint/templates/admin/socialstats.phtml b/themes/blueprint/templates/admin/socialstats/home.phtml
similarity index 100%
rename from themes/blueprint/templates/admin/socialstats.phtml
rename to themes/blueprint/templates/admin/socialstats/home.phtml
diff --git a/themes/blueprint/templates/admin/statistics.phtml b/themes/blueprint/templates/admin/statistics/home.phtml
similarity index 100%
rename from themes/blueprint/templates/admin/statistics.phtml
rename to themes/blueprint/templates/admin/statistics/home.phtml
diff --git a/themes/bootstrap/templates/admin/config.phtml b/themes/bootstrap/templates/admin/config/home.phtml
similarity index 53%
rename from themes/bootstrap/templates/admin/config.phtml
rename to themes/bootstrap/templates/admin/config/home.phtml
index 6c318fe5255..250fbde4b94 100644
--- a/themes/bootstrap/templates/admin/config.phtml
+++ b/themes/bootstrap/templates/admin/config/home.phtml
@@ -1,10 +1,7 @@
 <?
-  // Set page title.
-  $this->headTitle($this->translate('VuFind Administration - Configuration'));
-    
-  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('admin-home') . '">' . $this->transEsc('Admin') . '</a> <span class="divider">&gt;</span></li> <li class="active">Config</li>';
+    // Set page title.
+    $this->headTitle($this->translate('VuFind Administration - Configuration'));
 ?>
-
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('Configuration')?></h2>
   <?=$this->flashmessages()?>
@@ -12,9 +9,9 @@
   <p>Some basic settings can also be adjusted through the auto-configuration tool.</p>
   <? if (!$this->showInstallLink): ?>
     <p><?=$this->transEsc('Auto configuration is currently disabled') ?>.</p>
-    <p><a href="<?=$this->url('admin-enableautoconfig')?>" class="btn"><?=$this->transEsc('Enable Auto Config')?></a></p>
+    <p><a href="<?=$this->url('admin/config', array('action' => 'EnableAutoConfig'))?>"><?=$this->transEsc('Enable Auto Config')?></a></p>
   <? else: ?>
-    <p><a href="<?=$this->url('install-home')?>" class="btn btn-primary"><?=$this->transEsc('auto_configure_title')?></a></p>
+    <p><a href="<?=$this->url('install-home')?>"><?=$this->transEsc('auto_configure_title')?></a></p>
   <? endif; ?>
 </div>
 
diff --git a/themes/bootstrap/templates/admin/disabled.phtml b/themes/bootstrap/templates/admin/disabled.phtml
index 7b8760cf9d5..851a6116c64 100644
--- a/themes/bootstrap/templates/admin/disabled.phtml
+++ b/themes/bootstrap/templates/admin/disabled.phtml
@@ -1,3 +1,3 @@
-<div class="alert alert-error">
-  The Admin module is currently disabled.<br/>To turn it on, see the admin_enabled setting in the [Site] section of config.ini.
-</div>
\ No newline at end of file
+<p class="error">
+  <b>The Admin module is currently disabled.</b> To turn it on, see the admin_enabled setting in the [Site] section of config.ini.
+</p>
\ No newline at end of file
diff --git a/themes/bootstrap/templates/admin/home.phtml b/themes/bootstrap/templates/admin/home.phtml
index 4440edcb002..7b7fed8f372 100644
--- a/themes/bootstrap/templates/admin/home.phtml
+++ b/themes/bootstrap/templates/admin/home.phtml
@@ -1,25 +1,22 @@
 <?
-  // Set page title.
-  $this->headTitle($this->translate('VuFind Administration - Home'));
+    // Set page title.
+    $this->headTitle($this->translate('VuFind Administration - Home'));
 
-  // Set up map of core name => label
-  $coreLabels = array(
-    'biblio' => $this->translate('Bibliographic Index'),
-    'authority' => $this->translate('Authority Index'),
-    'stats' => $this->translate('Usage Statistics Index')
-  );
-  
-  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('admin-home') . '">' . $this->transEsc('Admin') . '</a> <span class="divider">&gt;</span></li> <li class="active">Home</li>';
+    // Set up map of core name => label
+    $coreLabels = array(
+        'biblio' => $this->translate('Bibliographic Index'),
+        'authority' => $this->translate('Authority Index'),
+        'stats' => $this->translate('Usage Statistics Index')
+    );
 ?>
 
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('VuFind Administration')?></h2>
-  <hr/>
   <? $cores = is_object($this->xml) ? $this->xml->xpath('/response/lst[@name="status"]/lst') : array(); ?>
   <? foreach ($cores as $core): ?>
     <? $coreName = (string)$core['name']; ?>
     <? $coreLabel = isset($coreLabels[$coreName]) ? $coreLabels[$coreName] : ucwords($coreName) . ' Index'; ?>
-    <p class="lead"><?=$this->transEsc($coreLabel)?></p>
+    <h3><?=$this->transEsc($coreLabel)?></h3>
     <table class="table table-striped">
       <tr>
         <th><?=$this->transEsc('Record Count')?>: </th>
diff --git a/themes/bootstrap/templates/admin/maintenance.phtml b/themes/bootstrap/templates/admin/maintenance.phtml
deleted file mode 100644
index b2857a2dd6e..00000000000
--- a/themes/bootstrap/templates/admin/maintenance.phtml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?
-  // Set page title.
-  $this->headTitle($this->translate('VuFind Administration - System Maintenance'));
-    
-  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('admin-home') . '">' . $this->transEsc('Admin') . '</a> <span class="divider">&gt;</span></li> <li class="active">System Maintenance</li>';
-?>
-
-<div class="<?=$this->layoutClass('mainbody')?>">
-  <h2><?=$this->transEsc('System Maintenance')?></h2>
-  <p class="lead">Utilities</p>
-  <?=$this->flashMessages()?>
-  <form class="form-inline" method="get" action="<?=$this->url('admin-deleteexpiredsearches')?>">
-    <label for="del_daysOld">Delete unsaved user search histories older than</label>
-    <input class="input-mini" id="del_daysOld" type="text" name="daysOld" value="2"/>
-    <span class="help-inline">days.</span>
-    <input class="btn btn-primary" type="submit" name="submit" value="<?=$this->transEsc('Submit')?>"/>
-  </form>
-</div>
-
-<div class="<?=$this->layoutClass('sidebar')?>">
-  <?=$this->render("admin/menu.phtml")?>
-</div>
diff --git a/themes/bootstrap/templates/admin/maintenance/home.phtml b/themes/bootstrap/templates/admin/maintenance/home.phtml
new file mode 100644
index 00000000000..f2b2ec9b2a4
--- /dev/null
+++ b/themes/bootstrap/templates/admin/maintenance/home.phtml
@@ -0,0 +1,19 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('VuFind Administration - System Maintenance'));
+?>
+<div class="<?=$this->layoutClass('mainbody')?>">
+  <h2><?=$this->transEsc('System Maintenance')?></h2>
+
+  <h3>Utilities</h3>
+  <?=$this->flashmessages()?>
+  <form method="get" action="<?=$this->url('admin/maintenance', array('action' => 'DeleteExpiredSearches'))?>">
+    <label for="del_daysOld" style="font-weight: normal;">Delete unsaved user search histories older than</label>
+    <input id="del_daysOld" type="text" name="daysOld" size="5" value="2"/> days.
+    <input type="submit" name="submit" value="<?=$this->transEsc('Submit')?>"/>
+  </form>
+</div>
+
+<div class="<?=$this->layoutClass('sidebar')?>">
+  <?=$this->render("admin/menu.phtml")?>
+</div>
\ No newline at end of file
diff --git a/themes/bootstrap/templates/admin/menu.phtml b/themes/bootstrap/templates/admin/menu.phtml
index fc6cf58f9c9..f9a2ebcda2f 100644
--- a/themes/bootstrap/templates/admin/menu.phtml
+++ b/themes/bootstrap/templates/admin/menu.phtml
@@ -1,7 +1,7 @@
 <ul class="nav nav-list">
-  <li<?=strtolower($this->layout()->templateName) == "home" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-home')?>"><?=$this->transEsc('Home')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "socialstats" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-socialstats')?>"><?=$this->transEsc('Social Statistics')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "statistics" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-statistics')?>"><?=$this->transEsc('Statistics')?></a></li>
-  <li<?=strtolower($this->layout()->templateName) == "config" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-config')?>"><?=$this->transEsc('Configuration')?></a>
-  <li<?=strtolower($this->layout()->templateName) == "maintenance" ? ' class="active"' : ''?>><a href="<?=$this->url('admin-maintenance')?>"><?=$this->transEsc('System Maintenance')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "home" ? ' class="active"' : ''?>><a href="<?=$this->url('admin')?>"><?=$this->transEsc('Home')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "socialstats" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/social')?>"><?=$this->transEsc('Social Statistics')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "statistics" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/statistics')?>"><?=$this->transEsc('Statistics')?></a></li>
+  <li<?=strtolower($this->layout()->templateName) == "config" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/config')?>"><?=$this->transEsc('Configuration')?></a>
+  <li<?=strtolower($this->layout()->templateName) == "maintenance" ? ' class="active"' : ''?>><a href="<?=$this->url('admin/maintenance')?>"><?=$this->transEsc('System Maintenance')?></a></li>
 </ul>
diff --git a/themes/bootstrap/templates/admin/socialstats.phtml b/themes/bootstrap/templates/admin/socialstats/home.phtml
similarity index 78%
rename from themes/bootstrap/templates/admin/socialstats.phtml
rename to themes/bootstrap/templates/admin/socialstats/home.phtml
index 5a2d5ec0221..cf04a0bb7cb 100644
--- a/themes/bootstrap/templates/admin/socialstats.phtml
+++ b/themes/bootstrap/templates/admin/socialstats/home.phtml
@@ -1,26 +1,23 @@
 <?
     // Set page title.
     $this->headTitle($this->translate('VuFind Administration - Social Statistics'));
-    
-  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('admin-home') . '">' . $this->transEsc('Admin') . '</a> <span class="divider">&gt;</span></li> <li class="active">Social Statistics</li>';
 ?>
-
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('Social Statistics')?></h2>
-  <hr/>
-  <p class="lead">Comments</p>
+
+  <h3>Comments</h3>
   <table class="table table-striped">
     <tr><th>Total Users</th><th>Total Resources</th><th>Total Comments</th></tr>
     <tr><td><?=$comments['users']?></td><td><?=$comments['resources']?></td><td><?=$comments['total']?></td></tr>
   </table>
 
-  <p class="lead">Favorites</p>
+  <h3>Favorites</h3>
   <table class="table table-striped">
     <tr><th>Total Users</th><th>Total Resources</th><th>Total Lists</th><th>Total Saved Items</th></tr>
     <tr><td><?=$favorites['users']?></td><td><?=$favorites['resources']?></td><td><?=$favorites['lists']?></td><td><?=$favorites['total']?></td></tr>
   </table>
 
-  <p class="lead">Tags</p>
+  <h3>Tags</h3>
   <table class="table table-striped">
     <tr><th>Total Users</th><th>Total Resources</th><th>Total Tags</th></tr>
     <tr><td><?=$tags['users']?></td><td><?=$tags['resources']?></td><td><?=$tags['total']?></td></tr>
diff --git a/themes/bootstrap/templates/admin/statistics.phtml b/themes/bootstrap/templates/admin/statistics/home.phtml
similarity index 78%
rename from themes/bootstrap/templates/admin/statistics.phtml
rename to themes/bootstrap/templates/admin/statistics/home.phtml
index 45f1c8c19d7..ff208f4f867 100644
--- a/themes/bootstrap/templates/admin/statistics.phtml
+++ b/themes/bootstrap/templates/admin/statistics/home.phtml
@@ -1,15 +1,21 @@
 <?
-  // Set page title.
-  $this->headTitle($this->translate('VuFind Administration - Statistics'));
-    
-  $this->layout()->breadcrumbs = '<li><a href="' . $this->url('admin-home') . '">' . $this->transEsc('Admin') . '</a> <span class="divider">&gt;</span></li> <li class="active">Statistics</li>';
+    // Set page title.
+    $this->headTitle($this->translate('VuFind Administration - Statistics'));
 ?>
-
+<style>
+table {
+    table-layout: fixed;
+    width: 100%;
+}
+tr td:first-child {
+    width:50%;
+}
+</style>
 <div class="<?=$this->layoutClass('mainbody')?>">
   <h2><?=$this->transEsc('Statistics')?></h2>
-  <hr/>
+
   <? if(null !== $this->totalSearches || null !== $this->emptySearches || null !== $this->totalRecordViews): ?>
-    <p class="lead">Executive Summary</p>
+    <h3>Executive Summary</h3>
     <table class="table table-striped">
       <? if(null !== $this->totalSearches): ?><tr><td>Total Searches</td><td><?=$this->totalSearches ?></td></tr><? endif; ?>
       <? if(null !== $this->emptySearches): ?><tr><td>0 Hit Searches</td><td><?=$this->emptySearches ?></td></tr><? endif; ?>
@@ -18,10 +24,10 @@
   <? endif; ?>
 
   <? if(!empty($this->topSearches)): ?>
-    <p class="lead">Top Searches<? if($this->searchesBySource): ?> by Source<? endif; ?></p>
+    <h3>Top Searches<? if($this->searchesBySource): ?> by Source<? endif; ?></h3>
     <? if($this->searchesBySource): ?>
       <? foreach($this->topSearches as $source=>$searches): ?>
-        <span><?=$source ?></span>
+        <span style="font-size:14px"><?=$source ?></span>
         <table class="table table-striped">
         <? foreach($searches as $search): ?>
           <tr><td><?=$search['value'] ?></td><td><?=$search['count'] ?></td></tr>
@@ -38,10 +44,10 @@
   <? endif; ?>
 
   <? if(!empty($this->topRecords)): ?>
-    <p class="lead">Top Records<? if($this->recordsBySource): ?> by Source<? endif; ?></p>
+    <h3>Top Records<? if($this->recordsBySource): ?> by Source<? endif; ?></h3>
     <? if($this->recordsBySource): ?>
       <? foreach($this->topRecords as $source=>$records): ?>
-        <span><?=$source ?></span>
+        <span style="font-size:14px"><?=$source ?></span>
         <table class="table table-striped">
         <? foreach($records as $record): ?>
           <tr><td><?=$record['value'] ?></td><td><?=$record['count'] ?></td></tr>
@@ -58,7 +64,7 @@
   <? endif; ?>
 
   <? if(!empty($this->browserStats)): ?>
-    <p class="lead">Browser Usage</p>
+    <h3>Browser Usage</h3>
     <?
       $total = 0;
       foreach($this->browserStats as $browser) {
@@ -72,7 +78,7 @@
     </table>
     <h4 style="display:inline">Top Versions</h4>:
     <? foreach($this->topVersions as $i=>$browser): ?>
-      <span><?=$browser['browserName'] ?> (<?=$browser['count'] ?>)</span><? if(++$i < count($this->topVersions)): ?>,<? endif; ?>
+      <span style="padding:0 3px<? if($this->currentBrowser == $browser['browserName']): ?>;background:#E5ECF9<? endif; ?>"><?=$browser['browserName'] ?> (<?=$browser['count'] ?>)</span><? if(++$i < count($this->topVersions)): ?>,<? endif; ?>
     <? endforeach; ?>
   <? endif; ?>
   
diff --git a/themes/bootstrap/templates/layout/layout.phtml b/themes/bootstrap/templates/layout/layout.phtml
index 944ccfec698..3322060f6da 100644
--- a/themes/bootstrap/templates/layout/layout.phtml
+++ b/themes/bootstrap/templates/layout/layout.phtml
@@ -70,7 +70,10 @@
         <? endif; ?>
       </div>
       
-      <? if((!isset($this->layout()->showBreadcrumbs) || $this->layout()->showBreadcrumbs == true) && $this->layout()->breadcrumbs !== false): ?>
+      <? if((!isset($this->layout()->showBreadcrumbs) || $this->layout()->showBreadcrumbs == true)
+        && !empty($this->layout()->breadcrumbs)
+        && $this->layout()->breadcrumbs !== false
+      ): ?>
         <ul class="breadcrumb">
         <? if(is_array($this->layout()->breadcrumbs)): ?>
           <? if(count($this->layout()->breadcrumbs) > 1): ?>
-- 
GitLab