diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 30c84dc4b66b973d49f6097ffa0fc8e08edd8d13..a1f08ce04aae0f037cddc1d659bb76e0b06bb786 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -1084,6 +1084,15 @@ treeSearchLimit = 100
 ;apiKey = "mykey"
 ;universal = false
 
+; Uncomment this section and provide your Piwik server address and site id to
+; enable Piwik analytics.
+[Piwik]
+;url = "http://server.address/piwik/"
+;site_id = 1
+; Uncomment the following setting to track additional information about searches
+; and displayed records with Piwik's custom variables
+;custom_variables = true
+
 ; Uncomment portions of this section to activate tabs in the search box for switching
 ; between search modules. Keys are search backend names, values are labels for use in
 ; the user interface (subject to translation).
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
index 3a2ef704d700bed7b27210177a6d22636d208182..d3da93b2b7c9d8a90916a5db524d216344ecdb8c 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php
@@ -189,6 +189,24 @@ class Factory
         return new GoogleAnalytics($key, $universal);
     }
 
+    /**
+     * Construct the Piwik helper.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return Piwik
+     */
+    public static function getPiwik(ServiceManager $sm)
+    {
+        $config = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        $url = isset($config->Piwik->url) ? $config->Piwik->url : false;
+        $siteId = isset($config->Piwik->site_id) ? $config->Piwik->site_id : 1;
+        $customVars = isset($config->Piwik->custom_variables)
+            ? $config->Piwik->custom_variables
+            : false;
+        return new Piwik($url, $siteId, $customVars);
+    }
+
     /**
      * Construct the GetLastSearchLink helper.
      *
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php b/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d257c4ef86df73ad579459635f2b19a9befd0e5
--- /dev/null
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php
@@ -0,0 +1,375 @@
+<?php
+/**
+ * Piwik view helper
+ *
+ * PHP version 5
+ *
+ * Copyright (C) The National Library of Finland 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+namespace VuFind\View\Helper\Root;
+
+/**
+ * Piwik Web Analytics view helper
+ *
+ * @category VuFind2
+ * @package  View_Helpers
+ * @author   Ere Maijala <ere.maijala@helsinki.fi>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class Piwik extends \Zend\View\Helper\AbstractHelper
+{
+    /**
+     * Piwik URL (false if disabled)
+     *
+     * @var string|bool
+     */
+    protected $url;
+
+    /**
+     * Piwik Site ID
+     *
+     * @var int
+     */
+    protected $siteId;
+
+    /**
+     * Whether to track use custom variables to track additional information
+     *
+     * @var bool
+     */
+    protected $customVars;
+
+    /**
+     * Constructor
+     *
+     * @param string|bool $url        Piwik address (false if disabled)
+     * @param int         $siteId     Piwik site ID
+     * @param bool        $customVars Whether to track additional information in
+     * custom variables
+     */
+    public function __construct($url, $siteId, $customVars)
+    {
+        $this->url = $url;
+        if ($url && substr($url, -1) != '/') {
+            $this->url .= '/';
+        }
+        $this->siteId = $siteId;
+        $this->customVars = $customVars;
+    }
+
+    /**
+     * Returns Piwik code (if active) or empty string if not.
+     *
+     * @return string
+     */
+    public function __invoke()
+    {
+        if (!$this->url) {
+            return '';
+        }
+
+        if ($results = $this->getSearchResults()) {
+            $code = $this->trackSearch($results);
+        } else if ($recordDriver = $this->getRecordDriver()) {
+            $code = $this->trackRecordPage($recordDriver);
+        } else {
+            $code = $this->trackPageView();
+        }
+
+        $inlineScript = $this->getView()->plugin('inlinescript');
+        return $inlineScript(\Zend\View\Helper\HeadScript::SCRIPT, $code, 'SET');
+    }
+
+    /**
+     * Track a Search
+     *
+     * @param VuFind\Search\Base\Results $results Search Results
+     *
+     * @return string Tracking Code
+     */
+    protected function trackSearch($results)
+    {
+        $customVars = $this->getSearchCustomVars($results);
+
+        $code = $this->getOpeningTrackingCode();
+        $code .= $this->getCustomVarsCode($customVars);
+        $code .= $this->getTrackSearchCode($results);
+        $code .= $this->getClosingTrackingCode();
+
+        return $code;
+    }
+
+    /**
+     * Track a Record View
+     *
+     * @param VuFind\RecordDriver\AbstractBase $recordDriver Record Driver
+     *
+     * @return string Tracking Code
+     */
+    protected function trackRecordPage($recordDriver)
+    {
+        $customVars = $this->getRecordPageCustomVars($recordDriver);
+
+        $code = $this->getOpeningTrackingCode();
+        $code .= $this->getCustomVarsCode($customVars);
+        $code .= $this->getTrackPageViewCode();
+        $code .= $this->getClosingTrackingCode();
+
+        return $code;
+    }
+
+    /**
+     * Track a Generic Page View
+     *
+     * @return string Tracking Code
+     */
+    protected function trackPageView()
+    {
+        $customVars = $this->getGenericCustomVars();
+
+        $code = $this->getOpeningTrackingCode();
+        $code .= $this->getCustomVarsCode($customVars);
+        $code .= $this->getTrackPageViewCode();
+        $code .= $this->getClosingTrackingCode();
+
+        return $code;
+    }
+
+    /**
+     * Get Search Results if on a Results Page
+     *
+     * @return VuFind\Search\Base\Results|null Search results or null if not
+     * on a search page
+     */
+    protected function getSearchResults()
+    {
+        $viewModel = $this->getView()->plugin('view_model');
+        $children = $viewModel->getCurrent()->getChildren();
+        if (isset($children[0])) {
+            $template = $children[0]->getTemplate();
+            if (!strstr($template, '/home')) {
+                $results = $children[0]->getVariable('results');
+                if (is_a($results, 'VuFind\Search\Base\Results')) {
+                    return $results;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get Record Driver if on a Record Page
+     *
+     * @return VuFind\RecordDriver\AbstractBase|null Record driver or null if not
+     * on a record page
+     */
+    protected function getRecordDriver()
+    {
+        $viewModel = $this->getView()->plugin('view_model');
+        $children = $viewModel->getCurrent()->getChildren();
+        if (isset($children[0])) {
+            $driver = $children[0]->getVariable('driver');
+            if (is_a($driver, 'VuFind\RecordDriver\AbstractBase')) {
+                return $driver;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get Custom Variables for Search Results
+     *
+     * @param VuFind\Search\Base\Results $results Search results
+     *
+     * @return array Associative array of custom variables
+     */
+    protected function getSearchCustomVars($results)
+    {
+        if (!$this->customVars) {
+            return array();
+        }
+
+        $facets = array();
+        $facetTypes = array();
+        $params = $results->getParams();
+        foreach ($params->getFilterList() as $filterType => $filters) {
+            $facetTypes[] = $filterType;
+            foreach ($filters as $filter) {
+                $facets[] = $filter['field'] . '|' . $filter['value'];
+            }
+        }
+        $facets = implode("\t", $facets);
+        $facetTypes = implode("\t", $facetTypes);
+
+        return array(
+            'Facets' => $facets,
+            'FacetTypes' => $facetTypes,
+            'SearchType' => $params->getSearchType(),
+            'SearchBackend' => $params->getSearchClassId(),
+            'Sort' => $params->getSort(),
+            'Page' => $params->getPage(),
+            'Limit' => $params->getLimit(),
+            'View' => $params->getView()
+        );
+    }
+
+    /**
+     * Get Custom Variables for a Record Page
+     *
+     * @param VuFind\RecordDriver\AbstractBase $recordDriver Record driver
+     *
+     * @return array Associative array of custom variables
+     */
+    protected function getRecordPageCustomVars($recordDriver)
+    {
+        $id = $recordDriver->getUniqueID();
+        $formats = $recordDriver->tryMethod('getFormats');
+        if (is_array($formats)) {
+            $formats = implode(',', $formats);
+        }
+        $formats = $formats;
+        $author = $recordDriver->tryMethod('getPrimaryAuthor');
+        if (empty($author)) {
+            $author = '-';
+        }
+        // Use breadcrumb for title since it's guaranteed to return something
+        $title = $recordDriver->tryMethod('getBreadcrumb');
+        if (empty($title)) {
+            $title = '-';
+        }
+        $institutions = $recordDriver->tryMethod('getInstitutions');
+        if (is_array($institutions)) {
+            $institutions = implode(',', $institutions);
+        }
+        $institutions = $institutions;
+
+        return array(
+            'RecordFormat' => $formats,
+            'RecordData' => "$id|$author|$title",
+            'RecordInstitution' => $institutions
+        );
+    }
+
+    /**
+     * Get Custom Variables for a Generic Page View
+     *
+     * @return array Associative array of custom variables
+     */
+    protected function getGenericCustomVars()
+    {
+        return array();
+    }
+
+    /**
+     * Get the Initialization Part of the Tracking Code
+     *
+     * @return string JavaScript Code Fragment
+     */
+    protected function getOpeningTrackingCode()
+    {
+        return <<<EOT
+var _paq = _paq || [];
+(function(){
+_paq.push(['setSiteId', {$this->siteId}]);
+_paq.push(['setTrackerUrl', '{$this->url}piwik.php']);
+_paq.push(['setCustomUrl', location.protocol + '//'
+     + location.host + location.pathname]);
+
+EOT;
+    }
+
+    /**
+     * Get the Finalization Part of the Tracking Code
+     *
+     * @return string JavaScript Code Fragment
+     */
+    protected function getClosingTrackingCode()
+    {
+        return <<<EOT
+_paq.push(['enableLinkTracking']);
+var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
+    g.type='text/javascript'; g.defer=true; g.async=true;
+    g.src='{$this->url}piwik.js';
+s.parentNode.insertBefore(g,s); })();
+
+EOT;
+    }
+
+    /**
+     * Convert a Custom Variables Array to JavaScript Code
+     *
+     * @param array $customVars Custom Variables
+     *
+     * @return string JavaScript Code Fragment
+     */
+    protected function getCustomVarsCode($customVars)
+    {
+        $escape = $this->getView()->plugin('escapeHtmlAttr');
+        $code = '';
+        $i = 0;
+        foreach ($customVars as $key => $value) {
+            ++$i;
+            $value = $escape($value);
+            $code .= <<<EOT
+_paq.push(['setCustomVariable', $i, '$key', '$value', 'page']);
+
+EOT;
+        }
+        return $code;
+    }
+
+    /**
+     * Get Site Search Tracking Code
+     *
+     * @param VuFind\Search\Base\Results $results Search results
+     *
+     * @return string JavaScript Code Fragment
+     */
+    protected function getTrackSearchCode($results)
+    {
+        $escape = $this->getView()->plugin('escapeHtmlAttr');
+        $params = $results->getParams();
+        $searchTerms = $escape($params->getDisplayQuery());
+        $searchType = $escape($params->getSearchType());
+        $resultCount = $results->getResultTotal();
+
+        // Use trackSiteSearch *instead* of trackPageView in searches
+        return <<<EOT
+_paq.push(['trackSiteSearch', '$searchTerms', '$searchType', $resultCount]);
+
+EOT;
+    }
+
+    /**
+     * Get Page View Tracking Code
+     *
+     * @return string JavaScript Code Fragment
+     */
+    protected function getTrackPageViewCode()
+    {
+        return <<<EOT
+_paq.push(['trackPageView']);
+
+EOT;
+    }
+}
diff --git a/themes/blueprint/templates/layout/layout.phtml b/themes/blueprint/templates/layout/layout.phtml
index 98e4bd09e93d561ae833cfb309a4bd1cae196109..cdd5a16b870bd3999b550dbd99ecdd1d0a7f7fd0 100644
--- a/themes/blueprint/templates/layout/layout.phtml
+++ b/themes/blueprint/templates/layout/layout.phtml
@@ -122,5 +122,6 @@
       </div>
     </div>
     <?=$this->googleanalytics()?>
+    <?=$this->piwik()?>
   </body>
 </html>
\ No newline at end of file
diff --git a/themes/bootstrap/templates/layout/layout.phtml b/themes/bootstrap/templates/layout/layout.phtml
index 569380385ccd0176c6bb005fece7fe4b66db6dbb..1e98cb170374ec0b45cd95f5487bd7ee63dd038f 100644
--- a/themes/bootstrap/templates/layout/layout.phtml
+++ b/themes/bootstrap/templates/layout/layout.phtml
@@ -54,7 +54,7 @@
             }
             $this->headScript()->appendScript($this->jsTranslations()->getScript());
         }
-        
+
         // Session keep-alive
         if ($this->KeepAlive()) {
             $this->headScript()->appendScript('var keepAliveInterval = '
@@ -87,7 +87,7 @@
         <?=$this->layout()->searchbox?>
         <? endif; ?>
       </div>
-      
+
       <? if((!isset($this->layout()->showBreadcrumbs) || $this->layout()->showBreadcrumbs == true)
         && !empty($this->layout()->breadcrumbs)
         && $this->layout()->breadcrumbs !== false
@@ -129,5 +129,6 @@
       <div class="modal-body"><?=$this->transEsc('Loading') ?>...</div>
     </div>
     <?=$this->googleanalytics()?>
+    <?=$this->piwik()?>
   </body>
 </html>
diff --git a/themes/bootstrap3/templates/layout/layout.phtml b/themes/bootstrap3/templates/layout/layout.phtml
index d8e7e5adcebd63ed128825fca5f5ef35814d7e30..944e3203fc7b3185c1e3400c4edc1b9b7fb22b11 100644
--- a/themes/bootstrap3/templates/layout/layout.phtml
+++ b/themes/bootstrap3/templates/layout/layout.phtml
@@ -136,5 +136,6 @@
       </div>
     </div>
     <?=$this->googleanalytics()?>
+    <?=$this->piwik()?>
   </body>
 </html>
diff --git a/themes/jquerymobile/templates/layout/layout.phtml b/themes/jquerymobile/templates/layout/layout.phtml
index e07261cda88e1d3d78a53231d63ca20b7489de1d..26eb02d6d6f2c38ece9dd799caac871decdadcc4 100644
--- a/themes/jquerymobile/templates/layout/layout.phtml
+++ b/themes/jquerymobile/templates/layout/layout.phtml
@@ -49,5 +49,6 @@
       </div>
     </div>
     <?=$this->googleanalytics()?>
+    <?=$this->piwik()?>
   </body>
 </html>
diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php
index eecfd12bdd7f2f69f36368a693286cbb7b2dd993..695695a8fd7dedd6185df30a76fc7874b0df7b88 100644
--- a/themes/root/theme.config.php
+++ b/themes/root/theme.config.php
@@ -22,6 +22,7 @@ return array(
             'keepalive' => 'VuFind\View\Helper\Root\Factory::getKeepAlive',
             'proxyurl' => 'VuFind\View\Helper\Root\Factory::getProxyUrl',
             'openurl' => 'VuFind\View\Helper\Root\Factory::getOpenUrl',
+            'piwik' => 'VuFind\View\Helper\Root\Factory::getPiwik',
             'recaptcha' => 'VuFind\View\Helper\Root\Factory::getRecaptcha',
             'record' => 'VuFind\View\Helper\Root\Factory::getRecord',
             'recordlink' => 'VuFind\View\Helper\Root\Factory::getRecordLink',