diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d61737e34a501c16e0ab98d38b94691ece444a8..6a300ac3d845a6047c5141d3b3bdb64ba018b9b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,10 +9,15 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
 * The API parameter `countVisitorsToFetch` of the API method `Live.getLastVisitsDetails` has been deprecated as `filter_offset` and `filter_limit` work correctly now.
 
 ### Breaking Changes
-* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large. .
+* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large.
+
+### New commands
+* There is now a `diagnostic:run` command to run the system check from the command line.
 
 ### APIs Improvements
 * Visitor details now additionally contain: `deviceTypeIcon`, `deviceBrand` and `deviceModel`
+* In 2.6.0 we added the possibility to use `filter_limit` and `filter_offset` if an API returns an indexed array. This was not working in all cases and is fixed now. 
+* The API parameter `filter_pattern` and `filter_offset[]` can now be used if an API returns an indexed array.
 
 ## Piwik 2.12.0
 
diff --git a/composer.lock b/composer.lock
index 3607544b662b60e8219f8427f8a48dbffbe464c9..48e17c8f112f49b4cc26fa4b4b2ea3b7ebe142b1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -272,12 +272,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/mnapoli/PHP-DI.git",
-                "reference": "ee5145095555c4532220eab991bf7782e9a645c6"
+                "reference": "92e2f01c7f2076649235aa50f415dda11b87e6da"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mnapoli/PHP-DI/zipball/ee5145095555c4532220eab991bf7782e9a645c6",
-                "reference": "ee5145095555c4532220eab991bf7782e9a645c6",
+                "url": "https://api.github.com/repos/mnapoli/PHP-DI/zipball/92e2f01c7f2076649235aa50f415dda11b87e6da",
+                "reference": "92e2f01c7f2076649235aa50f415dda11b87e6da",
                 "shasum": ""
             },
             "require": {
@@ -289,9 +289,9 @@
                 "php": ">=5.3.3"
             },
             "require-dev": {
-                "mnapoli/phpunit-easymock": "~0.1.1",
+                "mnapoli/phpunit-easymock": "~0.1.4",
                 "ocramius/proxy-manager": "~0.5",
-                "phpunit/phpunit": "~4.4"
+                "phpunit/phpunit": "~4.5"
             },
             "suggest": {
                 "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~0.5)"
@@ -321,7 +321,7 @@
                 "dependency injection",
                 "di"
             ],
-            "time": "2015-01-29 03:02:32"
+            "time": "2015-04-12 06:13:26"
         },
         {
             "name": "mnapoli/phpdocreader",
@@ -813,16 +813,16 @@
         },
         {
             "name": "piwik/device-detector",
-            "version": "3.0.1",
+            "version": "3.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/piwik/device-detector.git",
-                "reference": "87720bc573642ad96ae9bdc42d82a843a8e65899"
+                "reference": "3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/piwik/device-detector/zipball/87720bc573642ad96ae9bdc42d82a843a8e65899",
-                "reference": "87720bc573642ad96ae9bdc42d82a843a8e65899",
+                "url": "https://api.github.com/repos/piwik/device-detector/zipball/3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19",
+                "reference": "3d36a5bd0710896311d69b10fd6eb1ca3b2d3c19",
                 "shasum": ""
             },
             "require": {
@@ -859,7 +859,7 @@
                 "parser",
                 "useragent"
             ],
-            "time": "2015-02-21 19:36:09"
+            "time": "2015-04-04 16:44:30"
         },
         {
             "name": "piwik/ini",
@@ -1384,7 +1384,7 @@
                 "performance",
                 "profiling"
             ],
-            "time": "2014-08-28 17:34:52"
+            "time": "2015-02-26 14:37:51"
         },
         {
             "name": "guzzle/guzzle",
diff --git a/config/environment/cli.php b/config/environment/cli.php
index 2852c010074e61599e95827576d814a53cae5ef9..0db3009ad4a3bb580511c1554f82d9117016d305 100644
--- a/config/environment/cli.php
+++ b/config/environment/cli.php
@@ -10,7 +10,7 @@ return array(
 
     // Log
     'log.handlers' => array(
-        DI\link('Symfony\Bridge\Monolog\Handler\ConsoleHandler'),
+        DI\get('Symfony\Bridge\Monolog\Handler\ConsoleHandler'),
     ),
     'Symfony\Bridge\Monolog\Handler\ConsoleHandler' => function (ContainerInterface $c) {
         // Override the default verbosity map to make it more verbose by default
diff --git a/config/environment/dev.php b/config/environment/dev.php
index 0a730d708c826d6ae9a422d5538f287d04194dbf..a8bedecca99b17065fd9340afeaa9ae257af538e 100644
--- a/config/environment/dev.php
+++ b/config/environment/dev.php
@@ -5,8 +5,8 @@ return array(
     'Piwik\Cache\Backend' => DI\object('Piwik\Cache\Backend\ArrayCache'),
 
     'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache')
-        ->constructor(DI\link('Piwik\Translation\Loader\DevelopmentLoader')),
+        ->constructor(DI\get('Piwik\Translation\Loader\DevelopmentLoader')),
     'Piwik\Translation\Loader\DevelopmentLoader' => DI\object()
-        ->constructor(DI\link('Piwik\Translation\Loader\JsonFileLoader')),
+        ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')),
 
 );
diff --git a/config/global.ini.php b/config/global.ini.php
index 4e075a22a448f73dfdb9a129a20c538995825fb6..526f8b3e243bb7de1d6cba7d4adeef141f08f6b0 100644
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -462,7 +462,8 @@ enable_trusted_host_check = 1
 ; Examples:
 ;cors_domains[] = http://example.com
 ;cors_domains[] = http://stats.example.com
-; OR allow for all domains
+;
+; Or you may allow cross domain requests for all domains with:
 ;cors_domains[] = *
 
 ; If you use this Piwik instance over multiple hostnames, Piwik will need to know
@@ -719,6 +720,7 @@ password = ; Proxy password: optional; if specified, username is mandatory
 Plugins[] = CorePluginsAdmin
 Plugins[] = CoreAdminHome
 Plugins[] = CoreHome
+Plugins[] = Diagnostics
 Plugins[] = CoreVisualizations
 Plugins[] = Proxy
 Plugins[] = API
diff --git a/config/global.php b/config/global.php
index 0cb88f5b3dc00c2eb0a3c130a9b73c0f5c9082ee..8fa70111a19bc1158852015dff1ef2e73ad0e8dc 100644
--- a/config/global.php
+++ b/config/global.php
@@ -60,5 +60,6 @@ return array(
     'Psr\Log\LoggerInterface' => DI\object('Psr\Log\NullLogger'),
 
     'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache')
-        ->constructor(DI\link('Piwik\Translation\Loader\JsonFileLoader')),
+        ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')),
+
 );
diff --git a/core/API/CORSHandler.php b/core/API/CORSHandler.php
index 721d5a0ed36c7993b811ca4c01a272346c15d400..aeee2ad39d3875ba62e44da2f7d6db4726e3a94c 100644
--- a/core/API/CORSHandler.php
+++ b/core/API/CORSHandler.php
@@ -24,6 +24,13 @@ class CORSHandler
 
     public function handle()
     {
+        // allow Piwik to serve data to all domains
+        if (in_array("*", $this->domains)) {
+            header('Access-Control-Allow-Origin: *');
+            return;
+        }
+
+        // specifically allow if it is one of the whitelisted CORS domains
         if (!empty($_SERVER['HTTP_ORIGIN'])) {
             $origin = $_SERVER['HTTP_ORIGIN'];
             if (in_array($origin, $this->domains, true)) {
diff --git a/core/API/Request.php b/core/API/Request.php
index 2ed5dfc56761ea13eae94c9638c1ead23a705995..04d055f8598b3c7750022524f740f17a8f26bff7 100644
--- a/core/API/Request.php
+++ b/core/API/Request.php
@@ -263,7 +263,9 @@ class Request
     {
         // if a token_auth is specified in the API request, we load the right permissions
         $token_auth = Common::getRequestVar('token_auth', '', 'string', $request);
-        if ($token_auth) {
+        $access = Access::getInstance();
+
+        if ($token_auth && $token_auth !== $access->getTokenAuth()) {
 
             /**
              * Triggered when authenticating an API request, but only if the **token_auth**
diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php
index 68e448b0b4dc82030110e20410747cac0d6b4b2f..9688aff83bcb57e705d0d598a4d82e228f46c7a7 100644
--- a/core/API/ResponseBuilder.php
+++ b/core/API/ResponseBuilder.php
@@ -14,8 +14,8 @@ use Piwik\DataTable;
 use Piwik\DataTable\Renderer;
 use Piwik\DataTable\DataTableInterface;
 use Piwik\DataTable\Filter\ColumnDelete;
+use Piwik\DataTable\Filter\Pattern;
 use Piwik\Plugin\Report;
-use Piwik\Plugins\API\Renderer\Original;
 
 /**
  */
@@ -189,6 +189,23 @@ class ResponseBuilder
 
         $isAssoc = !empty($firstArray) && is_numeric($firstKey) && is_array($firstArray) && count(array_filter(array_keys($firstArray), 'is_string'));
 
+        if (is_numeric($firstKey)) {
+            $columns = Common::getRequestVar('filter_column', false, 'array', $this->request);
+            $pattern = Common::getRequestVar('filter_pattern', '', 'string', $this->request);
+
+            if ($columns != array(false) && $pattern !== '') {
+                $pattern = new Pattern(new DataTable(), $columns, $pattern);
+                $array   = $pattern->filterArray($array);
+            }
+
+            $limit  = Common::getRequestVar('filter_limit', -1, 'integer', $this->request);
+            $offset = Common::getRequestVar('filter_offset', '0', 'integer', $this->request);
+
+            if ($this->shouldApplyLimitOnArray($limit, $offset)) {
+                $array = array_slice($array, $offset, $limit, $firstKey !== 0);
+            }
+        }
+
         if ($isAssoc) {
             $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request);
             $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request);
@@ -196,18 +213,43 @@ class ResponseBuilder
                 $columnDelete = new ColumnDelete(new DataTable(), $hideColumns, $showColumns);
                 $array = $columnDelete->filter($array);
             }
-        } else if (is_numeric($firstKey)) {
-            $limit  = Common::getRequestVar('filter_limit', -1, 'integer', $this->request);
-            $offset = Common::getRequestVar('filter_offset', '0', 'integer', $this->request);
-
-            if (-1 !== $limit) {
-                $array = array_slice($array, $offset, $limit);
-            }
         }
 
         return $this->apiRenderer->renderArray($array);
     }
 
+    private function shouldApplyLimitOnArray($limit, $offset)
+    {
+        if ($limit === -1) {
+            // all fields are requested
+            return false;
+        }
+
+        if ($offset > 0) {
+            // an offset is specified, we have to apply the limit
+            return true;
+        }
+
+        // "api_datatable_default_limit" is set by API\Controller if no filter_limit is specified by the user.
+        // it holds the number of the configured default limit.
+        $limitSetBySystem = Common::getRequestVar('api_datatable_default_limit', -2, 'integer', $this->request);
+
+        // we ignore the limit if the datatable_default_limit was set by the system as this default filter_limit is
+        // only meant for dataTables but not for arrays. This way we stay BC as filter_limit was not applied pre
+        // Piwik 2.6 and some fixes were made in Piwik 2.13.
+        $wasFilterLimitSetBySystem = $limitSetBySystem !== -2;
+
+        // we check for "$limitSetBySystem === $limit" as an API method could request another API method with
+        // another limit. In this case we need to apply it again.
+        $isLimitStillDefaultLimit = $limitSetBySystem === $limit;
+
+        if ($wasFilterLimitSetBySystem && $isLimitStillDefaultLimit) {
+            return false;
+        }
+
+        return true;
+    }
+
     private function sendHeaderIfEnabled()
     {
         if ($this->sendHeader) {
diff --git a/core/Access.php b/core/Access.php
index a5f6f604c043e9f5c623b1d97f6f97b576adbc57..d6706237558be0a2df4d683d72971e6a528c96eb 100644
--- a/core/Access.php
+++ b/core/Access.php
@@ -117,6 +117,11 @@ class Access
      * Constructor
      */
     public function __construct()
+    {
+        $this->resetSites();
+    }
+
+    private function resetSites()
     {
         $this->idsitesByAccess = array(
             'view'      => array(),
@@ -138,19 +143,15 @@ class Access
      */
     public function reloadAccess(Auth $auth = null)
     {
-        if (!is_null($auth)) {
-            $this->auth = $auth;
-        }
+        $this->resetSites();
 
-        // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail
-        if (is_null($this->auth)) {
-            if ($this->hasSuperUserAccess()) {
-                return $this->reloadAccessSuperUser();
-            }
+        if (isset($auth)) {
+            $this->auth = $auth;
         }
 
         if ($this->hasSuperUserAccess()) {
-            return $this->reloadAccessSuperUser();
+            $this->makeSureLoginNameIsSet();
+            return true;
         }
 
         // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail TODO: docs + review
@@ -170,18 +171,7 @@ class Access
 
         // case the superUser is logged in
         if ($result->hasSuperUserAccess()) {
-            return $this->reloadAccessSuperUser();
-        }
-
-        // in case multiple calls to API using different tokens, we ensure we reset it as not SU
-        $this->setSuperUserAccess(false);
-
-        // we join with site in case there are rows in access for an idsite that doesn't exist anymore
-        // (backward compatibility ; before we deleted the site without deleting rows in _access table)
-        $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login);
-
-        foreach ($accessRaw as $access) {
-            $this->idsitesByAccess[$access['access']][] = $access['idsite'];
+            $this->setSuperUserAccess(true);
         }
 
         return true;
@@ -210,27 +200,42 @@ class Access
     }
 
     /**
-     * Reload Super User access
+     * Make sure a login name is set
      *
-     * @return bool
+     * @return true
      */
-    protected function reloadAccessSuperUser()
+    protected function makeSureLoginNameIsSet()
     {
-        $this->hasSuperUserAccess = true;
-
-        try {
-            $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId();
-        } catch (\Exception $e) {
-            $allSitesId = array();
-        }
-        $this->idsitesByAccess['superuser'] = $allSitesId;
-
-        if(empty($this->login)) {
+        if (empty($this->login)) {
             // flag to force non empty login so Super User is not mistaken for anonymous
             $this->login = 'super user was set';
         }
+    }
 
-        return true;
+    private function loadSitesIfNeeded()
+    {
+        if ($this->hasSuperUserAccess) {
+            if (empty($this->idsitesByAccess['superuser'])) {
+                try {
+                    $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId();
+                } catch (\Exception $e) {
+                    $allSitesId = array();
+                }
+                $this->idsitesByAccess['superuser'] = $allSitesId;
+            }
+        } else if (isset($this->login)) {
+            if (empty($this->idsitesByAccess['view'])
+                && empty($this->idsitesByAccess['admin'])) {
+
+                // we join with site in case there are rows in access for an idsite that doesn't exist anymore
+                // (backward compatibility ; before we deleted the site without deleting rows in _access table)
+                $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login);
+
+                foreach ($accessRaw as $access) {
+                    $this->idsitesByAccess[$access['access']][] = $access['idsite'];
+                }
+            }
+        }
     }
 
     /**
@@ -241,11 +246,12 @@ class Access
      */
     public function setSuperUserAccess($bool = true)
     {
+        $this->hasSuperUserAccess = (bool) $bool;
+
         if ($bool) {
-            $this->reloadAccessSuperUser();
+            $this->makeSureLoginNameIsSet();
         } else {
-            $this->hasSuperUserAccess = false;
-            $this->idsitesByAccess['superuser'] = array();
+            $this->resetSites();
         }
     }
 
@@ -288,6 +294,8 @@ class Access
      */
     public function getSitesIdWithAtLeastViewAccess()
     {
+        $this->loadSitesIfNeeded();
+
         return array_unique(array_merge(
                 $this->idsitesByAccess['view'],
                 $this->idsitesByAccess['admin'],
@@ -303,6 +311,8 @@ class Access
      */
     public function getSitesIdWithAdminAccess()
     {
+        $this->loadSitesIfNeeded();
+
         return array_unique(array_merge(
                 $this->idsitesByAccess['admin'],
                 $this->idsitesByAccess['superuser'])
@@ -318,6 +328,8 @@ class Access
      */
     public function getSitesIdWithViewAccess()
     {
+        $this->loadSitesIfNeeded();
+
         return $this->idsitesByAccess['view'];
     }
 
diff --git a/core/Archive/ArchivePurger.php b/core/Archive/ArchivePurger.php
index e41ce375c553076ec7f9ea88108f1b1fd052ce0a..f326558eb2c4bcb54df697ef3da5587ab0d4565f 100644
--- a/core/Archive/ArchivePurger.php
+++ b/core/Archive/ArchivePurger.php
@@ -10,12 +10,14 @@ namespace Piwik\Archive;
 
 use Piwik\ArchiveProcessor\Rules;
 use Piwik\Config;
+use Piwik\Container\StaticContainer;
 use Piwik\DataAccess\ArchiveTableCreator;
 use Piwik\DataAccess\Model;
 use Piwik\Date;
 use Piwik\Db;
-use Piwik\Log;
 use Piwik\Piwik;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
 
 /**
  * Service that purges temporary, error-ed, invalid and custom range archives from archive tables.
@@ -64,7 +66,12 @@ class ArchivePurger
      */
     private $now;
 
-    public function __construct(Model $model = null, Date $purgeCustomRangesOlderThan = null)
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    public function __construct(Model $model = null, Date $purgeCustomRangesOlderThan = null, LoggerInterface $logger = null)
     {
         $this->model = $model ?: new Model();
 
@@ -73,6 +80,7 @@ class ArchivePurger
         $this->yesterday = Date::factory('yesterday');
         $this->today = Date::factory('today');
         $this->now = time();
+        $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
     }
 
     /**
@@ -80,6 +88,7 @@ class ArchivePurger
      * table that stores data for `$date`.
      *
      * @param Date $date The date identifying the archive table.
+     * @return int The total number of archive rows deleted (from both the blog & numeric tables).
      */
     public function purgeInvalidatedArchivesFrom(Date $date)
     {
@@ -90,15 +99,27 @@ class ArchivePurger
         // the constraint will hit an INDEX and speed up the inner join that happens in getInvalidatedArchiveIdsSafeToDelete().
         $idSites = $this->model->getSitesWithInvalidatedArchive($numericTable);
         if (empty($idSites)) {
-            return;
+            $this->logger->debug("No sites with invalidated archives found in {table}.", array('table' => $numericTable));
+            return 0;
         }
 
         $archiveIds = $this->model->getInvalidatedArchiveIdsSafeToDelete($numericTable, $idSites);
         if (empty($archiveIds)) {
-            return;
+            $this->logger->debug("No invalidated archives found in {table} with newer, valid archives.", array('table' => $numericTable));
+            return 0;
         }
 
-        $this->deleteArchiveIds($date, $archiveIds);
+        $this->logger->info("Found {countArchiveIds} invalidated archives safe to delete in {table}.", array(
+            'table' => $numericTable, 'countArchiveIds' => count($archiveIds)
+        ));
+
+        $deletedRowCount = $this->deleteArchiveIds($date, $archiveIds);
+
+        $this->logger->debug("Deleted {count} rows in {table} and its associated blob table.", array(
+            'table' => $numericTable, 'count' => $deletedRowCount
+        ));
+
+        return $deletedRowCount;
     }
 
     /**
@@ -106,20 +127,32 @@ class ArchivePurger
      * (meaning they are marked with a done flag of ArchiveWriter::DONE_OK_TEMPORARY or ArchiveWriter::DONE_ERROR)
      *
      * @param Date $dateStart Only the month will be used
+     * @return int Returns the total number of rows deleted.
      */
     public function purgeOutdatedArchives(Date $dateStart)
     {
         $purgeArchivesOlderThan = $this->getOldestTemporaryArchiveToKeepThreshold();
+        $deletedRowCount = 0;
 
         $idArchivesToDelete = $this->getOutdatedArchiveIds($dateStart, $purgeArchivesOlderThan);
         if (!empty($idArchivesToDelete)) {
-            $this->deleteArchiveIds($dateStart, $idArchivesToDelete);
+            $deletedRowCount = $this->deleteArchiveIds($dateStart, $idArchivesToDelete);
+
+            $this->logger->info("Deleted {count} rows in archive tables (numeric + blob) for {date}.", array(
+                'count' => $deletedRowCount,
+                'date' => $dateStart
+            ));
+        } else {
+            $this->logger->debug("No outdated archives found in archive numeric table for {date}.", array('date' => $dateStart));
         }
 
-        Log::debug("Purging temporary archives: done [ purged archives older than %s in %s ] [Deleted IDs: %s]",
-                   $purgeArchivesOlderThan,
-                   $dateStart->toString("Y-m"),
-                   implode(',', $idArchivesToDelete));
+        $this->logger->debug("Purging temporary archives: done [ purged archives older than {date} in {yearMonth} ] [Deleted IDs: {deletedIds}]", array(
+            'date' => $purgeArchivesOlderThan,
+            'yearMonth' => $dateStart->toString('Y-m'),
+            'deletedIds' => implode(',', $idArchivesToDelete)
+        ));
+
+        return $deletedRowCount;
     }
 
     protected function getOutdatedArchiveIds(Date $date, $purgeArchivesOlderThan)
@@ -142,16 +175,26 @@ class ArchivePurger
      * Deleting "Custom Date Range" reports after 1 day, since they can be re-processed and would take up un-necessary space.
      *
      * @param $date Date
+     * @return int The total number of rows deleted from both the numeric & blob table.
      */
     public function purgeArchivesWithPeriodRange(Date $date)
     {
         $numericTable = ArchiveTableCreator::getNumericTable($date);
         $blobTable    = ArchiveTableCreator::getBlobTable($date);
 
-        $this->model->deleteArchivesWithPeriod($numericTable, $blobTable, Piwik::$idPeriods['range'], $this->purgeCustomRangesOlderThan);
+        $deletedCount = $this->model->deleteArchivesWithPeriod(
+            $numericTable, $blobTable, Piwik::$idPeriods['range'], $this->purgeCustomRangesOlderThan);
+
+        $level = $deletedCount == 0 ? LogLevel::DEBUG : LogLevel::INFO;
+        $this->logger->log($level, "Purged {count} range archive rows from {numericTable} & {blobTable}.", array(
+            'count' => $deletedCount,
+            'numericTable' => $numericTable,
+            'blobTable' => $blobTable
+        ));
+
+        $this->logger->debug("  [ purged archives older than {threshold} ]", array('threshold' => $this->purgeCustomRangesOlderThan));
 
-        Log::debug("Purging Custom Range archives: done [ purged archives older than %s from %s / blob ]",
-            $this->purgeCustomRangesOlderThan, $numericTable);
+        return $deletedCount;
     }
 
     /**
@@ -159,6 +202,7 @@ class ArchivePurger
      *
      * @param Date $date
      * @param $idArchivesToDelete
+     * @return int Number of rows deleted from both numeric + blob table.
      */
     protected function deleteArchiveIds(Date $date, $idArchivesToDelete)
     {
@@ -166,9 +210,11 @@ class ArchivePurger
         $numericTable = ArchiveTableCreator::getNumericTable($date);
         $blobTable    = ArchiveTableCreator::getBlobTable($date);
 
+        $deletedCount = 0;
         foreach ($batches as $idsToDelete) {
-            $this->model->deleteArchiveIds($numericTable, $blobTable, $idsToDelete);
+            $deletedCount += $this->model->deleteArchiveIds($numericTable, $blobTable, $idsToDelete);
         }
+        return $deletedCount;
     }
 
     /**
diff --git a/core/CliMulti/CliPhp.php b/core/CliMulti/CliPhp.php
index 9ee5b191b1b7c50af37bcfb852d795c9b77cd1d1..3bf7b3888e839eb76962548244ab94fee157bbee 100644
--- a/core/CliMulti/CliPhp.php
+++ b/core/CliMulti/CliPhp.php
@@ -9,7 +9,6 @@ namespace Piwik\CliMulti;
 
 use Piwik\CliMulti;
 use Piwik\Common;
-use Piwik\Plugins\Installation\SystemCheck;
 
 class CliPhp
 {
@@ -67,8 +66,9 @@ class CliPhp
 
     private function isValidPhpVersion($bin)
     {
+        global $piwik_minimumPHPVersion;
         $cliVersion = $this->getPhpVersion($bin);
-        $isCliVersionValid = SystemCheck::isPhpVersionValid($cliVersion);
+        $isCliVersionValid = version_compare($piwik_minimumPHPVersion, $cliVersion) <= 0;
         return $isCliVersionValid;
     }
 
diff --git a/core/CliMulti/Process.php b/core/CliMulti/Process.php
index c63dd5a94620a68987c8e4d03fe1b0875afb2266..bae7e316d6071a22e95c7476d0ff58c371440a73 100644
--- a/core/CliMulti/Process.php
+++ b/core/CliMulti/Process.php
@@ -144,7 +144,7 @@ class Process
         }
 
         $lockedPID   = trim($content);
-        $runningPIDs = explode("\n", trim( `ps ex | awk '{print $1}'` ));
+        $runningPIDs = self::getRunningProcesses();
 
         return !empty($lockedPID) && in_array($lockedPID, $runningPIDs);
     }
@@ -173,6 +173,10 @@ class Process
             return false;
         }
 
+        if (count(self::getRunningProcesses()) > 0) {
+            return true;
+        }
+
         if (!self::isProcFSMounted()) {
             return false;
         }
@@ -236,4 +240,18 @@ class Process
         return strpos($type, 'proc') === 0;
     }
 
+    /**
+     * @return int[] The ids of the currently running processes
+     */
+     static function getRunningProcesses()
+    {
+        $ids = explode("\n", trim(`ps ex 2>/dev/null | awk '{print $1}' 2>/dev/null`));
+
+        $ids = array_map('intval', $ids);
+        $ids = array_filter($ids, function ($id) {
+            return $id > 0;
+        });
+
+        return $ids;
+    }
 }
diff --git a/core/Common.php b/core/Common.php
index 1ed8d4893574662dd572ade8ca4665c333eb299b..14007045996dfa7a454584982ca8ce8d74a02465 100644
--- a/core/Common.php
+++ b/core/Common.php
@@ -379,9 +379,13 @@ class Common
      */
     private static function undoMagicQuotes($value)
     {
-        if (version_compare(PHP_VERSION, '5.4', '<') &&
-            get_magic_quotes_gpc()) {
+        static $shouldUndo;
 
+        if (!isset($shouldUndo)) {
+            $shouldUndo = version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc();
+        }
+
+        if ($shouldUndo) {
             $value = stripslashes($value);
         }
 
@@ -470,7 +474,7 @@ class Common
         }
 
         $value = self::sanitizeInputValues($requestArrayToUse[$varName]);
-        if (!is_null($varType)) {
+        if (isset($varType)) {
             $ok = false;
 
             if ($varType === 'string') {
@@ -1199,7 +1203,8 @@ class Common
             401 => 'Unauthorized',
             403 => 'Forbidden',
             404 => 'Not Found',
-            500 => 'Internal Server Error'
+            500 => 'Internal Server Error',
+            503 => 'Service Unavailable',
         );
 
         if (!array_key_exists($code, $messages)) {
diff --git a/core/Config.php b/core/Config.php
index 1ceb2594da87cad556591fc0b0e7953172d37a37..3e8ab98797ba862821d8385bbd6075b75e2bdee1 100644
--- a/core/Config.php
+++ b/core/Config.php
@@ -354,15 +354,29 @@ class Config extends Singleton
         return $section;
     }
 
+    /**
+     * @api
+     */
     public function getFromGlobalConfig($name)
     {
         return $this->settings->getIniFileChain()->getFrom($this->getGlobalPath(), $name);
     }
 
+    /**
+     * @api
+     */
     public function getFromCommonConfig($name)
     {
         return $this->settings->getIniFileChain()->getFrom($this->getCommonPath(), $name);
     }
+    
+    /**
+     * @api
+     */
+    public function getFromLocalConfig($name)
+    {
+        return $this->settings->getIniFileChain()->getFrom($this->getLocalPath(), $name);
+    }
 
     /**
      * Sets a configuration value or section.
@@ -443,4 +457,4 @@ class Config extends Singleton
         $path = "config/" . basename($this->getLocalPath());
         return new Exception(Piwik::translate('General_ConfigFileIsNotWritable', array("(" . $path . ")", "")));
     }
-}
\ No newline at end of file
+}
diff --git a/core/Config/IniFileChain.php b/core/Config/IniFileChain.php
index 4d264b2ec294836a02a2597e24f6b0305c20d729..3f6f076cd8d6176ae7487702f68aa8834db0ad4f 100644
--- a/core/Config/IniFileChain.php
+++ b/core/Config/IniFileChain.php
@@ -8,11 +8,9 @@
 namespace Piwik\Config;
 
 use Piwik\Common;
-use Piwik\Config;
 use Piwik\Ini\IniReader;
 use Piwik\Ini\IniReadingException;
 use Piwik\Ini\IniWriter;
-use Piwik\Piwik;
 
 /**
  * Manages a list of INI files where the settings in each INI file merge with or override the
@@ -119,7 +117,7 @@ class IniFileChain
      */
     public function dump($header = '')
     {
-        return $this->dumpSettingsArray($header, $this->mergedSettings);
+        return $this->dumpSettings($this->mergedSettings, $header);
     }
 
     /**
@@ -189,7 +187,7 @@ class IniFileChain
                 }
             });
 
-            return $this->dumpSettingsArray($header, $configToWrite);
+            return $this->dumpSettings($configToWrite, $header);
         } else {
             return null;
         }
@@ -210,7 +208,8 @@ class IniFileChain
         foreach ($this->settingsChain as $file => $ignore) {
             if (is_readable($file)) {
                 try {
-                    $this->settingsChain[$file] = $reader->readFile($file);
+                    $contents = $reader->readFile($file);
+                    $this->settingsChain[$file] = $this->decodeValues($contents);
                 } catch (IniReadingException $ex) {
                     throw new IniReadingException('Unable to read INI file {' . $file . '}: ' . $ex->getMessage() . "\n Your host may have disabled parse_ini_file().");
                 }
@@ -405,53 +404,51 @@ class IniFileChain
         return array_search($sectionName, $settingsDataSectionNames);
     }
 
-
     /**
-     * Decode HTML entities in setting data.
+     * Encode HTML entities
      *
      * @param mixed $values
      * @return mixed
      */
-    private function decodeValues(&$values)
+    protected function encodeValues(&$values)
     {
         if (is_array($values)) {
             foreach ($values as &$value) {
-                $value = self::decodeValues($value);
+                $value = $this->encodeValues($value);
             }
-            return $values;
+        } elseif (is_float($values)) {
+            $values = Common::forceDotAsSeparatorForDecimalPoint($values);
         } elseif (is_string($values)) {
-            return html_entity_decode($values, ENT_COMPAT, 'UTF-8');
+            $values = htmlentities($values, ENT_COMPAT, 'UTF-8');
+            $values = str_replace('$', '&#36;', $values);
         }
         return $values;
     }
 
-
     /**
-     * Encode HTML entities
+     * Decode HTML entities
      *
      * @param mixed $values
      * @return mixed
      */
-    protected function encodeValues(&$values)
+    protected function decodeValues(&$values)
     {
         if (is_array($values)) {
             foreach ($values as &$value) {
-                $value = $this->encodeValues($value);
+                $value = $this->decodeValues($value);
             }
-        } elseif (is_float($values)) {
-            $values = Common::forceDotAsSeparatorForDecimalPoint($values);
+            return $values;
         } elseif (is_string($values)) {
-            $values = htmlentities($values, ENT_COMPAT, 'UTF-8');
-            $values = str_replace('$', '&#36;', $values);
+            return html_entity_decode($values, ENT_COMPAT, 'UTF-8');
         }
         return $values;
     }
 
-    private function dumpSettingsArray($header, $settings)
+    private function dumpSettings($values, $header)
     {
-        $writer = new IniWriter();
+        $values = $this->encodeValues($values);
 
-        $this->encodeValues($settings);
-        return $writer->writeToString($settings, $header);
+        $writer = new IniWriter();
+        return $writer->writeToString($values, $header);
     }
 }
\ No newline at end of file
diff --git a/core/Container/IniConfigDefinitionSource.php b/core/Container/IniConfigDefinitionSource.php
index 2fb724a928c19aca3b58784a692f40e6589ff5fd..4b56d1aa9c36abd61d22a5cf0274a8f7603055eb 100644
--- a/core/Container/IniConfigDefinitionSource.php
+++ b/core/Container/IniConfigDefinitionSource.php
@@ -9,14 +9,18 @@
 namespace Piwik\Container;
 
 use DI\Definition\Exception\DefinitionException;
-use DI\Definition\Source\ChainableDefinitionSource;
+use DI\Definition\Source\DefinitionSource;
 use DI\Definition\ValueDefinition;
 use Piwik\Application\Kernel\GlobalSettingsProvider;
 
 /**
- * Import the old INI config into PHP-DI.
+ * Expose the INI config into PHP-DI.
+ *
+ * The INI config can be used by prefixing `ini.` before the setting we want to get:
+ *
+ *     $maintenanceMode = $container->get('ini.General.maintenance_mode');
  */
-class IniConfigDefinitionSource extends ChainableDefinitionSource
+class IniConfigDefinitionSource implements DefinitionSource
 {
     /**
      * @var GlobalSettingsProvider
@@ -38,7 +42,10 @@ class IniConfigDefinitionSource extends ChainableDefinitionSource
         $this->prefix = $prefix;
     }
 
-    protected function findDefinition($name)
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefinition($name)
     {
         if (strpos($name, $this->prefix) !== 0) {
             return null;
diff --git a/core/CronArchive.php b/core/CronArchive.php
index e5040be34f7f16e63d1f68781099e28ffd140c07..2c3cefc7a4d17d43ca97d9722417f04e041db2b5 100644
--- a/core/CronArchive.php
+++ b/core/CronArchive.php
@@ -277,15 +277,10 @@ class CronArchive
         $websitesIds = $this->initWebsiteIds();
         $this->filterWebsiteIds($websitesIds);
 
-        if (!empty($this->shouldArchiveSpecifiedSites)
-            || !empty($this->shouldArchiveAllSites)
-            || !SharedSiteIds::isSupported()) {
-            $this->websites = new FixedSiteIds($websitesIds);
-        } else {
-            $this->websites = new SharedSiteIds($websitesIds);
-            if ($this->websites->getInitialSiteIds() != $websitesIds) {
-                $this->log('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(', ', $this->websites->getInitialSiteIds()));
-            }
+        $this->websites = $this->createSitesToArchiveQueue($websitesIds);
+
+        if ($this->websites->getInitialSiteIds() != $websitesIds) {
+            $this->log('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(', ', $this->websites->getInitialSiteIds()));
         }
 
         if ($this->shouldStartProfiler) {
@@ -427,16 +422,12 @@ class CronArchive
             return;
         }
 
-        $this->log("Starting Scheduled tasks... ");
+        API\Request::processRequest("CoreAdminHome.runScheduledTasks", array(
+            'token_auth' => $this->token_auth,
+            'format' => 'original', // so exceptions get thrown
+            'trigger' => 'archivephp'
+        ));
 
-        $tasksOutput = $this->request("?module=API&method=CoreAdminHome.runScheduledTasks&format=csv&convertToUnicode=0&token_auth=" . $this->token_auth);
-
-        if ($tasksOutput == \Piwik\DataTable\Renderer\Csv::NO_DATA_AVAILABLE) {
-            $tasksOutput = " No task to run";
-        }
-
-        $this->log($tasksOutput);
-        $this->log("done");
         $this->logSection("");
     }
 
@@ -1016,17 +1007,14 @@ class CronArchive
         return array_unique($websiteIds);
     }
 
-    private function initTokenAuth()
+    public function initTokenAuth()
     {
-        $tokens = array();
-
-        /**
-         * @ignore
-         */
-        Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens));
+        $tokens = self::getSuperUserTokenAuths();
 
         $this->validTokenAuths = $tokens;
         $this->token_auth = array_shift($tokens);
+
+        return $this->token_auth;
     }
 
     public function isTokenAuthSuperUserToken($token_auth)
@@ -1504,11 +1492,20 @@ class CronArchive
     private function loadCustomDateRangeToPreProcess()
     {
         $customDateRangesToProcessForSites = array();
+
         // For all users who have selected this website to load by default,
         // we load the default period/date that will be loaded for this user
         // and make sure it's pre-archived
-        $userPreferences = APIUsersManager::getInstance()->getAllUsersPreferences(array(APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE, APIUsersManager::PREFERENCE_DEFAULT_REPORT));
-        foreach ($userPreferences as $userLogin => $userPreferences) {
+        $allUsersPreferences = APIUsersManager::getInstance()->getAllUsersPreferences(array(
+            APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE,
+            APIUsersManager::PREFERENCE_DEFAULT_REPORT
+        ));
+
+        foreach ($allUsersPreferences as $userLogin => $userPreferences) {
+
+            if (!isset($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE])) {
+                continue;
+            }
 
             $defaultDate = $userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE];
             $preference = new UserPreferences();
@@ -1543,6 +1540,18 @@ class CronArchive
         return $this->piwikUrl . $url . self::APPEND_TO_API_REQUEST;
     }
 
+    public static function getSuperUserTokenAuths()
+    {
+        $tokens = array();
+
+        /**
+         * @ignore
+         */
+        Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens));
+
+        return $tokens;
+    }
+
     /**
      * @param $idSite
      * @param $period
@@ -1562,4 +1571,19 @@ class CronArchive
         }
         return $urlsWithSegment;
     }
+
+    private function createSitesToArchiveQueue($websitesIds)
+    {
+        // use synchronous, single process queue if --force-idsites is used or sharing site IDs isn't supported
+        if (!SharedSiteIds::isSupported() || !empty($this->shouldArchiveSpecifiedSites)) {
+            return new FixedSiteIds($websitesIds);
+        }
+
+        // use separate shared queue if --force-all-websites is used
+        if (!empty($this->shouldArchiveAllSites)) {
+            return new SharedSiteIds($websitesIds, SharedSiteIds::OPTION_ALL_WEBSITES);
+        }
+
+        return new SharedSiteIds($websitesIds);
+   }
 }
\ No newline at end of file
diff --git a/core/CronArchive/SharedSiteIds.php b/core/CronArchive/SharedSiteIds.php
index 50f95ea2dd294af9df17a7be1b913643b71756fc..facc331a6dd727acfcfbdd9ad6db7bbad72c88fa 100644
--- a/core/CronArchive/SharedSiteIds.php
+++ b/core/CronArchive/SharedSiteIds.php
@@ -18,12 +18,22 @@ use Piwik\Option;
  */
 class SharedSiteIds
 {
+    const OPTION_DEFAULT = 'SharedSiteIdsToArchive';
+    const OPTION_ALL_WEBSITES = 'SharedSiteIdsToArchive_AllWebsites';
+
+    /**
+     * @var string
+     */
+    private $optionName;
+
     private $siteIds = array();
     private $currentSiteId;
     private $done = false;
 
-    public function __construct($websiteIds)
+    public function __construct($websiteIds, $optionName = self::OPTION_DEFAULT)
     {
+        $this->optionName = $optionName;
+
         if (empty($websiteIds)) {
             $websiteIds = array();
         }
@@ -86,16 +96,16 @@ class SharedSiteIds
     public function setSiteIdsToArchive($siteIds)
     {
         if (!empty($siteIds)) {
-            Option::set('SharedSiteIdsToArchive', implode(',', $siteIds));
+            Option::set($this->optionName, implode(',', $siteIds));
         } else {
-            Option::delete('SharedSiteIdsToArchive');
+            Option::delete($this->optionName);
         }
     }
 
     public function getAllSiteIdsToArchive()
     {
-        Option::clearCachedOption('SharedSiteIdsToArchive');
-        $siteIdsToArchive = Option::get('SharedSiteIdsToArchive');
+        Option::clearCachedOption($this->optionName);
+        $siteIdsToArchive = Option::get($this->optionName);
 
         if (empty($siteIdsToArchive)) {
             return array();
diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php
index 0980983fe31f3a49301361ce61d61abef61b9ce8..8df49d16a31d170a5fb6be4598cb9855d466c75f 100644
--- a/core/DataAccess/Model.php
+++ b/core/DataAccess/Model.php
@@ -10,9 +10,11 @@ namespace Piwik\DataAccess;
 
 use Exception;
 use Piwik\Common;
+use Piwik\Container\StaticContainer;
 use Piwik\Db;
 use Piwik\DbHelper;
 use Piwik\Sequence;
+use Psr\Log\LoggerInterface;
 
 /**
  * Cleans up outdated archives
@@ -21,6 +23,15 @@ use Piwik\Sequence;
  */
 class Model
 {
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    public function __construct(LoggerInterface $logger = null)
+    {
+        $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
+    }
 
     /**
      * Returns the archives IDs that have already been invalidated and have been since re-processed.
@@ -118,13 +129,21 @@ class Model
         $query = "DELETE FROM %s WHERE period = ? AND ts_archived < ?";
         $bind  = array($period, $date);
 
-        Db::query(sprintf($query, $numericTable), $bind);
+        $queryObj = Db::query(sprintf($query, $numericTable), $bind);
+        $deletedRows = $queryObj->rowCount();
 
         try {
-            Db::query(sprintf($query, $blobTable), $bind);
+            $queryObj = Db::query(sprintf($query, $blobTable), $bind);
+            $deletedRows += $queryObj->rowCount();
         } catch (Exception $e) {
             // Individual blob tables could be missing
+            $this->logger->debug("Unable to delete archives by period from {blobTable}.", array(
+                'blobTable' => $blobTable,
+                'exception' => $e
+            ));
         }
+
+        return $deletedRows;
     }
 
     public function deleteArchiveIds($numericTable, $blobTable, $idsToDelete)
@@ -132,13 +151,21 @@ class Model
         $idsToDelete = array_values($idsToDelete);
         $query = "DELETE FROM %s WHERE idarchive IN (" . Common::getSqlStringFieldsArray($idsToDelete) . ")";
 
-        Db::query(sprintf($query, $numericTable), $idsToDelete);
+        $queryObj = Db::query(sprintf($query, $numericTable), $idsToDelete);
+        $deletedRows = $queryObj->rowCount();
 
         try {
-            Db::query(sprintf($query, $blobTable), $idsToDelete);
+            $queryObj = Db::query(sprintf($query, $blobTable), $idsToDelete);
+            $deletedRows += $queryObj->rowCount();
         } catch (Exception $e) {
             // Individual blob tables could be missing
+            $this->logger->debug("Unable to delete archive IDs from {blobTable}.", array(
+                'blobTable' => $blobTable,
+                'exception' => $e
+            ));
         }
+
+        return $deletedRows;
     }
 
     public function getArchiveIdAndVisits($numericTable, $idSite, $period, $dateStartIso, $dateEndIso, $minDatetimeIsoArchiveProcessedUTC, $doneFlags, $doneFlagValues)
diff --git a/core/DataTable/Filter/Pattern.php b/core/DataTable/Filter/Pattern.php
index 832fb856a210cf7140400351023f3bf598e052c9..1327b4f40a6ec58b52e083e611cc2709aa2c85d8 100644
--- a/core/DataTable/Filter/Pattern.php
+++ b/core/DataTable/Filter/Pattern.php
@@ -23,6 +23,9 @@ use Piwik\DataTable\BaseFilter;
  */
 class Pattern extends BaseFilter
 {
+    /**
+     * @var string|array
+     */
     private $columnToFilter;
     private $patternToSearch;
     private $patternToSearchQuoted;
@@ -93,4 +96,30 @@ class Pattern extends BaseFilter
             }
         }
     }
+
+    /**
+     * See {@link Pattern}.
+     *
+     * @param array $array
+     * @return array
+     */
+    public function filterArray($array)
+    {
+        $newArray = array();
+
+        foreach ($array as $key => $row) {
+            foreach ($this->columnToFilter as $column) {
+                if (!array_key_exists($column, $row)) {
+                    continue;
+                }
+
+                if (self::match($this->patternToSearchQuoted, $row[$column], $this->invertedMatch)) {
+                    $newArray[$key] = $row;
+                    continue 2;
+                }
+            }
+        }
+
+        return $newArray;
+    }
 }
diff --git a/core/EventDispatcher.php b/core/EventDispatcher.php
index 87e8ca59c2f12837ae79ff8e5998b27948c8e348..47ae487cde594e5695cce454a31b2f065838bf75 100644
--- a/core/EventDispatcher.php
+++ b/core/EventDispatcher.php
@@ -52,6 +52,8 @@ class EventDispatcher extends Singleton
      */
     private $pluginManager;
 
+    private $pluginHooks = array();
+
     /**
      * Constructor.
      */
@@ -78,29 +80,36 @@ class EventDispatcher extends Singleton
             $this->pendingEvents[] = array($eventName, $params);
         }
 
+        $manager = $this->getPluginManager();
+
         if (empty($plugins)) {
-            $plugins = $this->getPluginManager()->getPluginsLoadedAndActivated();
+            $plugins = $manager->getPluginsLoadedAndActivated();
         }
 
         $callbacks = array();
 
         // collect all callbacks to execute
-        foreach ($plugins as $plugin) {
-            if (is_string($plugin)) {
-                $plugin = $this->getPluginManager()->getLoadedPlugin($plugin);
+        foreach ($plugins as $pluginName) {
+            if (!is_string($pluginName)) {
+                $pluginName = $pluginName->getPluginName();
             }
 
-            if (empty($plugin)) {
-                return; // may happen in unit tests
+            if (!isset($this->pluginHooks[$pluginName])) {
+                $plugin = $manager->getLoadedPlugin($pluginName);
+                $this->pluginHooks[$pluginName] = $plugin->getListHooksRegistered();
             }
 
-
-            $hooks = $plugin->getListHooksRegistered();
+            $hooks = $this->pluginHooks[$pluginName];
 
             if (isset($hooks[$eventName])) {
                 list($pluginFunction, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($hooks[$eventName]);
 
-                $callbacks[$callbackGroup][] = is_string($pluginFunction) ? array($plugin, $pluginFunction) : $pluginFunction;
+                if (is_string($pluginFunction)) {
+                    $plugin = $manager->getLoadedPlugin($pluginName);
+                    $callbacks[$callbackGroup][] = array($plugin, $pluginFunction) ;
+                } else {
+                    $callbacks[$callbackGroup][] = $pluginFunction;
+                }
             }
         }
 
diff --git a/core/FrontController.php b/core/FrontController.php
index d4aed5c31300681ac0b4403c8e9e775636cbbda3..8ee4f0c84a75a5e4cfba7b0ae8aca262ec9dc0f7 100644
--- a/core/FrontController.php
+++ b/core/FrontController.php
@@ -18,6 +18,7 @@ use Piwik\Exception\DatabaseSchemaIsNewerThanCodebaseException;
 use Piwik\Http\ControllerResolver;
 use Piwik\Http\Router;
 use Piwik\Plugin\Report;
+use Piwik\Plugins\CoreAdminHome\CustomLogo;
 use Piwik\Session;
 
 /**
@@ -389,28 +390,28 @@ class FrontController extends Singleton
 
     protected function handleMaintenanceMode()
     {
-        if (Config::getInstance()->General['maintenance_mode'] == 1
-            && !Common::isPhpCliMode()
-        ) {
-            $format = Common::getRequestVar('format', '');
-
-            $message = "Piwik is in scheduled maintenance. Please come back later."
-                . " The administrator can disable maintenance by editing the file piwik/config/config.ini.php and removing the following: "
-                . " maintenance_mode=1 ";
-            if (Config::getInstance()->Tracker['record_statistics'] == 0) {
-                $message .= ' and record_statistics=0';
-            }
+        if ((Config::getInstance()->General['maintenance_mode'] != 1) || Common::isPhpCliMode()) {
+            return;
+        }
+        Common::sendResponseCode(503);
 
-            $exception = new Exception($message);
-            // extend explain how to re-enable
-            // show error message when record stats = 0
-            if (empty($format)) {
-                throw $exception;
-            }
-            $response = new ResponseBuilder($format);
-            echo $response->getResponseException($exception);
-            exit;
+        $logoUrl = null;
+        $faviconUrl = null;
+        try {
+            $logo = new CustomLogo();
+            $logoUrl = $logo->getHeaderLogoUrl();
+            $faviconUrl = $logo->getPathUserFavicon();
+        } catch (Exception $ex) {
         }
+        $logoUrl = $logoUrl ?: 'plugins/Morpheus/images/logo-header.png';
+        $faviconUrl = $faviconUrl ?: 'plugins/CoreHome/images/favicon.ico';
+
+        $page = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/maintenance.tpl');
+        $page = str_replace('%logoUrl%', $logoUrl, $page);
+        $page = str_replace('%faviconUrl%', $faviconUrl, $page);
+        $page = str_replace('%piwikTitle%', Piwik::getRandomTitle(), $page);
+        echo $page;
+        exit;
     }
 
     protected function handleSSLRedirection()
diff --git a/core/Http.php b/core/Http.php
index ffd0bf438304a74ba6b67acb8742eb29913879a7..82809c7fdb72c409a9c231ed8ec0d8905865e6d2 100644
--- a/core/Http.php
+++ b/core/Http.php
@@ -22,7 +22,7 @@ class Http
     /**
      * Returns the "best" available transport method for {@link sendHttpRequest()} calls.
      *
-     * @return string Either `'curl'`, `'fopen'` or `'socket'`.
+     * @return string|null Either curl, fopen, socket or null if no method is supported.
      * @api
      */
     public static function getTransportMethod()
diff --git a/core/Piwik.php b/core/Piwik.php
index c846acec82d688e8dcb5be836fafe1bee040ea8e..fdb38ed753049d28505711f320b7c5f97645adb1 100644
--- a/core/Piwik.php
+++ b/core/Piwik.php
@@ -147,9 +147,6 @@ class Piwik
             'Analytics',
             'Real Time Analytics',
             'Analytics in Real time',
-            'Free/Libre Web Analytics',
-            'Free Website Analytics',
-            'Free Web Analytics',
             'Analytics Platform',
             'Data Platform',
         );
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php
index dca1b19e0b4ba4c1ea08cb048777857937dc0b52..499278538088e11d15b126aab0177e2d43092faa 100644
--- a/core/Plugin/Manager.php
+++ b/core/Plugin/Manager.php
@@ -50,6 +50,8 @@ class Manager
 
     protected $doLoadPlugins = true;
 
+    private $pluginsLoadedAndActivated;
+
     /**
      * @var Plugin[]
      */
@@ -441,6 +443,7 @@ class Manager
      */
     private function clearCache($pluginName)
     {
+        $this->resetTransientCache();
         Filesystem::deleteAllCacheOnUpdate($pluginName);
     }
 
@@ -693,6 +696,7 @@ class Manager
      */
     public function loadPlugins(array $pluginsToLoad)
     {
+        $this->resetTransientCache();
         $this->pluginsToLoad = $this->makePluginsToLoad($pluginsToLoad);
         $this->reloadActivatedPlugins();
     }
@@ -780,15 +784,21 @@ class Manager
      */
     public function getPluginsLoadedAndActivated()
     {
-        $plugins = $this->getLoadedPlugins();
-        $enabled = $this->getActivatedPlugins();
+        if (is_null($this->pluginsLoadedAndActivated)) {
+            $enabled = $this->getActivatedPlugins();
 
-        if (empty($enabled)) {
-            return array();
+            if (empty($enabled)) {
+                return array();
+            }
+
+            $plugins = $this->getLoadedPlugins();
+            $enabled = array_combine($enabled, $enabled);
+            $plugins = array_intersect_key($plugins, $enabled);
+
+            $this->pluginsLoadedAndActivated = $plugins;
         }
-        $enabled = array_combine($enabled, $enabled);
-        $plugins = array_intersect_key($plugins, $enabled);
-        return $plugins;
+
+        return $this->pluginsLoadedAndActivated;
     }
 
     /**
@@ -950,6 +960,11 @@ class Manager
         return "\\Piwik\\Plugins\\$pluginName\\$className";
     }
 
+    private function resetTransientCache()
+    {
+        $this->pluginsLoadedAndActivated = null;
+    }
+
     /**
      * Unload plugin
      *
@@ -958,6 +973,8 @@ class Manager
      */
     public function unloadPlugin($plugin)
     {
+        $this->resetTransientCache();
+
         if (!($plugin instanceof Plugin)) {
             $oPlugin = $this->loadPlugin($plugin);
             if ($oPlugin === null) {
@@ -976,6 +993,8 @@ class Manager
      */
     public function unloadPlugins()
     {
+        $this->resetTransientCache();
+
         $pluginsLoaded = $this->getLoadedPlugins();
         foreach ($pluginsLoaded as $plugin) {
             $this->unloadPlugin($plugin);
@@ -1006,6 +1025,8 @@ class Manager
      */
     public function addLoadedPlugin($pluginName, Plugin $newPlugin)
     {
+        $this->resetTransientCache();
+
         $this->loadedPlugins[$pluginName] = $newPlugin;
     }
 
diff --git a/core/Scheduler/Scheduler.php b/core/Scheduler/Scheduler.php
index 7156e1be9c6ff24c7bc936e17d54b8a514d46bd5..4080cc89d182cc10cd626064c4a916b724c9111e 100644
--- a/core/Scheduler/Scheduler.php
+++ b/core/Scheduler/Scheduler.php
@@ -97,11 +97,13 @@ class Scheduler
         // remove from timetable tasks that are not active anymore
         $this->timetable->removeInactiveTasks($tasks);
 
+        $this->logger->info("Starting Scheduled tasks... ");
+
         // for every priority level, starting with the highest and concluding with the lowest
         $executionResults = array();
-        for ($priority = Task::HIGHEST_PRIORITY;
-             $priority <= Task::LOWEST_PRIORITY;
-             ++$priority) {
+        for ($priority = Task::HIGHEST_PRIORITY; $priority <= Task::LOWEST_PRIORITY; ++$priority) {
+            $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority));
+
             // loop through each task
             foreach ($tasks as $task) {
                 // if the task does not have the current priority level, don't execute it yet
@@ -113,19 +115,31 @@ class Scheduler
                 $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName);
 
                 if ($this->timetable->taskShouldBeRescheduled($taskName)) {
-                    $this->timetable->rescheduleTask($task);
+                    $rescheduledDate = $this->timetable->rescheduleTask($task);
+
+                    $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate));
                 }
 
                 if ($shouldExecuteTask) {
+                    $this->logger->info("Scheduler: executing task {taskName}...", array('taskName' => $taskName));
+
+                    $timer = new Timer();
+
                     $this->isRunningTask = true;
                     $message = $this->executeTask($task);
                     $this->isRunningTask = false;
 
+                    $this->logger->info("Scheduler: finished. {timeElapsed}", array(
+                        'taskName' => $taskName, 'timeElapsed' => $timer
+                    ));
+
                     $executionResults[] = array('task' => $taskName, 'output' => $message);
                 }
             }
         }
 
+        $this->logger->info("done");
+
         return $executionResults;
     }
 
diff --git a/core/Scheduler/Timetable.php b/core/Scheduler/Timetable.php
index 5c9bc69696dec197225d74842d19675fb31e6ff7..c3c01ab1671d1db2223c4781986a81dd039740bb 100644
--- a/core/Scheduler/Timetable.php
+++ b/core/Scheduler/Timetable.php
@@ -105,9 +105,13 @@ class Timetable
 
     public function rescheduleTask(Task $task)
     {
+        $rescheduledTime = $task->getRescheduledTime();
+
         // update the scheduled time
-        $this->timetable[$task->getName()] = $task->getRescheduledTime();
+        $this->timetable[$task->getName()] = $rescheduledTime;
         $this->save();
+
+        return Date::factory($rescheduledTime);
     }
 
     public function save()
diff --git a/core/Settings/Setting.php b/core/Settings/Setting.php
index 32883876715e9aaf8c44e4e87fc4bbfea1aad1a3..4b1b36e5a14f8dbcf49f20aff460b0261f449753 100644
--- a/core/Settings/Setting.php
+++ b/core/Settings/Setting.php
@@ -147,8 +147,6 @@ abstract class Setting
 
     protected $key;
     protected $name;
-    protected $writableByCurrentUser = false;
-    protected $readableByCurrentUser = false;
 
     /**
      * @var StorageInterface
@@ -188,7 +186,7 @@ abstract class Setting
      */
     public function isWritableByCurrentUser()
     {
-        return $this->writableByCurrentUser;
+        return false;
     }
 
     /**
@@ -198,7 +196,7 @@ abstract class Setting
      */
     public function isReadableByCurrentUser()
     {
-        return $this->readableByCurrentUser;
+        return false;
     }
 
     /**
diff --git a/core/Settings/SystemSetting.php b/core/Settings/SystemSetting.php
index 935aec13883ba96a346c4917495f5c307d46954e..94f7416c19c70e7d2e0dd94f2d380af79c2b18fb 100644
--- a/core/Settings/SystemSetting.php
+++ b/core/Settings/SystemSetting.php
@@ -31,6 +31,11 @@ class SystemSetting extends Setting
      */
     public $readableByCurrentUser = false;
 
+    /**
+     * @var bool
+     */
+    private $writableByCurrentUser = false;
+
     /**
      * Constructor.
      *
@@ -45,6 +50,27 @@ class SystemSetting extends Setting
         $this->readableByCurrentUser = $this->writableByCurrentUser;
     }
 
+    /**
+     * Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns
+     * writable for the current user it will be visible in the Plugin settings UI.
+     *
+     * @return bool
+     */
+    public function isWritableByCurrentUser()
+    {
+        return $this->writableByCurrentUser;
+    }
+
+    /**
+     * Returns `true` if this setting can be displayed for the current user, `false` if otherwise.
+     *
+     * @return bool
+     */
+    public function isReadableByCurrentUser()
+    {
+        return $this->readableByCurrentUser;
+    }
+
     /**
      * Returns the display order. System settings are displayed before user settings.
      *
diff --git a/core/Settings/UserSetting.php b/core/Settings/UserSetting.php
index 80cf66ebf21a63a7053149b1a1a58e8a0851e563..60f63e3037b8b33a2acb9e1ce8be7518d3786a78 100644
--- a/core/Settings/UserSetting.php
+++ b/core/Settings/UserSetting.php
@@ -22,6 +22,12 @@ class UserSetting extends Setting
 {
     private $userLogin = null;
 
+    /**
+     * Null while not initialized, bool otherwise.
+     * @var null|bool
+     */
+    private $hasReadAndWritePermission = null;
+
     /**
      * Constructor.
      *
@@ -34,9 +40,32 @@ class UserSetting extends Setting
         parent::__construct($name, $title);
 
         $this->setUserLogin($userLogin);
+    }
+
+    /**
+     * Returns `true` if this setting can be displayed for the current user, `false` if otherwise.
+     *
+     * @return bool
+     */
+    public function isReadableByCurrentUser()
+    {
+        return $this->isWritableByCurrentUser();
+    }
+
+    /**
+     * Returns `true` if this setting can be displayed for the current user, `false` if otherwise.
+     *
+     * @return bool
+     */
+    public function isWritableByCurrentUser()
+    {
+        if (isset($this->hasReadAndWritePermission)) {
+            return $this->hasReadAndWritePermission;
+        }
+
+        $this->hasReadAndWritePermission = Piwik::isUserHasSomeViewAccess();
 
-        $this->writableByCurrentUser = Piwik::isUserHasSomeViewAccess();
-        $this->readableByCurrentUser = Piwik::isUserHasSomeViewAccess();
+        return $this->hasReadAndWritePermission;
     }
 
     /**
diff --git a/core/Tracker.php b/core/Tracker.php
index ff5694e3a07f688a9b69da403590cb8c73b1269e..2c38955ea08987b807997a14e49bb5b197d8f7f7 100644
--- a/core/Tracker.php
+++ b/core/Tracker.php
@@ -132,8 +132,6 @@ class Tracker
         if ($request->isEmptyRequest()) {
             Common::printDebug("The request is empty");
         } else {
-            $this->loadTrackerPlugins();
-
             Common::printDebug("Current datetime: " . date("Y-m-d H:i:s", $request->getCurrentTimestamp()));
 
             $visit = Visit\Factory::make();
@@ -171,6 +169,13 @@ class Tracker
         }
     }
 
+    public static function restoreTrackerPlugins()
+    {
+        if (SettingsServer::isTrackerApiRequest() && Tracker::$initTrackerMode) {
+            Plugin\Manager::getInstance()->loadTrackerPlugins();
+        }
+    }
+
     public function getCountOfLoggedRequests()
     {
         return $this->countOfLoggedRequests;
@@ -245,7 +250,9 @@ class Tracker
         }
 
         // Do not run scheduled tasks during tests
-        TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0);
+        if (!defined('DEBUG_FORCE_SCHEDULED_TASKS')) {
+            TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0);
+        }
 
         // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case,
         // we have to bypass authentication
diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php
index b8928667816f750960fdf846ca40ab698cdee35a..6775a0a65dccde7981d0dc01e243e69ce7395ee2 100644
--- a/core/Tracker/Action.php
+++ b/core/Tracker/Action.php
@@ -125,7 +125,12 @@ abstract class Action
 
     private static function getAllActions(Request $request)
     {
-        $actions   = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action');
+        static $actions;
+
+        if (is_null($actions)) {
+            $actions = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action');
+        }
+
         $instances = array();
 
         foreach ($actions as $action) {
diff --git a/core/Tracker/Cache.php b/core/Tracker/Cache.php
index 22b57a9cc2e10866e3f62ff0f92cbfd57a5e4cdf..1c333069eb137946eb7e752b230f29c3e688eb2f 100644
--- a/core/Tracker/Cache.php
+++ b/core/Tracker/Cache.php
@@ -15,6 +15,8 @@ use Piwik\Common;
 use Piwik\Config;
 use Piwik\Option;
 use Piwik\Piwik;
+use Piwik\Plugin;
+use Piwik\SettingsServer;
 use Piwik\Tracker;
 
 /**
@@ -105,6 +107,8 @@ class Cache
             $cache->save($cacheId, $content, self::getTtl());
         }
 
+        Tracker::restoreTrackerPlugins();
+
         return $content;
     }
 
@@ -160,6 +164,9 @@ class Cache
         Piwik::postEvent('Tracker.setTrackerCacheGeneral', array(&$cacheContent));
         self::setCacheGeneral($cacheContent);
         Common::printDebug("General tracker cache was re-created.");
+
+        Tracker::restoreTrackerPlugins();
+
         return $cacheContent;
     }
 
diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php
index 23f01a555b1f889b3af551aa241f55e17ab77675..7194101ee38848369be9380804fe1ccea39bac56 100644
--- a/core/Tracker/Request.php
+++ b/core/Tracker/Request.php
@@ -20,6 +20,7 @@ use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugins\CustomVariables\CustomVariables;
 use Piwik\Tracker;
+use Piwik\Cache as PiwikCache;
 
 /**
  * The Request object holding the http parameters for this tracking request. Use getParam() to fetch a named parameter.
@@ -27,6 +28,10 @@ use Piwik\Tracker;
  */
 class Request
 {
+    private $cdtCache;
+    private $idSiteCache;
+    private $paramsCache = array();
+
     /**
      * @var array
      */
@@ -108,13 +113,29 @@ class Request
 
         if ($shouldAuthenticate) {
 
+            try {
+                $idSite = $this->getIdSite();
+            } catch (Exception $e) {
+                $this->isAuthenticated = false;
+                return;
+            }
+
             if (empty($tokenAuth)) {
                 $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $this->params);
             }
 
+            $cache = PiwikCache::getTransientCache();
+            $cacheKey = 'tracker_request_authentication_' . $idSite . '_' . $tokenAuth;
+
+            if ($cache->contains($cacheKey)) {
+                Common::printDebug("token_auth is authenticated in cache!");
+                $this->isAuthenticated = $cache->fetch($cacheKey);
+                return;
+            }
+
             try {
-                $idSite = $this->getIdSite();
                 $this->isAuthenticated = self::authenticateSuperUserOrAdmin($tokenAuth, $idSite);
+                $cache->save($cacheKey, $this->isAuthenticated);
             } catch (Exception $e) {
                 $this->isAuthenticated = false;
             }
@@ -294,11 +315,11 @@ class Request
             // other
             'bots'         => array(0, 'int'),
             'dp'           => array(0, 'int'),
-            'rec'          => array(false, 'int'),
+            'rec'          => array(0, 'int'),
             'new_visit'    => array(0, 'int'),
 
             // Ecommerce
-            'ec_id'        => array(false, 'string'),
+            'ec_id'        => array('', 'string'),
             'ec_st'        => array(false, 'float'),
             'ec_tx'        => array(false, 'float'),
             'ec_sh'        => array(false, 'float'),
@@ -306,24 +327,24 @@ class Request
             'ec_items'     => array('', 'json'),
 
             // Events
-            'e_c'          => array(false, 'string'),
-            'e_a'          => array(false, 'string'),
-            'e_n'          => array(false, 'string'),
+            'e_c'          => array('', 'string'),
+            'e_a'          => array('', 'string'),
+            'e_n'          => array('', 'string'),
             'e_v'          => array(false, 'float'),
 
             // some visitor attributes can be overwritten
-            'cip'          => array(false, 'string'),
-            'cdt'          => array(false, 'string'),
-            'cid'          => array(false, 'string'),
-            'uid'          => array(false, 'string'),
+            'cip'          => array('', 'string'),
+            'cdt'          => array('', 'string'),
+            'cid'          => array('', 'string'),
+            'uid'          => array('', 'string'),
 
             // Actions / pages
-            'cs'           => array(false, 'string'),
+            'cs'           => array('', 'string'),
             'download'     => array('', 'string'),
             'link'         => array('', 'string'),
             'action_name'  => array('', 'string'),
             'search'       => array('', 'string'),
-            'search_cat'   => array(false, 'string'),
+            'search_cat'   => array('', 'string'),
             'search_count' => array(-1, 'int'),
             'gt_ms'        => array(-1, 'int'),
 
@@ -334,6 +355,10 @@ class Request
             'c_i'          => array('', 'string'),
         );
 
+        if (isset($this->paramsCache[$name])) {
+            return $this->paramsCache[$name];
+        }
+
         if (!isset($supportedParams[$name])) {
             throw new Exception("Requested parameter $name is not a known Tracking API Parameter.");
         }
@@ -341,9 +366,18 @@ class Request
         $paramDefaultValue = $supportedParams[$name][0];
         $paramType = $supportedParams[$name][1];
 
-        $value = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params);
+        if ($this->hasParam($name)) {
+            $this->paramsCache[$name] = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params);
+        } else {
+            $this->paramsCache[$name] = $paramDefaultValue;
+        }
+
+        return $this->paramsCache[$name];
+    }
 
-        return $value;
+    private function hasParam($name)
+    {
+        return isset($this->params[$name]);
     }
 
     public function getParams()
@@ -353,10 +387,12 @@ class Request
 
     public function getCurrentTimestamp()
     {
-        $cdt = $this->getCustomTimestamp();
+        if (!isset($this->cdtCache)) {
+            $this->cdtCache = $this->getCustomTimestamp();
+        }
 
-        if (!empty($cdt)) {
-            return $cdt;
+        if (!empty($this->cdtCache)) {
+            return $this->cdtCache;
         }
 
         return $this->timestamp;
@@ -369,6 +405,10 @@ class Request
 
     protected function getCustomTimestamp()
     {
+        if (!$this->hasParam('cdt')) {
+            return false;
+        }
+
         $cdt = $this->getParam('cdt');
 
         if (empty($cdt)) {
@@ -418,6 +458,10 @@ class Request
 
     public function getIdSite()
     {
+        if (isset($this->idSiteCache)) {
+            return $this->idSiteCache;
+        }
+
         $idSite = Common::getRequestVar('idsite', 0, 'int', $this->params);
 
         /**
@@ -438,6 +482,8 @@ class Request
             throw new UnexpectedWebsiteFoundException('Invalid idSite: \'' . $idSite . '\'');
         }
 
+        $this->idSiteCache = $idSite;
+
         return $idSite;
     }
 
diff --git a/core/Tracker/Response.php b/core/Tracker/Response.php
index 5258d65cd2ba2e1c060e083bff91300496d9420b..3edd2b27c69b98e0b386c016e156b78bf02a59f3 100644
--- a/core/Tracker/Response.php
+++ b/core/Tracker/Response.php
@@ -74,6 +74,7 @@ class Response
             $this->outputApiResponse($tracker);
             Common::printDebug("Logging disabled, display transparent logo");
         } elseif (!$tracker->hasLoggedRequests()) {
+            Common::sendResponseCode(400);
             Common::printDebug("Empty request => Piwik page");
             echo "<a href='/'>Piwik</a> is a free/libre web <a href='http://piwik.org'>analytics</a> that lets you keep control of your data.";
         } else {
diff --git a/core/Tracker/ScheduledTasksRunner.php b/core/Tracker/ScheduledTasksRunner.php
index 2a4683a4cb3e904c5581141d944387b7c741ef1f..332f2b704714eec66b64d26b21ca754b514f7b9b 100644
--- a/core/Tracker/ScheduledTasksRunner.php
+++ b/core/Tracker/ScheduledTasksRunner.php
@@ -10,6 +10,7 @@
 namespace Piwik\Tracker;
 
 
+use Piwik\CliMulti;
 use Piwik\Common;
 use Piwik\Config;
 use Piwik\CronArchive;
@@ -17,7 +18,6 @@ use Piwik\Option;
 use Piwik\Piwik;
 use Piwik\SettingsPiwik;
 use Piwik\Tracker;
-use Piwik\Translate;
 
 class ScheduledTasksRunner
 {
@@ -78,20 +78,20 @@ class ScheduledTasksRunner
             // Scheduled tasks assume Super User is running
             Piwik::setUserHasSuperUserAccess();
 
-            ob_start();
-            CronArchive::$url = SettingsPiwik::getPiwikUrl();
-            $cronArchive = new CronArchive();
-            $cronArchive->runScheduledTasksInTrackerMode();
+            $tokens = CronArchive::getSuperUserTokenAuths();
+            $tokenAuth = reset($tokens);
 
-            $resultTasks = ob_get_contents();
-            ob_clean();
+            $invokeScheduledTasksUrl = SettingsPiwik::getPiwikUrl()
+                . "?module=API&format=csv&convertToUnicode=0&method=CoreAdminHome.runScheduledTasks&trigger=archivephp&token_auth=$tokenAuth";
+
+            $cliMulti = new CliMulti();
+            $responses = $cliMulti->request(array($invokeScheduledTasksUrl));
+            $resultTasks = reset($responses);
 
             // restore original user privilege
             Piwik::setUserHasSuperUserAccess($isSuperUser);
 
-            foreach (explode('</pre>', $resultTasks) as $resultTask) {
-                Common::printDebug(str_replace('<pre>', '', $resultTask));
-            }
+            Common::printDebug($resultTasks);
 
             Common::printDebug('Finished Scheduled Tasks.');
         } else {
diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php
index f672cd96da20727c1d358cd6778c9ac5a22f351c..4d81bc2f3baf2414d9756e19a9128f64a679bf56 100644
--- a/core/Tracker/Visit.php
+++ b/core/Tracker/Visit.php
@@ -54,6 +54,8 @@ class Visit implements VisitInterface
     protected $userSettings;
     protected $visitorCustomVariables = array();
 
+    public static $dimensions;
+
     /**
      * @param Request $request
      */
@@ -584,16 +586,18 @@ class Visit implements VisitInterface
 
     protected function getAllVisitDimensions()
     {
-        $dimensions = VisitDimension::getAllDimensions();
+        if (is_null(self::$dimensions)) {
+            self::$dimensions = VisitDimension::getAllDimensions();
 
-        $dimensionNames = array();
-        foreach($dimensions as $dimension) {
-            $dimensionNames[] = $dimension->getColumnName();
-        }
+            $dimensionNames = array();
+            foreach(self::$dimensions as $dimension) {
+                $dimensionNames[] = $dimension->getColumnName();
+            }
 
-        Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", $dimensionNames));
+            Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", $dimensionNames));
+        }
 
-        return $dimensions;
+        return self::$dimensions;
     }
 
     private function getVisitStandardLength()
diff --git a/core/Tracker/Visit/Factory.php b/core/Tracker/Visit/Factory.php
index 71362dddeacb5cd14007ae0d0d757c98e8aeaa6f..1d9349dfb1900e0a3b38360f3b734d6ba28cf572 100644
--- a/core/Tracker/Visit/Factory.php
+++ b/core/Tracker/Visit/Factory.php
@@ -37,7 +37,7 @@ class Factory
          */
         Piwik::postEvent('Tracker.makeNewVisitObject', array(&$visit));
 
-        if (is_null($visit)) {
+        if (!isset($visit)) {
             $visit = new Visit();
         } elseif (!($visit instanceof VisitInterface)) {
             throw new Exception("The Visit object set in the plugin must implement VisitInterface");
diff --git a/core/Tracker/Visitor.php b/core/Tracker/Visitor.php
index d9aadb640e1d226891713c0071c8852cb2b1c557..4088e724f1bb5bcdbd8552d316a08e8c2ea19434 100644
--- a/core/Tracker/Visitor.php
+++ b/core/Tracker/Visitor.php
@@ -173,54 +173,58 @@ class Visitor
      */
     private function getVisitFieldsPersist()
     {
-        $fields = array(
-            'idvisitor',
-            'idvisit',
-            'user_id',
-
-            'visit_exit_idaction_url',
-            'visit_exit_idaction_name',
-            'visitor_returning',
-            'visitor_days_since_first',
-            'visitor_days_since_order',
-            'visitor_count_visits',
-            'visit_goal_buyer',
-
-            'location_country',
-            'location_region',
-            'location_city',
-            'location_latitude',
-            'location_longitude',
-
-            'referer_name',
-            'referer_keyword',
-            'referer_type',
-        );
-
-        $dimensions = VisitDimension::getAllDimensions();
-
-        foreach ($dimensions as $dimension) {
-            if ($dimension->hasImplementedEvent('onExistingVisit')) {
-                $fields[] = $dimension->getColumnName();
-            }
+        static $fields;
+
+        if (is_null($fields)) {
+            $fields = array(
+                'idvisitor',
+                'idvisit',
+                'user_id',
+
+                'visit_exit_idaction_url',
+                'visit_exit_idaction_name',
+                'visitor_returning',
+                'visitor_days_since_first',
+                'visitor_days_since_order',
+                'visitor_count_visits',
+                'visit_goal_buyer',
+
+                'location_country',
+                'location_region',
+                'location_city',
+                'location_latitude',
+                'location_longitude',
+
+                'referer_name',
+                'referer_keyword',
+                'referer_type',
+            );
+
+            $dimensions = VisitDimension::getAllDimensions();
+
+            foreach ($dimensions as $dimension) {
+                if ($dimension->hasImplementedEvent('onExistingVisit')) {
+                    $fields[] = $dimension->getColumnName();
+                }
 
-            foreach ($dimension->getRequiredVisitFields() as $field) {
-                $fields[] = $field;
+                foreach ($dimension->getRequiredVisitFields() as $field) {
+                    $fields[] = $field;
+                }
             }
-        }
 
-        /**
-         * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading
-         * the existing visit. Properties that appear in this list will be available in other tracking
-         * events such as 'onExistingVisit'.
-         *
-         * Plugins can use this event to load additional visit entity properties for later use during tracking.
-         */
-        Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields));
-
-        array_unshift($fields, 'visit_first_action_time');
-        array_unshift($fields, 'visit_last_action_time');
-        $fields = array_unique($fields);
+            /**
+             * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading
+             * the existing visit. Properties that appear in this list will be available in other tracking
+             * events such as 'onExistingVisit'.
+             *
+             * Plugins can use this event to load additional visit entity properties for later use during tracking.
+             */
+            Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields));
+
+            array_unshift($fields, 'visit_first_action_time');
+            array_unshift($fields, 'visit_last_action_time');
+            $fields = array_unique($fields);
+        }
 
         return $fields;
     }
diff --git a/core/Updates/2.13.0-b3.php b/core/Updates/2.13.0-b3.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d6cd83cea705727367cbf2fff2cbc20b0c84305
--- /dev/null
+++ b/core/Updates/2.13.0-b3.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Updates;
+
+use Piwik\Config;
+use Piwik\Updater;
+use Piwik\Updates;
+
+class Updates_2_13_0_b3 extends Updates
+{
+    public function doUpdate(Updater $updater)
+    {
+        $pluginManager = \Piwik\Plugin\Manager::getInstance();
+
+        try {
+            $pluginManager->activatePlugin('Diagnostics');
+        } catch(\Exception $e) {
+        }
+    }
+}
diff --git a/core/UrlHelper.php b/core/UrlHelper.php
index cf2eb468c8bf828858d7371c70a0704764d90393..20501515f2c1a2e467f115f6fe8f61ac5bc762ef 100644
--- a/core/UrlHelper.php
+++ b/core/UrlHelper.php
@@ -155,6 +155,14 @@ class UrlHelper
         if (strlen($urlQuery) == 0) {
             return array();
         }
+
+        $cache    = Cache::getTransientCache();
+        $cacheKey = 'arrayFromQuery' . $urlQuery;
+
+        if ($cache->contains($cacheKey)) {
+            return $cache->fetch($cacheKey);
+        }
+
         if ($urlQuery[0] == '?') {
             $urlQuery = substr($urlQuery, 1);
         }
@@ -200,6 +208,9 @@ class UrlHelper
                 $nameToValue[$name] = $value;
             }
         }
+
+        $cache->save($cacheKey, $nameToValue);
+
         return $nameToValue;
     }
 
@@ -214,6 +225,7 @@ class UrlHelper
     public static function getParameterFromQueryString($urlQuery, $parameter)
     {
         $nameToValue = self::getArrayFromQueryString($urlQuery);
+
         if (isset($nameToValue[$parameter])) {
             return $nameToValue[$parameter];
         }
diff --git a/core/Version.php b/core/Version.php
index 67c8215c61958d38e87f65ac547e4d0d344289e6..1b63ba15cf88f9e72fc8d983a1e0959f598dba61 100644
--- a/core/Version.php
+++ b/core/Version.php
@@ -20,7 +20,7 @@ final class Version
      * The current Piwik version.
      * @var string
      */
-    const VERSION = '2.13.0-b1';
+    const VERSION = '2.13.0-b3';
 
     public function isStableVersion($version)
     {
diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php
index eec5a50d1b997a0bb9f33bf9b5fcec71c9f7f4c5..ee0671247b72651d64690a6ea42d8d3cddc9bac1 100644
--- a/core/testMinimumPhpVersion.php
+++ b/core/testMinimumPhpVersion.php
@@ -76,6 +76,12 @@ if (!function_exists('Piwik_GetErrorMessagePage')) {
     {
         $bool = (defined('PIWIK_PRINT_ERROR_BACKTRACE') && PIWIK_PRINT_ERROR_BACKTRACE)
                 || !empty($GLOBALS['PIWIK_TRACKER_DEBUG']);
+
+        try {
+            $bool = $bool || \Piwik\Development::isEnabled();
+        } catch (Exception $e) {
+        }
+
         return $bool;
     }
 
@@ -113,7 +119,7 @@ if (!function_exists('Piwik_GetErrorMessagePage')) {
         }
 
         if ($optionalTrace) {
-            $optionalTrace = '<span class="exception-backtrace">Backtrace:<br /><pre>' . $optionalTrace . '</pre></span>';
+            $optionalTrace = '<h2>Stack trace</h2><pre>' . $optionalTrace . '</pre></span>';
         }
 
         if ($isCli === null) {
@@ -130,7 +136,7 @@ if (!function_exists('Piwik_GetErrorMessagePage')) {
                             </ul>';
         }
         if ($optionalLinkBack) {
-            $optionalLinkBack = '<a href="javascript:window.history.back();">Go Back</a><br/>';
+            $optionalLinkBack = '<a href="javascript:window.history.back();">Go Back</a>';
         }
 
         $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutHeader.tpl');
@@ -141,10 +147,10 @@ if (!function_exists('Piwik_GetErrorMessagePage')) {
 
         $headerPage = str_replace('{$HTML_TITLE}', PAGE_TITLE_WHEN_ERROR, $headerPage);
 
-        $content = '<p>' . $message . '</p>
+        $content = '<h2>' . $message . '</h2>
                     <p>'
             . $optionalLinkBack
-            . '<a href="index.php">Go to Piwik</a><br/>
+            . ' | <a href="index.php">Go to Piwik</a> |
                        <a href="index.php?module=Login">Login</a>'
             . '</p>'
             . ' ' . (Piwik_ShouldPrintBackTraceWithMessage() ? $optionalTrace : '')
diff --git a/lang/en.json b/lang/en.json
index 0c23f98ef5e6ce45c3094a6466db78cf6547f218..d8b02f220466c2ae5170d50d2270c91372e8fdf8 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -306,6 +306,8 @@
         "OverlayRowActionTooltipTitle": "Open Page Overlay",
         "Overview": "Overview",
         "Pages": "Pages",
+        "Pagination": "%s - %s of %s",
+        "PaginationWithoutTotal": "%s - %s",
         "ParameterMustIntegerBetween": "Parameter %s must be an integer value between %s and %s.",
         "Password": "Password",
         "Period": "Period",
diff --git a/misc/log-analytics b/misc/log-analytics
index 770f7758b3285004d88ea0ff9634f6d410199c33..ef6fbd1bd55d1f924e57e26c82be4240573eaa71 160000
--- a/misc/log-analytics
+++ b/misc/log-analytics
@@ -1 +1 @@
-Subproject commit 770f7758b3285004d88ea0ff9634f6d410199c33
+Subproject commit ef6fbd1bd55d1f924e57e26c82be4240573eaa71
diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php
index d11f953704f79b25e0652ee1bdeba08476b056c1..17dbe3fd124bbf66013ea64deedc127f01d19cd1 100644
--- a/plugins/API/Controller.php
+++ b/plugins/API/Controller.php
@@ -14,6 +14,7 @@ use Piwik\API\Request;
 use Piwik\Common;
 use Piwik\Config;
 use Piwik\Piwik;
+use Piwik\Plugin\Report;
 use Piwik\Url;
 use Piwik\View;
 
@@ -24,12 +25,15 @@ class Controller extends \Piwik\Plugin\Controller
 {
     function index()
     {
+        $token = 'token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string');
+
         // when calling the API through http, we limit the number of returned results
         if (!isset($_GET['filter_limit'])) {
             $_GET['filter_limit'] = Config::getInstance()->General['API_datatable_default_limit'];
+            $token .= '&api_datatable_default_limit=' . $_GET['filter_limit'];
         }
 
-        $request = new Request('token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string'));
+        $request  = new Request($token);
         $response = $request->process();
 
         if (is_array($response)) {
diff --git a/plugins/Actions/templates/indexSiteSearch.twig b/plugins/Actions/templates/indexSiteSearch.twig
index 7d6093c6ace93d2adf2c9a4420e046b4f104d5aa..8d9eaa0909f01ccc224012e404d488a272f1ac05 100644
--- a/plugins/Actions/templates/indexSiteSearch.twig
+++ b/plugins/Actions/templates/indexSiteSearch.twig
@@ -1,17 +1,21 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchKeywords'|translate }}</h2>
-    {{ keywords|raw }}
+<div class="row">
 
-    <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchNoResultKeywords'|translate }}</h2>
-    {{ noResultKeywords|raw }}
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchKeywords'|translate }}</h2>
+        {{ keywords|raw }}
 
-    {% if categories is defined %}
-        <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchCategories'|translate }}</h2>
-        {{ categories|raw }}
-    {% endif %}
-</div>
+        <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchNoResultKeywords'|translate }}</h2>
+        {{ noResultKeywords|raw }}
+
+        {% if categories is defined %}
+            <h2 piwik-enriched-headline>{{ 'Actions_WidgetSearchCategories'|translate }}</h2>
+            {{ categories|raw }}
+        {% endif %}
+    </div>
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Actions_WidgetPageUrlsFollowingSearch'|translate }}</h2>
+        {{ pagesUrlsFollowingSiteSearch|raw }}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Actions_WidgetPageUrlsFollowingSearch'|translate }}</h2>
-    {{ pagesUrlsFollowingSiteSearch|raw }}
 </div>
diff --git a/plugins/CoreAdminHome/API.php b/plugins/CoreAdminHome/API.php
index 6329fc8993640b2102e0ac8bdc0fd351427c9510..2b809b792a20bfbf963ed0ceba3b0a68e3c409e4 100644
--- a/plugins/CoreAdminHome/API.php
+++ b/plugins/CoreAdminHome/API.php
@@ -15,12 +15,23 @@ use Piwik\Db;
 use Piwik\Piwik;
 use Piwik\Scheduler\Scheduler;
 use Piwik\Site;
+use Psr\Log\LoggerInterface;
 
 /**
  * @method static \Piwik\Plugins\CoreAdminHome\API getInstance()
  */
 class API extends \Piwik\Plugin\API
 {
+    /**
+     * @var Scheduler
+     */
+    private $scheduler;
+
+    public function __construct(Scheduler $scheduler)
+    {
+        $this->scheduler = $scheduler;
+    }
+
     /**
      * Will run all scheduled tasks due to run at this time.
      *
@@ -31,10 +42,7 @@ class API extends \Piwik\Plugin\API
     {
         Piwik::checkUserHasSuperUserAccess();
 
-        /** @var Scheduler $scheduler */
-        $scheduler = StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler');
-
-        return $scheduler->run();
+        return $this->scheduler->run();
     }
 
     /**
diff --git a/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php b/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php
index bad6710d9fca959dd07a7cca22869b53706cad14..de1d58745bc4142c9f52b436f65f62fe48a4d5a1 100644
--- a/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php
+++ b/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php
@@ -15,6 +15,7 @@ use Piwik\Date;
 use Piwik\Db;
 use Piwik\Plugin\ConsoleCommand;
 use Piwik\Timer;
+use Psr\Log\NullLogger;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
@@ -28,11 +29,6 @@ class PurgeOldArchiveData extends ConsoleCommand
 {
     const ALL_DATES_STRING = 'all';
 
-    /**
-     * @var ArchivePurger
-     */
-    private $archivePurger;
-
     /**
      * For tests.
      *
@@ -40,11 +36,16 @@ class PurgeOldArchiveData extends ConsoleCommand
      */
     public static $todayOverride = null;
 
+    /**
+     * @var ArchivePurger
+     */
+    private $archivePurger;
+
     public function __construct(ArchivePurger $archivePurger = null)
     {
         parent::__construct();
-
-        $this->archivePurger = $archivePurger ?: new ArchivePurger();
+        
+        $this->archivePurger = $archivePurger;
     }
 
     protected function configure()
@@ -68,7 +69,14 @@ class PurgeOldArchiveData extends ConsoleCommand
 
     protected function execute(InputInterface $input, OutputInterface $output)
     {
-        $archivePurger = $this->archivePurger;
+        // during normal command execution, we don't want the INFO level logs logged by the ArchivePurger service
+        // to display in the console, so we use a NullLogger for the service
+        $logger = null;
+        if ($output->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) {
+            $logger = new NullLogger();
+        }
+
+        $archivePurger = $this->archivePurger ?: new ArchivePurger($model = null, $purgeDatesOlderThan = null, $logger);
 
         $dates = $this->getDatesToPurgeFor($input);
 
diff --git a/plugins/CoreAdminHome/Tasks.php b/plugins/CoreAdminHome/Tasks.php
index 700c7d69c279141ae92c0b79d5aa9f541dec70fe..7a3ff406b52488c6f60681a25e5b2537946ca4a4 100644
--- a/plugins/CoreAdminHome/Tasks.php
+++ b/plugins/CoreAdminHome/Tasks.php
@@ -14,9 +14,8 @@ use Piwik\Container\StaticContainer;
 use Piwik\DataAccess\ArchiveTableCreator;
 use Piwik\Date;
 use Piwik\Db;
-use Piwik\Log;
 use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList;
-use Piwik\SettingsServer;
+use Psr\Log\LoggerInterface;
 
 class Tasks extends \Piwik\Plugin\Tasks
 {
@@ -25,9 +24,15 @@ class Tasks extends \Piwik\Plugin\Tasks
      */
     private $archivePurger;
 
-    public function __construct(ArchivePurger $archivePurger = null)
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    public function __construct(ArchivePurger $archivePurger, LoggerInterface $logger)
     {
-        $this->archivePurger = $archivePurger ?: new ArchivePurger();
+        $this->archivePurger = $archivePurger;
+        $this->logger = $logger;
     }
 
     public function schedule()
@@ -44,16 +49,14 @@ class Tasks extends \Piwik\Plugin\Tasks
 
     public function purgeOutdatedArchives()
     {
-        $logger = StaticContainer::get('Psr\Log\LoggerInterface');
-
         if ($this->willPurgingCausePotentialProblemInUI()) {
-            $logger->info("Purging temporary archives: skipped (browser triggered archiving not enabled & not running after core:archive)");
+            $this->logger->info("Purging temporary archives: skipped (browser triggered archiving not enabled & not running after core:archive)");
             return false;
         }
 
         $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
 
-        $logger->info("Purging archives in {tableCount} archive tables.", array('tableCount' => count($archiveTables)));
+        $this->logger->info("Purging archives in {tableCount} archive tables.", array('tableCount' => count($archiveTables)));
 
         // keep track of dates we purge for, since getTablesArchivesInstalled() will return numeric & blob
         // tables (so dates will appear two times, and we should only purge once per date)
@@ -64,15 +67,19 @@ class Tasks extends \Piwik\Plugin\Tasks
             list($year, $month) = explode('_', $date);
 
             // Somehow we may have archive tables created with older dates, prevent exception from being thrown
-            if ($year > 1990
-                && empty($datesPurged[$date])
-            ) {
-                $dateObj = Date::factory("$year-$month-15");
-
-                $this->archivePurger->purgeOutdatedArchives($dateObj);
-                $this->archivePurger->purgeArchivesWithPeriodRange($dateObj);
-
-                $datesPurged[$date] = true;
+            if ($year > 1990) {
+                if (empty($datesPurged[$date])) {
+                    $dateObj = Date::factory("$year-$month-15");
+
+                    $this->archivePurger->purgeOutdatedArchives($dateObj);
+                    $this->archivePurger->purgeArchivesWithPeriodRange($dateObj);
+
+                    $datesPurged[$date] = true;
+                } else {
+                    $this->logger->debug("Date {date} already purged.", array('date' => $date));
+                }
+            } else {
+                $this->logger->info("Skipping purging of archive tables *_{year}_{month}, year <= 1990.", array('year' => $year, 'month' => $month));
             }
         }
     }
diff --git a/plugins/CoreAdminHome/stylesheets/pluginSettings.less b/plugins/CoreAdminHome/stylesheets/pluginSettings.less
index 0d8c8469cfd46787da45076d5efec0b233c9f7a3..b7e90637e952bacc97d774dd54313a9bc1b73a05 100644
--- a/plugins/CoreAdminHome/stylesheets/pluginSettings.less
+++ b/plugins/CoreAdminHome/stylesheets/pluginSettings.less
@@ -1,6 +1,6 @@
 #pluginSettings {
   width: 975px;
-  border-spacing: 0px 15px;
+  margin: 15px 0;
 
   .columnTitle {
     width:400px
@@ -21,7 +21,7 @@
   }
 
   .settingIntroduction {
-    padding-bottom: 0px;
+    padding-bottom: 0;
   }
 
   .form-description {
@@ -39,6 +39,6 @@
   .submitSeparator {
     background-color: #DADADA;
     height: 1px;
-    border: 0px;
+    border: 0;
   }
 }
\ No newline at end of file
diff --git a/plugins/CoreAdminHome/tests/Integration/TasksTest.php b/plugins/CoreAdminHome/tests/Integration/TasksTest.php
index da3800e9c199042488b3979f123e8c75f199eea2..ff4822fbce1b96ebd6878ff2eaf99f2f70df7fdd 100644
--- a/plugins/CoreAdminHome/tests/Integration/TasksTest.php
+++ b/plugins/CoreAdminHome/tests/Integration/TasksTest.php
@@ -13,6 +13,7 @@ use Piwik\Plugins\CoreAdminHome\Tasks;
 use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList;
 use Piwik\Tests\Fixtures\RawArchiveDataWithTempAndInvalidated;
 use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Psr\Log\NullLogger;
 
 /**
  * @group Core
@@ -51,7 +52,7 @@ class TasksTest extends IntegrationTestCase
         $archivePurger->setYesterdayDate(Date::factory('2015-02-26'));
         $archivePurger->setNow(Date::factory('2015-02-27 08:00:00')->getTimestamp());
 
-        $this->tasks = new Tasks($archivePurger);
+        $this->tasks = new Tasks($archivePurger, new NullLogger());
     }
 
     public function test_purgeInvalidatedArchives_PurgesCorrectInvalidatedArchives_AndOnlyPurgesDataForDatesAndSites_InInvalidatedReportsDistributedList()
diff --git a/plugins/CoreConsole/Commands/CoreArchiver.php b/plugins/CoreConsole/Commands/CoreArchiver.php
index ec151bf0ef405f39a7f9f8c605392d9a7dce5c2d..b44245988abe7801fa8d4151100c213ca62f4039 100644
--- a/plugins/CoreConsole/Commands/CoreArchiver.php
+++ b/plugins/CoreConsole/Commands/CoreArchiver.php
@@ -87,7 +87,7 @@ class CoreArchiver extends ConsoleCommand
 * If you have any suggestion about this script, please let the team know at feedback@piwik.org
 * Enjoy!");
         $command->addOption('url', null, InputOption::VALUE_REQUIRED, "Mandatory option as an alternative to '--piwik-domain'. Must be set to the Piwik base URL.\nFor example: --url=http://analytics.example.org/ or --url=https://example.org/piwik/");
-        $command->addOption('force-all-websites', null, InputOption::VALUE_NONE, "If specified, the script will trigger archiving on all websites.\nUse with --force-all-periods=[seconds] to also process those websites\nthat had visits in the last [seconds] seconds.");
+        $command->addOption('force-all-websites', null, InputOption::VALUE_NONE, "If specified, the script will trigger archiving on all websites.\nUse with --force-all-periods=[seconds] to also process those websites that had visits in the last [seconds] seconds.\n##Launching several processes with this option will make them share the list of sites to process.");
         $command->addOption('force-all-periods', null, InputOption::VALUE_OPTIONAL, "Limits archiving to websites with some traffic in the last [seconds] seconds. \nFor example --force-all-periods=86400 will archive websites that had visits in the last 24 hours. \nIf [seconds] is not specified, all websites with visits in the last " . CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE . "\n seconds (" . round(CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE / 86400) . " days) will be archived.");
         $command->addOption('force-timeout-for-periods', null, InputOption::VALUE_OPTIONAL, "The current week/ current month/ current year will be processed at most every [seconds].\nIf not specified, defaults to " . CronArchive::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES . ".");
         $command->addOption('skip-idsites', null, InputOption::VALUE_OPTIONAL, 'If specified, archiving will be skipped for these websites (in case these website ids would have been archived).');
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js
index 7fe4d524e1c02531b64e125cc4f68ef7da363c7b..f74624459f72fbbcec1ee5d75c5a3146ec97aa84 100644
--- a/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js
@@ -35,6 +35,13 @@
 
             angular.forEach(sites, function (site) {
                 if (site.group) site.name = '[' + site.group + '] ' + site.name;
+                if (!site.name) {
+                    return;
+                }
+                // Escape site names, see https://github.com/piwik/piwik/issues/7531
+                site.name = site.name.replace(/[\u0000-\u2666]/g, function(c) {
+                    return '&#'+c.charCodeAt(0)+';';
+                });
             });
 
             model.sites = $filter('orderBy')(sites, '+name');
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js
index 48befb08e6caaf6115721323613f0b1f15fedc2f..79e2439dad1e275b9241ec598a5661c7b9a37156 100644
--- a/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js
@@ -16,7 +16,6 @@
         $scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
         $scope.selectedSite = {id: '', name: ''};
         $scope.activeSiteId = piwik.idSite;
-        $scope.selectedSiteNameHtml = '';
 
         $scope.switchSite = function (site) {
             $scope.selectedSite = {id: site.idsite, name: site.name};
@@ -40,15 +39,6 @@
             '#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
         };
 
-        $scope.$watch('selectedSite', function (site) {
-            if (!site.name) {
-                return;
-            }
-            $scope.selectedSiteNameHtml = site.name.replace(/[\u0000-\u2666]/g, function(c) {
-                return '&#'+c.charCodeAt(0)+';';
-            });
-        });
-
         siteSelectorModel.loadInitialSites();
     }
 })();
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html
index dc53f3986d955f1ad66e2918bdad12c423129376..5f6a3cac8ad029ca3c96fbed82095c33e6e2cc74 100644
--- a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html
@@ -16,7 +16,7 @@
        href="javascript:void(0)"
        class="custom_select_main_link"
        ng-class="{'loading': model.isLoading}">
-        <span ng-bind-html="selectedSiteNameHtml || model.firstSiteName">?</span>
+        <span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
     </a>
 
     <div ng-show="view.showSitesList" class="custom_select_block">
@@ -29,7 +29,8 @@
                     ng-repeat="site in model.sites"
                     ng-hide="!showSelectedSite && activeSiteId==site.idsite">
                     <a piwik-ignore-click href="{{ getUrlForSiteId(site.idsite) }}"
-                       piwik-autocomplete-matched="view.searchTerm">{{ site.name }}</a>
+                       piwik-autocomplete-matched="view.searchTerm"
+                       ng-bind-html="site.name"></a>
                 </li>
             </ul>
             <ul ng-show="!model.sites.length && view.searchTerm" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect">
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less
index 4f1bd44a2019b304cd2e39d49324554113efa217..195178a8bf21d2c5fc5a5b3b2b81a3b4615e5e73 100644
--- a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less
@@ -27,7 +27,7 @@ table.dataTable tr td .sites_autocomplete a {
 
 .top_bar_sites_selector > .sites_autocomplete {
   position: static;
-  padding-left: 12px;
+  margin-left: 12px;
 }
 
 .autocompleteMatched {
@@ -52,9 +52,7 @@ table.dataTable tr td .sites_autocomplete a {
   text-decoration: none;
   background: none;
   cursor: default;
-  height:1.4em;
-  padding-top: 9px;
-  padding-bottom: 9px;
+  padding: 9px 5px;
 }
 
 #content.admin .adminTable .sites_autocomplete a {
@@ -69,7 +67,7 @@ table.dataTable tr td .sites_autocomplete a {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-  padding: 0 20px 0 4px;
+  padding: 0 10px 0 4px;
 }
 
 .sites_autocomplete--dropdown .custom_select_main_link:not(.loading):before {
@@ -168,7 +166,7 @@ table.dataTable tr td .sites_autocomplete a {
   overflow: hidden;
   max-width: inherit;
   visibility: visible;
-  padding-bottom: 7px;
+  padding: 0 5px 7px 5px;
 }
 
 .custom_select_block_show {
diff --git a/plugins/CoreHome/stylesheets/_donate.less b/plugins/CoreHome/stylesheets/_donate.less
index 5e907d491a5817de852c9162090fc584dc90cff0..b5ae42f6b79a5b3810bbd5502e122d18416521c5 100755
--- a/plugins/CoreHome/stylesheets/_donate.less
+++ b/plugins/CoreHome/stylesheets/_donate.less
@@ -1,8 +1,8 @@
 .piwik-donate-call {
-    padding: 1em;
+    padding: 13px;
     border: 1px solid #CCC;
     border-radius: 4px;
-    max-width: 432px;
+    max-width: 458px;
     position: relative;
 }
 
@@ -50,11 +50,9 @@
 
 .piwik-donate-slider .slider-donate-amount {
     display: inline-block;
-
     padding: .3em .5em .3em .5em;
     margin: 16px 8px 0 0;
     vertical-align: top;
-    width: 48px;
     text-align: center;
     background-color: #CCC;
     color: #333;
diff --git a/plugins/CoreHome/stylesheets/coreHome.less b/plugins/CoreHome/stylesheets/coreHome.less
index 848b7429b4da80c9b17b14f5045575208486d9eb..3f45859af5e546de3b1f4a1cd6290bb7bbb48403 100644
--- a/plugins/CoreHome/stylesheets/coreHome.less
+++ b/plugins/CoreHome/stylesheets/coreHome.less
@@ -6,29 +6,24 @@ h1 {
     clear: both;
 }
 
-h2 {
-    font-size: 18px;
-    font-weight: normal;
-    color: #7e7363;
-    padding: 12px 0 7px 0;
-    clear: both;
-}
-
-h2 a {
-    text-decoration: none;
-    color: #7e7363;
-}
-
-h3 {
-    font-size: 1.3em;
-    margin-top: 2em;
-    color: #1D3256;
-}
-
-.home p {
-    padding-bottom: 1em;
-    margin-right: 1em;
-    margin-left: 1em;
+.home {
+    h2 {
+        font-size: 18px;
+        padding: 12px 0 7px 0;
+        clear: both;
+        border: none;
+        margin: 0;
+    }
+
+    h3 {
+        font-size: 1.3em;
+        margin-top: 2em;
+    }
+    p {
+        padding-bottom: 1em;
+        margin-right: 1em;
+        margin-left: 1em;
+    }
 }
 
 .nav_sep {
@@ -38,13 +33,8 @@ h3 {
 }
 
 .pageWrap {
-  border-left: 1px solid #DDDDDD;
-  border-right: 1px solid #DDDDDD;
-  min-height: 10px;
-  overflow: visible;
-  padding: 15px 15px 0;
-  position: relative;
-  font-size: 13px;
+    margin-top: 15px;
+    font-size: 13px;
 }
 
 /* Content */
diff --git a/plugins/CoreHome/stylesheets/dataTable/_dataTable.less b/plugins/CoreHome/stylesheets/dataTable/_dataTable.less
index b4b69508549d250bed855d87821d78f9db9c5f66..90efbd892bd4e5bb675229bb194dc27a00d81ef1 100644
--- a/plugins/CoreHome/stylesheets/dataTable/_dataTable.less
+++ b/plugins/CoreHome/stylesheets/dataTable/_dataTable.less
@@ -318,7 +318,7 @@ div.dataTable, div.dataTable > .dataTableWrapper {
     margin-right: auto;
     background-color: @theme-color-background-base;
     border: 1px solid @theme-color-background-tinyContrast;
-    height: 7px;
+    height: 9px;
     width: 70px;
     -webkit-border-bottom-left-radius: 10px;
     -webkit-border-bottom-right-radius: 10px;
@@ -369,7 +369,7 @@ div.dataTable, div.dataTable > .dataTableWrapper {
     border-top-left-radius: 10px;
     border-top-right-radius: 10px;
     line-height: 0px;
-    height: 7px;
+    height: 9px;
     width: 70px;
     clear: both;
 
@@ -444,7 +444,7 @@ div.dataTable, div.dataTable > .dataTableWrapper {
 }
 
 .dataTableFooterIcons {
-  min-height: 20px;
+  min-height: 26px;
   white-space: nowrap;
   font-size: 10px;
   padding: 6px 5px 0px;
diff --git a/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less b/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less
index 1e77d030d7fc8148e7ecfa261808ffd132648e32..ffdc9e598537f6307063e0f953c72ad95900edbb 100644
--- a/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less
+++ b/plugins/CoreHome/stylesheets/dataTable/_limitSelection.less
@@ -16,8 +16,8 @@
   background: url(plugins/Morpheus/images/sort_subtable_desc_light.png) no-repeat right 2px;
   padding: 0 14px 0 4px;
   display: block;
-  width: 24px;
-  height: 20px;
+  width: 41px;
+  height: 26px;
   cursor: pointer;
 }
 
diff --git a/plugins/CoreHome/stylesheets/menu.less b/plugins/CoreHome/stylesheets/menu.less
index 703470a95683a7ab0882b4cedbadd8ca716f9bf7..76c8b9023808fa5a7673f78f672d547e4b918121 100644
--- a/plugins/CoreHome/stylesheets/menu.less
+++ b/plugins/CoreHome/stylesheets/menu.less
@@ -68,7 +68,7 @@
     display: block;
     float: left;
     padding: 8px 27px 0;
-    height: 25px;
+    height: 50px;
     text-decoration: none;
     font-weight: normal;
 }
diff --git a/plugins/CoreHome/templates/_indexContent.twig b/plugins/CoreHome/templates/_indexContent.twig
index c0bc872bd7db78d60d65ecde27e4012617bbde7e..46d2f135796b3fdc96ead5940d3bd986e4108fd0 100644
--- a/plugins/CoreHome/templates/_indexContent.twig
+++ b/plugins/CoreHome/templates/_indexContent.twig
@@ -1,5 +1,5 @@
 {% import 'ajaxMacros.twig' as ajax %}
-<div class="pageWrap">
+<div class="pageWrap container-fluid">
 <a name="main"></a>
     {% include "@CoreHome/_notifications.twig" %}
     <div class="top_controls">
@@ -17,5 +17,3 @@
     </div>
     <div class="clear"></div>
 </div>
-
-<br/><br/>
diff --git a/plugins/CorePluginsAdmin/stylesheets/marketplace.less b/plugins/CorePluginsAdmin/stylesheets/marketplace.less
index ade343711498801ceab9f626152239a0cf17aebe..dba8ce15233e844c0b1ffdf04cc5f7f8b7e4ba27 100644
--- a/plugins/CorePluginsAdmin/stylesheets/marketplace.less
+++ b/plugins/CorePluginsAdmin/stylesheets/marketplace.less
@@ -80,12 +80,9 @@
   clear: right;
 
   .plugin {
-    width: 280px;
-    float: left;
     border: 1px solid #dadada;
     padding: 15px;
     background-color: #f2f2f2;
-    margin-right: 14px;
     margin-bottom: 15px;
     position: relative;
 
@@ -117,7 +114,7 @@
 
       &.even {
         padding-right: 0px;
-        width: 140px;
+        width: 130px;
         overflow: hidden;
         white-space: nowrap;
       }
@@ -212,7 +209,6 @@
       height: 250px;
     }
     .footer {
-      height: 55px;
       position: absolute;
       bottom: 7px;
       left: 0px;
diff --git a/plugins/CorePluginsAdmin/templates/browsePlugins.twig b/plugins/CorePluginsAdmin/templates/browsePlugins.twig
index 068d9e80abee4e58dabe24e580f01542c1897a35..6dad365703d553e33dc01dea1bc238ee90ae537a 100644
--- a/plugins/CorePluginsAdmin/templates/browsePlugins.twig
+++ b/plugins/CorePluginsAdmin/templates/browsePlugins.twig
@@ -25,22 +25,24 @@
         </div>
     {% endif %}
 
-    <div class="pluginslist">
+    <div class="row pluginslist">
 
         {% if plugins|length %}
 
             {% for plugin in plugins %}
 
-                <div class="plugin">
-                    <div class="content" data-pluginName="{{ plugin.name }}">
-                        {% include "@CorePluginsAdmin/pluginOverview.twig" %}
-                    </div>
-
-                    <div class="footer" data-pluginName="{{ plugin.name }}">
-                        {% if plugin.featured %}
-                            {{ pluginsMacro.featuredIcon('right') }}
-                        {% endif %}
-                        {% include "@CorePluginsAdmin/pluginMetadata.twig" %}
+                <div class="col-md-4">
+                    <div class="plugin">
+                        <div class="content" data-pluginName="{{ plugin.name }}">
+                            {% include "@CorePluginsAdmin/pluginOverview.twig" %}
+                        </div>
+
+                        <div class="footer" data-pluginName="{{ plugin.name }}">
+                            {% if plugin.featured %}
+                                {{ pluginsMacro.featuredIcon('right') }}
+                            {% endif %}
+                            {% include "@CorePluginsAdmin/pluginMetadata.twig" %}
+                        </div>
                     </div>
                 </div>
 
diff --git a/plugins/CorePluginsAdmin/templates/browseThemes.twig b/plugins/CorePluginsAdmin/templates/browseThemes.twig
index 685ef4ebb3c3f282e373e643f1ef4e58a31c9de0..1c8066b3f7e906e5b315632a44b3404b524b0d6f 100644
--- a/plugins/CorePluginsAdmin/templates/browseThemes.twig
+++ b/plugins/CorePluginsAdmin/templates/browseThemes.twig
@@ -24,18 +24,20 @@
         </div>
     {% endif %}
 
-    <div class="pluginslist themes">
+    <div class="row pluginslist themes">
 
         {% if plugins|length %}
             {% for plugin in plugins %}
 
-                <div class="plugin">
-                    <div class="content" data-pluginName="{{ plugin.name }}">
-                        {% include "@CorePluginsAdmin/themeOverview.twig" %}
-                    </div>
+                <div class="col-md-4">
+                    <div class="plugin">
+                        <div class="content" data-pluginName="{{ plugin.name }}">
+                            {% include "@CorePluginsAdmin/themeOverview.twig" %}
+                        </div>
 
-                    <div class="footer" data-pluginName="{{ plugin.name }}">
-                        {% include "@CorePluginsAdmin/pluginMetadata.twig" %}
+                        <div class="footer" data-pluginName="{{ plugin.name }}">
+                            {% include "@CorePluginsAdmin/pluginMetadata.twig" %}
+                        </div>
                     </div>
                 </div>
 
diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php
index d15dc231e6178337233078d85a97d3f367e82b4b..47a06f226d77cd9cd032d194f88c9d0521808e4f 100644
--- a/plugins/CoreUpdater/Controller.php
+++ b/plugins/CoreUpdater/Controller.php
@@ -42,6 +42,8 @@ class Controller extends \Piwik\Plugin\Controller
     public function __construct(Updater $updater)
     {
         $this->updater = $updater;
+
+        parent::__construct();
     }
 
     public function newVersionAvailable()
@@ -53,6 +55,7 @@ class Controller extends \Piwik\Plugin\Controller
 
         $view = new View('@CoreUpdater/newVersionAvailable');
         $this->addCustomLogoInfo($view);
+        $this->setBasicVariablesView($view);
 
         $view->piwik_version = Version::VERSION;
         $view->piwik_new_version = $newVersion;
@@ -102,11 +105,22 @@ class Controller extends \Piwik\Plugin\Controller
 
     public function oneClickResults()
     {
-        $view = new View('@CoreUpdater/oneClickResults');
-        $view->error = Common::getRequestVar('error', '', 'string', $_POST);
-        $view->feedbackMessages = safe_unserialize(Common::unsanitizeInputValue(Common::getRequestVar('messages', '', 'string', $_POST)));
-        $view->httpsFail = (bool) Common::getRequestVar('httpsFail', 0, 'int', $_POST);
+        $httpsFail = (bool) Common::getRequestVar('httpsFail', 0, 'int', $_POST);
+        $error = Common::getRequestVar('error', '', 'string', $_POST);
+
+        if ($httpsFail) {
+            $view = new View('@CoreUpdater/updateHttpsError');
+            $view->error = $error;
+        } elseif ($error) {
+            $view = new View('@CoreUpdater/updateHttpError');
+            $view->error = $error;
+            $view->feedbackMessages = safe_unserialize(Common::unsanitizeInputValue(Common::getRequestVar('messages', '', 'string', $_POST)));
+        } else {
+            $view = new View('@CoreUpdater/updateSuccess');
+        }
+
         $this->addCustomLogoInfo($view);
+        $this->setBasicVariablesView($view);
         return $view->render();
     }
 
@@ -158,9 +172,11 @@ class Controller extends \Piwik\Plugin\Controller
 
         $viewWelcome = new View($welcomeTemplate);
         $this->addCustomLogoInfo($viewWelcome);
+        $this->setBasicVariablesView($viewWelcome);
 
         $viewDone = new View($doneTemplate);
         $this->addCustomLogoInfo($viewDone);
+        $this->setBasicVariablesView($viewDone);
 
         $doExecuteUpdates = Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1;
 
@@ -191,7 +207,7 @@ class Controller extends \Piwik\Plugin\Controller
     private function doWelcomeUpdates($view, $componentsWithUpdateFile)
     {
         $view->new_piwik_version = Version::VERSION;
-        $view->commandUpgradePiwik = "<br /><code>php " . Filesystem::getPathToPiwikRoot() . "/console core:update </code>";
+        $view->commandUpgradePiwik = "php " . Filesystem::getPathToPiwikRoot() . "/console core:update";
         $pluginNamesToUpdate = array();
         $dimensionsToUpdate = array();
         $coreToUpdate = false;
diff --git a/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php b/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..f07e00449930a23428eb53c35cd82971640bd461
--- /dev/null
+++ b/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Piwik\Plugins\CoreUpdater\Diagnostic;
+
+use Piwik\Config;
+use Piwik\Plugins\CoreUpdater;
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Translation\Translator;
+
+/**
+ * Check the HTTPS update.
+ */
+class HttpsUpdateCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckUpdateHttps');
+
+        if (CoreUpdater\Controller::isUpdatingOverHttps()) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        $comment = $this->translator->translate('Installation_SystemCheckUpdateHttpsNotSupported');
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/CoreUpdater/config/config.php b/plugins/CoreUpdater/config/config.php
index 419e43cc7befa12bfe265e17767609b75923b778..b9c87c9f63bcdcd72837315c3feddf7209fb7260 100644
--- a/plugins/CoreUpdater/config/config.php
+++ b/plugins/CoreUpdater/config/config.php
@@ -2,5 +2,9 @@
 
 return array(
     'Piwik\Plugins\CoreUpdater\Updater' => DI\object()
-        ->constructorParameter('tmpPath', DI\link('path.tmp')),
+        ->constructorParameter('tmpPath', DI\get('path.tmp')),
+
+    'diagnostics.optional' => DI\add(array(
+        DI\get('Piwik\Plugins\CoreUpdater\Diagnostic\HttpsUpdateCheck'),
+    )),
 );
diff --git a/plugins/CoreUpdater/javascripts/updateLayout.js b/plugins/CoreUpdater/javascripts/updateLayout.js
index a3325675126d4ce939752e61a80c77bd7ff949c7..bffd8181f7dc55b0ced5c35f146af60849b5101f 100644
--- a/plugins/CoreUpdater/javascripts/updateLayout.js
+++ b/plugins/CoreUpdater/javascripts/updateLayout.js
@@ -1,5 +1,6 @@
 $(document).ready(function () {
-    $('#showSql').click(function () {
+    $('#showSql').click(function (e) {
+        e.preventDefault();
         $('#sqlQueries').toggle();
     });
     $('#upgradeCorePluginsForm').submit(function () {
diff --git a/plugins/CoreUpdater/lang/en.json b/plugins/CoreUpdater/lang/en.json
index 09651ab0c88d48145e026361d61ae898de2d6437..8429c9b597651151253350d6dd502ee9fc1bc024 100644
--- a/plugins/CoreUpdater/lang/en.json
+++ b/plugins/CoreUpdater/lang/en.json
@@ -47,6 +47,13 @@
         "UpdateAutomatically": "Update Automatically",
         "UpdateHasBeenCancelledExplanation": "Piwik One Click Update has been cancelled. If you can't fix the above error message, it is recommended that you manually update Piwik. %1$s Please check out the %2$sUpdate documentation%3$s to get started!",
         "UpdateTitle": "Update",
+        "UpdateSuccessTitle": "Piwik has been successfully updated!",
+        "UpdateErrorTitle": "Update error",
+        "ThankYouUpdatePiwik": "Thank you for using Piwik and keeping it up to date!",
+        "PostUpdateMessage": "Piwik will always be free to download and use, but it needs your continued support to grow and improve.",
+        "PostUpdateSupport": "If you need help using Piwik, you can get support from its creators:",
+        "EnterpriseSolutions": "Enterprise solutions",
+        "CloudHosting": "Cloud hosting",
         "Updating": "Updating",
         "UpdateUsingHttpsFailed": "Downloading the latest Piwik version over secure HTTPS connection did not succeed, because of the following error:",
         "UpdateUsingHttpsFailedHelp": "Why did it fail? Downloading the latest Piwik version (over secure HTTPS connection) can fail for various reasons, for example because of a network error, slow network speed or wrong system configuration. Note that it could also mean that your server is the target of a MITM attack and someone is trying to replace the update with a malicious version of Piwik.",
diff --git a/plugins/CoreUpdater/stylesheets/updateLayout.css b/plugins/CoreUpdater/stylesheets/updateLayout.css
index 1975a4b4b47af1d60eeb5794546b668402b93d92..fe334626b1ffd21e98b831243f195fea3616e796 100644
--- a/plugins/CoreUpdater/stylesheets/updateLayout.css
+++ b/plugins/CoreUpdater/stylesheets/updateLayout.css
@@ -9,19 +9,27 @@
     line-height: 1.33;
 }
 
-#donate-form-container {
-    margin: 0 0 2em 2em;
+.piwik-donate-call {
+    border: none;
+    padding: 0;
+    max-width: none;
+}
+.piwik-donate-call > .piwik-donate-message p {
+    margin-left: 0;
 }
 
 code {
-    background-color: #F0F7FF;
-    border: 1px dashed #00008B;
-    border-left: 5px solid;
+    font-size: 13px;
+    color: #F3F3F3;
+    background-color: #4D4D4D;
+    border: none;
+    border-radius: 3px;
     direction: ltr;
     display: block;
     margin: 2px 2px 20px;
-    padding: 4px;
+    padding: 20px;
     text-align: left;
+    overflow-x: hidden;
 }
 
 li {
diff --git a/plugins/CoreUpdater/templates/layout.twig b/plugins/CoreUpdater/templates/layout.twig
index 36b0dc1224a7aeea58f7368803d29ae4481d7ceb..3ea05aa2e01023d68c6996b805238ebe31cf23e5 100644
--- a/plugins/CoreUpdater/templates/layout.twig
+++ b/plugins/CoreUpdater/templates/layout.twig
@@ -42,13 +42,17 @@
 <body id="simple" ng-app="app" class="old-ie"> <![endif]-->
 <!--[if (gte IE 9)|!(IE)]><!-->
 <body id="simple" ng-app="app"><!--<![endif]-->
-<div id="contentsimple">
-    <div id="title">
-        <img title='Piwik' alt="Piwik" src="plugins/Morpheus/images/logo-header.png" style="margin-left:10px;"/>
-        <span id="subh1"> # {{ 'General_OpenSourceWebAnalytics'|translate }}</span>
-    </div>
+
+<div class="logo">
+    <img title="Piwik" alt="Piwik" src="{{ logoHeader }}"/>
+    <br/>
+    {{ 'General_OpenSourceWebAnalytics'|translate }}
+</div>
+
+<div class="box">
     {% block content %}
     {% endblock %}
 </div>
+
 </body>
 </html>
diff --git a/plugins/CoreUpdater/templates/newVersionAvailable.twig b/plugins/CoreUpdater/templates/newVersionAvailable.twig
index 8691d975952a6822186edcb146dac97b0dc1ba30..348b78923b59dd125ba6060f91e600737f7b5bb9 100644
--- a/plugins/CoreUpdater/templates/newVersionAvailable.twig
+++ b/plugins/CoreUpdater/templates/newVersionAvailable.twig
@@ -2,41 +2,49 @@
 {% import '@CorePluginsAdmin/macros.twig' as pluginsMacro %}
 
 {% block content %}
-<br/>
-<p><strong>{{ 'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate }}</strong></p>
-
-{% if can_auto_update %}
-    <p>{{ 'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate(piwik_new_version) }}</p>
-{% else %}
-    <p>{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</p>
-    <p>{{ 'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate(piwik_new_version) }}
-        {{ makeWritableCommands|raw }}
-    </p>
-{% endif %}
-
-{% if incompatiblePlugins %}
-    <p>{{ 'CoreUpdater_IncompatbilePluginsWillBeDisabledInfo'|translate(piwik_new_version) }}</p>
-
-    <ul style="list-style: disc;">
-    {% for plugin in incompatiblePlugins %}
-        <li>{{ pluginsMacro.missingRequirementsInfo(plugin.getPluginName, plugin.getInformation, plugin.getMissingDependencies(piwik_new_version), marketplacePlugins) }}</li>
-    {% endfor %}
-    </ul>
-    <p> </p>
-{% endif %}
-
-{% if can_auto_update %}
-<form id="oneclickupdate" action="index.php">
-    <input type="hidden" name="module" value="CoreUpdater"/>
-    <input type="hidden" name="action" value="oneClickUpdate"/>
-    <input id="updateAutomatically" type="submit" class="submit" value="{{ 'CoreUpdater_UpdateAutomatically'|translate }}"/>
-    {% endif %}
-    <a style="margin-left:50px;" class="submit button"
-       href="{{ piwik_latest_version_url }}?cb={{ piwik_new_version }}">{{ 'CoreUpdater_DownloadX'|translate(piwik_new_version) }}</a><br/>
-    {% if can_auto_update %}
-</form>
-{% endif %}
-<br/>
-<a href="index.php">&laquo; {{ 'General_BackToPiwik'|translate }}</a>
+
+    <div class="header">
+        <h1>{{ 'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate }}</h1>
+    </div>
+
+    <div class="content">
+
+        {% if can_auto_update %}
+            <p>{{ 'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate(piwik_new_version) }}</p>
+        {% else %}
+            <p>{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</p>
+            <p>{{ 'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate(piwik_new_version) }}
+                {{ makeWritableCommands|raw }}
+            </p>
+        {% endif %}
+
+        {% if can_auto_update %}
+            <form id="oneclickupdate" action="index.php">
+                <input type="hidden" name="module" value="CoreUpdater"/>
+                <input type="hidden" name="action" value="oneClickUpdate"/>
+                <input id="updateAutomatically" type="submit" class="btn" value="{{ 'CoreUpdater_UpdateAutomatically'|translate }}"/>
+                {% endif %}
+                <a class="btn"
+                   href="{{ piwik_latest_version_url }}?cb={{ piwik_new_version }}">{{ 'CoreUpdater_DownloadX'|translate(piwik_new_version) }}</a><br/>
+                {% if can_auto_update %}
+            </form>
+        {% endif %}
+
+        {% if incompatiblePlugins %}
+            <p>{{ 'CoreUpdater_IncompatbilePluginsWillBeDisabledInfo'|translate(piwik_new_version) }}</p>
+
+            <ul>
+                {% for plugin in incompatiblePlugins %}
+                    <li>{{ pluginsMacro.missingRequirementsInfo(plugin.getPluginName, plugin.getInformation, plugin.getMissingDependencies(piwik_new_version), marketplacePlugins) }}</li>
+                {% endfor %}
+            </ul>
+        {% endif %}
+
+    </div>
+
+    <div class="footer">
+        <a href="index.php">&laquo; {{ 'General_BackToPiwik'|translate }}</a>
+    </div>
+
 {% endblock %}
 
diff --git a/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig b/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig
index ceb7f44e53bb9a057e05f69f85ebc43983b62289..c97204bc6a66878ce671ae0c70a5667d695a90dc 100644
--- a/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig
+++ b/plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig
@@ -4,75 +4,82 @@
 {% endset %}
 
 {% block content %}
+
 {% if coreError %}
-    <br/>
-    <br/>
-    <div class="error">
-        <img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }}
-        {% for message in errorMessages %}
-            <pre>{{ message }}</pre>
-            <br/>
-        {% endfor %}
+    <div class="header">
+        <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1>
     </div>
-    <br/>
-    <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}
-    <ul>
-        <li>{{ helpMessage }}</li>
-    </ul>
-    </p>
-    <p>{{ 'CoreUpdater_ErrorDIYHelp'|translate }}
-    <ul>
-        <li>{{ 'CoreUpdater_ErrorDIYHelp_1'|translate }}</li>
-        <li>{{ 'CoreUpdater_ErrorDIYHelp_2'|translate }}</li>
-        <li>{{ 'CoreUpdater_ErrorDIYHelp_3'|translate }} <a href='https://piwik.org/faq/how-to-update/#faq_179' rel='noreferrer' target='_blank'>(see FAQ)</a></li>
-        <li>{{ 'CoreUpdater_ErrorDIYHelp_4'|translate }}</li>
-        <li>{{ 'CoreUpdater_ErrorDIYHelp_5'|translate }}</li>
-    </ul>
-    </p>
-{% else %}
-    {% if warningMessages|length > 0 %}
-        <div class="warning">
-            <p><img src="plugins/Morpheus/images/warning_medium.png"/> {{ 'CoreUpdater_WarningMessages'|translate }}</p>
-            {% for message in warningMessages %}
-                <pre>{{ message }}</pre>
-                <br/>
+    <div class="content">
+        <div class="alert alert-danger">
+            {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }}
+            {% for message in errorMessages %}
+                <br/><strong>{{ message }}</strong>
             {% endfor %}
         </div>
+        <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}</p>
+        <ul>
+            <li>{{ helpMessage }}</li>
+        </ul>
+        <p>{{ 'CoreUpdater_ErrorDIYHelp'|translate }}</p>
+        <ul>
+            <li>{{ 'CoreUpdater_ErrorDIYHelp_1'|translate }}</li>
+            <li>{{ 'CoreUpdater_ErrorDIYHelp_2'|translate }}</li>
+            <li>{{ 'CoreUpdater_ErrorDIYHelp_3'|translate }} <a href='https://piwik.org/faq/how-to-update/#faq_179' rel='noreferrer' target='_blank'>(see FAQ)</a></li>
+            <li>{{ 'CoreUpdater_ErrorDIYHelp_4'|translate }}</li>
+            <li>{{ 'CoreUpdater_ErrorDIYHelp_5'|translate }}</li>
+        </ul>
+    </div>
+{% else %}
+
+    {% if errorMessages|length == 0 and warningMessages|length == 0 %}
+        <div class="header">
+            <h1>{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</h1>
+        </div>
     {% endif %}
 
-    {% if errorMessages|length > 0 %}
-        <div class="warning">
-            <p><img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_ErrorDuringPluginsUpdates'|translate }}</p>
-            {% for message in errorMessages %}
-                <pre>{{ message }}</pre>
-                <br/>
-            {% endfor %}
+    <div class="content">
 
+        {% if warningMessages|length > 0 %}
+            <div class="alert alert-warning">
+                <p>{{ 'CoreUpdater_WarningMessages'|translate }}</p>
+                {% for message in warningMessages %}
+                    <br/><strong>{{ message }}</strong>
+                {% endfor %}
+            </div>
+        {% endif %}
+
+        {% if errorMessages|length > 0 %}
+            <div class="alert alert-warning">
+                <p>{{ 'CoreUpdater_ErrorDuringPluginsUpdates'|translate }}</p>
+                {% for message in errorMessages %}
+                    <br/><strong>{{ message }}</strong>
+                {% endfor %}
+            </div>
             {% if deactivatedPlugins is defined and deactivatedPlugins|length > 0 %}
                 {% set listOfDeactivatedPlugins=deactivatedPlugins|join(', ') %}
-                <p style="color:red;">
-                    <img src="plugins/Morpheus/images/error_medium.png"/>
+                <div class="alert alert-danger">
                     {{ 'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate(listOfDeactivatedPlugins) }}
-                </p>
+                </div>
             {% endif %}
-        </div>
-    {% endif %}
+        {% endif %}
+
+        {% if errorMessages|length > 0 or warningMessages|length > 0 %}
+            <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate }}</p>
+            <ul>
+                <li>{{ helpMessage }}</li>
+            </ul>
+        {% else %}
+            <div id="donate-form-container">
+                {% include "@CoreHome/_donate.twig" %}
+            </div>
+        {% endif %}
+
+    </div>
+
+    <div class="footer">
+        <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a>
+    </div>
 
-    {% if errorMessages|length > 0 or warningMessages|length > 0 %}
-        <br/>
-        <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate }}
-        <ul>
-            <li>{{ helpMessage }}</li>
-        </ul>
-        </p>
-    {% else %}
-        <p class="success">{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</p>
-        <div id="donate-form-container">
-            {% include "@CoreHome/_donate.twig" %}
-        </div>
-    {% endif %}
-    <form action="index.php">
-        <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
-    </form>
 {% endif %}
+
 {% endblock %}
diff --git a/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig b/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig
index 305741ed14de3f51b8da3b649d242835a0e30ea7..ed0a987f4de07c80800409ce3a9e2f446201260f 100644
--- a/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig
+++ b/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig
@@ -1,106 +1,105 @@
 {% extends '@CoreUpdater/layout.twig' %}
 
 {% block content %}
-{% spaceless %}
-<span style="float:right;">{{ postEvent('Template.topBar')|raw }}</span>
-{% set helpMessage %}
-    {{ 'CoreUpdater_HelpMessageContent'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">','</a>','</li><li>')|raw }}
-{% endset %}
 
-{% if coreError %}
-    <br/>
-    <br/>
-    <div class="error">
-        <img src="plugins/Morpheus/images/error_medium.png"/> {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }}
-        {% for message in errorMessages %}
-            <pre>{{ message|raw }}</pre>
-        {% endfor %}
-    </div>
-    <br/>
-    <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}
-    <ul>
-        <li>{{ helpMessage|raw }}</li>
-    </ul>
-    </p>
-{% else %}
-    {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %}
-        <p style='font-size:110%;padding-top:1em;'><strong id='titleUpdate'>{{ 'CoreUpdater_DatabaseUpgradeRequired'|translate }}</strong></p>
+    <div class="header">
+        <h1>{{ 'CoreUpdater_DatabaseUpgradeRequired'|translate }}</h1>
         <p>{{ 'CoreUpdater_YourDatabaseIsOutOfDate'|translate }}</p>
-        {% if coreToUpdate %}
-            <p>{{ 'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate(current_piwik_version,new_piwik_version) }}</p>
-        {% endif %}
+        {{ postEvent('Template.topBar')|raw }}
+    </div>
 
-        {% if pluginNamesToUpdate|length > 0 %}
-            {% set listOfPlugins=pluginNamesToUpdate|join(', ') %}
-            <p>{{ 'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate(listOfPlugins) }}</p>
-        {% endif %}
+    <div class="content text-left">
 
-        {% if dimensionsToUpdate|length > 0 %}
-            {% set listOfDimensions=dimensionsToUpdate|join(', ') %}
-            <p>{{ 'CoreUpdater_TheFollowingDimensionsWillBeUpgradedX'|translate(listOfDimensions) }}</p>
-        {% endif %}
+        {% set helpMessage %}
+            {{ 'CoreUpdater_HelpMessageContent'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">','</a>','</li><li>')|raw }}
+        {% endset %}
 
-        <h3 id='titleUpdate'>{{ 'CoreUpdater_NoteForLargePiwikInstances'|translate }}</h3>
-        {% if isMajor %}
-            <p class="warning normalFontSize">
-                {{ 'CoreUpdater_MajorUpdateWarning1'|translate }}<br/>
-                {{ 'CoreUpdater_MajorUpdateWarning2'|translate }}
-            </p>
-        {% endif %}
-        <ul>
-            <li>{{ 'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate(commandUpgradePiwik)|raw }}</li>
-            <li>{{ 'CoreUpdater_HighTrafficPiwikServerEnableMaintenance'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http%3A%2F%2Fpiwik.org%2Ffaq%2Fhow-to%2F%23faq_111">', '</a>')|raw }}</li>
-            <li>{{ 'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate }}<br/>
-                <a href="#titleUpdate" id="showSql" style="margin-left:20px;">› {{ 'CoreUpdater_ClickHereToViewSqlQueries'|translate }}</a>
+        {% if coreError %}
+            <div class="alert alert-danger">
+                {{ 'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate }}
+                {% for message in errorMessages %}
+                    <br/><strong>{{ message|raw }}</strong>
+                {% endfor %}
+            </div>
+            <p>{{ 'CoreUpdater_HelpMessageIntroductionWhenError'|translate }}</p>
+            <ul>
+                <li>{{ helpMessage|raw }}</li>
+            </ul>
+        {% else %}
+
+            {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0 %}
+                {% if coreToUpdate %}
+                    <p>{{ 'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate(current_piwik_version,new_piwik_version) }}</p>
+                {% endif %}
 
+                {% if pluginNamesToUpdate|length > 0 %}
+                    {% set listOfPlugins=pluginNamesToUpdate|join(', ') %}
+                    <p>{{ 'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate(listOfPlugins) }}</p>
+                {% endif %}
+
+                {% if dimensionsToUpdate|length > 0 %}
+                    {% set listOfDimensions=dimensionsToUpdate|join(', ') %}
+                    <p>{{ 'CoreUpdater_TheFollowingDimensionsWillBeUpgradedX'|translate(listOfDimensions) }}</p>
+                {% endif %}
+
+                <h2>{{ 'CoreUpdater_NoteForLargePiwikInstances'|translate }}</h2>
+                {% if isMajor %}
+                    <div class="alert alert-danger">
+                        {{ 'CoreUpdater_MajorUpdateWarning1'|translate }}
+                        {{ 'CoreUpdater_MajorUpdateWarning2'|translate }}
+                    </div>
+                {% endif %}
+                <p>{{ 'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate('') }}</p>
+                <code>{{ commandUpgradePiwik }}</code>
+                <p>{{ 'CoreUpdater_HighTrafficPiwikServerEnableMaintenance'|translate('<a target="_blank" href="?module=Proxy&action=redirect&url=http%3A%2F%2Fpiwik.org%2Ffaq%2Fhow-to%2F%23faq_111">', '</a>')|raw }}</p>
+                <p>{{ 'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate }}</p>
+                <p><a href="#" id="showSql">› {{ 'CoreUpdater_ClickHereToViewSqlQueries'|translate }}</a></p>
                 <div id="sqlQueries" style="display:none;">
-                    <br/>
                     <code>
                         # {{ 'CoreUpdater_NoteItIsExpectedThatQueriesFail'|translate }}<br/><br/>
                         {% for query in queries %}
-                        &nbsp;&nbsp;&nbsp;{{ query }}
-                            <br/>
+                            {{ query }}
+                            <br/><br/>
                         {% endfor %}
                     </code>
                 </div>
-            </li>
-        </ul>
-        <br/>
-        <br/>
-        <h4 id="titleUpdate">{{ 'CoreUpdater_ReadyToGo'|translate }}</h4>
-        <p>{{ 'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate }}</p>
-    {% endif %}
 
-    {% if warningMessages|length > 0 %}
-        <p><em>{{ warningMessages[0] }}</em>
-            {% if warningMessages|length > 1 %}
-                <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button>
+                <h2>{{ 'CoreUpdater_ReadyToGo'|translate }}</h2>
+                <p>{{ 'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate }}</p>
             {% endif %}
-        </p>
-    {% endif %}
 
-    {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0  %}
-        <br/>
-        <form action="index.php" id="upgradeCorePluginsForm" data-updating="{{ 'CoreUpdater_Updating'|translate }}...">
-            <input type="hidden" name="updateCorePlugins" value="1"/>
-            {% if queries|length == 1 %}
-                <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
+            {% if warningMessages|length > 0 %}
+                <div class="alert alert-info">
+                    {{ warningMessages[0] }}
+                    {% if warningMessages|length > 1 %}
+                        <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button>
+                    {% endif %}
+                </div>
+            {% endif %}
+
+            {% if coreToUpdate or pluginNamesToUpdate|length > 0 or dimensionsToUpdate|length > 0  %}
+                <form action="index.php" id="upgradeCorePluginsForm" class="clearfix" data-updating="{{ 'CoreUpdater_Updating'|translate }}...">
+                    <input type="hidden" name="updateCorePlugins" value="1"/>
+                    {% if queries|length == 1 %}
+                        <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
+                    {% else %}
+                        <input type="submit" class="submit" value="{{ 'CoreUpdater_UpgradePiwik'|translate }}"/>
+                    {% endif %}
+                </form>
             {% else %}
-                <input type="submit" class="submit" value="{{ 'CoreUpdater_UpgradePiwik'|translate }}"/>
+                {% if warningMessages|length >= 0 %}
+                    <div class="alert alert-success">
+                        {{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}
+                    </div>
+                {% endif %}
+                <form action="index.php" class="clearfix">
+                    <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
+                </form>
             {% endif %}
-        </form>
-    {% else %}
-        {% if warningMessages|length == 0 %}
-            <p class="success">{{ 'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate }}</p>
         {% endif %}
-        <br/>
-        <form action="index.php">
-            <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
-        </form>
-    {% endif %}
-{% endif %}
 
-{% include "@Installation/_integrityDetails.twig" %}
+        {% include "@Installation/_integrityDetails.twig" %}
+
+    </div>
 
-{% endspaceless %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/plugins/CoreUpdater/templates/updateHttpError.twig b/plugins/CoreUpdater/templates/updateHttpError.twig
new file mode 100644
index 0000000000000000000000000000000000000000..c7d6a90dfbd8ced0448edd739ca49e7670b319e6
--- /dev/null
+++ b/plugins/CoreUpdater/templates/updateHttpError.twig
@@ -0,0 +1,30 @@
+{% extends '@CoreUpdater/layout.twig' %}
+
+{% block content %}
+
+    <div class="header">
+        <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1>
+    </div>
+
+    <div class="content">
+
+        {% for message in feedbackMessages %}
+            <p>&#10003; {{ message }}</p>
+        {% endfor %}
+
+        <div class="alert alert-danger">
+            <strong>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}:</strong>
+            {{ error }}
+        </div>
+
+        <p>
+            {{ 'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate("<br /><br />","<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>","</a>")|raw }}
+        </p>
+
+    </div>
+
+    <div class="footer">
+        <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a>
+    </div>
+
+{% endblock %}
diff --git a/plugins/CoreUpdater/templates/oneClickResults.twig b/plugins/CoreUpdater/templates/updateHttpsError.twig
similarity index 52%
rename from plugins/CoreUpdater/templates/oneClickResults.twig
rename to plugins/CoreUpdater/templates/updateHttpsError.twig
index 664dc92097690ff93c17e355b2b64c699d8af37f..399908e1f662fdb95d053bdb505777be65d4fe03 100644
--- a/plugins/CoreUpdater/templates/oneClickResults.twig
+++ b/plugins/CoreUpdater/templates/updateHttpsError.twig
@@ -2,20 +2,21 @@
 
 {% block content %}
 
-    {% for message in feedbackMessages %}
-        <p>&#10003; {{ message }}</p>
-    {% endfor %}
+    <div class="header">
+        <h1>{{ 'CoreUpdater_UpdateErrorTitle'|translate }}</h1>
+    </div>
 
-    {% if httpsFail %}
-        <br/>
-        <br/>
-        <div class="warning">
-            <img src="plugins/Morpheus/images/warning_medium.png"/>
+    <div class="content text-left">
+
+        <div class="alert alert-warning">
             {{ 'CoreUpdater_UpdateUsingHttpsFailed'|translate }}<br/>
-            "{{ error }}"
+            <em>{{ error }}</em>
         </div>
+
         <p>{{ 'CoreUpdater_UpdateUsingHttpsFailedHelp'|translate }}</p>
+
         <p>{{ 'CoreUpdater_UpdateUsingHttpsFailedHelpWhatToDo'|translate }}</p>
+
         <form action="index.php">
             <input type="hidden" name="module" value="CoreUpdater"/>
             <input type="hidden" name="action" value="oneClickUpdate"/>
@@ -24,6 +25,7 @@
             {{ 'CoreUpdater_UsingHttps'|translate }}
         </form>
         <br/>
+
         <form action="index.php">
             <input type="hidden" name="module" value="CoreUpdater"/>
             <input type="hidden" name="action" value="oneClickUpdate"/>
@@ -32,32 +34,15 @@
             {{ 'CoreUpdater_UsingHttp'|translate }}
         </form>
         <br/>
+
         <form action="index.php">
             <input type="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
         </form>
-        <br/>
-    {% elseif error %}
-        <br/>
-        <br/>
-        <div class="error">
-            <img src="plugins/Morpheus/images/error_medium.png"/>
-            {{ error }}
-        </div>
-        <br/>
-        <br/>
-        <div class="warning">
-            <img src="plugins/Morpheus/images/warning_medium.png"/>
-            {{ 'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate("<br /><br />","<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>","</a>")|raw }}
-        </div>
-        <br/>
-        <br/>
-        <form action="index.php">
-            <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
-        </form>
-    {% else %}
-        <form action="index.php">
-            <input type="submit" class="submit" value="{{ 'General_ContinueToPiwik'|translate }}"/>
-        </form>
-    {% endif %}
+
+    </div>
+
+    <div class="footer">
+        <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a>
+    </div>
 
 {% endblock %}
diff --git a/plugins/CoreUpdater/templates/updateSuccess.twig b/plugins/CoreUpdater/templates/updateSuccess.twig
new file mode 100644
index 0000000000000000000000000000000000000000..eb89e7df3d903823cbe3956dde0711484d898b61
--- /dev/null
+++ b/plugins/CoreUpdater/templates/updateSuccess.twig
@@ -0,0 +1,40 @@
+{% extends '@CoreUpdater/layout.twig' %}
+
+{% block content %}
+
+    <div class="header">
+        <h1>{{ 'CoreUpdater_UpdateSuccessTitle'|translate }}</h1>
+    </div>
+
+    <div class="content">
+
+        <h2>
+            {{ 'CoreUpdater_ThankYouUpdatePiwik'|translate }}
+        </h2>
+
+        <p>
+            {{ 'CoreUpdater_PostUpdateMessage'|translate }}
+
+        </p>
+        <p></p>
+
+        <h2>
+            {{ 'CoreUpdater_PostUpdateSupport'|translate }}
+        </h2>
+
+        <div class="row">
+            <div class="col-sm-4 col-sm-offset-2">
+                <a href="https://piwik.pro/enterprise" class="btn btn-lg btn-block">{{ 'CoreUpdater_EnterpriseSolutions'|translate }}</a>
+            </div>
+            <div class="col-sm-4">
+                <a href="https://piwik.pro/cloud" class="btn btn-lg btn-block">{{ 'CoreUpdater_CloudHosting'|translate }}</a>
+            </div>
+        </div>
+
+    </div>
+
+    <div class="footer">
+        <a href="index.php">{{ 'General_ContinueToPiwik'|translate }}</a>
+    </div>
+
+{% endblock %}
diff --git a/plugins/CoreVisualizations/stylesheets/jqplot.css b/plugins/CoreVisualizations/stylesheets/jqplot.css
index 63c231a96721892b59f07770c6997494b8023a68..25b6535f5c9a6bb99bd072f6e98e9106dba13421 100644
--- a/plugins/CoreVisualizations/stylesheets/jqplot.css
+++ b/plugins/CoreVisualizations/stylesheets/jqplot.css
@@ -84,6 +84,7 @@
     font-size: 16px;
     margin: 0;
     padding: 0;
+    border: none;
 }
 
 .rowevolution .metrics-container {
diff --git a/plugins/Dashboard/javascripts/dashboardObject.js b/plugins/Dashboard/javascripts/dashboardObject.js
index cd2d848253c73c646efa1c110c356f0b3774c28f..f80f485fb852c15c6ef60d7e295a8ed82108d10c 100644
--- a/plugins/Dashboard/javascripts/dashboardObject.js
+++ b/plugins/Dashboard/javascripts/dashboardObject.js
@@ -297,8 +297,49 @@
             }
         }
 
-        for (var i = 0; i < columnCount; i++) {
-            $('.col', dashboardElement)[i].className = 'col width-' + columnWidth[i];
+        switch (layout) {
+            case '100':
+                $('.col', dashboardElement).removeClass()
+                    .addClass('col col-md-12');
+                break;
+            case '50-50':
+                $('.col', dashboardElement).removeClass()
+                    .addClass('col col-md-6');
+                break;
+            case '67-33':
+                $('.col', dashboardElement)[0].className = 'col col-md-8';
+                $('.col', dashboardElement)[1].className = 'col col-md-4';
+                break;
+            case '33-67':
+                $('.col', dashboardElement)[0].className = 'col col-md-4';
+                $('.col', dashboardElement)[1].className = 'col col-md-8';
+                break;
+            case '33-33-33':
+                $('.col', dashboardElement)[0].className = 'col col-md-4';
+                $('.col', dashboardElement)[1].className = 'col col-md-4';
+                $('.col', dashboardElement)[2].className = 'col col-md-4';
+                break;
+            case '40-30-30':
+                $('.col', dashboardElement)[0].className = 'col col-md-6';
+                $('.col', dashboardElement)[1].className = 'col col-md-3';
+                $('.col', dashboardElement)[2].className = 'col col-md-3';
+                break;
+            case '30-40-30':
+                $('.col', dashboardElement)[0].className = 'col col-md-3';
+                $('.col', dashboardElement)[1].className = 'col col-md-6';
+                $('.col', dashboardElement)[2].className = 'col col-md-3';
+                break;
+            case '30-30-40':
+                $('.col', dashboardElement)[0].className = 'col col-md-3';
+                $('.col', dashboardElement)[1].className = 'col col-md-3';
+                $('.col', dashboardElement)[2].className = 'col col-md-6';
+                break;
+            case '25-25-25-25':
+                $('.col', dashboardElement)[0].className = 'col col-md-3';
+                $('.col', dashboardElement)[1].className = 'col col-md-3';
+                $('.col', dashboardElement)[2].className = 'col col-md-3';
+                $('.col', dashboardElement)[3].className = 'col col-md-3';
+                break;
         }
 
         makeWidgetsSortable();
diff --git a/plugins/Dashboard/stylesheets/dashboard.less b/plugins/Dashboard/stylesheets/dashboard.less
index 57a916c010ccdc8d482738ec441ea36438969653..bb2c61b3e2f58fb1abb3df170fc571d7a2021c81 100644
--- a/plugins/Dashboard/stylesheets/dashboard.less
+++ b/plugins/Dashboard/stylesheets/dashboard.less
@@ -26,59 +26,39 @@
   }
 }
 
-#dashboardWidgetsArea {
-  padding-bottom: 100px;
-}
-
-.col {
-  float: left;
-  min-height: 100px;
-}
-
-.col.width-100 {
-  width: 100%;
-}
-
-.col.width-75 {
-  width: 75%;
-}
-
-.col.width-67 {
-  width: 66.67%;
-}
-
-.col.width-50 {
-  width: 50%;
-}
-
-.col.width-40 {
-  width: 40%;
-}
-
-.col.width-33 {
-  width: 33.33%;
-}
-
-.col.width-30 {
-  width: 30%;
-}
-
-.col.width-25 {
-  width: 25%;
-}
+#dashboard {
+  .col {
+    min-height: 100px;
+    // Customize Bootstrap gutter between columns
+    padding-right: 7px;
+    padding-left: 7px;
 
-.hover {
-  border: 2px dashed #E3E3E3;
+    >.sortable {
+      padding: 5px 0;
+    }
+  }
 }
 
 .widget {
   background: @theme-color-background-base;
   border: 1px solid #bbb6ad;
-  margin: 10px 7px;
   border-radius: 4px;
   overflow: hidden;
   font-size: 14px;
   z-index: 1;
+
+  h3 {
+    font-size: 15px;
+    margin: 0;
+    font-weight: normal;
+    color: #0D0D0D;
+    text-shadow: none;
+    padding: 15px 15px 10px;
+  }
+}
+
+.hover {
+  border: 2px dashed #E3E3E3;
 }
 
 .widgetHover {
@@ -116,18 +96,6 @@
   padding-bottom: 4px;
 }
 
-.widgetTopHover {
-}
-
-h3.widgetName {
-  font-size: 15px;
-  padding: 0px;
-  margin: 0px;
-  font-weight: normal;
-  color: #0D0D0D;
-  text-shadow: none;
-}
-
 .widgetNameOffScreen {
   overflow: hidden;
   width:1px;
@@ -137,7 +105,11 @@ h3.widgetName {
 // Overriding some dataTable css for better dashboard display
 .widget .dataTableWrapper {
   width: 100% !important;
+  table * {
+    // Because of Bootstrap
+    box-sizing: content-box;
   }
+}
 
 .widgetTop .button {
   cursor: pointer;
@@ -327,31 +299,24 @@ h3.widgetName {
 #columnPreview .width-100 {
   width: 120px;
 }
-
 #columnPreview .width-75 {
   width: 90px;
 }
-
 #columnPreview .width-67 {
   width: 80.4px;
 }
-
 #columnPreview .width-50 {
   width: 60px;
 }
-
 #columnPreview .width-40 {
   width: 48px;
 }
-
 #columnPreview .width-33 {
   width: 40px;
 }
-
 #columnPreview .width-30 {
   width: 36px;
 }
-
 #columnPreview .width-25 {
   width: 30px;
 }
@@ -507,6 +472,10 @@ div.widgetpreview-preview {
   float: left;
 }
 
+#dashboardWidgetsArea {
+  margin-top: 5px;
+}
+
 @media all and (max-width: 749px) {
   #dashboardWidgetsArea {
     padding-right: 7px;
diff --git a/plugins/DevicesDetection/functions.php b/plugins/DevicesDetection/functions.php
index f47ba4c175e0aa33ac3e0dc2edfba376226ebe5b..d19f0e3b46efa5c04dede0311ce4292968dc2714 100644
--- a/plugins/DevicesDetection/functions.php
+++ b/plugins/DevicesDetection/functions.php
@@ -127,6 +127,7 @@ function getDeviceTypeLabel($label)
         'desktop'       => 'General_Desktop',
         'smartphone'    => 'DevicesDetection_Smartphone',
         'tablet'        => 'DevicesDetection_Tablet',
+        'phablet'        => 'DevicesDetection_Phablet',
         'feature phone' => 'DevicesDetection_FeaturePhone',
         'console'       => 'DevicesDetection_Console',
         'tv'            => 'DevicesDetection_TV',
diff --git a/plugins/DevicesDetection/images/brand/3Q.ico b/plugins/DevicesDetection/images/brand/3Q.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8a2e5e2f1c3ffdff09edfba01cf06cf5c7d10922
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/3Q.ico differ
diff --git a/plugins/DevicesDetection/images/brand/BBK.ico b/plugins/DevicesDetection/images/brand/BBK.ico
new file mode 100644
index 0000000000000000000000000000000000000000..6352fd1b02e383622f286899efa4340c59d5e03c
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/BBK.ico differ
diff --git a/plugins/DevicesDetection/images/brand/Celkon.ico b/plugins/DevicesDetection/images/brand/Celkon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..c35d5864b474124d97e6ef9bc1c5d70bac230be0
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Celkon.ico differ
diff --git a/plugins/DevicesDetection/images/brand/Gionee.ico b/plugins/DevicesDetection/images/brand/Gionee.ico
new file mode 100644
index 0000000000000000000000000000000000000000..704085f1bea9f738be9eab47593824b07d4c92d7
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Gionee.ico differ
diff --git a/plugins/DevicesDetection/images/brand/Hyundai.ico b/plugins/DevicesDetection/images/brand/Hyundai.ico
new file mode 100644
index 0000000000000000000000000000000000000000..3565bcf03f01c1ddba48d1b8f7856f1c84c644a9
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Hyundai.ico differ
diff --git a/plugins/DevicesDetection/images/brand/Le_Pan.ico b/plugins/DevicesDetection/images/brand/Le_Pan.ico
new file mode 100644
index 0000000000000000000000000000000000000000..cea70792e3339b11a80eaff60a8b5b11e0d8f15b
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Le_Pan.ico differ
diff --git a/plugins/DevicesDetection/images/brand/Smartfren.ico b/plugins/DevicesDetection/images/brand/Smartfren.ico
new file mode 100644
index 0000000000000000000000000000000000000000..853f6404487a702f357e341f0db3a2e46ec6d40d
Binary files /dev/null and b/plugins/DevicesDetection/images/brand/Smartfren.ico differ
diff --git a/plugins/DevicesDetection/images/browsers/CR.gif b/plugins/DevicesDetection/images/browsers/CR.gif
new file mode 100644
index 0000000000000000000000000000000000000000..7df03605da9fde90eceaf164917c5f51708cc1f3
Binary files /dev/null and b/plugins/DevicesDetection/images/browsers/CR.gif differ
diff --git a/plugins/DevicesDetection/images/os/BMP.gif b/plugins/DevicesDetection/images/os/BMP.gif
new file mode 100644
index 0000000000000000000000000000000000000000..acd95961191db57ecd1b0767c7a969ece764d014
Binary files /dev/null and b/plugins/DevicesDetection/images/os/BMP.gif differ
diff --git a/plugins/DevicesDetection/lang/en.json b/plugins/DevicesDetection/lang/en.json
index 7009992d3414ba05c5cc558292a7b3e05eaadf26..df313e9142b288a689373a87dd7a9d17ade9e84c 100644
--- a/plugins/DevicesDetection/lang/en.json
+++ b/plugins/DevicesDetection/lang/en.json
@@ -35,6 +35,7 @@
         "PortableMediaPlayer": "Portable media player",
         "Devices": "Devices",
         "Tablet": "Tablet",
+        "Phablet": "Phablet",
         "TV": "Tv",
         "UserAgent": "User-Agent",
         "WidgetBrowsers": "Visitor Browser",
diff --git a/plugins/DevicesDetection/templates/devices.twig b/plugins/DevicesDetection/templates/devices.twig
index 0f93aa61be1d6f57b677ed69f2b08e68aeff8f40..a37079c9fd60c6f74d2024b6de0d96b67dd6ff16 100644
--- a/plugins/DevicesDetection/templates/devices.twig
+++ b/plugins/DevicesDetection/templates/devices.twig
@@ -1,15 +1,19 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceType"|translate }}</h2>
-    {{ deviceTypes  | raw}}
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceBrand"|translate }}</h2>
-    {{ deviceBrands | raw }}
-</div>
+<div class="row">
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceType"|translate }}</h2>
+        {{ deviceTypes  | raw}}
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceBrand"|translate }}</h2>
+        {{ deviceBrands | raw }}
+    </div>
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceModel"|translate }}</h2>
+        {{ deviceModels | raw }}
+        {% if resolutions|default is not empty %}
+            <h2 piwik-enriched-headline>{{ 'Resolution_Resolutions'|translate }}</h2>
+            {{ resolutions|raw }}
+        {% endif %}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_DeviceModel"|translate }}</h2>
-    {{ deviceModels | raw }}
-    {% if resolutions|default is not empty %}
-        <h2 piwik-enriched-headline>{{ 'Resolution_Resolutions'|translate }}</h2>
-        {{ resolutions|raw }}
-    {% endif %}
 </div>
diff --git a/plugins/DevicesDetection/templates/software.twig b/plugins/DevicesDetection/templates/software.twig
index 98b496d6f07af2fa64dcb41ef824e72087abf8b3..d2dfd0d1717c853540a3a3d9e18bcfe6eabbf18a 100644
--- a/plugins/DevicesDetection/templates/software.twig
+++ b/plugins/DevicesDetection/templates/software.twig
@@ -1,19 +1,23 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_OperatingSystems"|translate }}</h2>
-    {{ osReport | raw}}
-    {% if configurations|default is not empty %}
-        <h2 piwik-enriched-headline>{{ 'Resolution_Configurations'|translate }}</h2>
-        {{ configurations|raw }}
-    {% endif %}
-</div>
+<div class="row">
+
+    <div id="col-md-6">
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_OperatingSystems"|translate }}</h2>
+        {{ osReport | raw}}
+        {% if configurations|default is not empty %}
+            <h2 piwik-enriched-headline>{{ 'Resolution_Configurations'|translate }}</h2>
+            {{ configurations|raw }}
+        {% endif %}
+    </div>
+
+    <div id="col-md-6">
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_Browsers"|translate }}</h2>
+        {{ browserReport | raw }}
+        <h2 piwik-enriched-headline>{{ "DevicesDetection_BrowserEngines"|translate }}</h2>
+        {{ browserEngineReport | raw }}
+        {% if browserPlugins|default is not empty %}
+            <h2 piwik-enriched-headline>{{ 'General_Plugins'|translate }}</h2>
+            {{ browserPlugins|raw }}
+        {% endif %}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_Browsers"|translate }}</h2>
-    {{ browserReport | raw }}
-    <h2 piwik-enriched-headline>{{ "DevicesDetection_BrowserEngines"|translate }}</h2>
-    {{ browserEngineReport | raw }}
-    {% if browserPlugins|default is not empty %}
-        <h2 piwik-enriched-headline>{{ 'General_Plugins'|translate }}</h2>
-        {{ browserPlugins|raw }}
-    {% endif %}
 </div>
diff --git a/plugins/Diagnostics/Commands/Run.php b/plugins/Diagnostics/Commands/Run.php
new file mode 100644
index 0000000000000000000000000000000000000000..8014ac24ccdf9ace485fb58f7652126821dd1531
--- /dev/null
+++ b/plugins/Diagnostics/Commands/Run.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Commands;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem;
+use Piwik\Plugins\Diagnostics\DiagnosticService;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Run the diagnostics.
+ */
+class Run extends ConsoleCommand
+{
+    /**
+     * @var DiagnosticService
+     */
+    private $diagnosticService;
+
+    public function __construct()
+    {
+        // Replace this with dependency injection once available
+        $this->diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService');
+
+        parent::__construct();
+    }
+
+    protected function configure()
+    {
+        $this->setName('diagnostics:run')
+            ->setDescription('Run diagnostics to check that Piwik is installed and runs correctly')
+            ->addOption('all', null, InputOption::VALUE_NONE, 'Show all diagnostics, including those that passed with success');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $showAll = $input->getOption('all');
+
+        $report = $this->diagnosticService->runDiagnostics();
+
+        foreach ($report->getAllResults() as $result) {
+            $items = $result->getItems();
+
+            if (! $showAll && ($result->getStatus() === DiagnosticResult::STATUS_OK)) {
+                continue;
+            }
+
+            if (count($items) === 1) {
+                $output->writeln($result->getLabel() . ': ' . $this->formatItem($items[0]), OutputInterface::OUTPUT_PLAIN);
+                continue;
+            }
+
+            $output->writeln($result->getLabel() . ':');
+            foreach ($items as $item) {
+                $output->writeln("\t- " . $this->formatItem($item), OutputInterface::OUTPUT_PLAIN);
+            }
+        }
+
+        if ($report->hasWarnings()) {
+            $output->writeln(sprintf('<comment>%d warnings detected</comment>', $report->getWarningCount()));
+        }
+        if ($report->hasErrors()) {
+            $output->writeln(sprintf('<error>%d errors detected</error>', $report->getErrorCount()));
+            return 1;
+        }
+
+        return 0;
+    }
+
+    private function formatItem(DiagnosticResultItem $item)
+    {
+        if ($item->getStatus() === DiagnosticResult::STATUS_ERROR) {
+            $tag = 'error';
+        } elseif ($item->getStatus() === DiagnosticResult::STATUS_WARNING) {
+            $tag = 'comment';
+        } else {
+            $tag = 'info';
+        }
+
+        return sprintf(
+            '<%s>%s %s</%s>',
+            $tag,
+            strtoupper($item->getStatus()),
+            preg_replace('/\<br\s*\/?\>/i', "\n", $item->getComment()),
+            $tag
+        );
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php b/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1c96f82716849d21e2f48654cc59c179a4741a0
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\CliMulti;
+use Piwik\Config;
+use Piwik\Http;
+use Piwik\Translation\Translator;
+use Piwik\Url;
+
+/**
+ * Check if cron archiving can run through CLI.
+ */
+class CronArchivingCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckCronArchiveProcess');
+        $comment = $this->translator->translate('Installation_SystemCheckCronArchiveProcessCLI') . ': ';
+
+        $process = new CliMulti();
+
+        if ($process->supportsAsync()) {
+            $comment .= $this->translator->translate('General_Ok');
+        } else {
+            $comment .= $this->translator->translate('Installation_NotSupported')
+                . ' ' . $this->translator->translate('Goals_Optional');
+        }
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php b/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..71edf6540b79ca777e922fd95f1090d4b19ba1bd
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Db\Adapter;
+use Piwik\SettingsServer;
+use Piwik\Translation\Translator;
+
+/**
+ * Check supported DB adapters are available.
+ */
+class DbAdapterCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $results = array();
+        $results[] = $this->checkPdo();
+        $results = array_merge($results, $this->checkDbAdapters());
+
+        return $results;
+    }
+
+    private function checkPdo()
+    {
+        $label = 'PDO ' . $this->translator->translate('Installation_Extension');
+
+        if (in_array('PDO', $this->getPhpExtensionsLoaded())) {
+            $status = DiagnosticResult::STATUS_OK;
+        } else {
+            $status = DiagnosticResult::STATUS_WARNING;
+        }
+
+        return DiagnosticResult::singleResult($label, $status);
+    }
+
+    private function checkDbAdapters()
+    {
+        $results = array();
+        $adapters = Adapter::getAdapters();
+
+        foreach ($adapters as $adapter => $port) {
+            $label = $adapter . ' ' . $this->translator->translate('Installation_Extension');
+
+            $results[] = DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK);
+        }
+
+        if (empty($adapters)) {
+            $label = $this->translator->translate('Installation_SystemCheckDatabaseExtensions');
+            $comment = $this->translator->translate('Installation_SystemCheckDatabaseHelp');
+
+            $result = DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $comment);
+            $result->setLongErrorMessage($this->getLongErrorMessage());
+
+            $results[] = $result;
+        }
+
+        return $results;
+    }
+
+    private function getPhpExtensionsLoaded()
+    {
+        return @get_loaded_extensions();
+    }
+
+    private function getLongErrorMessage()
+    {
+        $message = '<p>';
+
+        if (SettingsServer::isWindows()) {
+            $message .= $this->translator->translate(
+                'Installation_SystemCheckWinPdoAndMysqliHelp',
+                array('<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />')
+            );
+        } else {
+            $message .= $this->translator->translate(
+                'Installation_SystemCheckPdoAndMysqliHelp',
+                array(
+                    '<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />',
+                    '<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />'
+                )
+            );
+        }
+
+        $message .= $this->translator->translate('Installation_RestartWebServer') . '<br/><br/>';
+        $message .= $this->translator->translate('Installation_SystemCheckPhpPdoAndMysqli', array(
+            '<a style="color:red" href="http://php.net/pdo">',
+            '</a>',
+            '<a style="color:red" href="http://php.net/mysqli">',
+            '</a>',
+        ));
+        $message .= '</p>';
+
+        return $message;
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/Diagnostic.php b/plugins/Diagnostics/Diagnostic/Diagnostic.php
new file mode 100644
index 0000000000000000000000000000000000000000..39c9be4c39db645332e7dcd117dfdafa15687d48
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/Diagnostic.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+/**
+ * Performs a diagnostic on the system or Piwik.
+ *
+ * Example:
+ *
+ *     class MyDiagnostic implements Diagnostic
+ *     {
+ *         public function execute()
+ *         {
+ *             $results = array();
+ *
+ *             // First check (error)
+ *             $status = testSomethingIsOk() ? DiagnosticResult::STATUS_OK : DiagnosticResult::STATUS_ERROR;
+ *             $results[] = DiagnosticResult::singleResult('First check', $status);
+ *
+ *             // Second check (warning)
+ *             $status = testSomethingElseIsOk() ? DiagnosticResult::STATUS_OK : DiagnosticResult::STATUS_WARNING;
+ *             $results[] = DiagnosticResult::singleResult('Second check', $status);
+ *
+ *             return $results;
+ *         }
+ *     }
+ *
+ * Diagnostics are loaded with dependency injection support.
+ *
+ * @api
+ */
+interface Diagnostic
+{
+    /**
+     * @return DiagnosticResult[]
+     */
+    public function execute();
+}
diff --git a/plugins/Diagnostics/Diagnostic/DiagnosticResult.php b/plugins/Diagnostics/Diagnostic/DiagnosticResult.php
new file mode 100644
index 0000000000000000000000000000000000000000..68c543676310e4b75b5b74b18eeeb0bc845fc8ae
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/DiagnosticResult.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+/**
+ * The result of a diagnostic.
+ *
+ * @api
+ */
+class DiagnosticResult
+{
+    const STATUS_ERROR = 'error';
+    const STATUS_WARNING = 'warning';
+    const STATUS_OK = 'ok';
+
+    /**
+     * @var string
+     */
+    private $label;
+
+    /**
+     * @var string
+     */
+    private $longErrorMessage = '';
+
+    /**
+     * @var DiagnosticResultItem[]
+     */
+    private $items = array();
+
+    public function __construct($label)
+    {
+        $this->label = $label;
+    }
+
+    /**
+     * @param string $label
+     * @param string $status
+     * @param string $comment
+     * @return DiagnosticResult
+     */
+    public static function singleResult($label, $status, $comment = '')
+    {
+        $result = new self($label);
+        $result->addItem(new DiagnosticResultItem($status, $comment));
+        return $result;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    /**
+     * @return DiagnosticResultItem[]
+     */
+    public function getItems()
+    {
+        return $this->items;
+    }
+
+    public function addItem(DiagnosticResultItem $item)
+    {
+        $this->items[] = $item;
+    }
+
+    /**
+     * @param DiagnosticResultItem[] $items
+     */
+    public function setItems(array $items)
+    {
+        $this->items = $items;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLongErrorMessage()
+    {
+        return $this->longErrorMessage;
+    }
+
+    /**
+     * @param string $longErrorMessage
+     */
+    public function setLongErrorMessage($longErrorMessage)
+    {
+        $this->longErrorMessage = $longErrorMessage;
+    }
+
+    /**
+     * Returns the worst status of the items.
+     *
+     * @return string One of the `STATUS_*` constants.
+     */
+    public function getStatus()
+    {
+        $status = self::STATUS_OK;
+
+        foreach ($this->getItems() as $item) {
+            if ($item->getStatus() === self::STATUS_ERROR) {
+                return self::STATUS_ERROR;
+            }
+
+            if ($item->getStatus() === self::STATUS_WARNING) {
+                $status = self::STATUS_WARNING;
+            }
+        }
+
+        return $status;
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php b/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php
new file mode 100644
index 0000000000000000000000000000000000000000..a39cb6dac413712d4595c1a571a212fc565d9146
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+/**
+ * @api
+ */
+class DiagnosticResultItem
+{
+    /**
+     * @var string
+     */
+    private $status;
+
+    /**
+     * Optional comment about the item.
+     *
+     * @var string
+     */
+    private $comment;
+
+    public function __construct($status, $comment = '')
+    {
+        $this->status = $status;
+        $this->comment = $comment;
+    }
+
+    /**
+     * @return string
+     */
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    /**
+     * @return string
+     */
+    public function getComment()
+    {
+        return $this->comment;
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php b/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6730bc52e6fd6c6bbf5db1b2582092cd9111e53
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Filechecks;
+use Piwik\Translation\Translator;
+
+/**
+ * Check the files integrity.
+ */
+class FileIntegrityCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckFileIntegrity');
+
+        $messages = Filechecks::getFileIntegrityInformation();
+        $ok = array_shift($messages);
+
+        if (empty($messages)) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        if ($ok) {
+            $status = DiagnosticResult::STATUS_WARNING;
+            return array(DiagnosticResult::singleResult($label, $status, $messages[0]));
+        }
+
+        $comment = $this->translator->translate('General_FileIntegrityWarningExplanation');
+
+        // Keep only the 20 first lines else it becomes unmanageable
+        if (count($messages) > 20) {
+            $messages = array_slice($messages, 0, 20);
+            $messages[] = '...';
+        }
+        $comment .= '<br/><br/><pre style="overflow-x: scroll;max-width: 600px;">'
+            . implode("\n", $messages) . '</pre>';
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php b/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..00bb10a19d2692734fd97f2d6497eb510d60bbe6
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\SettingsServer;
+use Piwik\Translation\Translator;
+
+/**
+ * Check that the GD extension is installed and the correct version.
+ */
+class GdExtensionCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckGDFreeType');
+
+        if (SettingsServer::isGdExtensionEnabled()) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        $comment = sprintf(
+            '%s<br />%s',
+            $this->translator->translate('Installation_SystemCheckGDFreeType'),
+            $this->translator->translate('Installation_SystemCheckGDHelp')
+        );
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/HttpClientCheck.php b/plugins/Diagnostics/Diagnostic/HttpClientCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..998831a794e29893d078860111f1883c60abbaf4
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/HttpClientCheck.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\Filechecks;
+use Piwik\Http;
+use Piwik\Translation\Translator;
+
+/**
+ * Check that Piwik's HTTP client can work correctly.
+ */
+class HttpClientCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckOpenURL');
+
+        $httpMethod = Http::getTransportMethod();
+
+        if ($httpMethod) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $httpMethod));
+        }
+
+        $canAutoUpdate = Filechecks::canAutoUpdate();
+
+        $comment = $this->translator->translate('Installation_SystemCheckOpenURLHelp');
+
+        if (! $canAutoUpdate) {
+            $comment .= '<br/>' . $this->translator->translate('Installation_SystemCheckAutoUpdateHelp');
+        }
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php b/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6caab824c2d07d530dd01b5d7bf1a5adcf9ab9c
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Common;
+use Piwik\Config;
+use Piwik\Db;
+use Piwik\Translation\Translator;
+
+/**
+ * Check if Piwik can use LOAD DATA INFILE.
+ */
+class LoadDataInfileCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $isPiwikInstalling = !Config::getInstance()->existsLocalConfig();
+        if ($isPiwikInstalling) {
+            // Skip the diagnostic if Piwik is being installed
+            return array();
+        }
+
+        $label = $this->translator->translate('Installation_DatabaseAbilities');
+
+        $optionTable = Common::prefixTable('option');
+        $testOptionNames = array('test_system_check1', 'test_system_check2');
+
+        $loadDataInfile = false;
+        $errorMessage = null;
+        try {
+            $loadDataInfile = Db\BatchInsert::tableInsertBatch(
+                $optionTable,
+                array('option_name', 'option_value'),
+                array(
+                    array($testOptionNames[0], '1'),
+                    array($testOptionNames[1], '2'),
+                ),
+                $throwException = true
+            );
+        } catch (\Exception $ex) {
+            $errorMessage = str_replace("\n", "<br/>", $ex->getMessage());
+        }
+
+        // delete the temporary rows that were created
+        Db::exec("DELETE FROM `$optionTable` WHERE option_name IN ('" . implode("','", $testOptionNames) . "')");
+
+        if ($loadDataInfile) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, 'LOAD DATA INFILE'));
+        }
+
+        $comment = sprintf(
+            'LOAD DATA INFILE<br/>%s<br/>%s',
+            $this->translator->translate('Installation_LoadDataInfileUnavailableHelp', array(
+                'LOAD DATA INFILE',
+                'FILE',
+            )),
+            $this->translator->translate('Installation_LoadDataInfileRecommended')
+        );
+
+        if ($errorMessage) {
+            $comment .= sprintf(
+                '<br/><strong>%s:</strong> %s<br/>%s',
+                $this->translator->translate('General_Error'),
+                $errorMessage,
+                'Troubleshooting: <a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/troubleshooting/%23faq_194">FAQ on piwik.org</a>'
+            );
+        }
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php b/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f1ba1cf388a47350424dfc48ddae015a9b47382
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\SettingsServer;
+use Piwik\Translation\Translator;
+
+/**
+ * Check that the memory limit is enough.
+ */
+class MemoryLimitCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    /**
+     * @var int
+     */
+    private $minimumMemoryLimit;
+
+    public function __construct(Translator $translator, $minimumMemoryLimit)
+    {
+        $this->translator = $translator;
+        $this->minimumMemoryLimit = $minimumMemoryLimit;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckMemoryLimit');
+
+        SettingsServer::raiseMemoryLimitIfNecessary();
+
+        $memoryLimit = SettingsServer::getMemoryLimitValue();
+        $comment = $memoryLimit . 'M';
+
+        if ($memoryLimit >= $this->minimumMemoryLimit) {
+            $status = DiagnosticResult::STATUS_OK;
+        } else {
+            $status = DiagnosticResult::STATUS_WARNING;
+            $comment .= sprintf(
+                '<br />%s<br />%s',
+                $this->translator->translate('Installation_SystemCheckMemoryLimitHelp'),
+                $this->translator->translate('Installation_RestartWebServer')
+            );
+        }
+
+        return array(DiagnosticResult::singleResult($label, $status, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php b/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..b563ae3d4a29d9b9373e8032148fd48765e9bbe4
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\Filesystem;
+use Piwik\Translation\Translator;
+
+/**
+ * Checks if the filesystem Piwik stores sessions in is NFS or not.
+ *
+ * This check is done in order to avoid using file based sessions on NFS system,
+ * since on such a filesystem file locking can make file based sessions incredibly slow.
+ */
+class NfsDiskCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_Filesystem');
+
+        if (! Filesystem::checkIfFileSystemIsNFS()) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        $isPiwikInstalling = !Config::getInstance()->existsLocalConfig();
+        if ($isPiwikInstalling) {
+            $help = 'Installation_NfsFilesystemWarningSuffixInstall';
+        } else {
+            $help = 'Installation_NfsFilesystemWarningSuffixAdmin';
+        }
+
+        $comment = sprintf(
+            '%s<br />%s',
+            $this->translator->translate('Installation_NfsFilesystemWarning'),
+            $this->translator->translate($help)
+        );
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php b/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7444b8823446d3cca140ddece682fb1fad3be17
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\Http;
+use Piwik\Translation\Translator;
+use Piwik\Url;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Check that mod_pagespeed is not enabled.
+ */
+class PageSpeedCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    /**
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    public function __construct(Translator $translator, LoggerInterface $logger)
+    {
+        $this->translator = $translator;
+        $this->logger = $logger;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckPageSpeedDisabled');
+
+        if (! $this->isPageSpeedEnabled()) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        $comment = $this->translator->translate('Installation_SystemCheckPageSpeedWarn', array(
+            '(eg. Apache, Nginx or IIS)',
+        ));
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+
+    private function isPageSpeedEnabled()
+    {
+        $url = Url::getCurrentUrlWithoutQueryString() . '?module=Installation&action=getEmptyPageForSystemCheck';
+
+        try {
+            $page = Http::sendHttpRequest($url,
+                $timeout = 1,
+                $userAgent = null,
+                $destinationPath = null,
+                $followDepth = 0,
+                $acceptLanguage = false,
+                $byteRange = false,
+
+                // Return headers
+                $getExtendedInfo = true
+            );
+        } catch(\Exception $e) {
+            $this->logger->info('Unable to test if mod_pagespeed is enabled: the request to {url} failed', array(
+                'url' => $url,
+            ));
+            // If the test failed, we assume Page speed is not enabled
+            return false;
+        }
+
+        $headers = $page['headers'];
+
+        return isset($headers['X-Mod-Pagespeed']) || isset($headers['X-Page-Speed']);
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php b/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..901c18c580f963e85734d21a80266bf6c4472bda
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check the PHP extensions.
+ */
+class PhpExtensionsCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckExtensions');
+
+        $result = new DiagnosticResult($label);
+        $longErrorMessage = '';
+
+        $requiredExtensions = $this->getRequiredExtensions();
+        $loadedExtensions = @get_loaded_extensions();
+
+        foreach ($requiredExtensions as $extension) {
+            if (! in_array($extension, $loadedExtensions)) {
+                $status = DiagnosticResult::STATUS_ERROR;
+                $comment = $extension . ': ' . $this->translator->translate('Installation_RestartWebServer');
+                $longErrorMessage .= '<p>' . $this->getHelpMessage($extension) . '</p>';
+            } else {
+                $status = DiagnosticResult::STATUS_OK;
+                $comment = $extension;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $comment));
+        }
+
+        $result->setLongErrorMessage($longErrorMessage);
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getRequiredExtensions()
+    {
+        $requiredExtensions = array(
+            'zlib',
+            'SPL',
+            'iconv',
+            'json',
+            'mbstring',
+        );
+
+        if (! defined('HHVM_VERSION')) {
+            // HHVM provides the required subset of Reflection but lists Reflections as missing
+            $requiredExtensions[] = 'Reflection';
+        }
+
+        return $requiredExtensions;
+    }
+
+    private function getHelpMessage($missingExtension)
+    {
+        $messages = array(
+            'zlib'       => 'Installation_SystemCheckZlibHelp',
+            'SPL'        => 'Installation_SystemCheckSplHelp',
+            'iconv'      => 'Installation_SystemCheckIconvHelp',
+            'json'       => 'Installation_SystemCheckWarnJsonHelp',
+            'mbstring'   => 'Installation_SystemCheckMbstringHelp',
+            'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php',
+        );
+
+        return $this->translator->translate($messages[$missingExtension]);
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php b/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e68c9538edebb956dc916985cd3ef46d5d79a35
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check the enabled PHP functions.
+ */
+class PhpFunctionsCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckFunctions');
+
+        $result = new DiagnosticResult($label);
+
+        foreach ($this->getRequiredFunctions() as $function) {
+            if (! self::functionExists($function)) {
+                $status = DiagnosticResult::STATUS_ERROR;
+                $comment = sprintf(
+                    '%s <br/><br/><em>%s</em><br/><em>%s</em><br/>',
+                    $function,
+                    $this->getHelpMessage($function),
+                    $this->translator->translate('Installation_RestartWebServer')
+                );
+            } else {
+                $status = DiagnosticResult::STATUS_OK;
+                $comment = $function;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $comment));
+        }
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getRequiredFunctions()
+    {
+        return array(
+            'debug_backtrace',
+            'create_function',
+            'eval',
+            'gzcompress',
+            'gzuncompress',
+            'pack',
+        );
+    }
+
+    /**
+     * Tests if a function exists. Also handles the case where a function is disabled via Suhosin.
+     *
+     * @param string $function
+     * @return bool
+     */
+    public static function functionExists($function)
+    {
+        // eval() is a language construct
+        if ($function == 'eval') {
+            // does not check suhosin.executor.eval.whitelist (or blacklist)
+            if (extension_loaded('suhosin')) {
+                return @ini_get("suhosin.executor.disable_eval") != "1";
+            }
+            return true;
+        }
+
+        $exists = function_exists($function);
+
+        if (extension_loaded('suhosin')) {
+            $blacklist = @ini_get("suhosin.executor.func.blacklist");
+            if (!empty($blacklist)) {
+                $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist)));
+                return $exists && !in_array($function, $blacklistFunctions);
+            }
+        }
+
+        return $exists;
+    }
+
+    private function getHelpMessage($missingFunction)
+    {
+        $messages = array(
+            'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp',
+            'create_function' => 'Installation_SystemCheckCreateFunctionHelp',
+            'eval'            => 'Installation_SystemCheckEvalHelp',
+            'gzcompress'      => 'Installation_SystemCheckGzcompressHelp',
+            'gzuncompress'    => 'Installation_SystemCheckGzuncompressHelp',
+            'pack'            => 'Installation_SystemCheckPackHelp',
+        );
+
+        return $this->translator->translate($messages[$missingFunction]);
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php b/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..6760c1d6be06a6311425dbbebe9a9ef86ccb7042
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check some PHP INI settings.
+ */
+class PhpSettingsCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckSettings');
+
+        $result = new DiagnosticResult($label);
+
+        foreach ($this->getRequiredSettings() as $setting) {
+            list($settingName, $requiredValue) = explode('=', $setting);
+
+            $currentValue = (int) ini_get($settingName);
+
+            if ($currentValue != $requiredValue) {
+                $status = DiagnosticResult::STATUS_ERROR;
+                $comment = sprintf(
+                    '%s <br/><br/><em>%s</em><br/><em>%s</em><br/>',
+                    $setting,
+                    $this->translator->translate('Installation_SystemCheckPhpSetting', array($setting)),
+                    $this->translator->translate('Installation_RestartWebServer')
+                );
+            } else {
+                $status = DiagnosticResult::STATUS_OK;
+                $comment = $setting;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $comment));
+        }
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getRequiredSettings()
+    {
+        $requiredSettings = array(
+            // setting = required value
+            // Note: value must be an integer only
+            'session.auto_start=0',
+        );
+
+        if ($this->isPhpVersionAtLeast56()) {
+            // always_populate_raw_post_data must be -1
+            $requiredSettings[] = 'always_populate_raw_post_data=-1';
+        }
+
+        return $requiredSettings;
+    }
+
+    private function isPhpVersionAtLeast56()
+    {
+        return version_compare(PHP_VERSION, '5.6', '>=');
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php b/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7010804a0fce1e3965ea7f370360998fdce076c
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check the PHP version.
+ */
+class PhpVersionCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        global $piwik_minimumPHPVersion;
+
+        $actualVersion = PHP_VERSION;
+
+        $label = sprintf(
+            '%s >= %s',
+            $this->translator->translate('Installation_SystemCheckPhp'),
+            $piwik_minimumPHPVersion
+        );
+
+        if ($this->isPhpVersionValid($piwik_minimumPHPVersion, $actualVersion)) {
+            $status = DiagnosticResult::STATUS_OK;
+            $comment = $actualVersion;
+        } else {
+            $status = DiagnosticResult::STATUS_ERROR;
+            $comment = sprintf(
+                '%s: %s',
+                $this->translator->translate('General_Error'),
+                $this->translator->translate('General_Required', array($label))
+            );
+        }
+
+        return array(DiagnosticResult::singleResult($label, $status, $comment));
+    }
+
+    private function isPhpVersionValid($requiredVersion, $actualVersion)
+    {
+        return version_compare($requiredVersion, $actualVersion) <= 0;
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php b/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..c68a65d0a629fdf654ef8f35b46ef9be88c476e1
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check the PHP extensions that are not required but recommended.
+ */
+class RecommendedExtensionsCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckOtherExtensions');
+
+        $result = new DiagnosticResult($label);
+
+        $loadedExtensions = @get_loaded_extensions();
+
+        foreach ($this->getRecommendedExtensions() as $extension) {
+            if (! in_array($extension, $loadedExtensions)) {
+                $status = DiagnosticResult::STATUS_WARNING;
+                $comment = $extension . '<br/>' . $this->getHelpMessage($extension);
+            } else {
+                $status = DiagnosticResult::STATUS_OK;
+                $comment = $extension;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $comment));
+        }
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getRecommendedExtensions()
+    {
+        return array(
+            'json',
+            'libxml',
+            'dom',
+            'SimpleXML',
+        );
+    }
+
+    private function getHelpMessage($missingExtension)
+    {
+        $messages = array(
+            'json'      => 'Installation_SystemCheckWarnJsonHelp',
+            'libxml'    => 'Installation_SystemCheckWarnLibXmlHelp',
+            'dom'       => 'Installation_SystemCheckWarnDomHelp',
+            'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp',
+        );
+
+        return $this->translator->translate($messages[$missingExtension]);
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..eedf8e92249e9c8e8195338f689939e1bf1ab83b
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Translation\Translator;
+
+/**
+ * Check the PHP functions that are not required but recommended.
+ */
+class RecommendedFunctionsCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckOtherFunctions');
+
+        $result = new DiagnosticResult($label);
+
+        foreach ($this->getRecommendedFunctions() as $function) {
+            if (! PhpFunctionsCheck::functionExists($function)) {
+                $status = DiagnosticResult::STATUS_WARNING;
+                $comment = $function . '<br/>' . $this->getHelpMessage($function);
+            } else {
+                $status = DiagnosticResult::STATUS_OK;
+                $comment = $function;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $comment));
+        }
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getRecommendedFunctions()
+    {
+        return array(
+            'set_time_limit',
+            'mail',
+            'parse_ini_file',
+            'glob',
+            'gzopen',
+        );
+    }
+
+    private function getHelpMessage($function)
+    {
+        $messages = array(
+            'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp',
+            'mail'           => 'Installation_SystemCheckMailHelp',
+            'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp',
+            'glob'           => 'Installation_SystemCheckGlobHelp',
+            'gzopen'         => 'Installation_SystemCheckZlibHelp',
+        );
+
+        return $this->translator->translate($messages[$function]);
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/TimezoneCheck.php b/plugins/Diagnostics/Diagnostic/TimezoneCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfecd498bd824022b74753194fa927602ed1b9b0
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/TimezoneCheck.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Config;
+use Piwik\SettingsServer;
+use Piwik\Translation\Translator;
+
+/**
+ * Check that the PHP timezone setting is set.
+ */
+class TimezoneCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('SitesManager_Timezone');
+
+        if (SettingsServer::isTimezoneSupportEnabled()) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        $comment = sprintf(
+            '%s<br />%s.',
+            $this->translator->translate('SitesManager_AdvancedTimezoneSupportNotFound'),
+            '<a href="http://php.net/manual/en/datetime.installation.php" rel="noreferrer" target="_blank">Timezone PHP documentation</a>'
+        );
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/TrackerCheck.php b/plugins/Diagnostics/Diagnostic/TrackerCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d2a95febf23624a2fa068077d3340bf5a005231
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/TrackerCheck.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\Common;
+use Piwik\Translation\Translator;
+
+/**
+ * Check that the tracker is working correctly.
+ */
+class TrackerCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckTracker');
+
+        $trackerStatus = Common::getRequestVar('trackerStatus', 0, 'int');
+
+        if ($trackerStatus == 0) {
+            $status = DiagnosticResult::STATUS_OK;
+            $comment = '';
+        } else {
+            $status = DiagnosticResult::STATUS_WARNING;
+            $comment = sprintf(
+                '%s<br />%s<br />%s',
+                $trackerStatus,
+                $this->translator->translate('Installation_SystemCheckTrackerHelp'),
+                $this->translator->translate('Installation_RestartWebServer')
+            );
+        }
+
+        return array(DiagnosticResult::singleResult($label, $status, $comment));
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php b/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..b4e9a4c8b8be6eef668b85bc22130c42b6b7fdbf
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics\Diagnostic;
+
+use Piwik\DbHelper;
+use Piwik\Filechecks;
+use Piwik\Translation\Translator;
+
+/**
+ * Check the permissions for some directories.
+ */
+class WriteAccessCheck implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    /**
+     * Path to the temp directory.
+     * @var string
+     */
+    private $tmpPath;
+
+    /**
+     * @param Translator $translator
+     * @param string $tmpPath Path to the temp directory.
+     */
+    public function __construct(Translator $translator, $tmpPath)
+    {
+        $this->translator = $translator;
+        $this->tmpPath = $tmpPath;
+    }
+
+    public function execute()
+    {
+        $label = $this->translator->translate('Installation_SystemCheckWriteDirs');
+
+        $result = new DiagnosticResult($label);
+
+        $directories = Filechecks::checkDirectoriesWritable($this->getDirectories());
+
+        $error = false;
+        foreach ($directories as $directory => $isWritable) {
+            if ($isWritable) {
+                $status = DiagnosticResult::STATUS_OK;
+            } else {
+                $status = DiagnosticResult::STATUS_ERROR;
+                $error = true;
+            }
+
+            $result->addItem(new DiagnosticResultItem($status, $directory));
+        }
+
+        if ($error) {
+            $longErrorMessage = $this->translator->translate('Installation_SystemCheckWriteDirsHelp');
+            $longErrorMessage .= '<ul>';
+            foreach ($directories as $directory => $isWritable) {
+                if (! $isWritable) {
+                    $longErrorMessage .= sprintf('<li><pre>chmod a+w %s</pre></li>', $directory);
+                }
+            }
+            $longErrorMessage .= '</ul>';
+            $result->setLongErrorMessage($longErrorMessage);
+        }
+
+        return array($result);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getDirectories()
+    {
+        $directoriesToCheck = array(
+            $this->tmpPath,
+            $this->tmpPath . '/assets/',
+            $this->tmpPath . '/cache/',
+            $this->tmpPath . '/climulti/',
+            $this->tmpPath . '/latest/',
+            $this->tmpPath . '/logs/',
+            $this->tmpPath . '/sessions/',
+            $this->tmpPath . '/tcpdf/',
+            $this->tmpPath . '/templates_c/',
+        );
+
+        if (! DbHelper::isInstalled()) {
+            // at install, need /config to be writable (so we can create config.ini.php)
+            $directoriesToCheck[] = '/config/';
+        }
+
+        return $directoriesToCheck;
+    }
+}
diff --git a/plugins/Diagnostics/DiagnosticReport.php b/plugins/Diagnostics/DiagnosticReport.php
new file mode 100644
index 0000000000000000000000000000000000000000..865225a66d7474034bfd61342e4457144b90f72a
--- /dev/null
+++ b/plugins/Diagnostics/DiagnosticReport.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+
+/**
+ * A diagnostic report contains all the results of all the diagnostics.
+ */
+class DiagnosticReport
+{
+    /**
+     * @var DiagnosticResult[]
+     */
+    private $mandatoryDiagnosticResults;
+
+    /**
+     * @var DiagnosticResult[]
+     */
+    private $optionalDiagnosticResults;
+
+    /**
+     * @var int
+     */
+    private $errorCount = 0;
+
+    /**
+     * @var int
+     */
+    private $warningCount = 0;
+
+    /**
+     * @param DiagnosticResult[] $mandatoryDiagnosticResults
+     * @param DiagnosticResult[] $optionalDiagnosticResults
+     */
+    public function __construct(array $mandatoryDiagnosticResults, array $optionalDiagnosticResults)
+    {
+        $this->mandatoryDiagnosticResults = $mandatoryDiagnosticResults;
+        $this->optionalDiagnosticResults = $optionalDiagnosticResults;
+
+        $this->computeErrorAndWarningCount();
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasErrors()
+    {
+        return $this->getErrorCount() > 0;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasWarnings()
+    {
+        return $this->getWarningCount() > 0;
+    }
+
+    /**
+     * @return int
+     */
+    public function getErrorCount()
+    {
+        return $this->errorCount;
+    }
+
+    /**
+     * @return int
+     */
+    public function getWarningCount()
+    {
+        return $this->warningCount;
+    }
+
+    /**
+     * @return DiagnosticResult[]
+     */
+    public function getAllResults()
+    {
+        return array_merge($this->mandatoryDiagnosticResults, $this->optionalDiagnosticResults);
+    }
+
+    /**
+     * @return DiagnosticResult[]
+     */
+    public function getMandatoryDiagnosticResults()
+    {
+        return $this->mandatoryDiagnosticResults;
+    }
+
+    /**
+     * @return DiagnosticResult[]
+     */
+    public function getOptionalDiagnosticResults()
+    {
+        return $this->optionalDiagnosticResults;
+    }
+
+    private function computeErrorAndWarningCount()
+    {
+        foreach ($this->getAllResults() as $result) {
+            switch ($result->getStatus()) {
+                case DiagnosticResult::STATUS_ERROR:
+                    $this->errorCount++;
+                    break;
+                case DiagnosticResult::STATUS_WARNING:
+                    $this->warningCount++;
+                    break;
+            }
+        }
+    }
+}
diff --git a/plugins/Diagnostics/DiagnosticService.php b/plugins/Diagnostics/DiagnosticService.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ce53fb5ca33ed338213b43f619b6209293a4ba0
--- /dev/null
+++ b/plugins/Diagnostics/DiagnosticService.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Piwik\Plugins\Diagnostics;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+
+/**
+ * Runs the Piwik diagnostics.
+ *
+ * @api
+ */
+class DiagnosticService
+{
+    /**
+     * @var Diagnostic[]
+     */
+    private $mandatoryDiagnostics;
+
+    /**
+     * @var Diagnostic[]
+     */
+    private $optionalDiagnostics;
+
+    /**
+     * @param Diagnostic[] $mandatoryDiagnostics
+     * @param Diagnostic[] $optionalDiagnostics
+     * @param Diagnostic[] $disabledDiagnostics
+     */
+    public function __construct(array $mandatoryDiagnostics, array $optionalDiagnostics, array $disabledDiagnostics)
+    {
+        $this->mandatoryDiagnostics = $this->removeDisabledDiagnostics($mandatoryDiagnostics, $disabledDiagnostics);
+        $this->optionalDiagnostics = $this->removeDisabledDiagnostics($optionalDiagnostics, $disabledDiagnostics);
+    }
+
+    /**
+     * @return DiagnosticReport
+     */
+    public function runDiagnostics()
+    {
+        return new DiagnosticReport(
+            $this->run($this->mandatoryDiagnostics),
+            $this->run($this->optionalDiagnostics)
+        );
+    }
+
+    /**
+     * @param Diagnostic[] $diagnostics
+     * @return DiagnosticResult[]
+     */
+    private function run(array $diagnostics)
+    {
+        $results = array();
+
+        foreach ($diagnostics as $diagnostic) {
+            $results = array_merge($results, $diagnostic->execute());
+        }
+
+        return $results;
+    }
+
+    private function removeDisabledDiagnostics(array $diagnostics, array $disabledDiagnostics)
+    {
+        return array_filter($diagnostics, function (Diagnostic $diagnostic) use ($disabledDiagnostics) {
+            return ! in_array($diagnostic, $disabledDiagnostics, true);
+        });
+    }
+}
diff --git a/plugins/Diagnostics/Diagnostics.php b/plugins/Diagnostics/Diagnostics.php
new file mode 100644
index 0000000000000000000000000000000000000000..f69d1f45d128a5bc0eb829b5c38e62c84cb41e1f
--- /dev/null
+++ b/plugins/Diagnostics/Diagnostics.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics;
+
+use Piwik\Plugin;
+
+class Diagnostics extends Plugin
+{
+}
diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php
new file mode 100644
index 0000000000000000000000000000000000000000..bc7d6db966214167c1d56509ef561a8885d4de38
--- /dev/null
+++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Mock;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+
+class DiagnosticWithError implements Diagnostic
+{
+    public function execute()
+    {
+        return array(
+            DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment'),
+        );
+    }
+}
diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad01d957106060890647ba060bc25bd6cfeeec5d
--- /dev/null
+++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Mock;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+
+class DiagnosticWithSuccess implements Diagnostic
+{
+    public function execute()
+    {
+        return array(
+            DiagnosticResult::singleResult('Success', DiagnosticResult::STATUS_OK, 'Comment'),
+        );
+    }
+}
diff --git a/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php b/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php
new file mode 100644
index 0000000000000000000000000000000000000000..a7e891500509199dccb223392332955641fa276c
--- /dev/null
+++ b/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Mock;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+
+class DiagnosticWithWarning implements Diagnostic
+{
+    public function execute()
+    {
+        return array(
+            DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment'),
+        );
+    }
+}
diff --git a/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php b/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..efddeb74f2adc1ff24434f72a9907c62220367d7
--- /dev/null
+++ b/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Unit\Diagnostic;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResultItem;
+
+class DiagnosticResultTest extends \PHPUnit_Framework_TestCase
+{
+    public function test_getStatus_shouldReturnTheWorstStatus()
+    {
+        $result = new DiagnosticResult('Label');
+
+        $this->assertEquals(DiagnosticResult::STATUS_OK, $result->getStatus());
+
+        $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_WARNING));
+        $this->assertEquals(DiagnosticResult::STATUS_WARNING, $result->getStatus());
+
+        $result->addItem(new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR));
+        $this->assertEquals(DiagnosticResult::STATUS_ERROR, $result->getStatus());
+    }
+
+    public function test_singleResult_shouldReturnAResultWithASingleItem()
+    {
+        $result = DiagnosticResult::singleResult('Label', DiagnosticResult::STATUS_ERROR);
+
+        $this->assertInstanceOf('Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult', $result);
+        $this->assertEquals('Label', $result->getLabel());
+        $this->assertEquals(DiagnosticResult::STATUS_ERROR, $result->getStatus());
+    }
+}
diff --git a/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php b/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bdd673b3c0fa6a4ea3f07cebc5e1be3fdb9438ae
--- /dev/null
+++ b/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Unit;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Plugins\Diagnostics\DiagnosticReport;
+
+class DiagnosticReportTest extends \PHPUnit_Framework_TestCase
+{
+    public function test_shouldComputeErrorAndWarningCount()
+    {
+        $report = new DiagnosticReport(
+            array(DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment')),
+            array(DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment'))
+        );
+
+        $this->assertEquals(1, $report->getErrorCount());
+        $this->assertTrue($report->hasErrors());
+        $this->assertEquals(1, $report->getWarningCount());
+        $this->assertTrue($report->hasWarnings());
+
+        $report = new DiagnosticReport(array(), array());
+
+        $this->assertEquals(0, $report->getErrorCount());
+        $this->assertFalse($report->hasErrors());
+        $this->assertEquals(0, $report->getWarningCount());
+        $this->assertFalse($report->hasWarnings());
+    }
+
+    public function test_getAllResults()
+    {
+        $report = new DiagnosticReport(
+            array(DiagnosticResult::singleResult('Error', DiagnosticResult::STATUS_ERROR, 'Comment')),
+            array(DiagnosticResult::singleResult('Warning', DiagnosticResult::STATUS_WARNING, 'Comment'))
+        );
+
+        $this->assertCount(1, $report->getMandatoryDiagnosticResults());
+        $this->assertCount(1, $report->getOptionalDiagnosticResults());
+        $this->assertCount(2, $report->getAllResults());
+    }
+}
diff --git a/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php b/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cfe11dcc8d6a9c0fdab4cb6684fe690d372adb55
--- /dev/null
+++ b/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\Diagnostics\Test\Unit;
+
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Plugins\Diagnostics\DiagnosticService;
+use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithError;
+use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithSuccess;
+use Piwik\Plugins\Diagnostics\Test\Mock\DiagnosticWithWarning;
+
+class DiagnosticServiceTest extends \PHPUnit_Framework_TestCase
+{
+    public function test_runDiagnostics()
+    {
+        $mandatoryDiagnostics = array(
+            new DiagnosticWithError(),
+        );
+        $optionalDiagnostics = array(
+            new DiagnosticWithWarning(),
+            new DiagnosticWithSuccess(),
+        );
+
+        $service = new DiagnosticService($mandatoryDiagnostics, $optionalDiagnostics, array());
+
+        $report = $service->runDiagnostics();
+
+        $results = $report->getAllResults();
+
+        $this->assertCount(3, $results);
+        $this->assertEquals('Error', $results[0]->getLabel());
+        $this->assertEquals(DiagnosticResult::STATUS_ERROR, $results[0]->getStatus());
+        $this->assertEquals('Warning', $results[1]->getLabel());
+        $this->assertEquals(DiagnosticResult::STATUS_WARNING, $results[1]->getStatus());
+        $this->assertEquals('Success', $results[2]->getLabel());
+        $this->assertEquals(DiagnosticResult::STATUS_OK, $results[2]->getStatus());
+    }
+}
diff --git a/plugins/Diagnostics/config/config.php b/plugins/Diagnostics/config/config.php
new file mode 100644
index 0000000000000000000000000000000000000000..566b4765354af50e0a21c9f579b2a5182419d895
--- /dev/null
+++ b/plugins/Diagnostics/config/config.php
@@ -0,0 +1,39 @@
+<?php
+
+return array(
+    // Diagnostics for everything that is required for Piwik to run
+    'diagnostics.required' => array(
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpVersionCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\DbAdapterCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpExtensionsCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpFunctionsCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PhpSettingsCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\WriteAccessCheck'),
+    ),
+    // Diagnostics for recommended features
+    'diagnostics.optional' => array(
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\TrackerCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\TimezoneCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\HttpClientCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\PageSpeedCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\GdExtensionCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedExtensionsCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\RecommendedFunctionsCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\NfsDiskCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\CronArchivingCheck'),
+        DI\get('Piwik\Plugins\Diagnostics\Diagnostic\LoadDataInfileCheck'),
+    ),
+    // Allows other plugins to disable diagnostics that were previously registered
+    'diagnostics.disabled' => array(),
+
+    'Piwik\Plugins\Diagnostics\DiagnosticService' => DI\object()
+        ->constructor(DI\get('diagnostics.required'), DI\get('diagnostics.optional'), DI\get('diagnostics.disabled')),
+
+    'Piwik\Plugins\Diagnostics\Diagnostic\MemoryLimitCheck' => DI\object()
+        ->constructorParameter('minimumMemoryLimit', DI\get('ini.General.minimum_memory_limit')),
+
+    'Piwik\Plugins\Diagnostics\Diagnostic\WriteAccessCheck' => DI\object()
+        ->constructorParameter('tmpPath', DI\get('path.tmp')),
+);
diff --git a/plugins/Diagnostics/plugin.json b/plugins/Diagnostics/plugin.json
new file mode 100644
index 0000000000000000000000000000000000000000..da53096d70316d8aa9958e9672c43afaecc98d4a
--- /dev/null
+++ b/plugins/Diagnostics/plugin.json
@@ -0,0 +1,3 @@
+{
+  "description": "Performs diagnostics to check that Piwik is installed and runs correctly."
+}
\ No newline at end of file
diff --git a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png
index 7b84c80fc159c6e13c8278c4adeb0d7400b29776..8026c389a618f1c6781b5b62324385c9cfa9d8c3 100644
Binary files a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png and b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePage.png differ
diff --git a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png
index 386280a3b83dcdebf00cc1da1a7e3f2bf7f028b0..39af7946e30ae85e9a429469b0727282ed90abc9 100644
Binary files a/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png and b/plugins/ExamplePlugin/tests/UI/expected-ui-screenshots/SimpleUITest_simplePagePartial.png differ
diff --git a/plugins/Goals/templates/getOverviewView.twig b/plugins/Goals/templates/getOverviewView.twig
index 281d65952c03258ab9b6f8177d307382b4c0eede..798c8fc285d62db02beb0d587607bb3b945d5a74 100644
--- a/plugins/Goals/templates/getOverviewView.twig
+++ b/plugins/Goals/templates/getOverviewView.twig
@@ -15,20 +15,21 @@
             </a>
         </h2>
 
-        <div id='leftcolumn'>
-            <div class="sparkline">{{ sparkline(goal.urlSparklineConversions) }}
-                {{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }}
-                {% if goal.goalAllowMultipleConversionsPerVisit %}
-                    ({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>") | raw }})
-                {% endif %}
+        <div class="row">
+            <div class="col-md-6">
+                <div class="sparkline">{{ sparkline(goal.urlSparklineConversions) }}
+                    {{ 'Goals_Conversions'|translate("<strong>"~nb_conversions~"</strong>")|raw }}
+                    {% if goal.goalAllowMultipleConversionsPerVisit %}
+                        ({{ 'General_NVisits'|translate("<strong>"~nb_visits_converted~"</strong>") | raw }})
+                    {% endif %}
+                </div>
             </div>
-        </div>
-        <div id='rightcolumn'>
-            <div class="sparkline">{{ sparkline(goal.urlSparklineConversionRate) }}
-                {{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }}
+            <div class="col-md-6">
+                <div class="sparkline">{{ sparkline(goal.urlSparklineConversionRate) }}
+                    {{ 'Goals_ConversionRate'|translate("<strong>"~conversion_rate~"</strong>")|raw }}
+                </div>
             </div>
         </div>
-        <br class="clear"/>
     </div>
 {% endfor %}
 
diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php
index cdce51d111a41dbe4e0069aa9dee8d0c25d7ac86..d2c391fdb358500d1ed38455533487f17a35500d 100644
--- a/plugins/Installation/Controller.php
+++ b/plugins/Installation/Controller.php
@@ -23,9 +23,8 @@ use Piwik\Http;
 use Piwik\Option;
 use Piwik\Piwik;
 use Piwik\Plugin\Manager;
-use Piwik\Plugins\CoreUpdater\CoreUpdater;
+use Piwik\Plugins\Diagnostics\DiagnosticService;
 use Piwik\Plugins\LanguagesManager\LanguagesManager;
-use Piwik\Plugins\PrivacyManager\IPAnonymizer;
 use Piwik\Plugins\SitesManager\API as APISitesManager;
 use Piwik\Plugins\UserCountry\LocationProvider;
 use Piwik\Plugins\UsersManager\API as APIUsersManager;
@@ -112,15 +111,12 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
             __FUNCTION__
         );
 
-        $view->duringInstall = true;
+        // Do not use dependency injection because this service requires a lot of sub-services across plugins
+        /** @var DiagnosticService $diagnosticService */
+        $diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService');
+        $view->diagnosticReport = $diagnosticService->runDiagnostics();
 
-        $this->setupSystemCheckView($view);
-
-        $view->showNextStep = !$view->problemWithSomeDirectories
-            && $view->infos['phpVersion_ok']
-            && count($view->infos['adapters'])
-            && !count($view->infos['missing_extensions'])
-            && !count($view->infos['missing_functions']);
+        $view->showNextStep = !$view->diagnosticReport->hasErrors();
 
         // On the system check page, if all is green, display Next link at the top
         $view->showNextStepAtTop = $view->showNextStep;
@@ -445,15 +441,6 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
         return 'Hello, world!';
     }
 
-    /**
-     * Get system information
-     */
-    public static function getSystemInformation()
-    {
-        $systemCheck = new SystemCheck();
-        return $systemCheck->getSystemInformation();
-    }
-
     /**
      * This controller action renders an admin tab that runs the installation
      * system check, so people can see if there are any issues w/ their running
@@ -472,13 +459,9 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
         );
         $this->setBasicVariablesView($view);
 
-        $view->duringInstall = false;
-
-        $this->setupSystemCheckView($view);
-
-        $infos = $view->infos;
-        $infos['extra'] = SystemCheck::performAdminPageOnlySystemCheck();
-        $view->infos = $infos;
+        /** @var DiagnosticService $diagnosticService */
+        $diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService');
+        $view->diagnosticReport = $diagnosticService->runDiagnostics();
 
         return $view->render();
     }
@@ -645,20 +628,6 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
         }
     }
 
-    /**
-     * Utility function, sets up a view that will display system check info.
-     *
-     * @param View $view
-     */
-    private function setupSystemCheckView($view)
-    {
-        $view->infos = self::getSystemInformation();
-
-        $view->helpMessages = $this->getSystemCheckHelpMessages();
-
-        $view->problemWithSomeDirectories = (false !== array_search(false, $view->infos['directories']));
-    }
-
     private function createSuperUser($login, $password, $email)
     {
         $self = $this;
@@ -746,44 +715,4 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
             return $result;
         });
     }
-
-    /**
-     * @return array
-     */
-    private function getSystemCheckHelpMessages()
-    {
-        $helpMessages = array(
-            // Extensions
-            'zlib' => 'Installation_SystemCheckZlibHelp',
-            'gzopen' => 'Installation_SystemCheckZlibHelp',
-            'SPL' => 'Installation_SystemCheckSplHelp',
-            'iconv' => 'Installation_SystemCheckIconvHelp',
-            'mbstring' => 'Installation_SystemCheckMbstringHelp',
-            'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php',
-            'json' => 'Installation_SystemCheckWarnJsonHelp',
-            'libxml' => 'Installation_SystemCheckWarnLibXmlHelp',
-            'dom' => 'Installation_SystemCheckWarnDomHelp',
-            'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp',
-
-            // Functions
-            'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp',
-            'mail' => 'Installation_SystemCheckMailHelp',
-            'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp',
-            'glob' => 'Installation_SystemCheckGlobHelp',
-            'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp',
-            'create_function' => 'Installation_SystemCheckCreateFunctionHelp',
-            'eval' => 'Installation_SystemCheckEvalHelp',
-            'gzcompress' => 'Installation_SystemCheckGzcompressHelp',
-            'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp',
-            'pack' => 'Installation_SystemCheckPackHelp',
-            'php5-json' => 'Installation_SystemCheckJsonHelp',
-        );
-
-        // Add standard message for required PHP.ini settings
-        $requiredSettings = SystemCheck::getRequiredPhpSettings();
-        foreach($requiredSettings as $requiredSetting) {
-            $helpMessages[$requiredSetting] = Piwik::translate('Installation_SystemCheckPhpSetting', $requiredSetting);
-        }
-        return $helpMessages;
-    }
 }
diff --git a/plugins/Installation/SystemCheck.php b/plugins/Installation/SystemCheck.php
deleted file mode 100644
index 93ed1ac75f162843772dda856b6c26beb4468818..0000000000000000000000000000000000000000
--- a/plugins/Installation/SystemCheck.php
+++ /dev/null
@@ -1,520 +0,0 @@
-<?php
-/**
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
-namespace Piwik\Plugins\Installation;
-
-use Piwik\CliMulti;
-use Piwik\Common;
-use Piwik\Config;
-use Piwik\Container\StaticContainer;
-use Piwik\Db;
-use Piwik\Db\Adapter;
-use Piwik\DbHelper;
-use Piwik\Filechecks;
-use Piwik\Filesystem;
-use Piwik\Http;
-use Piwik\Piwik;
-use Piwik\Plugins\CoreUpdater;
-use Piwik\Plugins\UserCountry\LocationProvider;
-use Piwik\SettingsServer;
-use Piwik\Url;
-
-class SystemCheck
-{
-    /**
-     * Get system information
-     */
-    public static function getSystemInformation()
-    {
-        global $piwik_minimumPHPVersion;
-
-        $infos = array();
-
-        $infos['directories'] = self::getDirectoriesWritableStatus();
-        $infos['can_auto_update'] = Filechecks::canAutoUpdate();
-
-        self::initServerFilesForSecurity();
-
-        $infos['phpVersion_minimum'] = $piwik_minimumPHPVersion;
-        $infos['phpVersion'] = PHP_VERSION;
-        $infos['phpVersion_ok'] = self::isPhpVersionValid($infos['phpVersion']);
-
-        // critical errors
-        $infos['needed_extensions'] = self::getRequiredExtensions();
-        $infos['missing_extensions'] = self::getRequiredExtensionsMissing();
-
-        $infos['pdo_ok'] = self::isPhpExtensionLoaded('PDO');
-        $infos['adapters'] = Adapter::getAdapters();
-
-        $infos['needed_functions'] = self::getRequiredFunctions();
-        $infos['missing_functions'] = self::getRequiredFunctionsMissing();
-
-        // warnings
-        $infos['desired_extensions'] = self::getRecommendedExtensions();
-        $infos['missing_desired_extensions'] = self::getRecommendedExtensionsMissing();
-
-        $infos['desired_functions'] = self::getRecommendedFunctions();
-        $infos['missing_desired_functions'] = self::getRecommendedFunctionsMissing();
-
-        $infos['needed_settings'] = self::getRequiredPhpSettings();
-        $infos['missing_settings'] = self::getMissingPhpSettings();
-
-        $infos['pagespeed_module_disabled_ok'] = self::isPageSpeedDisabled();
-
-        $infos['openurl'] = Http::getTransportMethod();
-        $infos['gd_ok'] = SettingsServer::isGdExtensionEnabled();
-        $infos['serverVersion'] = addslashes(isset($_SERVER['SERVER_SOFTWARE']) ?: '');
-        $infos['serverOs'] = @php_uname();
-        $infos['serverTime'] = date('H:i:s');
-
-        $infos['memoryMinimum'] = self::getMinimumRecommendedMemoryLimit();
-        $infos['memory_ok'] = true;
-        $infos['memoryCurrent'] = '';
-
-        SettingsServer::raiseMemoryLimitIfNecessary();
-        if (($memoryValue = SettingsServer::getMemoryLimitValue()) > 0) {
-            $infos['memoryCurrent'] = $memoryValue . 'M';
-            $infos['memory_ok'] = $memoryValue >= self::getMinimumRecommendedMemoryLimit();
-        }
-
-        $infos['isWindows'] = SettingsServer::isWindows();
-
-        $integrityInfo = Filechecks::getFileIntegrityInformation();
-        $infos['integrity'] = $integrityInfo[0];
-
-        $infos['integrityErrorMessages'] = array();
-        if (isset($integrityInfo[1])) {
-            if ($infos['integrity'] == false) {
-                $infos['integrityErrorMessages'][] = Piwik::translate('General_FileIntegrityWarningExplanation');
-            }
-            $infos['integrityErrorMessages'] = array_merge($infos['integrityErrorMessages'], array_slice($integrityInfo, 1));
-        }
-
-        $infos['timezone'] = SettingsServer::isTimezoneSupportEnabled();
-
-        $process = new CliMulti();
-        $infos['cli_process_ok'] = $process->supportsAsync();
-
-        $infos['tracker_status'] = Common::getRequestVar('trackerStatus', 0, 'int');
-
-        $infos['is_nfs'] = Filesystem::checkIfFileSystemIsNFS();
-
-        $infos['https_update'] = CoreUpdater\Controller::isUpdatingOverHttps();
-
-        $infos = self::enrichSystemChecks($infos);
-
-        return $infos;
-    }
-
-    /**
-     * This can be overriden to provide a Customised System Check.
-     *
-     * @api
-     * @param $infos
-     * @return mixed
-     */
-    public static function enrichSystemChecks($infos)
-    {
-        // determine whether there are any errors/warnings from the checks done above
-        $infos['has_errors'] = false;
-        $infos['has_warnings'] = false;
-        if (in_array(0, $infos['directories']) // if a directory is not writable
-            || !$infos['phpVersion_ok']
-            || !empty($infos['missing_extensions'])
-            || empty($infos['adapters'])
-            || !empty($infos['missing_functions'])
-        ) {
-            $infos['has_errors'] = true;
-        }
-
-        if (   !empty($infos['missing_desired_extensions'])
-            || !empty($infos['missing_desired_functions'])
-            || !empty($infos['missing_settings'])
-            || !$infos['pagespeed_module_disabled_ok']
-            || !$infos['gd_ok']
-            || !$infos['memory_ok']
-            || !empty($infos['integrityErrorMessages'])
-            || !$infos['timezone'] // if timezone support isn't available
-            || $infos['tracker_status'] != 0
-            || $infos['is_nfs']
-        ) {
-            $infos['has_warnings'] = true;
-        }
-        return $infos;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getDirectoriesShouldBeWritable()
-    {
-        $tmpPath = StaticContainer::get('path.tmp');
-
-        $directoriesToCheck = array(
-            $tmpPath,
-            $tmpPath . '/assets/',
-            $tmpPath . '/cache/',
-            $tmpPath . '/climulti/',
-            $tmpPath . '/latest/',
-            $tmpPath . '/logs/',
-            $tmpPath . '/sessions/',
-            $tmpPath . '/tcpdf/',
-            $tmpPath . '/templates_c/',
-        );
-
-        if (!DbHelper::isInstalled()) {
-            // at install, need /config to be writable (so we can create config.ini.php)
-            $directoriesToCheck[] = '/config/';
-        }
-        return $directoriesToCheck;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRequiredFunctions()
-    {
-        return array(
-            'debug_backtrace',
-            'create_function',
-            'eval',
-            'gzcompress',
-            'gzuncompress',
-            'pack',
-        );
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRecommendedExtensions()
-    {
-        return array(
-            'json',
-            'libxml',
-            'dom',
-            'SimpleXML',
-        );
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRecommendedFunctions()
-    {
-        return array(
-            'set_time_limit',
-            'mail',
-            'parse_ini_file',
-            'glob',
-            'gzopen',
-        );
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRequiredExtensions()
-    {
-        $requiredExtensions = array(
-            'zlib',
-            'SPL',
-            'iconv',
-            'json',
-            'mbstring',
-        );
-
-        if (!defined('HHVM_VERSION')) {
-            // HHVM provides the required subset of Reflection but lists Reflections as missing
-            $requiredExtensions[] = 'Reflection';
-        }
-
-        return $requiredExtensions;
-    }
-
-    /**
-     * Performs extra system checks for the 'System Check' admin page. These
-     * checks are not performed during Installation.
-     *
-     * The following checks are performed:
-     *  - Check for whether LOAD DATA INFILE can be used. The result of the check
-     *    is stored in $result['load_data_infile_available']. The error message is
-     *    stored in $result['load_data_infile_error'].
-     *
-     * - Check whether geo location is setup correctly
-     *
-     * @return array
-     */
-    public static function performAdminPageOnlySystemCheck()
-    {
-        $result = array();
-        self::checkLoadDataInfile($result);
-        self::checkGeolocation($result);
-        return $result;
-    }
-
-    /**
-     * Test if function exists.  Also handles case where function is disabled via Suhosin.
-     *
-     * @param string $functionName Function name
-     * @return bool True if function exists (not disabled); False otherwise.
-     */
-    protected static function functionExists($functionName)
-    {
-        // eval() is a language construct
-        if ($functionName == 'eval') {
-            // does not check suhosin.executor.eval.whitelist (or blacklist)
-            if (extension_loaded('suhosin')) {
-                return @ini_get("suhosin.executor.disable_eval") != "1";
-            }
-            return true;
-        }
-
-        $exists = function_exists($functionName);
-        if (extension_loaded('suhosin')) {
-            $blacklist = @ini_get("suhosin.executor.func.blacklist");
-            if (!empty($blacklist)) {
-                $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist)));
-                return $exists && !in_array($functionName, $blacklistFunctions);
-            }
-        }
-        return $exists;
-    }
-
-    private static function checkGeolocation(&$result)
-    {
-        $currentProviderId = LocationProvider::getCurrentProviderId();
-        $allProviders = LocationProvider::getAllProviderInfo();
-        $isRecommendedProvider = in_array($currentProviderId, array( LocationProvider\GeoIp\Php::ID, $currentProviderId == LocationProvider\GeoIp\Pecl::ID));
-        $isProviderInstalled = ($allProviders[$currentProviderId]['status'] == LocationProvider::INSTALLED);
-
-        $result['geolocation_using_non_recommended'] = $result['geolocation_ok'] = false;
-        if ($isRecommendedProvider && $isProviderInstalled) {
-            $result['geolocation_ok'] = true;
-        } elseif ($isProviderInstalled) {
-            $result['geolocation_using_non_recommended'] = true;
-        }
-    }
-
-    private static function checkLoadDataInfile(&$result)
-    {
-        // check if LOAD DATA INFILE works
-        $optionTable = Common::prefixTable('option');
-        $testOptionNames = array('test_system_check1', 'test_system_check2');
-
-        $result['load_data_infile_available'] = false;
-        try {
-            $result['load_data_infile_available'] = \Piwik\Db\BatchInsert::tableInsertBatch(
-                $optionTable,
-                array('option_name', 'option_value'),
-                array(
-                    array($testOptionNames[0], '1'),
-                    array($testOptionNames[1], '2'),
-                ),
-                $throwException = true
-            );
-        } catch (\Exception $ex) {
-            $result['load_data_infile_error'] = str_replace("\n", "<br/>", $ex->getMessage());
-        }
-
-        // delete the temporary rows that were created
-        Db::exec("DELETE FROM `$optionTable` WHERE option_name IN ('" . implode("','", $testOptionNames) . "')");
-    }
-
-    protected static function initServerFilesForSecurity()
-    {
-        ServerFilesGenerator::createWebConfigFiles();
-        ServerFilesGenerator::createHtAccessFiles();
-        ServerFilesGenerator::createWebRootFiles();
-    }
-
-    /**
-     * @param string $phpVersion
-     * @return bool
-     */
-    public static function isPhpVersionValid($phpVersion)
-    {
-        global $piwik_minimumPHPVersion;
-        return version_compare($piwik_minimumPHPVersion, $phpVersion) <= 0;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getDirectoriesWritableStatus()
-    {
-        $directoriesToCheck = self::getDirectoriesShouldBeWritable();
-        $directoriesWritableStatus = Filechecks::checkDirectoriesWritable($directoriesToCheck);
-        return $directoriesWritableStatus;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getLoadedExtensions()
-    {
-        static $extensions = null;
-
-        if(is_null($extensions)) {
-            $extensions = @get_loaded_extensions();
-        }
-        return $extensions;
-    }
-
-
-    /**
-     * @param $needed_extension
-     * @return bool
-     */
-    protected static function isPhpExtensionLoaded($needed_extension)
-    {
-        return in_array($needed_extension, self::getLoadedExtensions());
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRequiredExtensionsMissing()
-    {
-        $missingExtensions = array();
-        foreach (self::getRequiredExtensions() as $requiredExtension) {
-            if (!self::isPhpExtensionLoaded($requiredExtension)) {
-                $missingExtensions[] = $requiredExtension;
-            }
-        }
-
-        // Special case for mbstring
-        if (!function_exists('mb_get_info')
-            || ((int)ini_get('mbstring.func_overload')) != 0) {
-            $missingExtensions[] = 'mbstring';
-        }
-
-        return $missingExtensions;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRecommendedExtensionsMissing()
-    {
-        return array_diff(self::getRecommendedExtensions(), self::getLoadedExtensions());
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRecommendedFunctionsMissing()
-    {
-        return self::getFunctionsMissing(self::getRecommendedFunctions());
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getRequiredFunctionsMissing()
-    {
-        return self::getFunctionsMissing(self::getRequiredFunctions());
-    }
-
-    protected static function getFunctionsMissing($functionsToTestFor)
-    {
-        $missingFunctions = array();
-        foreach ($functionsToTestFor as $function) {
-            if (!self::functionExists($function)) {
-                $missingFunctions[] = $function;
-            }
-        }
-        return $missingFunctions;
-    }
-
-    /**
-     * @return mixed
-     */
-    protected static function getMinimumRecommendedMemoryLimit()
-    {
-        return Config::getInstance()->General['minimum_memory_limit'];
-    }
-
-    private static function isPhpVersionAtLeast56()
-    {
-       return version_compare( PHP_VERSION, '5.6', '>=');
-    }
-
-    /**
-     * @return array
-     */
-    public static function getRequiredPhpSettings()
-    {
-        $requiredPhpSettings = array(
-            // setting = required value
-            // Note: value must be an integer only
-            'session.auto_start=0',
-        );
-
-        if (self::isPhpVersionAtLeast56()) {
-            // always_populate_raw_post_data must be -1
-            $requiredPhpSettings[] = 'always_populate_raw_post_data=-1';
-        }
-        return $requiredPhpSettings;
-    }
-
-    /**
-     * @return array
-     */
-    protected static function getMissingPhpSettings()
-    {
-        $missingPhpSettings = array();
-        foreach(self::getRequiredPhpSettings() as $requiredSetting) {
-            list($requiredSettingName, $requiredSettingValue) = explode('=', $requiredSetting);
-
-            $currentValue = ini_get($requiredSettingName);
-            $currentValue = (int)$currentValue;
-
-            if($currentValue != $requiredSettingValue) {
-                $missingPhpSettings[] = $requiredSetting;
-            }
-        }
-        return $missingPhpSettings;
-    }
-
-    protected static function isPageSpeedDisabled()
-    {
-        $url = Url::getCurrentUrlWithoutQueryString() . '?module=Installation&action=getEmptyPageForSystemCheck';
-
-        try {
-            $page = Http::sendHttpRequest($url,
-                $timeout = 1,
-                $userAgent = null,
-                $destinationPath = null,
-                $followDepth = 0,
-                $acceptLanguage = false,
-                $byteRange = false,
-
-                // Return headers
-                $getExtendedInfo = true
-            );
-        } catch(\Exception $e) {
-
-            // If the test failed, we assume Page speed is not enabled
-            return true;
-        }
-
-        $headers = $page['headers'];
-
-        return !self::isPageSpeedHeaderFound($headers);
-    }
-
-    /**
-     * @param $headers
-     * @return bool
-     */
-    protected static function isPageSpeedHeaderFound($headers)
-    {
-        return isset($headers['X-Mod-Pagespeed']) || isset($headers['X-Page-Speed']);
-    }
-}
\ No newline at end of file
diff --git a/plugins/Installation/lang/en.json b/plugins/Installation/lang/en.json
index 2d3afbf8c92ee76aa12f10efaf865b76c14f2e49..689760ef0ebd58709d2c92c050974f94b9a68d9b 100644
--- a/plugins/Installation/lang/en.json
+++ b/plugins/Installation/lang/en.json
@@ -65,6 +65,7 @@
         "SystemCheck": "System Check",
         "SystemCheckAutoUpdateHelp": "Note: Piwik's One Click update requires write-permissions to the Piwik folder and its contents.",
         "SystemCheckCreateFunctionHelp": "Piwik uses anonymous functions for callbacks.",
+        "SystemCheckDatabaseExtensions": "MySQL extensions",
         "SystemCheckDatabaseHelp": "Piwik requires either the mysqli extension or both the PDO and pdo_mysql extensions.",
         "SystemCheckDebugBacktraceHelp": "View::factory won't be able to create views for the calling module.",
         "SystemCheckError": "An error occured - must be fixed before you proceed",
diff --git a/plugins/Installation/templates/_integrityDetails.twig b/plugins/Installation/templates/_integrityDetails.twig
index b285bb46f9ea6e7fcc50cafbd6178be979da3346..ddf9712ec0e07ddfa659e9199b41bfae93e34680 100644
--- a/plugins/Installation/templates/_integrityDetails.twig
+++ b/plugins/Installation/templates/_integrityDetails.twig
@@ -1,6 +1,3 @@
-{% if warningMessages is not defined %}
-    {% set warningMessages=infos.integrityErrorMessages %}
-{% endif %}
 <div id="integrity-results" title="{{ 'Installation_SystemCheckFileIntegrity'|translate }}" style="display:none; font-size: 62.5%;">
     <table>
         {% for msg in warningMessages %}
diff --git a/plugins/Installation/templates/_systemCheckSection.twig b/plugins/Installation/templates/_systemCheckSection.twig
index b56db9b6726e8e6cf2da6b8b8926abc44a610f38..c1f8c14332c723f6a646a648299bcd4dc86c97a4 100755
--- a/plugins/Installation/templates/_systemCheckSection.twig
+++ b/plugins/Installation/templates/_systemCheckSection.twig
@@ -1,372 +1,50 @@
-{% set ok %}<img src='plugins/Morpheus/images/ok.png' />{% endset %}
-{% set error %}<img src='plugins/Morpheus/images/error.png' />{% endset %}
-{% set warning %}<img src='plugins/Morpheus/images/warning.png' />{% endset %}
-{% set link %}<img src='plugins/Morpheus/images/link.gif' />{% endset %}
+{% import _self as local %}
 
 <table class="infosServer" id="systemCheckRequired">
-    <tr>
-        {% set MinPHP %}{{ 'Installation_SystemCheckPhp'|translate }} &gt;= {{ infos.phpVersion_minimum }}{% endset %}
-        <td class="label">{{ MinPHP }}</td>
-
-        <td>
-            {% if infos.phpVersion_ok %}
-                {{ ok }} {{ infos.phpVersion }}
-            {% else %}
-                {{ error }} <span class="err">{{ 'General_Error'|translate }}: {{ 'General_Required'|translate(MinPHP)|raw }}</span>
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">PDO {{ 'Installation_Extension'|translate }}</td>
-        <td>
-            {% if infos.pdo_ok %}
-                {{- ok -}}
-            {% else %}
-                -
-            {% endif %}
-        </td>
-    </tr>
-    {% for adapter, port in infos.adapters %}
-        <tr>
-            <td class="label">{{ adapter }} {{ 'Installation_Extension'|translate }}</td>
-            <td>{{ ok }}</td>
-        </tr>
-    {% endfor %}
-    {% if infos.adapters|length == 0 %}
-        <tr>
-            <td colspan="2" class="error" style="font-size: small;">
-                {{ 'Installation_SystemCheckDatabaseHelp'|translate }}
-                <p>
-                    {% if infos.isWindows %}
-                        {{ 'Installation_SystemCheckWinPdoAndMysqliHelp'|translate("<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />")|raw|nl2br }}
-                    {% else %}
-                        {{ 'Installation_SystemCheckPdoAndMysqliHelp'|translate("<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />","<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />")|raw }}
-                    {% endif %}
-                    {{ 'Installation_RestartWebServer'|translate }}
-                    <br/>
-                    <br/>
-                    {{ 'Installation_SystemCheckPhpPdoAndMysqli'|translate("<a style=\"color:red\" href=\"http:\/\/php.net\/pdo\">","<\/a>","<a style=\"color:red\" href=\"http:\/\/php.net\/mysqli\">","<\/a>")|raw|nl2br }}
-                </p>
-            </td>
-        </tr>
-    {% endif %}
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckExtensions'|translate }}</td>
-        <td>
-            {% for needed_extension in infos.needed_extensions %}
-                {% if needed_extension in infos.missing_extensions %}
-                    {{ error }}
-                    <br/>{{ 'Installation_RestartWebServer'|translate }}
-                {% else %}
-                    {{ ok }}
-                {% endif %}
-                {{ needed_extension }}
-                <br/>
-            {% endfor %}
-        </td>
-    </tr>
-    {% if infos.missing_extensions|length > 0 %}
-        <tr>
-            <td colspan="2" class="error" style="font-size: small;">
-                {% for missing_extension in infos.missing_extensions %}
-                    <p>
-                        <em>{{ helpMessages[missing_extension]|translate }}</em>
-                    </p>
-                {% endfor %}
-            </td>
-        </tr>
-    {% endif %}
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckFunctions'|translate }}</td>
-        <td>
-            {% for needed_function in infos.needed_functions %}
-                {% if needed_function in infos.missing_functions %}
-                    {{ error }}
-                    <span class='err'>{{ needed_function }}</span>
-                    <p>
-                        <em>
-                            {{ helpMessages[needed_function]|translate }}
-                            <br/>{{ 'Installation_RestartWebServer'|translate }}
-                        </em>
-                    </p>
-                {% else %}
-                    {{ ok }} {{ needed_function }}
-                    <br/>
-                {% endif %}
-            {% endfor %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckSettings'|translate }}</td>
-        <td>
-            {% for needed_setting in infos.needed_settings %}
-                {% if needed_setting in infos.missing_settings %}
-                    {{ error }}
-                    <span class='err'>{{ needed_setting }}</span>
-                    <p>
-                        <em>
-                            {{ helpMessages[needed_setting]|translate }}
-                            <br/>{{ 'Installation_RestartWebServer'|translate }}
-                        </em>
-                    </p>
-                {% else %}
-                    {{ ok }} {{ needed_setting }}
-                    <br/>
-                {% endif %}
-            {% endfor %}
-        </td>
-    </tr>
-    <tr>
-        <td valign="top">
-            {{ 'Installation_SystemCheckWriteDirs'|translate }}
-        </td>
-        <td style="font-size: small;">
-            {% for dir, bool in infos.directories %}
-                {% if bool %}
-                    {{ ok }}
-                {% else %}
-                    <span style="color:red;">{{ error }}</span>
-                {% endif %}
-                {{ dir }}
-                <br/>
-            {% endfor %}
-        </td>
-    </tr>
-    {% if problemWithSomeDirectories %}
-        <tr>
-            <td colspan="2" class="error">
-                {{ 'Installation_SystemCheckWriteDirsHelp'|translate }}:
-                {% for dir,bool in infos.directories %}
-                    <ul>
-                        {% if not bool %}
-                            <li>
-                                <pre>chmod a+w {{ dir }}</pre>
-                            </li>
-                        {% endif %}
-                    </ul>
-                {% endfor %}
-            </td>
-        </tr>
-    {% endif %}
+    {{ local.diagnosticTable(diagnosticReport.getMandatoryDiagnosticResults()) }}
 </table>
-<br/>
 
 <h3>{{ 'Installation_Optional'|translate }}</h3>
+
 <table class="infos" id="systemCheckOptional">
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckFileIntegrity'|translate }}</td>
-        <td>
-            {% if infos.integrityErrorMessages is empty %}
-                {{ ok }}
-            {% else %}
-                {% if infos.integrity %}
-                    {{ warning }}
-                    <em>{{ infos.integrityErrorMessages[0] }}</em>
-                {% else %}
-                    {{ error }}
-                    <em>{{ infos.integrityErrorMessages[0] }}</em>
-                {% endif %}
-                {% if infos.integrityErrorMessages|length > 1 %}
-                    <button id="more-results" class="ui-button ui-state-default ui-corner-all">{{ 'General_Details'|translate }}</button>
-                {% endif %}
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckTracker'|translate }}</td>
-        <td>
-            {% if infos.tracker_status == 0 %}
-                {{ ok }}
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ infos.tracker_status }}
-                    <br/>{{ 'Installation_SystemCheckTrackerHelp'|translate }} </span>
-                <br/>
-                {{ 'Installation_RestartWebServer'|translate }}
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckMemoryLimit'|translate }}</td>
-        <td>
-            {% if infos.memory_ok %}
-                {{ ok }} {{ infos.memoryCurrent }}
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ infos.memoryCurrent }}</span>
-                <br/>
-                {{ 'Installation_SystemCheckMemoryLimitHelp'|translate }}
-                {{ 'Installation_RestartWebServer'|translate }}
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'SitesManager_Timezone'|translate }}</td>
-        <td>
-            {% if infos.timezone %}
-                {{ ok }}
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ 'SitesManager_AdvancedTimezoneSupportNotFound'|translate }} </span>
-                <br/>
-                <a href="http://php.net/manual/en/datetime.installation.php" rel="noreferrer" target="_blank">Timezone PHP documentation</a>
-                .
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckOpenURL'|translate }}</td>
-        <td>
-            {% if infos.openurl %}
-                {{ ok }} {{ infos.openurl }}
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ 'Installation_SystemCheckOpenURLHelp'|translate }}</span>
-            {% endif %}
-            {% if not infos.can_auto_update %}
-                <br/>
-                {{ warning }} <span class="warn">{{ 'Installation_SystemCheckAutoUpdateHelp'|translate }}</span>
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckPageSpeedDisabled'|translate }}</td>
-        <td>
-            {% if infos.pagespeed_module_disabled_ok %}
-                {{ ok }}
-            {% else %}
-                {{ warning }} <span class="warn">{{ 'Installation_SystemCheckPageSpeedWarn'|translate('(eg. Apache, Nginx or IIS)') }}</span>
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckGDFreeType'|translate }}</td>
-        <td>
-            {% if infos.gd_ok %}
-                {{ ok }}
-            {% else %}
-                {{ warning }} <span class="warn">{{ 'Installation_SystemCheckGDFreeType'|translate }}
-                <br/>
-                {{ 'Installation_SystemCheckGDHelp'|translate }} </span>
-            {% endif %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckOtherExtensions'|translate }}</td>
-        <td>
-            {% for desired_extension in infos.desired_extensions %}
-                {% if desired_extension in infos.missing_desired_extensions %}
-                    {{ warning }}<span class="warn">{{ desired_extension }}</span>
-                    <p>{{ helpMessages[desired_extension]|translate }}</p>
-                {% else %}
-                    {{ ok }} {{ desired_extension }}
-                    <br/>
-                {% endif %}
-            {% endfor %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckOtherFunctions'|translate }}</td>
-        <td>
-            {% for desired_function in infos.desired_functions %}
-                {% if desired_function in infos.missing_desired_functions %}
-                    {{ warning }}
-                    <span class="warn">{{ desired_function }}</span>
-                    <p>{{ helpMessages[desired_function]|translate }}</p>
-                {% else %}
-                    {{ ok }} {{ desired_function }}
-                    <br/>
-                {% endif %}
-            {% endfor %}
-        </td>
-    </tr>
-    <tr>
-        <td class="label">{{ 'Installation_Filesystem'|translate }}</td>
-        <td>
-            {% if not infos.is_nfs %}
-                {{ ok }} {{ 'General_Ok'|translate }}
-                <br/>
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ 'Installation_NfsFilesystemWarning'|translate }}</span>
-                {% if duringInstall is not empty %}
-                    <p>{{ 'Installation_NfsFilesystemWarningSuffixInstall'|translate }}</p>
-                {% else %}
-                    <p>{{ 'Installation_NfsFilesystemWarningSuffixAdmin'|translate }}</p>
-                {% endif %}
-            {% endif %}
-        </td>
-    </tr>
+    {{ local.diagnosticTable(diagnosticReport.getOptionalDiagnosticResults()) }}
+</table>
 
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckCronArchiveProcess'|translate }}</td>
-        <td>
-            {# Both results are OK, but we report the status to help troubleshoot #}
-            {% if infos.cli_process_ok %}
-                {{ ok }} {{ 'Installation_SystemCheckCronArchiveProcessCLI'|translate }}: {{ 'General_Ok'|translate }}
-            {% else %}
-                {{ ok }} {{ 'Installation_SystemCheckCronArchiveProcessCLI'|translate }}:
-                {{ 'Installation_NotSupported'|translate }} {{ 'Goals_Optional'|translate }}
-            {% endif %}
-        </td>
-    </tr>
+{% macro diagnosticTable(results) %}
 
-    {% if duringInstall is empty %}
-    <tr>
-        <td class="label">{{ 'UserCountry_Geolocation'|translate }}</td>
-        <td>
-            {% if infos.extra.geolocation_ok %}
-                {{ ok }} {{ 'General_Ok'|translate }}
-                <br/>
-            {% elseif infos.extra.geolocation_using_non_recommended %}
-                {{ warning }}
-                <span class="warn">{{ 'UserCountry_GeoIpLocationProviderNotRecomnended'|translate }}
-                    {{ 'UserCountry_GeoIpLocationProviderDesc_ServerBased2'|translate('<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>')|raw }}</span>
-                <br/>
-            {% else %}
-                {{ warning }}
-                <span class="warn">{{ 'UserCountry_DefaultLocationProviderDesc1'|translate }}
-                    {{ 'UserCountry_DefaultLocationProviderDesc2'|translate('<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>')|raw }} </span>
-                </span>
-            {% endif %}
-        </td>
-    </tr>
-    {% endif %}
-    {% if infos.extra.load_data_infile_available is defined %}
+    {% set error = constant('Piwik\\Plugins\\Diagnostics\\Diagnostic\\DiagnosticResult::STATUS_ERROR') %}
+    {% set warning = constant('Piwik\\Plugins\\Diagnostics\\Diagnostic\\DiagnosticResult::STATUS_WARNING') %}
+
+    {% set errorIcon %}<img src='plugins/Morpheus/images/error.png' />{% endset %}
+    {% set warningIcon %}<img src='plugins/Morpheus/images/warning.png' />{% endset %}
+    {% set okIcon %}<img src='plugins/Morpheus/images/ok.png' />{% endset %}
+
+    {% for result in results %}
         <tr>
-            <td class="label">{{ 'Installation_DatabaseAbilities'|translate }}</td>
+            <td class="label">{{ result.label }}</td>
             <td>
-                {% if infos.extra.load_data_infile_available %}
-                    {{ ok }} LOAD DATA INFILE
-                    <br/>
-                {% else %}
-                    {{ warning }}
-                    <span class="warn">LOAD DATA INFILE</span>
-                    <br/>
-                    <br/>
-                    <p>{{ 'Installation_LoadDataInfileUnavailableHelp'|translate("LOAD DATA INFILE","FILE") }}</p>
-                    <p>{{ 'Installation_LoadDataInfileRecommended'|translate }}</p>
-                    {% if infos.extra.load_data_infile_error is defined %}
-                        <em><strong>{{ 'General_Error'|translate }}:</strong></em>
-                        {{ infos.extra.load_data_infile_error|raw }}
+                {% for item in result.items %}
+
+                    {% if item.status == error %}
+                        {{ errorIcon }} <span class="err">{{ item.comment|raw }}</span>
+                    {% elseif item.status == warning %}
+                        {{ warningIcon }} {{ item.comment|raw }}
+                    {% else %}
+                        {{ okIcon }} {{ item.comment|raw }}
                     {% endif %}
-                    <p>Troubleshooting: <a target='_blank' href="?module=Proxy&action=redirect&url=http://piwik.org/faq/troubleshooting/%23faq_194">FAQ on piwik.org</a></p>
-                {% endif %}
-            </td>
-        </tr>
-    {% endif %}
 
-    <tr>
-        <td class="label">{{ 'Installation_SystemCheckUpdateHttps'|translate }}</td>
-        <td>
-            {% if infos.https_update %}
-                {{ ok }}
-            {% else %}
-                {{ warning }} {{ 'Installation_SystemCheckUpdateHttpsNotSupported'|translate }}
-            {% endif %}
-        </td>
-    </tr>
+                    <br/>
 
-</table>
+                {% endfor %}
+            </td>
+        </tr>
+        {% if result.longErrorMessage %}
+            <tr>
+                <td colspan="2" class="error" style="font-size: small;">
+                    {{ result.longErrorMessage|raw }}
+                </td>
+            </tr>
+        {% endif %}
+    {% endfor %}
 
-{% include "@Installation/_integrityDetails.twig" %}
+{% endmacro %}
diff --git a/plugins/Installation/templates/systemCheckPage.twig b/plugins/Installation/templates/systemCheckPage.twig
index b66c493eea23f7dc9519d2c44cf608b1f2f43534..88f6b42688da23db3905b0799649caf16c6a7aa8 100755
--- a/plugins/Installation/templates/systemCheckPage.twig
+++ b/plugins/Installation/templates/systemCheckPage.twig
@@ -1,20 +1,24 @@
 {% extends 'admin.twig' %}
 
 {% block content %}
-{% if isSuperUser %}
+
     <h2 piwik-enriched-headline>{{ 'Installation_SystemCheck'|translate }}</h2>
+
     <p style="margin-left:0.2em;">
-        {% if infos.has_errors %}
+        {% if diagnosticReport.hasErrors() %}
             <img src="plugins/Morpheus/images/error.png"/>
-            {{ 'Installation_SystemCheckSummaryThereWereErrors'|translate('<strong>','</strong>','<strong><em>','</em></strong>')|raw }} {{ 'Installation_SeeBelowForMoreInfo'|translate }}
-        {% elseif infos.has_warnings %}
+            {{ 'Installation_SystemCheckSummaryThereWereErrors'|translate('<strong>','</strong>','<strong><em>','</em></strong>')|raw }}
+            {{ 'Installation_SeeBelowForMoreInfo'|translate }}
+        {% elseif diagnosticReport.hasWarnings() %}
             <img src="plugins/Morpheus/images/warning.png"/>
-            {{ 'Installation_SystemCheckSummaryThereWereWarnings'|translate }} {{ 'Installation_SeeBelowForMoreInfo'|translate }}
+            {{ 'Installation_SystemCheckSummaryThereWereWarnings'|translate }}
+            {{ 'Installation_SeeBelowForMoreInfo'|translate }}
         {% else %}
             <img src="plugins/Morpheus/images/ok.png"/>
             {{ 'Installation_SystemCheckSummaryNoProblems'|translate }}
         {% endif %}
     </p>
+
     {% include "@Installation/_systemCheckSection.twig" %}
-{% endif %}
+
 {% endblock %}
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js
index ed701d8ef182315480ab7e9d4f988b9955659cd4..36e057ea9889f7e64d3b546c0377758702a7d0af 100644
--- a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js
+++ b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js
@@ -19,6 +19,7 @@
         function fetchTranslations() {
             piwikApi.fetch({
                 method: 'LanguagesManager.getTranslationsForLanguage',
+                filter_limit: -1,
                 languageCode: 'en'
             }).then(function (response) {
                 if (response) {
diff --git a/plugins/LeftMenu/stylesheets/theme.less b/plugins/LeftMenu/stylesheets/theme.less
index 8e9ea0eab8eded6cdf93d557d12880b2f557f8ca..869d5608281f0f3213a040562e32259902062e58 100644
--- a/plugins/LeftMenu/stylesheets/theme.less
+++ b/plugins/LeftMenu/stylesheets/theme.less
@@ -126,8 +126,7 @@
 
   .pageWrap {
     margin-left: 240px;
-    border-width: 0;
-    padding-top: 0;
+    margin-top: 0;
     max-height: none;
   }
 
diff --git a/plugins/Live/javascripts/live.js b/plugins/Live/javascripts/live.js
index 17e08cded38390d586cb1041acc907318ba937ac..fd634ec61ce0b24cc8831ce0fd386881e6f9b85c 100644
--- a/plugins/Live/javascripts/live.js
+++ b/plugins/Live/javascripts/live.js
@@ -82,7 +82,7 @@
 
                 if (that.isStarted) {
                     window.clearTimeout(that.updateInterval);
-                    if ($(that.element).closest('body').length) {
+                    if (that.element.length && $.contains(document, that.element[0])) {
                         that.updateInterval = window.setTimeout(function() { that._update() }, that.currentInterval);
                     }
                 }
@@ -211,7 +211,7 @@
 $(function() {
     var refreshWidget = function (element, refreshAfterXSecs) {
         // if the widget has been removed from the DOM, abort
-        if ($(element).parent().length == 0) {
+        if (!element.length || !$.contains(document, element[0])) {
             return;
         }
 
diff --git a/plugins/Live/stylesheets/visitor_profile.less b/plugins/Live/stylesheets/visitor_profile.less
index 88b34ff152e0f6f7d8d9d78fc765bcfad98feabb..7b82aee711a459b411ed1616d0ff45b3d94c7204 100644
--- a/plugins/Live/stylesheets/visitor_profile.less
+++ b/plugins/Live/stylesheets/visitor_profile.less
@@ -33,6 +33,7 @@
         padding:0;
         font-weight:bold;
         color:black;
+        border: none;
     }
 
     span.truncated-text-line {
@@ -303,7 +304,6 @@
 }
 
 .visitor-profile-pages-visited {
-    height:42px;
     overflow-y:auto;
     position:relative;
     margin-right:10px;
@@ -317,6 +317,7 @@
 
 .visitor-profile-visits-container {
     overflow-y:auto;
+    overflow-x: hidden;
     position:relative;
     margin-right:10px;
     border-bottom:none!important;
diff --git a/plugins/Live/templates/getVisitList.twig b/plugins/Live/templates/getVisitList.twig
index 4d1fec2a0c1e6950c3cf487a262f80c25e13fa73..18047bf3c2eb7201e3b94ad9ede74bdb17a872d2 100644
--- a/plugins/Live/templates/getVisitList.twig
+++ b/plugins/Live/templates/getVisitList.twig
@@ -4,13 +4,14 @@
         <div class="visitor-profile-visit-title-row">
             <h2 class="visitor-profile-visit-title" data-idvisit="{{ visitInfo.getColumn('idVisit') }}" title="{{ 'Live_ClickToViewMoreAboutVisit'|translate }}">
                 {{ 'General_Visit'|translate }} #{{ startCounter }}
+
+                {% if visitInfo.getColumn('visitDuration') != 0 %}
+                    <span>&nbsp;- ({{ visitInfo.getColumn('visitDurationPretty')|raw }})</span>
+                {% endif %}
+                <span class="visitor-profile-date" title="{{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}">
+                    {{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}
+                </span>
             </h2>
-            {% if visitInfo.getColumn('visitDuration') != 0 %}
-                <span>&nbsp;- ({{ visitInfo.getColumn('visitDurationPretty')|raw }})</span>
-            {% endif %}
-            <span class="visitor-profile-date" title="{{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}">
-                {{ visitInfo.getColumn('serverDatePrettyFirstAction') }} {{ visitInfo.getColumn('serverTimePrettyFirstAction') }}
-            </span>
         </div>
         <ol class="visitor-profile-actions">
             {% include "@Live/_actionsList.twig" with {'actionDetails': visitInfo.getColumn('actionDetails'),
diff --git a/plugins/Login/stylesheets/login.less b/plugins/Login/stylesheets/login.less
index d957f2014ce79e8b28cc1668d6a12510b256f0fe..98dd41624aec5de4d3ac6ba42137c2744d014ea8 100644
--- a/plugins/Login/stylesheets/login.less
+++ b/plugins/Login/stylesheets/login.less
@@ -62,7 +62,7 @@
 
     .loginSection {
         background-color: @login-section-background;
-        width: 360px;
+        width: 420px;
         padding: 30px;
         margin: 50px auto 0 auto;
         border-radius: 3px;
diff --git a/plugins/Monolog/config/config.php b/plugins/Monolog/config/config.php
index 7005573ced9ee32fa39cacf0427bd99df967406b..3fd3fcf63a75067280d2abdfaaba8caee9f9e19b 100644
--- a/plugins/Monolog/config/config.php
+++ b/plugins/Monolog/config/config.php
@@ -7,7 +7,7 @@ use Piwik\Log;
 return array(
 
     'Psr\Log\LoggerInterface' => DI\object('Monolog\Logger')
-        ->constructor('piwik', DI\link('log.handlers'), DI\link('log.processors')),
+        ->constructor('piwik', DI\get('log.handlers'), DI\get('log.processors')),
 
     'log.handlers' => DI\factory(function (ContainerInterface $c) {
         if ($c->has('ini.log.log_writers')) {
@@ -31,25 +31,25 @@ return array(
     }),
 
     'log.processors' => array(
-        DI\link('Piwik\Plugins\Monolog\Processor\SprintfProcessor'),
-        DI\link('Piwik\Plugins\Monolog\Processor\ClassNameProcessor'),
-        DI\link('Piwik\Plugins\Monolog\Processor\RequestIdProcessor'),
-        DI\link('Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor'),
-        DI\link('Monolog\Processor\PsrLogMessageProcessor'),
-        DI\link('Piwik\Plugins\Monolog\Processor\TokenProcessor'),
+        DI\get('Piwik\Plugins\Monolog\Processor\SprintfProcessor'),
+        DI\get('Piwik\Plugins\Monolog\Processor\ClassNameProcessor'),
+        DI\get('Piwik\Plugins\Monolog\Processor\RequestIdProcessor'),
+        DI\get('Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor'),
+        DI\get('Monolog\Processor\PsrLogMessageProcessor'),
+        DI\get('Piwik\Plugins\Monolog\Processor\TokenProcessor'),
     ),
 
     'Piwik\Plugins\Monolog\Handler\FileHandler' => DI\object()
-        ->constructor(DI\link('log.file.filename'), DI\link('log.level'))
-        ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
+        ->constructor(DI\get('log.file.filename'), DI\get('log.level'))
+        ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
 
     'Piwik\Plugins\Monolog\Handler\DatabaseHandler' => DI\object()
-        ->constructor(DI\link('log.level'))
-        ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
+        ->constructor(DI\get('log.level'))
+        ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
 
     'Piwik\Plugins\Monolog\Handler\WebNotificationHandler' => DI\object()
-        ->constructor(DI\link('log.level'))
-        ->method('setFormatter', DI\link('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
+        ->constructor(DI\get('log.level'))
+        ->method('setFormatter', DI\get('Piwik\Plugins\Monolog\Formatter\LineMessageFormatter')),
 
     'log.level' => DI\factory(function (ContainerInterface $c) {
         if ($c->has('ini.log.log_level')) {
@@ -88,7 +88,7 @@ return array(
     }),
 
     'Piwik\Plugins\Monolog\Formatter\LineMessageFormatter' => DI\object()
-        ->constructor(DI\link('log.format')),
+        ->constructor(DI\get('log.format')),
 
     'log.format' => DI\factory(function (ContainerInterface $c) {
         if ($c->has('ini.log.string_message_format')) {
diff --git a/plugins/Morpheus/stylesheets/base.less b/plugins/Morpheus/stylesheets/base.less
index 4d7ca9a78815138da90e8051254b5bee0ef3189a..49077a6aa3a64c036bda009e11f271b8c702830b 100644
--- a/plugins/Morpheus/stylesheets/base.less
+++ b/plugins/Morpheus/stylesheets/base.less
@@ -33,4 +33,8 @@
 @import "uibase/_loading.less";
 
 /* Remote components */
-@import "../../CoreHome/stylesheets/_donate.less";
\ No newline at end of file
+@import "../../CoreHome/stylesheets/_donate.less";
+
+@import "bootstrap.css";
+
+@import "ui/_alerts";
diff --git a/plugins/Morpheus/stylesheets/bootstrap.css b/plugins/Morpheus/stylesheets/bootstrap.css
new file mode 100755
index 0000000000000000000000000000000000000000..38d8c40900596a652b848acdb7ae88f5b981da12
--- /dev/null
+++ b/plugins/Morpheus/stylesheets/bootstrap.css
@@ -0,0 +1,1011 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e32d98c35d1ff80598b4)
+ * Config saved to config.json and https://gist.github.com/e32d98c35d1ff80598b4
+ */
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+html {
+  font-family: sans-serif;
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+}
+body {
+  margin: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+audio,
+canvas,
+progress,
+video {
+  display: inline-block;
+  vertical-align: baseline;
+}
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+[hidden],
+template {
+  display: none;
+}
+a {
+  background-color: transparent;
+}
+a:active,
+a:hover {
+  outline: 0;
+}
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+b,
+strong {
+  font-weight: bold;
+}
+dfn {
+  font-style: italic;
+}
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+mark {
+  background: #ff0;
+  color: #000;
+}
+small {
+  font-size: 80%;
+}
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+sup {
+  top: -0.5em;
+}
+sub {
+  bottom: -0.25em;
+}
+img {
+  border: 0;
+}
+svg:not(:root) {
+  overflow: hidden;
+}
+figure {
+  margin: 1em 40px;
+}
+hr {
+  -moz-box-sizing: content-box;
+  -webkit-box-sizing: content-box;
+          box-sizing: content-box;
+  height: 0;
+}
+pre {
+  overflow: auto;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit;
+  font: inherit;
+  margin: 0;
+}
+button {
+  overflow: visible;
+}
+button,
+select {
+  text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+input {
+  line-height: normal;
+}
+input[type="checkbox"],
+input[type="radio"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 0;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+input[type="search"] {
+  -webkit-appearance: textfield;
+  -moz-box-sizing: content-box;
+  -webkit-box-sizing: content-box;
+  box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+legend {
+  border: 0;
+  padding: 0;
+}
+textarea {
+  overflow: auto;
+}
+optgroup {
+  font-weight: bold;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+td,
+th {
+  padding: 0;
+}
+* {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+html {
+  font-size: 10px;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #333333;
+  background-color: #ffffff;
+}
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+a {
+  color: #337ab7;
+  text-decoration: none;
+}
+a:hover,
+a:focus {
+  color: #23527c;
+  text-decoration: underline;
+}
+a:focus {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+figure {
+  margin: 0;
+}
+img {
+  vertical-align: middle;
+}
+.img-responsive {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+.img-rounded {
+  border-radius: 6px;
+}
+.img-thumbnail {
+  padding: 4px;
+  line-height: 1.42857143;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+  -o-transition: all 0.2s ease-in-out;
+  transition: all 0.2s ease-in-out;
+  display: inline-block;
+  max-width: 100%;
+  height: auto;
+}
+.img-circle {
+  border-radius: 50%;
+}
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  padding: 0;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
+}
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  padding-left: 15px;
+  padding-right: 15px;
+}
+@media (min-width: 768px) {
+  .container {
+    width: 750px;
+  }
+}
+@media (min-width: 992px) {
+  .container {
+    width: 970px;
+  }
+}
+@media (min-width: 1200px) {
+  .container {
+    width: 1170px;
+  }
+}
+.container-fluid {
+  margin-right: auto;
+  margin-left: auto;
+  padding-left: 15px;
+  padding-right: 15px;
+}
+.row {
+  margin-left: -15px;
+  margin-right: -15px;
+}
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-left: 15px;
+  padding-right: 15px;
+}
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+  float: left;
+}
+.col-xs-12 {
+  width: 100%;
+}
+.col-xs-11 {
+  width: 91.66666667%;
+}
+.col-xs-10 {
+  width: 83.33333333%;
+}
+.col-xs-9 {
+  width: 75%;
+}
+.col-xs-8 {
+  width: 66.66666667%;
+}
+.col-xs-7 {
+  width: 58.33333333%;
+}
+.col-xs-6 {
+  width: 50%;
+}
+.col-xs-5 {
+  width: 41.66666667%;
+}
+.col-xs-4 {
+  width: 33.33333333%;
+}
+.col-xs-3 {
+  width: 25%;
+}
+.col-xs-2 {
+  width: 16.66666667%;
+}
+.col-xs-1 {
+  width: 8.33333333%;
+}
+.col-xs-pull-12 {
+  right: 100%;
+}
+.col-xs-pull-11 {
+  right: 91.66666667%;
+}
+.col-xs-pull-10 {
+  right: 83.33333333%;
+}
+.col-xs-pull-9 {
+  right: 75%;
+}
+.col-xs-pull-8 {
+  right: 66.66666667%;
+}
+.col-xs-pull-7 {
+  right: 58.33333333%;
+}
+.col-xs-pull-6 {
+  right: 50%;
+}
+.col-xs-pull-5 {
+  right: 41.66666667%;
+}
+.col-xs-pull-4 {
+  right: 33.33333333%;
+}
+.col-xs-pull-3 {
+  right: 25%;
+}
+.col-xs-pull-2 {
+  right: 16.66666667%;
+}
+.col-xs-pull-1 {
+  right: 8.33333333%;
+}
+.col-xs-pull-0 {
+  right: auto;
+}
+.col-xs-push-12 {
+  left: 100%;
+}
+.col-xs-push-11 {
+  left: 91.66666667%;
+}
+.col-xs-push-10 {
+  left: 83.33333333%;
+}
+.col-xs-push-9 {
+  left: 75%;
+}
+.col-xs-push-8 {
+  left: 66.66666667%;
+}
+.col-xs-push-7 {
+  left: 58.33333333%;
+}
+.col-xs-push-6 {
+  left: 50%;
+}
+.col-xs-push-5 {
+  left: 41.66666667%;
+}
+.col-xs-push-4 {
+  left: 33.33333333%;
+}
+.col-xs-push-3 {
+  left: 25%;
+}
+.col-xs-push-2 {
+  left: 16.66666667%;
+}
+.col-xs-push-1 {
+  left: 8.33333333%;
+}
+.col-xs-push-0 {
+  left: auto;
+}
+.col-xs-offset-12 {
+  margin-left: 100%;
+}
+.col-xs-offset-11 {
+  margin-left: 91.66666667%;
+}
+.col-xs-offset-10 {
+  margin-left: 83.33333333%;
+}
+.col-xs-offset-9 {
+  margin-left: 75%;
+}
+.col-xs-offset-8 {
+  margin-left: 66.66666667%;
+}
+.col-xs-offset-7 {
+  margin-left: 58.33333333%;
+}
+.col-xs-offset-6 {
+  margin-left: 50%;
+}
+.col-xs-offset-5 {
+  margin-left: 41.66666667%;
+}
+.col-xs-offset-4 {
+  margin-left: 33.33333333%;
+}
+.col-xs-offset-3 {
+  margin-left: 25%;
+}
+.col-xs-offset-2 {
+  margin-left: 16.66666667%;
+}
+.col-xs-offset-1 {
+  margin-left: 8.33333333%;
+}
+.col-xs-offset-0 {
+  margin-left: 0%;
+}
+@media (min-width: 768px) {
+  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+    float: left;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-11 {
+    width: 91.66666667%;
+  }
+  .col-sm-10 {
+    width: 83.33333333%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-8 {
+    width: 66.66666667%;
+  }
+  .col-sm-7 {
+    width: 58.33333333%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-5 {
+    width: 41.66666667%;
+  }
+  .col-sm-4 {
+    width: 33.33333333%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-2 {
+    width: 16.66666667%;
+  }
+  .col-sm-1 {
+    width: 8.33333333%;
+  }
+  .col-sm-pull-12 {
+    right: 100%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-sm-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-sm-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-sm-pull-0 {
+    right: auto;
+  }
+  .col-sm-push-12 {
+    left: 100%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666667%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666667%;
+  }
+  .col-sm-push-7 {
+    left: 58.33333333%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666667%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-2 {
+    left: 16.66666667%;
+  }
+  .col-sm-push-1 {
+    left: 8.33333333%;
+  }
+  .col-sm-push-0 {
+    left: auto;
+  }
+  .col-sm-offset-12 {
+    margin-left: 100%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-sm-offset-0 {
+    margin-left: 0%;
+  }
+}
+@media (min-width: 992px) {
+  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+    float: left;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-11 {
+    width: 91.66666667%;
+  }
+  .col-md-10 {
+    width: 83.33333333%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-8 {
+    width: 66.66666667%;
+  }
+  .col-md-7 {
+    width: 58.33333333%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-5 {
+    width: 41.66666667%;
+  }
+  .col-md-4 {
+    width: 33.33333333%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-2 {
+    width: 16.66666667%;
+  }
+  .col-md-1 {
+    width: 8.33333333%;
+  }
+  .col-md-pull-12 {
+    right: 100%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-md-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-md-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-md-pull-0 {
+    right: auto;
+  }
+  .col-md-push-12 {
+    left: 100%;
+  }
+  .col-md-push-11 {
+    left: 91.66666667%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-8 {
+    left: 66.66666667%;
+  }
+  .col-md-push-7 {
+    left: 58.33333333%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-5 {
+    left: 41.66666667%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-2 {
+    left: 16.66666667%;
+  }
+  .col-md-push-1 {
+    left: 8.33333333%;
+  }
+  .col-md-push-0 {
+    left: auto;
+  }
+  .col-md-offset-12 {
+    margin-left: 100%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-md-offset-0 {
+    margin-left: 0%;
+  }
+}
+@media (min-width: 1200px) {
+  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+    float: left;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-11 {
+    width: 91.66666667%;
+  }
+  .col-lg-10 {
+    width: 83.33333333%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-8 {
+    width: 66.66666667%;
+  }
+  .col-lg-7 {
+    width: 58.33333333%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-5 {
+    width: 41.66666667%;
+  }
+  .col-lg-4 {
+    width: 33.33333333%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-2 {
+    width: 16.66666667%;
+  }
+  .col-lg-1 {
+    width: 8.33333333%;
+  }
+  .col-lg-pull-12 {
+    right: 100%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666667%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666667%;
+  }
+  .col-lg-pull-7 {
+    right: 58.33333333%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666667%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-2 {
+    right: 16.66666667%;
+  }
+  .col-lg-pull-1 {
+    right: 8.33333333%;
+  }
+  .col-lg-pull-0 {
+    right: auto;
+  }
+  .col-lg-push-12 {
+    left: 100%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666667%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666667%;
+  }
+  .col-lg-push-7 {
+    left: 58.33333333%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666667%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-2 {
+    left: 16.66666667%;
+  }
+  .col-lg-push-1 {
+    left: 8.33333333%;
+  }
+  .col-lg-push-0 {
+    left: auto;
+  }
+  .col-lg-offset-12 {
+    margin-left: 100%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666667%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666667%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.33333333%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666667%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.66666667%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.33333333%;
+  }
+  .col-lg-offset-0 {
+    margin-left: 0%;
+  }
+}
+.clearfix:before,
+.clearfix:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after {
+  content: " ";
+  display: table;
+}
+.clearfix:after,
+.container:after,
+.container-fluid:after,
+.row:after {
+  clear: both;
+}
+.center-block {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+.pull-right {
+  float: right !important;
+}
+.pull-left {
+  float: left !important;
+}
+.hide {
+  display: none !important;
+}
+.show {
+  display: block !important;
+}
+.invisible {
+  visibility: hidden;
+}
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+.affix {
+  position: fixed;
+}
diff --git a/plugins/Morpheus/stylesheets/general/_admin.less b/plugins/Morpheus/stylesheets/general/_admin.less
index 3d6aa21f61e528c8077c1777263d9773f97f5d09..8b13c25ad93da21e381cc34ab41721fb84b54026 100644
--- a/plugins/Morpheus/stylesheets/general/_admin.less
+++ b/plugins/Morpheus/stylesheets/general/_admin.less
@@ -35,21 +35,6 @@
     }
 }
 
-.admin {
-    h2 {
-        border-bottom: 1px solid @color-gray;
-        margin-right:0;
-    }
-    h3 {
-        color: @theme-color-text;
-        .font-default(18px, 24px);
-        font-weight: normal;
-    }
-    a:hover {
-        text-decoration:underline;
-    }
-}
-
 .ui-state-highlight {
     border-color: @color-silver-l80 !important;
     background: @color-silver-l95 !important;
@@ -61,7 +46,7 @@
 
 .adminTable {
     td {
-        padding: 0;
+        padding: 2px 0;
     }
     label {
         cursor: pointer;
diff --git a/plugins/Morpheus/stylesheets/general/_forms.less b/plugins/Morpheus/stylesheets/general/_forms.less
index 3a23231bcc1e4f7fb4fb49c5b75f1bc121425755..47277940af262166e078fe32896edded4a0c54fa 100644
--- a/plugins/Morpheus/stylesheets/general/_forms.less
+++ b/plugins/Morpheus/stylesheets/general/_forms.less
@@ -3,7 +3,7 @@ input:not([type="checkbox"]), select, textarea {
     .border-radius(0px);
     margin-left: 0;
     padding: 8px 10px;
-    min-height: 30px;
+    min-height: 20px;
     .box-sizing(border-box);
     background: @theme-color-background-base;
 }
@@ -12,6 +12,7 @@ button,
 .add-cors-host,
 input[type="submit"],
 button[type="button"],
+.btn,
 .submit {
     .border-radius(3px) !important;
     background: none !important;
@@ -41,6 +42,14 @@ button[type="button"],
         }
     }
 }
+.btn-lg {
+    padding: 12px 16px !important;;
+    font-size: 14px !important;;
+}
+.btn-block {
+    display: block;
+    width: 100%;
+}
 
 .top_bar_sites_selector {
   .sites_autocomplete .custom_select {
@@ -57,7 +66,7 @@ button[type="button"],
         .border-radius(0px);
         background: @theme-color-background-base;
         .box-shadow(~"inset 1px 1px 3px #d8d8d8");
-        padding: 0px 5px 0px;
+        padding: 0;
         color: @theme-color-text;
         text-transform: uppercase;
         .font-default(10px, 12px);
@@ -94,7 +103,7 @@ button[type="button"],
         color: @theme-color-brand;
         font-size: 0.7em;
         top: 2px;
-        right: 5px;
+        right: 10px;
         content: '';
         border-left: 4px solid transparent;
         border-right: 4px solid transparent;
diff --git a/plugins/Morpheus/stylesheets/general/_jqueryUI.less b/plugins/Morpheus/stylesheets/general/_jqueryUI.less
index cff51214c55efacdb790ea8c3b2589d8cff70e56..66b2384011e2eb8cff72a44edec60d02734e0268 100644
--- a/plugins/Morpheus/stylesheets/general/_jqueryUI.less
+++ b/plugins/Morpheus/stylesheets/general/_jqueryUI.less
@@ -234,6 +234,7 @@ td.ui-datepicker-other-month.ui-state-hover.ui-datepicker-current-period {
 .ui-tooltip h3 {
   font-size: 12px;
   margin: 0 0 2px 0;
+  line-height: 14px;
 }
 
 body .ui-tooltip.small {
diff --git a/plugins/Morpheus/stylesheets/general/_misc.less b/plugins/Morpheus/stylesheets/general/_misc.less
index de419973f1e4132d766ab16275aafc7e32bb1156..e26c52f798d873e893a1b13f3973fb3ae9801288 100644
--- a/plugins/Morpheus/stylesheets/general/_misc.less
+++ b/plugins/Morpheus/stylesheets/general/_misc.less
@@ -2,10 +2,6 @@
     color: #95AECB;
 }
 
-.exception-backtrace {
-    color: #888;
-}
-
 .section-toggler-link {
   font-size: .8em;
   font-style: italic;
diff --git a/plugins/Morpheus/stylesheets/general/_typography.less b/plugins/Morpheus/stylesheets/general/_typography.less
index c20217655687f01e378038660c5c353233fb29cc..bbec765ed2eaf79e28645b5e8728f85d5d6ea2bb 100644
--- a/plugins/Morpheus/stylesheets/general/_typography.less
+++ b/plugins/Morpheus/stylesheets/general/_typography.less
@@ -121,3 +121,13 @@ body > a.ddmetric {
 .segment-element .segment-content .segment-input input {
     font-family: @theme-fontFamily-base !important;
 }
+
+.text-left {
+    text-align: left;
+}
+.text-center {
+    text-align: center;
+}
+.text-right {
+    text-align: right;
+}
diff --git a/plugins/Morpheus/stylesheets/simple_structure.css b/plugins/Morpheus/stylesheets/simple_structure.css
index 6198916ad6c93faa4068858c284494f21662e62e..20239c8800cdd3ef5af7d99d5e91d278baa830e9 100644
--- a/plugins/Morpheus/stylesheets/simple_structure.css
+++ b/plugins/Morpheus/stylesheets/simple_structure.css
@@ -1,6 +1,98 @@
+body {
+	font-family: Verdana, sans-serif;
+}
 body#simple  {
-	background: #eee;
+    background: #fff;
+}
+#simple .logo {
+    color: #888;
+    text-align: center;
+    font-size: 12px;
+    margin-top: 30px;
+}
+#simple .logo a {
+	color: #888;
+	text-decoration: none;
+}
+#simple .box {
+    border-radius: 12px;
+    border: solid 1px #ccc;
+    max-width: 780px;
+    margin: 30px auto 60px auto;
+    overflow: hidden;
+    box-shadow: 0 1px 2px 0 #ccc;
+}
+
+#simple .box .header {
+    background-color: #f0f0f0;
+    border-bottom: solid 1px #ccc;
+	padding: 40px 80px;
+    text-align: center;
+}
+#simple .box .header h1 {
+	font-size: 30px;
+	font-weight: normal;
+	margin: 0;
+}
+#simple .box .header p {
+	font-size: 13px;
+	margin-top: 10px;
+	padding: 0;
 }
+
+#simple .box .content {
+	margin: 1em 2em;
+	text-align: center;
+	padding: 30px 50px;
+	font-size: 14px;
+}
+#simple .box .content.text-left {
+	text-align: left;
+}
+#simple .box .content ul {
+	text-align: left;
+	list-style: disc;
+	width: 60%;
+	margin: 0 auto;
+}
+#simple .box .content.text-left ul {
+	width: 100%;
+}
+#simple .box .content form {
+    margin: 10px 0;
+    min-height: 0;
+}
+#simple .box .content .btn {
+	float: none;
+	margin: 0;
+}
+#simple .box .content h2 {
+	font-weight: normal;
+	font-size: 19px;
+	margin-bottom: 25px;
+	margin-top: 30px;
+}
+#simple .box .content h2:first-child {
+	margin-top: 0;
+}
+#simple .box .content pre {
+	overflow-x: scroll;
+	font-size: 11px;
+	text-align: left;
+}
+
+#simple .box .footer {
+    background-color: #f0f0f0;
+    border-top: solid 1px #ccc;
+    padding: 15px;
+    text-align: center;
+}
+#simple .box .footer a {
+    text-decoration: none;
+}
+
+/* Old style below */
+
 #contentsimple {
 	background: #fff;
 	color: #000;
@@ -23,11 +115,6 @@ body#simple  {
     vertical-align: bottom;
 }
 
-#titleUpdate {
-	font-size: 18px;
- 	color: #7e7363;
-	clear:both;
-}
 #subh1 a {
 	color: #444;
     text-decoration:none;
@@ -42,11 +129,9 @@ body#simple  {
 	font:42px Georgia, serif;
 }
 p, dt {
-	line-height: 120%;
+	line-height: 1.5;
 	padding-bottom: 1em;
-	margin:10px;
 }
-a { color: #006; }
 #logo { margin-bottom: 2em; }
 .submit {
 	font-size:18pt;
diff --git a/plugins/Morpheus/stylesheets/theme.less b/plugins/Morpheus/stylesheets/theme.less
index c4cbdec5a62afcb068f7f98f00f5da28c71a20f8..ed72fac4ba74e42459db0632b60a50bcd57a85ad 100644
--- a/plugins/Morpheus/stylesheets/theme.less
+++ b/plugins/Morpheus/stylesheets/theme.less
@@ -14,10 +14,24 @@ body {
     font-family: @theme-fontFamily-base;
 }
 
-.pageWrap {
-    padding-left: 10px;
-    padding-right: 10px;
-    border: 0px;
+h2 {
+    font-weight: normal;
+    border-bottom: 1px solid @color-gray;
+    margin: 15px -15px 20px 0;
+    font-size: 24px;
+    width: 100%;
+    a, a:hover {
+        text-decoration: none;
+        color: @color-black-piwik;
+    }
+}
+h3 {
+    color: @theme-color-text;
+    .font-default(18px, 24px);
+    font-weight: normal;
+}
+a:hover {
+    text-decoration:underline;
 }
 
 #content {
@@ -55,7 +69,7 @@ table.entityTable tr td a:hover {
 }
 
 #root {
-    margin: 0;
+    margin: 0 0 100px 0;
     padding: 0;
 
     #header {
@@ -164,6 +178,7 @@ table.entityTable tr td a:hover {
     .widgetize {
         width: auto;
     }
+
 }
 
 .dashboardSettings {
@@ -302,7 +317,7 @@ table.entityTable tr td a:hover {
       border: 1px solid @color-silver-l80;
       padding: 8px 10px 8px 10px;
       color: @theme-color-text-light;
-      height: 12px;
+      height: 30px;
       line-height:0.5em;
       .border-radius(0px);
     }
@@ -564,11 +579,9 @@ div.sparkline {
     .widgetTop {
         background: @theme-color-widget-title-background;
         border-bottom: 1px solid @color-silver-l80;
-        .widgetName {
+        h3 {
             .font-default(15px, 18px);
             color: @theme-color-widget-title-text;
-            text-shadow: none;
-            padding: 15px 15px 10px;
         }
     }
 
diff --git a/plugins/Morpheus/stylesheets/ui/_alerts.less b/plugins/Morpheus/stylesheets/ui/_alerts.less
new file mode 100644
index 0000000000000000000000000000000000000000..d0b1b3f10551747369e8c5ed13d43c42eb44326b
--- /dev/null
+++ b/plugins/Morpheus/stylesheets/ui/_alerts.less
@@ -0,0 +1,28 @@
+.alert {
+    padding: 20px;
+    margin-bottom: 20px;
+    border: 1px solid transparent;
+    border-radius: 2px;
+}
+.alert-success {
+    color: #3c763d;
+    background-color: #dff0d8;
+    border-color: #d6e9c6;
+}
+.alert-info {
+    color: #B3B3B3;
+    background-color: #F5F5F5;
+    border-color: #E5E5E5;
+    font-size: 13px;
+    padding: 15px 20px;
+}
+.alert-warning {
+    color: #8a6d3b;
+    background-color: #fcf8e3;
+    border-color: #faebcc;
+}
+.alert-danger {
+    color: #E0645D;
+    background: none;
+    border-color: #D4291F;
+}
diff --git a/plugins/Morpheus/stylesheets/ui/_components.less b/plugins/Morpheus/stylesheets/ui/_components.less
index 7e93cf057bb33c56c2a276a310fd09aeb9b0f4b8..cf373a147c5cf180100379ea3ae9cafa4fd060e8 100644
--- a/plugins/Morpheus/stylesheets/ui/_components.less
+++ b/plugins/Morpheus/stylesheets/ui/_components.less
@@ -88,8 +88,8 @@
                 .font-default(12px, 14px);
                 color: @theme-color-text;
                 font-weight: 500;
-                margin: 0px;
-                min-height: auto;
+                margin: 0;
+                height: 30px;
             }
         }
     }
diff --git a/plugins/Morpheus/templates/maintenance.tpl b/plugins/Morpheus/templates/maintenance.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..1f1545c0150a644c8f2d7c7414fc682c30f10296
--- /dev/null
+++ b/plugins/Morpheus/templates/maintenance.tpl
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+	<title>Piwik is undergoing maintenance</title>
+	<link rel="shortcut icon" href="%faviconUrl%" />
+	<link rel="stylesheet" type="text/css" href="plugins/Morpheus/stylesheets/simple_structure.css" />
+</head>
+<body id="simple">
+
+<div class="logo">
+	<img title="Piwik" alt="Piwik" src="%logoUrl%" />
+	<br/>
+	<a href='http://piwik.org/'>%piwikTitle%</a>
+</div>
+
+<div class="box">
+
+	<div class="header">
+		<h1>Piwik is undergoing maintenance</h1>
+	</div>
+
+	<div class="content">
+        <p>
+            We will be back up and running again soon, so please try again later. Your analytics data will continue to be tracked as normal. Thank you for your patience.
+        </p>
+	</div>
+
+	<div class="footer">
+	</div>
+
+</div>
+
+</body>
+</html>
diff --git a/plugins/Morpheus/templates/simpleLayoutFooter.tpl b/plugins/Morpheus/templates/simpleLayoutFooter.tpl
index 9943ff0f851844f5daaccd7e1adca02a193936dc..f308a692e87a8592c6a866aa06db1db7ed651209 100644
--- a/plugins/Morpheus/templates/simpleLayoutFooter.tpl
+++ b/plugins/Morpheus/templates/simpleLayoutFooter.tpl
@@ -1,3 +1,9 @@
+    </div>
+
+    <div class="footer">
+        <a href="index.php">&laquo; Back to Piwik</a>
+    </div>
+
 </div>
 </body>
 </html>
diff --git a/plugins/Morpheus/templates/simpleLayoutHeader.tpl b/plugins/Morpheus/templates/simpleLayoutHeader.tpl
index 377e9a75391f3adc25ea7403a16eb1e64beecdfb..8d11a5b1129b7a30d93edf718b1c7e5b4bd456e5 100644
--- a/plugins/Morpheus/templates/simpleLayoutHeader.tpl
+++ b/plugins/Morpheus/templates/simpleLayoutHeader.tpl
@@ -7,5 +7,17 @@
 	<link rel="stylesheet" type="text/css" href="plugins/Morpheus/stylesheets/simple_structure.css" />
 </head>
 <body id="simple">
-<div id="contentsimple">
-	<div id="title"><img title="Piwik" alt="Piwik" src="%logoUrl%" style="margin-left:10px;" /><span id="subh1"> # <a href='http://piwik.org/'>Web Analytics</a></span></div>
+
+<div class="logo">
+	<img title="Piwik" alt="Piwik" src="%logoUrl%"/>
+	<br/>
+	<a href='http://piwik.org/'>free/libre analytics platform</a>
+</div>
+
+<div class="box">
+
+	<div class="header">
+		<h1>An error occurred</h1>
+	</div>
+
+	<div class="content">
\ No newline at end of file
diff --git a/plugins/Morpheus/templates/user.twig b/plugins/Morpheus/templates/user.twig
index 4234f0729693580a70b6ab517248181c13613978..346aa1f7f444922b73ffc291037681f59bd8ac5a 100644
--- a/plugins/Morpheus/templates/user.twig
+++ b/plugins/Morpheus/templates/user.twig
@@ -9,8 +9,10 @@
         {% set topMenuModule = 'Feedback' %}
         {% set topMenuAction = 'index' %}
     {% else %}
-        {% set topMenuModule = 'UsersManager' %}
-        {% set topMenuAction = 'userSettings' %}
+        {% if currentModule != 'Feedback' %}
+            {% set topMenuModule = 'UsersManager' %}
+            {% set topMenuAction = 'userSettings' %}
+        {% endif %}
     {% endif %}
     {{ parent() }}
 {% endblock %}
diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php
index c76551bb79e6f16dc3bca78a7be82daed333f175..f3f62ecae45a3790ea5a1b052e72c821116087aa 100644
--- a/plugins/MultiSites/MultiSites.php
+++ b/plugins/MultiSites/MultiSites.php
@@ -67,7 +67,7 @@ class MultiSites extends \Piwik\Plugin
         $translations[] = 'Actions_SubmenuSitesearch';
         $translations[] = 'MultiSites_LoadingWebsites';
         $translations[] = 'General_ErrorRequest';
-        $translations[] = 'MultiSites_Pagination';
+        $translations[] = 'General_Pagination';
     }
 
     public function getJsFiles(&$jsFiles)
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
index 7066fd5d1fcb34bdfa1f52aaa8cfa67f838c7d36..287ef25c98c926aa51620e398c03f99b3359f1cb 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
@@ -97,7 +97,7 @@
                 </span>
                 <span class="dataTablePages">
                     <span id="counter">
-                        {{ 'MultiSites_Pagination'|translate:model.getCurrentPagingOffsetStart():model.getCurrentPagingOffsetEnd():model.getNumberOfFilteredSites() }}
+                        {{ 'General_Pagination'|translate:model.getCurrentPagingOffsetStart():model.getCurrentPagingOffsetEnd():model.getNumberOfFilteredSites() }}
                     </span>
                 </span>
                 <span id="next" class="next" ng-hide="model.currentPage >= model.getNumberOfPages()" ng-click="model.nextPage()">
diff --git a/plugins/MultiSites/templates/getSitesInfo.twig b/plugins/MultiSites/templates/getSitesInfo.twig
index dde3f9898df8a648862f7c82d44da190f08aa588..d1bdb81df054e4ac8b433be40c7405d5c0a1c4fd 100644
--- a/plugins/MultiSites/templates/getSitesInfo.twig
+++ b/plugins/MultiSites/templates/getSitesInfo.twig
@@ -8,10 +8,9 @@
     </div>
 {% endif %}
 
-<div class="pageWrap" id="multisites">
+<div class="pageWrap container" id="multisites">
     <div id="main">
         <div piwik-multisites-dashboard
-             class="centerLargeDiv"
              display-revenue-column="{% if displayRevenueColumn %}true{% else %}false{%endif%}"
              page-size="{{ limit }}"
              show-sparklines="{% if show_sparklines %}true{% else %}false{%endif%}"
diff --git a/plugins/Referrers/Columns/Base.php b/plugins/Referrers/Columns/Base.php
index feacc90e55b0a64ec30d34577a5d5c944e242bb0..5c80aa63498752c63dc0b217d982706625ca8640 100644
--- a/plugins/Referrers/Columns/Base.php
+++ b/plugins/Referrers/Columns/Base.php
@@ -70,7 +70,7 @@ abstract class Base extends VisitDimension
     {
         $cacheKey = $referrerUrl . $currentUrl . $idSite;
 
-        if (array_key_exists($cacheKey, self::$cachedReferrer)) {
+        if (isset(self::$cachedReferrer[$cacheKey])) {
             return self::$cachedReferrer[$cacheKey];
         }
 
@@ -108,9 +108,7 @@ abstract class Base extends VisitDimension
             }
         }
 
-        if (!empty($this->referrerHost)
-            && !$referrerDetected
-        ) {
+        if (!$referrerDetected && !empty($this->referrerHost)) {
             $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_WEBSITE;
             $this->nameReferrerAnalyzed = Common::mb_strtolower($this->referrerHost);
         }
@@ -345,7 +343,6 @@ abstract class Base extends VisitDimension
         $type    = $visitor->getVisitorColumn('referer_type');
         $name    = $visitor->getVisitorColumn('referer_name');
         $keyword = $visitor->getVisitorColumn('referer_keyword');
-        $time    = $visitor->getVisitorColumn('visit_first_action_time');
 
         // 0) In some (unknown!?) cases the campaign is not found in the attribution cookie, but the URL ref was found.
         //    In this case we look up if the current visit is credited to a campaign and will credit this campaign rather than the URL ref (since campaigns have higher priority)
@@ -359,7 +356,6 @@ abstract class Base extends VisitDimension
             $type    = Common::REFERRER_TYPE_CAMPAIGN;
             $name    = $referrerCampaignName;
             $keyword = $referrerCampaignKeyword;
-            $time    = $referrerTimestamp;
         } // 2) Referrer URL parsing
         elseif (!empty($referrerUrl)) {
 
@@ -371,7 +367,6 @@ abstract class Base extends VisitDimension
                 $type    = $referrer['referer_type'];
                 $name    = $referrer['referer_name'];
                 $keyword = $referrer['referer_keyword'];
-                $time    = $referrerTimestamp;
             }
         }
 
diff --git a/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig b/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig
index ce2f5f2e8d9b28a6249cb84c2cd6c247e0879dfa..29b2b3f0af8a38b9be8a8d69a2e6fbd24bc1511d 100644
--- a/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig
+++ b/plugins/Referrers/templates/getSearchEnginesAndKeywords.twig
@@ -1,9 +1,13 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Referrers_Keywords'|translate }}</h2>
-    {{ keywords|raw }}
-</div>
+<div class="row">
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Referrers_Keywords'|translate }}</h2>
+        {{ keywords|raw }}
+    </div>
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Referrers_SearchEngines'|translate }}</h2>
+        {{ searchEngines|raw }}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Referrers_SearchEngines'|translate }}</h2>
-    {{ searchEngines|raw }}
 </div>
diff --git a/plugins/Referrers/templates/index.twig b/plugins/Referrers/templates/index.twig
index 3b07a4063f34e1ea1d1903dc241a51aabb1b88b3..c6294312105be99482223cfaea7568ba2bb51e40 100644
--- a/plugins/Referrers/templates/index.twig
+++ b/plugins/Referrers/templates/index.twig
@@ -4,51 +4,51 @@
 
 <h2 piwik-enriched-headline>{{ 'Referrers_Type'|translate }}</h2>
 
-<div id='leftcolumn'>
-    <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDirectEntry) }}
-        {{ 'Referrers_TypeDirectEntries'|translate("<strong>"~visitorsFromDirectEntry~"</strong>")|raw }}
-        {% if visitorsFromDirectEntryPercent|default is not empty %},
-            {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromDirectEntryPercent~"</strong>")|raw }}
-        {% endif %}
-        {% if visitorsFromDirectEntryEvolution|default is not empty %}
-            {{ visitorsFromDirectEntryEvolution|raw }}
-        {% endif %}
-    </div>
-    <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineSearchEngines) }}
-        {{ 'Referrers_TypeSearchEngines'|translate("<strong>"~visitorsFromSearchEngines~"</strong>")|raw }}
-        {% if visitorsFromSearchEnginesPercent|default is not empty %},
-            {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromSearchEnginesPercent~"</strong>")|raw }}
-        {% endif %}
-        {% if visitorsFromSearchEnginesEvolution|default is not empty %}
-            {{ visitorsFromSearchEnginesEvolution|raw }}
-        {% endif %}
-    </div>
-</div>
-<div id='rightcolumn'>
-    <div class="sparkline">{{ sparkline(urlSparklineWebsites) }}
-        {{ 'Referrers_TypeWebsites'|translate("<strong>"~visitorsFromWebsites~"</strong>")|raw }}
-        {% if visitorsFromWebsitesPercent|default is not empty %},
-            {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromWebsitesPercent~"</strong>")|raw }}
-        {% endif %}
-        {% if visitorsFromWebsitesEvolution|default is not empty %}
-            {{ visitorsFromWebsitesEvolution|raw }}
-        {% endif %}
+<div class="row">
+    <div class="col-md-6">
+        <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDirectEntry) }}
+            {{ 'Referrers_TypeDirectEntries'|translate("<strong>"~visitorsFromDirectEntry~"</strong>")|raw }}
+            {% if visitorsFromDirectEntryPercent|default is not empty %},
+                {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromDirectEntryPercent~"</strong>")|raw }}
+            {% endif %}
+            {% if visitorsFromDirectEntryEvolution|default is not empty %}
+                {{ visitorsFromDirectEntryEvolution|raw }}
+            {% endif %}
+        </div>
+        <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineSearchEngines) }}
+            {{ 'Referrers_TypeSearchEngines'|translate("<strong>"~visitorsFromSearchEngines~"</strong>")|raw }}
+            {% if visitorsFromSearchEnginesPercent|default is not empty %},
+                {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromSearchEnginesPercent~"</strong>")|raw }}
+            {% endif %}
+            {% if visitorsFromSearchEnginesEvolution|default is not empty %}
+                {{ visitorsFromSearchEnginesEvolution|raw }}
+            {% endif %}
+        </div>
     </div>
-    <div class="sparkline">{{ sparkline(urlSparklineCampaigns) }}
-        {{ 'Referrers_TypeCampaigns'|translate("<strong>"~visitorsFromCampaigns~"</strong>")|raw }}
-        {% if visitorsFromCampaignsPercent|default is not empty %},
-            {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromCampaignsPercent~"</strong>")|raw }}
-        {% endif %}
-        {% if visitorsFromCampaignsEvolution|default is not empty %}
-            {{ visitorsFromCampaignsEvolution|raw }}
-        {% endif %}
+    <div class="col-md-6">
+        <div class="sparkline">{{ sparkline(urlSparklineWebsites) }}
+            {{ 'Referrers_TypeWebsites'|translate("<strong>"~visitorsFromWebsites~"</strong>")|raw }}
+            {% if visitorsFromWebsitesPercent|default is not empty %},
+                {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromWebsitesPercent~"</strong>")|raw }}
+            {% endif %}
+            {% if visitorsFromWebsitesEvolution|default is not empty %}
+                {{ visitorsFromWebsitesEvolution|raw }}
+            {% endif %}
+        </div>
+        <div class="sparkline">{{ sparkline(urlSparklineCampaigns) }}
+            {{ 'Referrers_TypeCampaigns'|translate("<strong>"~visitorsFromCampaigns~"</strong>")|raw }}
+            {% if visitorsFromCampaignsPercent|default is not empty %},
+                {{ 'Referrers_XPercentOfVisits'|translate("<strong>"~visitorsFromCampaignsPercent~"</strong>")|raw }}
+            {% endif %}
+            {% if visitorsFromCampaignsEvolution|default is not empty %}
+                {{ visitorsFromCampaignsEvolution|raw }}
+            {% endif %}
+        </div>
     </div>
 </div>
 
-<div style="clear:both;"/>
-
-<div id="distinctReferrersByType">
-    <div id='leftcolumn'>
+<div id="distinctReferrersByType" class="row">
+    <div class="col-md-6">
         <div class="sparkline" style="padding-left: 12px;">{{ sparkline(urlSparklineDistinctSearchEngines) }}
             <strong>{{ numberDistinctSearchEngines }}</strong> {{ 'Referrers_DistinctSearchEngines'|translate }}
             {% if numberDistinctSearchEnginesEvolution|default is not empty %}
@@ -62,7 +62,7 @@
             {% endif %}
         </div>
     </div>
-    <div id='rightcolumn'>
+    <div class="col-md-6">
         <div class="sparkline">{{ sparkline(urlSparklineDistinctWebsites) }}
             <strong>{{ numberDistinctWebsites }}</strong> {{ 'Referrers_DistinctWebsites'|translate }}
             {{ 'Referrers_UsingNDistinctUrls'|translate("<strong>"~numberDistinctWebsitesUrls~"</strong>")|raw }}
@@ -80,8 +80,6 @@
     <br/>
 </div>
 
-<p style="clear:both;"/>
-
 <div style="float:left;" class="relatedReferrerReports">{{ 'General_View'|translate }}
     <a href="javascript:broadcast.propagateAjax('module=Referrers&action=getSearchEnginesAndKeywords')">{{ 'Referrers_SubmenuSearchEngines'|translate }}</a>,
     <a href="javascript:broadcast.propagateAjax('module=Referrers&action=indexWebsites')">{{ 'Referrers_SubmenuWebsites'|translate }}</a>,
diff --git a/plugins/Referrers/templates/indexWebsites.twig b/plugins/Referrers/templates/indexWebsites.twig
index adfea9d6e108a90a6f65609b092b9c3105220aa2..cdfa6efe0184bad664ebc211ec2a94dafd2c4c37 100644
--- a/plugins/Referrers/templates/indexWebsites.twig
+++ b/plugins/Referrers/templates/indexWebsites.twig
@@ -1,9 +1,13 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Referrers_Websites'|translate }}</h2>
-    {{ websites|raw }}
-</div>
+<div class="row">
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Referrers_Websites'|translate }}</h2>
+        {{ websites|raw }}
+    </div>
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'Referrers_Socials'|translate }}</h2>
+        {{ socials|raw }}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ 'Referrers_Socials'|translate }}</h2>
-    {{ socials|raw }}
 </div>
diff --git a/plugins/ScheduledReports/API.php b/plugins/ScheduledReports/API.php
index 1f404765c3f4b140da85ece6093c224945fdedcd..f77fa4b2237265f523d9d49ba322a4379b288929 100644
--- a/plugins/ScheduledReports/API.php
+++ b/plugins/ScheduledReports/API.php
@@ -449,7 +449,7 @@ class API extends \Piwik\Plugin\API
         // render report
         $description = str_replace(array("\r", "\n"), ' ', $report['description']);
 
-        list($reportSubject, $reportTitle) = self::getReportSubjectAndReportTitle(Site::getNameFor($idSite), $report['reports']);
+        list($reportSubject, $reportTitle) = self::getReportSubjectAndReportTitle(Common::unsanitizeInputValue(Site::getNameFor($idSite)), $report['reports']);
 
         // if reporting for a segment, use the segment's name in the title
         if(is_array($segment) && strlen($segment['name'])) {
diff --git a/plugins/SegmentEditor/stylesheets/segmentation.less b/plugins/SegmentEditor/stylesheets/segmentation.less
index fdf4a1d579d5cc753806177fa96cecd5ff08b19e..126badb9481711d3f07ca1fa55eb0a304fc335cd 100644
--- a/plugins/SegmentEditor/stylesheets/segmentation.less
+++ b/plugins/SegmentEditor/stylesheets/segmentation.less
@@ -89,7 +89,6 @@ div.scrollable {
 
 .segment-element .custom_select_search {
   width: 146px;
-  height: 21px;
   background: url(plugins/SegmentEditor/images/bg-segment-search.png) 0 10px no-repeat;
   padding: 10px 0 0 0;
   margin: 10px 0 10px 15px;
@@ -249,7 +248,7 @@ div.scrollable {
 .segment-element .segment-content .segment-add-or {
   text-shadow: 0 1px 0 #fff;
   display: inline-block;
-  width: 98%;
+  width: 100%;
   padding: 0 1%;
   background: #efefeb;
   border-radius: 3px 3px 3px 3px;
@@ -569,7 +568,7 @@ div.scrollable {
 span.segmentationTitle {
   background: url(plugins/Morpheus/images/sort_subtable_desc.png) no-repeat right 0;
   padding-right: 20px;
-  min-width: 160px;
+  min-width: 180px;
   display: block;
   cursor: pointer;
 }
@@ -744,7 +743,7 @@ a.metric_category {
 
 .segmentationTitle,
 .segment-element .segment-nav a.dropdown {
-  max-width: 160px;
+  max-width: 180px;
 }
 
 .segname {
@@ -752,7 +751,7 @@ a.metric_category {
 }
 
 .segmentEditorPanel.visible .segmentationTitle {
-  max-width: 180px;
+  max-width: 200px;
   overflow: visible; /* restore default */
   white-space: normal; /* restore default */
 }
diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php
index 8457778ab5b984b703e1341d62b29bacca0264fb..36e29a86f0ae16b89a60dadfc0caf74613b412a6 100644
--- a/plugins/SitesManager/API.php
+++ b/plugins/SitesManager/API.php
@@ -240,7 +240,7 @@ class API extends \Piwik\Plugin\API
     {
         Piwik::checkUserHasSuperUserAccess();
         try {
-            return API::getInstance()->getSitesId();
+            return $this->getSitesId();
         } catch (Exception $e) {
             // can be called before Piwik tables are created so return empty
             return array();
@@ -278,16 +278,24 @@ class API extends \Piwik\Plugin\API
      * For the superUser it returns all the websites in the database.
      *
      * @param bool $fetchAliasUrls
+     * @param false|string $pattern
+     * @param false|int    $limit
      * @return array for each site, an array of information (idsite, name, main_url, etc.)
      */
-    public function getSitesWithAdminAccess($fetchAliasUrls = false)
+    public function getSitesWithAdminAccess($fetchAliasUrls = false, $pattern = false, $limit = false)
     {
         $sitesId = $this->getSitesIdWithAdminAccess();
-        $sites = $this->getSitesFromIds($sitesId);
+
+        if ($pattern === false) {
+            $sites = $this->getSitesFromIds($sitesId, $limit);
+        } else {
+            $sites = $this->getModel()->getPatternMatchSites($sitesId, $pattern, $limit);
+            Site::setSitesFromArray($sites);
+        }
 
         if ($fetchAliasUrls) {
             foreach ($sites as &$site) {
-                $site['alias_urls'] = API::getInstance()->getSiteUrlsFromId($site['idsite']);
+                $site['alias_urls'] = $this->getSiteUrlsFromId($site['idsite']);
             }
         }
 
@@ -593,7 +601,7 @@ class API extends \Piwik\Plugin\API
     {
         Piwik::checkUserHasSuperUserAccess();
 
-        $idSites = API::getInstance()->getSitesId();
+        $idSites = $this->getSitesId();
         if (!in_array($idSite, $idSites)) {
             throw new Exception("website id = $idSite not found");
         }
@@ -1048,7 +1056,8 @@ class API extends \Piwik\Plugin\API
     {
         Piwik::checkUserHasAdminAccess($idSite);
 
-        $idSites = API::getInstance()->getSitesId();
+        $idSites = $this->getSitesId();
+
         if (!in_array($idSite, $idSites)) {
             throw new Exception("website id = $idSite not found");
         }
diff --git a/plugins/SitesManager/Controller.php b/plugins/SitesManager/Controller.php
index b02a60c7b9e94975810732ca60487d0dc1c42e11..33b00235f617637751fa8bb8ca1277bc25a58359 100644
--- a/plugins/SitesManager/Controller.php
+++ b/plugins/SitesManager/Controller.php
@@ -28,6 +28,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
      */
     public function index()
     {
+        Piwik::checkUserHasSomeAdminAccess();
+
         return $this->renderTemplate('index');
     }
 
diff --git a/plugins/SitesManager/Model.php b/plugins/SitesManager/Model.php
index 0646453633d050d3dab4913cce7e402f3a0f59f1..632792683ff690f6c779113e9769de1fad79f5ff 100644
--- a/plugins/SitesManager/Model.php
+++ b/plugins/SitesManager/Model.php
@@ -357,8 +357,11 @@ class Model
                           OR s.main_url like ?
                           OR s.`group` like ?
                           $where )
-                     AND idsite in ($ids_str)
-                 LIMIT " . (int) $limit;
+                     AND idsite in ($ids_str)";
+
+        if ($limit !== false) {
+            $query .= " LIMIT " . (int) $limit;
+        }
 
         $db    = $this->getDb();
         $sites = $db->fetchAll($query, $bind);
diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php
index cbd0d9283858d1d423357a47865ce8d2919270d9..8e45c733c4d83fb67b428af5d4fbb9f86096bcf4 100644
--- a/plugins/SitesManager/SitesManager.php
+++ b/plugins/SitesManager/SitesManager.php
@@ -88,6 +88,7 @@ class SitesManager extends \Piwik\Plugin
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-helper.service.js";
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-site.service.js";
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-core.service.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js";
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js";
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js";
         $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/scroll.directive.js";
@@ -261,6 +262,9 @@ class SitesManager extends \Piwik\Plugin
         $translationKeys[] = "General_Save";
         $translationKeys[] = "General_OrCancel";
         $translationKeys[] = "General_Actions";
+        $translationKeys[] = "General_Search";
+        $translationKeys[] = "General_Pagination";
+        $translationKeys[] = "General_PaginationWithoutTotal";
         $translationKeys[] = "Actions_SubmenuSitesearch";
         $translationKeys[] = "SitesManager_OnlyOneSiteAtTime";
         $translationKeys[] = "SitesManager_DeleteConfirm";
@@ -286,6 +290,7 @@ class SitesManager extends \Piwik\Plugin
         $translationKeys[] = "SitesManager_EnableSiteSearch";
         $translationKeys[] = "SitesManager_DisableSiteSearch";
         $translationKeys[] = "SitesManager_SearchUseDefault";
+        $translationKeys[] = "SitesManager_Sites";
         $translationKeys[] = "SitesManager_SiteSearchUse";
         $translationKeys[] = "SitesManager_SearchKeywordLabel";
         $translationKeys[] = "SitesManager_SearchCategoryLabel";
@@ -322,5 +327,6 @@ class SitesManager extends \Piwik\Plugin
         $translationKeys[] = "SitesManager_SelectDefaultCurrency";
         $translationKeys[] = "SitesManager_AddSite";
         $translationKeys[] = "Goals_Ecommerce";
+        $translationKeys[] = "SitesManager_NotFound";
     }
 }
diff --git a/plugins/SitesManager/angularjs/sites-manager/api-site.service.js b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js
index 63a8aa7cba31cdced4749b980664817fc9bba84c..ad2d9d235184d7d657e2e216c0df8d77d0726a6b 100644
--- a/plugins/SitesManager/angularjs/sites-manager/api-site.service.js
+++ b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js
@@ -13,20 +13,21 @@
 
         return {
             getCurrencyList: getCurrencyList(),
-            getSitesWithAdminAccess: getSitesWithAdminAccess(),
             getTimezonesList: getTimezonesList(),
             isTimezoneSupportEnabled: isTimezoneSupportEnabled(),
-            getGlobalSettings: getGlobalSettings()
+            getGlobalSettings: getGlobalSettings(),
+            getSitesIdWithAdminAccess: getSitesIdWithAdminAccess()
         };
 
+        function getSitesIdWithAdminAccess () {
+            return api.fetchApi('SitesManager.getSitesIdWithAdminAccess', api.noop, {
+                filter_limit: '-1',
+            });
+        }
         function getCurrencyList () {
             return api.fetchApi('SitesManager.getCurrencyList', api.noop);
         }
 
-        function getSitesWithAdminAccess () {
-            return api.fetchApi('SitesManager.getSitesWithAdminAccess', api.noop, {fetchAliasUrls: true});
-        }
-
         function getTimezonesList () {
             return api.fetchApi('SitesManager.getTimezonesList', api.noop);
         }
diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js
new file mode 100644
index 0000000000000000000000000000000000000000..537cbc4f7066a6eae088c018bf66b536287c9a2a
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js
@@ -0,0 +1,111 @@
+/**
+ * Model for Sites Manager. Fetches only sites one has at least Admin permission.
+ */
+(function () {
+    angular.module('piwikApp').factory('sitesManagerAdminSitesModel', sitesManagerAdminSitesModel);
+
+    sitesManagerAdminSitesModel.$inject = ['piwikApi'];
+
+    function sitesManagerAdminSitesModel(piwikApi)
+    {
+        var model = {
+            sites        : [],
+            searchTerm   : '',
+            isLoading    : false,
+            pageSize     : 25,
+            currentPage  : 0,
+            offsetStart  : 0,
+            offsetEnd    : 25,
+            hasPrev      : false,
+            hasNext      : false,
+            previousPage: previousPage,
+            nextPage: nextPage,
+            searchSite: searchSite,
+            fetchLimitedSitesWithAdminAccess: fetchLimitedSitesWithAdminAccess
+        };
+
+        return model;
+
+        function onError ()
+        {
+            setSites([]);
+        }
+
+        function setSites(sites)
+        {
+            model.sites = sites;
+
+            var numSites      = sites.length;
+            model.offsetStart = model.currentPage * model.pageSize;
+            model.offsetEnd   = model.offsetStart + numSites;
+            model.hasPrev     = model.currentPage >= 1;
+            model.hasNext     = numSites === model.pageSize;
+        }
+
+        function setCurrentPage(page)
+        {
+            if (page < 0) {
+                page = 0;
+            }
+
+            model.currentPage = page;
+        }
+
+        function previousPage()
+        {
+            setCurrentPage(model.currentPage - 1);
+            fetchLimitedSitesWithAdminAccess();
+        }
+
+        function nextPage()
+        {
+            setCurrentPage(model.currentPage + 1);
+            fetchLimitedSitesWithAdminAccess();
+        }
+
+        function searchSite (term)
+        {
+            model.searchTerm = term;
+            setCurrentPage(0);
+            fetchLimitedSitesWithAdminAccess();
+        }
+
+        function fetchLimitedSitesWithAdminAccess(searchTerm)
+        {
+            if (model.isLoading) {
+                piwikApi.abort();
+            }
+
+            model.isLoading = true;
+
+            var limit  = model.pageSize;
+            var offset = model.currentPage * model.pageSize;
+
+            var params = {
+                method: 'SitesManager.getSitesWithAdminAccess',
+                fetchAliasUrls: true,
+                limit: limit + offset, // this is applied in SitesManager.getSitesWithAdminAccess API
+                filter_offset: offset, // filter_offset and filter_limit is applied in response builder
+                filter_limit: limit
+            };
+
+            if (model.searchTerm) {
+                params.pattern = model.searchTerm;
+            }
+
+            return piwikApi.fetch(params).then(function (sites) {
+
+                if (!sites) {
+                    onError();
+                    return;
+                }
+
+                setSites(sites);
+
+            }, onError)['finally'](function () {
+                model.isLoading = false;
+            });
+        }
+
+    }
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js
index 2aea880d2498e29e3741e9164bcec47ca29111ee..1edc69490deb2b3ff9bf7d0a35b710595b437de1 100644
--- a/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js
+++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js
@@ -7,25 +7,22 @@
 (function () {
     angular.module('piwikApp').controller('SitesManagerController', SitesManagerController);
 
-    SitesManagerController.$inject = ['$scope', '$filter', 'coreAPI', 'sitesManagerAPI', 'piwik', 'sitesManagerApiHelper'];
+    SitesManagerController.$inject = ['$scope', '$filter', 'coreAPI', 'sitesManagerAPI', 'sitesManagerAdminSitesModel', 'piwik', 'sitesManagerApiHelper'];
 
-    function SitesManagerController($scope, $filter, coreAPI, sitesManagerAPI, piwik, sitesManagerApiHelper) {
+    function SitesManagerController($scope, $filter, coreAPI, sitesManagerAPI, adminSites, piwik, sitesManagerApiHelper) {
 
         var translate = $filter('translate');
 
         var init = function () {
 
-            initModel();
-            initActions();
-        };
-
-        var initModel = function() {
-
             $scope.period = piwik.broadcast.getValueFromUrl('period');
             $scope.date = piwik.broadcast.getValueFromUrl('date');
-            $scope.sites = [];
+            $scope.adminSites = adminSites;
             $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
             $scope.redirectParams = {showaddsite: false};
+            $scope.siteIsBeingEdited = false;
+            $scope.cacheBuster = piwik.cacheBuster;
+            $scope.totalNumberOfSites = '?';
 
             initSelectLists();
             initUtcTime();
@@ -33,6 +30,8 @@
             initCustomVariablesActivated();
             initIsTimezoneSupportEnabled();
             initGlobalParams();
+
+            initActions();
         };
 
         var initActions = function () {
@@ -72,9 +71,16 @@
                 $scope.globalSettings.excludedQueryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedQueryParametersGlobal);
                 $scope.globalSettings.excludedUserAgentsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedUserAgentsGlobal);
 
+                hideLoading();
+
                 initKeepURLFragmentsList();
 
-                initSiteList();
+                adminSites.fetchLimitedSitesWithAdminAccess();
+                sitesManagerAPI.getSitesIdWithAdminAccess(function (siteIds) {
+                    if (siteIds && siteIds.length) {
+                        $scope.totalNumberOfSites = siteIds.length;
+                    }
+                });
 
                 triggerAddSiteIfRequested();
             });
@@ -176,7 +182,7 @@
         };
 
         var addSite = function() {
-            $scope.sites.push({});
+            $scope.adminSites.sites.push({});
         };
 
         var saveGlobalSettings = function() {
@@ -220,18 +226,6 @@
             return sitesInEditMode[0];
         };
 
-        var initSiteList = function () {
-
-            sitesManagerAPI.getSitesWithAdminAccess(function (sites) {
-
-                angular.forEach(sites, function(site) {
-                    $scope.sites.push(site);
-                });
-
-                hideLoading();
-            });
-        };
-
         var initCurrencyList = function () {
 
             sitesManagerAPI.getCurrencyList(function (currencies) {
diff --git a/plugins/SitesManager/stylesheets/SitesManager.less b/plugins/SitesManager/stylesheets/SitesManager.less
index d847d0e8a7727a458c287cc271570f1cec5d0156..f710c3e9dc68af9eab1aabb3f02ee226449cceff 100644
--- a/plugins/SitesManager/stylesheets/SitesManager.less
+++ b/plugins/SitesManager/stylesheets/SitesManager.less
@@ -15,6 +15,60 @@
     text-align: justify;
 }
 
+.SitesManager {
+  .bottomButtonBar {
+    margin-top: 5px
+  }
+
+  .visible {
+    visibility: visible;
+  }
+  .hidden {
+    visibility: hidden;
+  }
+
+  .sitesButtonBar {
+    width:100%;
+    text-align: center;
+  }
+
+  .addSite {
+    float: left;
+    display: inline-block;
+
+    .addRowSite {
+      font-size: 14px;
+    }
+  }
+
+  .paging {
+    text-align: center;
+    display: inline-block;
+    min-width: 400px;
+    margin-top: 7px;
+
+    .counter {
+      margin-left: 10px;
+      margin-right: 10px;
+    }
+
+  }
+
+  .search {
+    display: inline-block;
+    text-align: right;
+    float: right;
+  }
+}
+
+.sitesManagerList {
+  clear:both;
+
+  .isLoading {
+    opacity: 0.5;
+  }
+}
+
 .addRowSite {
   text-decoration: none;
 }
diff --git a/plugins/SitesManager/templates/index.html b/plugins/SitesManager/templates/index.html
index 9ecd346dff45f1869eab5185e874c76fb03977e8..1c5b9c4087f32d37395b7ed0b83a94589047039e 100644
--- a/plugins/SitesManager/templates/index.html
+++ b/plugins/SitesManager/templates/index.html
@@ -1,11 +1,15 @@
-<div ng-controller="SitesManagerController">
+<div ng-controller="SitesManagerController" class="SitesManager">
 
-    <div ng-include="'plugins/SitesManager/templates/sites-manager-header.html'"></div>
+    <div ng-include="'plugins/SitesManager/templates/sites-manager-header.html?cb=' + cacheBuster"></div>
 
-    <div ng-include="'plugins/SitesManager/templates/loading.html'"></div>
+    <div ng-include="'plugins/SitesManager/templates/loading.html?cb=' + cacheBuster"></div>
 
-    <div ng-include="'plugins/SitesManager/templates/sites-list/sites-list.html'"></div>
+    <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html?cb=' + cacheBuster"></div>
 
-    <div ng-include="'plugins/SitesManager/templates/global-settings.html'"></div>
+    <div ng-include="'plugins/SitesManager/templates/sites-list/sites-list.html?cb=' + cacheBuster"></div>
 
+    <div class="bottomButtonBar" ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html?cb=' + cacheBuster"></div>
+
+    <div ng-include="'plugins/SitesManager/templates/global-settings.html?cb=' + cacheBuster"></div>
+    
 </div>
diff --git a/plugins/SitesManager/templates/index.twig b/plugins/SitesManager/templates/index.twig
index 40cf84c0f2aea988726a79da3f96e5c284d6538e..1d4994fd5fb0bc5288755ad1e34d263da48ca098 100644
--- a/plugins/SitesManager/templates/index.twig
+++ b/plugins/SitesManager/templates/index.twig
@@ -2,6 +2,6 @@
 
 {% block content %}
 
-    <div ng-include="'plugins/SitesManager/templates/index.html'"></div>
+    <div ng-include="'plugins/SitesManager/templates/index.html?cb={{ cacheBuster }}'"></div>
 
 {% endblock %}
diff --git a/plugins/SitesManager/templates/loading.html b/plugins/SitesManager/templates/loading.html
index 6aaa13d4367942ca91947adc2b38f959b7e05817..55195fd2bf8263c0377d45e633f9905dbb8e0d4a 100644
--- a/plugins/SitesManager/templates/loading.html
+++ b/plugins/SitesManager/templates/loading.html
@@ -1,4 +1,4 @@
-<div ng-show="loading">
+<div ng-class="{'hidden': !loading && !adminSites.isLoading}">
     <div class="loadingPiwik">
         <img src="plugins/Morpheus/images/loading-blue.gif" alt="{{ 'General_LoadingData'|translate }}" />
         {{ 'General_LoadingData'|translate }}
diff --git a/plugins/SitesManager/templates/sites-list/add-site-link.html b/plugins/SitesManager/templates/sites-list/add-site-link.html
index 58a5effd5936b753cc0d660f0f5d456bd3ebed6c..225da15d5d0dbbb4beb22f4fa79a33ad2f55b54f 100644
--- a/plugins/SitesManager/templates/sites-list/add-site-link.html
+++ b/plugins/SitesManager/templates/sites-list/add-site-link.html
@@ -1,3 +1,32 @@
-<a ng-show="hasSuperUserAccess && !siteIsBeingEdited" class="addRowSite" ng-click="addSite()" tabindex="1">
-    {{ 'SitesManager_AddSite'|translate }}
-</a>
+<div ng-show="!siteIsBeingEdited" class="sitesButtonBar">
+    <div class="addSite">
+        <a ng-show="hasSuperUserAccess" class="addRowSite" ng-click="addSite()" tabindex="1">
+            {{ 'SitesManager_AddSite'|translate }}
+        </a>
+    </div>
+    <div class="paging">
+        <a class="submit prev"
+           ng-class="{'visible': adminSites.hasPrev, 'hidden': !adminSites.hasPrev}"
+           ng-click="adminSites.previousPage()">
+            <span style="cursor:pointer;">&#171; {{ 'General_Previous'|translate }}</span>
+        </a>
+        <span class="counter" ng-show="adminSites.hasPrev || adminSites.hasNext">
+            <span ng-if="adminSites.searchTerm">
+                {{ 'General_PaginationWithoutTotal'|translate:adminSites.offsetStart:adminSites.offsetEnd }}
+            </span>
+            <span ng-if="!adminSites.searchTerm">
+                {{ 'General_Pagination'|translate:adminSites.offsetStart:adminSites.offsetEnd:totalNumberOfSites }}
+            </span>
+        </span>
+        <a class="submit next"
+           ng-class="{'visible': adminSites.hasNext, 'hidden': !adminSites.hasNext}"
+           ng-click="adminSites.nextPage()">
+            <span style="cursor:pointer;" class="pointer">{{ 'General_Next'|translate }} &#187;</span>
+        </a>
+    </div>
+    <div class="search">
+        <input ng-model="adminSites.search" piwik-onenter="adminSites.searchSite(adminSites.search)"
+               placeholder="{{ 'Actions_SubmenuSitesearch' | translate }}" type="text">
+        <a class="submit" ng-click="adminSites.searchSite(adminSites.search)">{{ 'General_Search'|translate }}</a>
+    </div>
+</div>
diff --git a/plugins/SitesManager/templates/sites-list/sites-list.html b/plugins/SitesManager/templates/sites-list/sites-list.html
index 94cfa8512b76820b679a87b1b982d96a8cbaa868..cae686dd5f1906c479f124f76298a35ababde579 100644
--- a/plugins/SitesManager/templates/sites-list/sites-list.html
+++ b/plugins/SitesManager/templates/sites-list/sites-list.html
@@ -1,10 +1,8 @@
-<div class="entityContainer">
+<div class="entityContainer sitesManagerList">
 
-    <div ng-repeat="site in sites" ng-include="'plugins/SitesManager/templates/dialogs/dialogs.html'"></div>
+    <div ng-repeat="site in adminSites.sites" ng-include="'plugins/SitesManager/templates/dialogs/dialogs.html?cb=' + cacheBuster"></div>
 
-    <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html'"></div>
-
-    <table class="entityTable dataTable">
+    <table class="entityTable dataTable" ng-class="{'isLoading': adminSites.isLoading==true}">
         <thead>
         <tr>
             <th>{{ 'General_Id'|translate }}</th>
@@ -24,15 +22,17 @@
         </tr>
         </thead>
         <tbody>
-            <tr
-                    sites-manager-scroll
-                    ng-controller="SitesManagerSiteController"
-                    ng-repeat="site in sites"
-                    ng-include="'plugins/SitesManager/templates/sites-list/site-fields.html'">
+            <tr ng-show="adminSites.searchTerm && 0 === adminSites.sites.length && !adminSites.isLoading">
+                <td colspan="12">
+                    {{ 'SitesManager_NotFound'|translate }} <strong>{{ adminSites.searchTerm }}</strong>
+                </td>
+            </tr>
+            <tr sites-manager-scroll
+                ng-controller="SitesManagerSiteController"
+                ng-repeat="site in adminSites.sites"
+                ng-include="'plugins/SitesManager/templates/sites-list/site-fields.html?cb=' + cacheBuster">
             </tr>
         </tbody>
     </table>
 
-    <div ng-include="'plugins/SitesManager/templates/sites-list/add-site-link.html'"></div>
-
 </div>
diff --git a/plugins/SitesManager/templates/sites-manager-header.html b/plugins/SitesManager/templates/sites-manager-header.html
index a817dd8511289316651e7d2719849741b6f9da09..59762777fdfad18df159546fe0c419b24e406452 100644
--- a/plugins/SitesManager/templates/sites-manager-header.html
+++ b/plugins/SitesManager/templates/sites-manager-header.html
@@ -8,7 +8,7 @@
 <p>
     {{ 'SitesManager_MainDescription'|translate }}
 
-    <span ng-bind-html="'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:'<strong>' + sites.length + '</strong>'"></span>
+    <span ng-bind-html="'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:'<strong>' + totalNumberOfSites + '</strong>'"></span>
 
     <span ng-show="hasSuperUserAccess">
         <br/>
diff --git a/plugins/SitesManager/tests/Fixtures/ManySites.php b/plugins/SitesManager/tests/Fixtures/ManySites.php
new file mode 100644
index 0000000000000000000000000000000000000000..fe3d2fd32524a9faeba88f92df2abc1ede8554a5
--- /dev/null
+++ b/plugins/SitesManager/tests/Fixtures/ManySites.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\SitesManager\tests\Fixtures;
+
+use Piwik\Plugin;
+use Piwik\Tests\Framework\Fixture;
+
+/**
+ * Track actions with bandwidth
+ */
+class ManySites extends Fixture
+{
+    public $dateTime = '2010-01-03 11:22:33';
+
+    public function setUp()
+    {
+        for ($idSite = 1; $idSite < 64; $idSite++) {
+            if (!self::siteCreated($idSite)) {
+                if ($idSite < 35) {
+                    $siteName = 'SiteTest' . $idSite; // we generate different site names to be used in search
+                } else {
+                    $siteName = 'Site ' . $idSite;
+                }
+
+                self::createWebsite($this->dateTime, $ecommerce = 0, $siteName);
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/plugins/SitesManager/tests/Integration/ApiTest.php b/plugins/SitesManager/tests/Integration/ApiTest.php
index d4a4b6c87b8969f32486e0b0cb9c510ca5e2df9d..6d5bd6373f2c531232d25f69d3f5930a411374ac 100644
--- a/plugins/SitesManager/tests/Integration/ApiTest.php
+++ b/plugins/SitesManager/tests/Integration/ApiTest.php
@@ -23,6 +23,8 @@ use PHPUnit_Framework_Constraint_IsType;
  * Class Plugins_SitesManagerTest
  *
  * @group Plugins
+ * @group ApiTest
+ * @group SitesManager
  */
 class ApiTest extends IntegrationTestCase
 {
@@ -38,8 +40,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * empty name -> exception
-     *
-     * @group Plugins
      */
     public function testAddSiteEmptyName()
     {
@@ -69,7 +69,6 @@ class ApiTest extends IntegrationTestCase
      * wrong urls -> exception
      *
      * @dataProvider getInvalidUrlData
-     * @group Plugins
      */
     public function testAddSiteWrongUrls($url)
     {
@@ -83,8 +82,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * Test with valid IPs
-     *
-     * @group Plugins
      */
     public function testAddSiteExcludedIpsAndtimezoneAndCurrencyAndExcludedQueryParametersValid()
     {
@@ -138,7 +135,6 @@ class ApiTest extends IntegrationTestCase
      * Test with invalid IPs
      *
      * @dataProvider getInvalidIPsData
-     * @group Plugins
      */
     public function testAddSiteExcludedIpsNotValid($ip)
     {
@@ -153,8 +149,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * one url -> one main_url and nothing inserted as alias urls
-     *
-     * @group Plugins
      */
     public function testAddSiteOneUrl()
     {
@@ -173,8 +167,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * several urls -> one main_url and others as alias urls
-     *
-     * @group Plugins
      */
     public function testAddSiteSeveralUrls()
     {
@@ -192,8 +184,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * strange name
-     *
-     * @group Plugins
      */
     public function testAddSiteStrangeName()
     {
@@ -228,8 +218,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * no duplicate -> all the urls are saved
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsnoDuplicate()
     {
@@ -268,8 +256,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * duplicate -> don't save the already existing URLs
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsDuplicate()
     {
@@ -294,8 +280,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * case empty array => nothing happens
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsNoUrlsToAdd1()
     {
@@ -320,8 +304,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * case array only duplicate => nothing happens
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsNoUrlsToAdd2()
     {
@@ -346,8 +328,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong format urls => exception
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsWrongUrlsFormat3()
     {
@@ -363,8 +343,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong idsite => no exception because simply no access to this resource
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsWrongIdSite1()
     {
@@ -379,8 +357,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong idsite => exception
-     *
-     * @group Plugins
      */
     public function testAddSiteUrlsWrongIdSite2()
     {
@@ -395,8 +371,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * no Id -> empty array
-     *
-     * @group Plugins
      */
     public function testGetAllSitesIdNoId()
     {
@@ -406,8 +380,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * several Id -> normal array
-     *
-     * @group Plugins
      */
     public function testGetAllSitesIdSeveralId()
     {
@@ -426,8 +398,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong id => exception
-     *
-     * @group Plugins
      */
     public function testGetSiteFromIdWrongId1()
     {
@@ -441,8 +411,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong id => exception
-     *
-     * @group Plugins
      */
     public function testGetSiteFromIdWrongId2()
     {
@@ -456,8 +424,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrong id : no access => exception
-     *
-     * @group Plugins
      */
     public function testGetSiteFromIdWrongId3()
     {
@@ -478,8 +444,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * normal case
-     *
-     * @group Plugins
      */
     public function testGetSiteFromIdNormalId()
     {
@@ -494,8 +458,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * there is no admin site available -> array()
-     *
-     * @group Plugins
      */
     public function testGetSitesWithAdminAccessNoResult()
     {
@@ -507,10 +469,8 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * normal case, admin and view and noaccess website => return only admin
-     *
-     * @group Plugins
      */
-    public function testGetSitesWithAdminAccess()
+    public function testGetSitesWithAdminAccess_shouldOnlyReturnSitesHavingActuallyAdminAccess()
     {
         API::getInstance()->addSite("site1", array("http://piwik.net", "http://piwik.com/test/"));
         API::getInstance()->addSite("site2", array("http://piwik.com/test/"));
@@ -531,10 +491,81 @@ class ApiTest extends IntegrationTestCase
         $this->assertEquals($resultWanted, $sites);
     }
 
+    public function testGetSitesWithAdminAccess_shouldApplyLimit_IfSet()
+    {
+        $this->createManySitesWithAdminAccess(40);
+
+        // should return all sites by default
+        $sites = API::getInstance()->getSitesWithAdminAccess();
+        $this->assertReturnedSitesContainsSiteIds(range(1, 40), $sites);
+
+        // return only 5 sites
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, false, 5);
+        $this->assertReturnedSitesContainsSiteIds(array(1, 2, 3, 4, 5), $sites);
+
+        // return only 10 sites
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, false, 10);
+        $this->assertReturnedSitesContainsSiteIds(range(1, 10), $sites);
+    }
+
+    public function testGetSitesWithAdminAccess_shouldApplyPattern_IfSetAndFindBySiteName()
+    {
+        $this->createManySitesWithAdminAccess(40);
+
+        // by site name
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, 'site38');
+        $this->assertReturnedSitesContainsSiteIds(array(38), $sites);
+    }
+
+    public function testGetSitesWithAdminAccess_shouldApplyPattern_IfSetAndFindByUrl()
+    {
+        $this->createManySitesWithAdminAccess(40);
+
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, 'piwik38.o');
+        $this->assertReturnedSitesContainsSiteIds(array(38), $sites);
+    }
+
+    public function testGetSitesWithAdminAccess_shouldApplyPattern_AndFindMany()
+    {
+        $this->createManySitesWithAdminAccess(40);
+
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, '5');
+        $this->assertReturnedSitesContainsSiteIds(array(5, 15, 25, 35), $sites);
+    }
+
+    public function testGetSitesWithAdminAccess_shouldApplyPatternAndLimit()
+    {
+        $this->createManySitesWithAdminAccess(40);
+
+        $sites = API::getInstance()->getSitesWithAdminAccess(false, '5', 2);
+        $this->assertReturnedSitesContainsSiteIds(array(5, 15), $sites);
+    }
+
+    private function createManySitesWithAdminAccess($numSites)
+    {
+        for ($i = 1; $i <= $numSites; $i++) {
+            API::getInstance()->addSite("site" . $i, array("http://piwik$i.org"));
+        }
+
+        FakeAccess::setIdSitesAdmin(range(1, $numSites));
+    }
+
+    private function assertReturnedSitesContainsSiteIds($expectedSiteIds, $sites)
+    {
+        $this->assertCount(count($expectedSiteIds), $sites);
+
+        foreach ($sites as $site) {
+            $key = array_search($site['idsite'], $expectedSiteIds);
+            $this->assertNotFalse($key, 'Did not find expected siteId "' . $site['idsite'] . '" in the expected siteIds');
+            unset($expectedSiteIds[$key]);
+        }
+
+        $siteIds = var_export($expectedSiteIds, 1);
+        $this->assertEmpty($expectedSiteIds, 'Not all expected sites were found, remaining site ids: ' . $siteIds);
+    }
+
     /**
      * there is no admin site available -> array()
-     *
-     * @group Plugins
      */
     public function testGetSitesWithViewAccessNoResult()
     {
@@ -547,8 +578,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * normal case, admin and view and noaccess website => return only admin
-     *
-     * @group Plugins
      */
     public function testGetSitesWithViewAccess()
     {
@@ -573,8 +602,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * there is no admin site available -> array()
-     *
-     * @group Plugins
      */
     public function testGetSitesWithAtLeastViewAccessNoResult()
     {
@@ -587,8 +614,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * normal case, admin and view and noaccess website => return only admin
-     *
-     * @group Plugins
      */
     public function testGetSitesWithAtLeastViewAccess()
     {
@@ -613,8 +638,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * no urls for this site => array()
-     *
-     * @group Plugins
      */
     public function testGetSiteUrlsFromIdNoUrls()
     {
@@ -626,8 +649,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * normal case
-     *
-     * @group Plugins
      */
     public function testGetSiteUrlsFromIdManyUrls()
     {
@@ -650,8 +671,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * wrongId => exception
-     *
-     * @group Plugins
      */
     public function testGetSiteUrlsFromIdWrongId()
     {
@@ -667,8 +686,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * one url => no change to alias urls
-     *
-     * @group Plugins
      */
     public function testUpdateSiteOneUrl()
     {
@@ -717,8 +734,6 @@ class ApiTest extends IntegrationTestCase
 
     /**
      * strange name and NO URL => name ok, main_url not updated
-     *
-     * @group Plugins
      */
     public function testUpdateSiteStrangeNameNoUrl()
     {
@@ -737,8 +752,6 @@ class ApiTest extends IntegrationTestCase
     /**
      * several urls => both main and alias are updated
      * also test the update of group field
-     *
-     * @group Plugins
      */
     public function testUpdateSiteSeveralUrlsAndGroup()
     {
@@ -861,9 +874,6 @@ class ApiTest extends IntegrationTestCase
         $this->assertEmpty($siteInfo);
     }
 
-    /**
-     * @group Plugins
-     */
     public function testGetSitesGroups()
     {
         $groups = array('group1', ' group1 ', '', 'group2');
@@ -886,7 +896,6 @@ class ApiTest extends IntegrationTestCase
     /**
      *
      * @dataProvider getInvalidTimezoneData
-     * @group Plugins
      */
     public function testAddSitesInvalidTimezone($timezone)
     {
@@ -899,9 +908,6 @@ class ApiTest extends IntegrationTestCase
         $this->fail('Expected exception not raised');
     }
 
-    /**
-     * @group Plugins
-     */
     public function testAddSitesInvalidCurrency()
     {
         try {
@@ -914,9 +920,6 @@ class ApiTest extends IntegrationTestCase
         $this->fail('Expected exception not raised');
     }
 
-    /**
-     * @group Plugins
-     */
     public function testSetDefaultTimezoneAndCurrencyAndExcludedQueryParametersAndExcludedIps()
     {
         // test that they return default values
@@ -983,9 +986,6 @@ class ApiTest extends IntegrationTestCase
         $this->assertFalse(Site::isEcommerceEnabledFor($idsite));
     }
 
-    /**
-     * @group Plugins
-     */
     public function testGetSitesIdFromSiteUrlSuperUser()
     {
         API::getInstance()->addSite("site1", array("http://piwik.net", "http://piwik.com"));
@@ -1005,9 +1005,6 @@ class ApiTest extends IntegrationTestCase
         $this->assertTrue(count($idsites) == 3);
     }
 
-    /**
-     * @group Plugins
-     */
     public function testGetSitesIdFromSiteUrlUser()
     {
         API::getInstance()->addSite("site1", array("http://www.piwik.net", "http://piwik.com"));
@@ -1063,9 +1060,6 @@ class ApiTest extends IntegrationTestCase
         Access::setSingletonInstance($saveAccess);
     }
 
-    /**
-     * @group Plugins
-     */
     public function testGetSitesFromTimezones()
     {
         API::getInstance()->addSite("site3", array("http://piwik.org"), null, $siteSearch = 1, $searchKeywordParameters = null, $searchCategoryParameters = null, null, null, 'UTC');
diff --git a/plugins/Transitions/stylesheets/transitions.less b/plugins/Transitions/stylesheets/transitions.less
index 7610e928d756bc55098239a59f24a292c3d68b5d..8e293d91e1dfab8fcdb4bafc7fa51f6d4b2fc3f3 100644
--- a/plugins/Transitions/stylesheets/transitions.less
+++ b/plugins/Transitions/stylesheets/transitions.less
@@ -65,6 +65,7 @@
     font-weight: bold;
     overflow: hidden;
     color: #255792;
+    margin: 0;
 }
 
 .Transitions_Pageviews {
diff --git a/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php b/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php
new file mode 100644
index 0000000000000000000000000000000000000000..720d24e2798ae91194e8c20f3e094c9e1e8eba6b
--- /dev/null
+++ b/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Piwik\Plugins\UserCountry\Diagnostic;
+
+use Piwik\Config;
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\Plugins\UserCountry\LocationProvider;
+use Piwik\Translation\Translator;
+
+/**
+ * Check the geolocation setup.
+ */
+class GeolocationDiagnostic implements Diagnostic
+{
+    /**
+     * @var Translator
+     */
+    private $translator;
+
+    public function __construct(Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
+    public function execute()
+    {
+        $isPiwikInstalling = !Config::getInstance()->existsLocalConfig();
+        if ($isPiwikInstalling) {
+            // Skip the diagnostic if Piwik is being installed
+            return array();
+        }
+
+        $label = $this->translator->translate('UserCountry_Geolocation');
+
+        $currentProviderId = LocationProvider::getCurrentProviderId();
+        $allProviders = LocationProvider::getAllProviderInfo();
+        $isRecommendedProvider = in_array($currentProviderId, array(LocationProvider\GeoIp\Php::ID, $currentProviderId == LocationProvider\GeoIp\Pecl::ID));
+        $isProviderInstalled = ($allProviders[$currentProviderId]['status'] == LocationProvider::INSTALLED);
+
+        if ($isRecommendedProvider && $isProviderInstalled) {
+            return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK));
+        }
+
+        if ($isProviderInstalled) {
+            $comment = $this->translator->translate('UserCountry_GeoIpLocationProviderNotRecomnended') . ' ';
+            $comment .= $this->translator->translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2', array(
+                '<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>'
+            ));
+        } else {
+            $comment = $this->translator->translate('UserCountry_DefaultLocationProviderDesc1') . ' ';
+            $comment .= $this->translator->translate('UserCountry_DefaultLocationProviderDesc2', array(
+                '<a href="http://piwik.org/docs/geo-locate/" rel="noreferrer" target="_blank">', '', '', '</a>'
+            ));
+        }
+
+        return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+    }
+}
diff --git a/plugins/UserCountry/config/config.php b/plugins/UserCountry/config/config.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd725049207865144424d3ee827a1b633b12f5bb
--- /dev/null
+++ b/plugins/UserCountry/config/config.php
@@ -0,0 +1,7 @@
+<?php
+
+return array(
+    'diagnostics.optional' => DI\add(array(
+        DI\get('Piwik\Plugins\UserCountry\Diagnostic\GeolocationDiagnostic'),
+    )),
+);
diff --git a/plugins/UserCountry/lang/en.json b/plugins/UserCountry/lang/en.json
index 90b2ca7e7c835c99acf4a90fc5687a1c8e5f813d..0eb36c6775ce46c0fd6f86a2ea833b9fd95093b6 100644
--- a/plugins/UserCountry/lang/en.json
+++ b/plugins/UserCountry/lang/en.json
@@ -268,7 +268,7 @@
         "country_tr": "Turkey",
         "country_tt": "Trinidad and Tobago",
         "country_tv": "Tuvalu",
-        "country_tw": "Taiwan, Province of China",
+        "country_tw": "Taiwan",
         "country_tz": "Tanzania, United Republic of",
         "country_ua": "Ukraine",
         "country_ug": "Uganda",
@@ -371,4 +371,4 @@
         "UpdaterWillRunNext": "It is next scheduled to run on %s.",
         "WidgetLocation": "Visitor Location"
     }
-}
\ No newline at end of file
+}
diff --git a/plugins/UserCountry/templates/index.twig b/plugins/UserCountry/templates/index.twig
index 72d49613eb689aed42e01c3d5f843a0f428c4298..70ac5f8ba72d72110ee1aefe2d0d7218111eaf7d 100644
--- a/plugins/UserCountry/templates/index.twig
+++ b/plugins/UserCountry/templates/index.twig
@@ -1,26 +1,28 @@
-<div id="leftcolumn">
-    {{ postEvent("Template.leftColumnUserCountry") }}
+<div class="row">
 
-    <h2 piwik-enriched-headline>{{ 'UserCountry_Continent'|translate }}</h2>
-    {{ dataTableContinent|raw }}
+    <div class="col-md-6">
+        {{ postEvent("Template.leftColumnUserCountry") }}
 
-    <div class="sparkline">
-        {{ sparkline(urlSparklineCountries) }}
-        {{ 'UserCountry_DistinctCountries'|translate("<strong>"~numberDistinctCountries~"</strong>")|raw }}
-    </div>
+        <h2 piwik-enriched-headline>{{ 'UserCountry_Continent'|translate }}</h2>
+        {{ dataTableContinent|raw }}
 
-    {{ postEvent("Template.footerUserCountry") }}
+        <div class="sparkline">
+            {{ sparkline(urlSparklineCountries) }}
+            {{ 'UserCountry_DistinctCountries'|translate("<strong>"~numberDistinctCountries~"</strong>")|raw }}
+        </div>
 
-</div>
+        {{ postEvent("Template.footerUserCountry") }}
+    </div>
 
-<div id="rightcolumn">
-    <h2 piwik-enriched-headline>{{ 'UserCountry_Country'|translate }}</h2>
-    {{ dataTableCountry|raw }}
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'UserCountry_Country'|translate }}</h2>
+        {{ dataTableCountry|raw }}
 
-    <h2 piwik-enriched-headline>{{ 'UserCountry_Region'|translate }}</h2>
-    {{ dataTableRegion|raw }}
+        <h2 piwik-enriched-headline>{{ 'UserCountry_Region'|translate }}</h2>
+        {{ dataTableRegion|raw }}
 
-    <h2 piwik-enriched-headline>{{ 'UserCountry_City'|translate }}</h2>
-    {{ dataTableCity|raw }}
-</div>
+        <h2 piwik-enriched-headline>{{ 'UserCountry_City'|translate }}</h2>
+        {{ dataTableCity|raw }}
+    </div>
 
+</div>
diff --git a/plugins/UserCountryMap/javascripts/realtime-map.js b/plugins/UserCountryMap/javascripts/realtime-map.js
index 5fe57e179057fa1fcc2ebfa382dc08bc2741613f..0881a198f2a0f23fd0bc428bf08ce748a89dbc2c 100644
--- a/plugins/UserCountryMap/javascripts/realtime-map.js
+++ b/plugins/UserCountryMap/javascripts/realtime-map.js
@@ -359,7 +359,7 @@
                  */
                 function gotNewReport(report) {
                     // if the map has been destroyed, do nothing
-                    if (!self.map) {
+                    if (!self.map || !self.$element.length || !$.contains(document, self.$element[0])) {
                         return;
                     }
 
diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php
index ccb4dac126e6c6b4ad54a00f91ec5869ab7d3b14..752f452924689ad0222ae9aa827decff43d173b3 100644
--- a/plugins/UsersManager/API.php
+++ b/plugins/UsersManager/API.php
@@ -101,7 +101,7 @@ class API extends \Piwik\Plugin\API
     {
         Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
 
-        $optionValue = Option::get($this->getPreferenceId($userLogin, $preferenceName));
+        $optionValue = $this->getPreferenceValue($userLogin, $preferenceName);
 
         if ($optionValue !== false) {
             return $optionValue;
@@ -110,6 +110,25 @@ class API extends \Piwik\Plugin\API
         return $this->getDefaultUserPreference($preferenceName, $userLogin);
     }
 
+    /**
+     * Sets a user preference in the DB using the preference's default value.
+     * @param string $userLogin
+     * @param string $preferenceName
+     * @ignore
+     */
+    public function initUserPreferenceWithDefault($userLogin, $preferenceName)
+    {
+        Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
+
+        $optionValue = $this->getPreferenceValue($userLogin, $preferenceName);
+
+        if ($optionValue === false) {
+            $defaultValue = $this->getDefaultUserPreference($preferenceName, $userLogin);
+
+            $this->setUserPreference($userLogin, $preferenceName, $defaultValue);
+        }
+    }
+
     /**
      * Returns an array of Preferences
      * @param $preferenceNames array of preference names
@@ -143,6 +162,11 @@ class API extends \Piwik\Plugin\API
         return $login . self::OPTION_NAME_PREFERENCE_SEPARATOR . $preference;
     }
 
+    private function getPreferenceValue($userLogin, $preferenceName)
+    {
+        return Option::get($this->getPreferenceId($userLogin, $preferenceName));
+    }
+
     private function getDefaultUserPreference($preferenceName, $login)
     {
         switch ($preferenceName) {
diff --git a/plugins/UsersManager/Tasks.php b/plugins/UsersManager/Tasks.php
new file mode 100644
index 0000000000000000000000000000000000000000..668ce559c992c6c5d9f04552c5902cb6b047a2e2
--- /dev/null
+++ b/plugins/UsersManager/Tasks.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\UsersManager;
+
+use Piwik\Access;
+
+class Tasks extends \Piwik\Plugin\Tasks
+{
+    /**
+     * @var Model
+     */
+    private $usersModel;
+
+    /**
+     * @param API
+     */
+    private $usersManagerApi;
+
+    public function __construct(Model $usersModel, API $usersManagerApi)
+    {
+        $this->usersModel = $usersModel;
+        $this->usersManagerApi = $usersManagerApi;
+    }
+
+    public function schedule()
+    {
+        $this->daily("setUserDefaultReportPreference");
+    }
+
+    public function setUserDefaultReportPreference()
+    {
+        // We initialize the default report user preference for each user (if it hasn't been inited before) for performance,
+        // doing this lets us avoid loading all siteIds (which can be 50k or more) when this preference is requested.
+        // getting the user preference can be called quite often when generating links etc (to get defaultWebsiteId).
+        $usersModel = $this->usersModel;
+        $usersManagerApi = $this->usersManagerApi;
+        Access::getInstance()->doAsSuperUser(function () use ($usersModel, $usersManagerApi) {
+            $allUsers = $usersModel->getUsers(array());
+            foreach ($allUsers as $user) {
+                $usersManagerApi->initUserPreferenceWithDefault($user['login'], API::PREFERENCE_DEFAULT_REPORT);
+            }
+        });
+    }
+}
diff --git a/plugins/UsersManager/tests/Integration/APITest.php b/plugins/UsersManager/tests/Integration/APITest.php
index d743ca01a04756382764c11fb82b5c90bb2e6c95..f4e4c41f5dbc5c330b5167094ed8a5764354f9aa 100644
--- a/plugins/UsersManager/tests/Integration/APITest.php
+++ b/plugins/UsersManager/tests/Integration/APITest.php
@@ -8,6 +8,7 @@
 
 namespace Piwik\Plugins\UsersManager\tests;
 use Piwik\Access;
+use Piwik\Option;
 use Piwik\Piwik;
 use Piwik\Plugins\UsersManager\API;
 use Piwik\Tests\Framework\Fixture;
@@ -25,6 +26,8 @@ class APITest extends IntegrationTestCase
      * @var API
      */
     private $api;
+    
+    private $login = 'userLogin';
 
     public function setUp()
     {
@@ -39,7 +42,7 @@ class APITest extends IntegrationTestCase
         Fixture::createWebsite('2014-01-01 00:00:00');
         Fixture::createWebsite('2014-01-01 00:00:00');
         Fixture::createWebsite('2014-01-01 00:00:00');
-        $this->api->addUser('userLogin', 'password', 'userlogin@password.de');
+        $this->api->addUser($this->login, 'password', 'userlogin@password.de');
     }
 
     public function test_setUserAccess_ShouldTriggerRemoveSiteAccessEvent_IfAccessToAWebsiteIsRemoved()
@@ -48,11 +51,11 @@ class APITest extends IntegrationTestCase
         $self = $this;
         Piwik::addAction('UsersManager.removeSiteAccess', function ($login, $idSites) use (&$eventTriggered, $self) {
             $eventTriggered = true;
-            $self->assertEquals('userLogin', $login);
+            $self->assertEquals($this->login, $login);
             $self->assertEquals(array(1, 2), $idSites);
         });
 
-        $this->api->setUserAccess('userLogin', 'noaccess', array(1, 2));
+        $this->api->setUserAccess($this->login, 'noaccess', array(1, 2));
 
         $this->assertTrue($eventTriggered, 'UsersManager.removeSiteAccess event was not triggered');
     }
@@ -64,7 +67,7 @@ class APITest extends IntegrationTestCase
             $eventTriggered = true;
         });
 
-        $this->api->setUserAccess('userLogin', 'admin', array(1, 2));
+        $this->api->setUserAccess($this->login, 'admin', array(1, 2));
 
         $this->assertFalse($eventTriggered, 'UsersManager.removeSiteAccess event was triggered but should not');
     }
@@ -81,6 +84,48 @@ class APITest extends IntegrationTestCase
         $this->assertEmpty($preferences);
     }
 
+    public function test_getUserPreference_ShouldReturnADefaultPreference_IfNoneIsSet()
+    {
+        $siteId = $this->api->getUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT);
+        $this->assertEquals('1', $siteId);
+    }
+
+    public function test_getUserPreference_ShouldReturnASetreference_IfNoneIsSet()
+    {
+        $this->api->setUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT, 5);
+
+        $siteId = $this->api->getUserPreference($this->login, API::PREFERENCE_DEFAULT_REPORT);
+        $this->assertEquals('5', $siteId);
+    }
+
+    public function test_initUserPreferenceWithDefault_ShouldSaveTheDefaultPreference_IfPreferenceIsNotSet()
+    {
+        // make sure there is no value saved so it will use default preference
+        $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT));
+        $this->assertFalse($siteId);
+
+        $this->api->initUserPreferenceWithDefault($this->login, API::PREFERENCE_DEFAULT_REPORT);
+
+        // make sure it did save the preference
+        $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT));
+        $this->assertEquals('1', $siteId);
+    }
+
+    public function test_initUserPreferenceWithDefault_ShouldNotSaveTheDefaultPreference_IfPreferenceIsAlreadySet()
+    {
+        // set value so there will already be a default
+        Option::set($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT), '999');
+
+        $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT));
+        $this->assertEquals('999', $siteId);
+
+        $this->api->initUserPreferenceWithDefault($this->login, API::PREFERENCE_DEFAULT_REPORT);
+
+        // make sure it did not save the preference
+        $siteId = Option::get($this->getPreferenceId(API::PREFERENCE_DEFAULT_REPORT));
+        $this->assertEquals('999', $siteId);
+    }
+
     public function test_getAllUsersPreferences_shouldGetMultiplePreferences()
     {
         $user2 = 'userLogin2';
@@ -164,4 +209,9 @@ class APITest extends IntegrationTestCase
         );
         $this->assertEquals($expected, $access);
     }
+
+    private function getPreferenceId($preferenceName)
+    {
+        return $this->login . '_' . $preferenceName;
+    }
 }
diff --git a/plugins/VisitFrequency/templates/_sparklines.twig b/plugins/VisitFrequency/templates/_sparklines.twig
index 97697272e84385f46553fc4a6b27fbd768583a0b..e5639e2e367714aafb41a0d2e6ef29d8f1d198d8 100644
--- a/plugins/VisitFrequency/templates/_sparklines.twig
+++ b/plugins/VisitFrequency/templates/_sparklines.twig
@@ -1,26 +1,29 @@
-<div id="leftcolumn">
-    <div class="sparkline">
-        {{ sparkline(urlSparklineNbVisitsReturning) }}
-        {{ 'VisitFrequency_ReturnVisits'|translate("<strong>"~nbVisitsReturning~"</strong>")|raw }}
+<div class="row">
+
+    <div class="col-md-6">
+        <div class="sparkline">
+            {{ sparkline(urlSparklineNbVisitsReturning) }}
+            {{ 'VisitFrequency_ReturnVisits'|translate("<strong>"~nbVisitsReturning~"</strong>")|raw }}
+        </div>
+        <div class="sparkline">
+            {{ sparkline(urlSparklineNbActionsReturning) }}
+            {{ 'VisitFrequency_ReturnActions'|translate("<strong>"~nbActionsReturning~"</strong>")|raw }}
+        </div>
+        <div class="sparkline">
+            {{ sparkline(urlSparklineActionsPerVisitReturning) }}
+            {{ 'VisitFrequency_ReturnAvgActions'|translate("<strong>"~nbActionsPerVisitReturning~"</strong>")|raw }}
+        </div>
     </div>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineNbActionsReturning) }}
-        {{ 'VisitFrequency_ReturnActions'|translate("<strong>"~nbActionsReturning~"</strong>")|raw }}
+    <div class="col-md-6">
+        <div class="sparkline">
+            {{ sparkline(urlSparklineAvgVisitDurationReturning) }}
+            {% set avgVisitDurationReturning=avgVisitDurationReturning|sumtime %}
+            {{ 'VisitFrequency_ReturnAverageVisitDuration'|translate("<strong>"~avgVisitDurationReturning~"</strong>")|raw }}
+        </div>
+        <div class="sparkline">
+            {{ sparkline(urlSparklineBounceRateReturning) }}
+            {{ 'VisitFrequency_ReturnBounceRate'|translate("<strong>"~bounceRateReturning~"</strong>")|raw }}
+        </div>
+        {% include "_sparklineFooter.twig" %}
     </div>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineActionsPerVisitReturning) }}
-        {{ 'VisitFrequency_ReturnAvgActions'|translate("<strong>"~nbActionsPerVisitReturning~"</strong>")|raw }}
-    </div>
-</div><div id="rightcolumn">
-    <div class="sparkline">
-        {{ sparkline(urlSparklineAvgVisitDurationReturning) }}
-        {% set avgVisitDurationReturning=avgVisitDurationReturning|sumtime %}
-        {{ 'VisitFrequency_ReturnAverageVisitDuration'|translate("<strong>"~avgVisitDurationReturning~"</strong>")|raw }}
-    </div>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineBounceRateReturning) }}
-        {{ 'VisitFrequency_ReturnBounceRate'|translate("<strong>"~bounceRateReturning~"</strong>")|raw }}
-    </div>
-    {% include "_sparklineFooter.twig" %}
 </div>
-<div style="clear:both"></div>
\ No newline at end of file
diff --git a/plugins/VisitTime/templates/index.twig b/plugins/VisitTime/templates/index.twig
index c0c2a85feb036dd24fa149fd7412a7b5294723bd..688cde0338343f218d54061c51cc71f58b7f8808 100644
--- a/plugins/VisitTime/templates/index.twig
+++ b/plugins/VisitTime/templates/index.twig
@@ -1,9 +1,13 @@
-<div id='leftcolumn'>
-    <h2 piwik-enriched-headline>{{ 'VisitTime_LocalTime'|translate }}</h2>
-    {{ dataTableVisitInformationPerLocalTime|raw }}
-</div>
+<div class="row">
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitTime_LocalTime'|translate }}</h2>
+        {{ dataTableVisitInformationPerLocalTime|raw }}
+    </div>
+
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitTime_ServerTime'|translate }}</h2>
+        {{ dataTableVisitInformationPerServerTime|raw }}
+    </div>
 
-<div id='rightcolumn'>
-    <h2 piwik-enriched-headline>{{ 'VisitTime_ServerTime'|translate }}</h2>
-    {{ dataTableVisitInformationPerServerTime|raw }}
 </div>
diff --git a/plugins/VisitorInterest/templates/index.twig b/plugins/VisitorInterest/templates/index.twig
index af98d8f1ffd3adb519df9c05d4a6bf2adcc89a95..332289e10648fd606b58da5618cce66cdc7193bb 100644
--- a/plugins/VisitorInterest/templates/index.twig
+++ b/plugins/VisitorInterest/templates/index.twig
@@ -1,20 +1,21 @@
-<br />
-<div id="leftcolumn">
-    <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerDuration'|translate }}</h2>
-    {{ dataTableNumberOfVisitsPerVisitDuration|raw }}
+<div class="row">
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerDuration'|translate }}</h2>
+        {{ dataTableNumberOfVisitsPerVisitDuration|raw }}
+    </div>
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerNbOfPages'|translate }}</h2>
+        {{ dataTableNumberOfVisitsPerPage|raw }}
+    </div>
 </div>
-<div id="rightcolumn">
-    <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsPerNbOfPages'|translate }}</h2>
-    {{ dataTableNumberOfVisitsPerPage|raw }}
+
+<div class="row">
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitorInterest_visitsByVisitCount'|translate }}</h2>
+        {{ dataTableNumberOfVisitsByVisitNum|raw }}
+    </div>
+    <div class="col-md-6">
+        <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsByDaysSinceLast'|translate }}</h2>
+        {{ dataTableNumberOfVisitsByDaysSinceLast|raw }}
+    </div>
 </div>
-<div style="clear:both"></div>
-<br />
-<div id="leftcolumn">
-    <h2 piwik-enriched-headline>{{ 'VisitorInterest_visitsByVisitCount'|translate }}</h2>
-    {{ dataTableNumberOfVisitsByVisitNum|raw }}
-</div>
-<div id="rightcolumn">
-    <h2 piwik-enriched-headline>{{ 'VisitorInterest_VisitsByDaysSinceLast'|translate }}</h2>
-    {{ dataTableNumberOfVisitsByDaysSinceLast|raw }}
-</div>
-<div style="clear:both"></div>
\ No newline at end of file
diff --git a/plugins/VisitsSummary/templates/_sparklines.twig b/plugins/VisitsSummary/templates/_sparklines.twig
index a61a31d2300e2db73f24c9e4b68182d2bebb6aff..dbef25aba55b803276c79ee533c7fe0f2ad0cee3 100644
--- a/plugins/VisitsSummary/templates/_sparklines.twig
+++ b/plugins/VisitsSummary/templates/_sparklines.twig
@@ -1,78 +1,81 @@
-<div id='leftcolumn'>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineNbVisits)|raw }}
-        {{ 'General_NVisits'|translate("<strong>"~nbVisits~"</strong>")|raw }}{% if displayUniqueVisitors %},
-            {{ 'VisitsSummary_NbUniqueVisitors'|translate("<strong>"~nbUniqVisitors~"</strong>")|raw }}{% endif %}
-    </div>
-    {% if nbUsers > 0 %}
-        {# Most of users will not have used `setUserId` so this would be confusingly zero #}
+<div class="row">
+
+    <div class="col-md-6">
         <div class="sparkline">
-            {{ sparkline(urlSparklineNbUsers)|raw }}
-            {{ 'General_NUsers'|translate("<strong>"~nbUsers~"</strong>")|raw }}
+            {{ sparkline(urlSparklineNbVisits)|raw }}
+            {{ 'General_NVisits'|translate("<strong>"~nbVisits~"</strong>")|raw }}{% if displayUniqueVisitors %},
+                {{ 'VisitsSummary_NbUniqueVisitors'|translate("<strong>"~nbUniqVisitors~"</strong>")|raw }}{% endif %}
         </div>
-    {% endif %}
-    <div class="sparkline">
-        {{ sparkline(urlSparklineAvgVisitDuration)|raw }}
-        {% set averageVisitDuration=averageVisitDuration|sumtime %}
-        {{ 'VisitsSummary_AverageVisitDuration'|translate("<strong>"~averageVisitDuration~"</strong>")|raw }}
-    </div>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineBounceRate)|raw }}
-        {{ 'VisitsSummary_NbVisitsBounced'|translate("<strong>"~bounceRate~"</strong>")|raw }}
-    </div>
-    <div class="sparkline">
-        {{ sparkline(urlSparklineActionsPerVisit)|raw }}
-        {{ 'VisitsSummary_NbActionsPerVisit'|translate("<strong>"~nbActionsPerVisit~"</strong>")|raw }}
-    </div>
-    {% if showActionsPluginReports|default(false) %}
-	<div class="sparkline">
-        {{ sparkline(urlSparklineAvgGenerationTime)|raw }}
-		{% set averageGenerationTime=averageGenerationTime|sumtime %}
-		{{ 'VisitsSummary_AverageGenerationTime'|translate("<strong>"~averageGenerationTime~"</strong>")|raw }}
-	</div>
-    {% endif %}
-</div>
-
-<div id='rightcolumn'>
-    {% if showActionsPluginReports|default(false) %}
-        {% if showOnlyActions %}
-            <div class="sparkline">
-                {{ sparkline(urlSparklineNbActions)|raw }}
-                {{ 'VisitsSummary_NbActionsDescription'|translate("<strong>"~nbActions~"</strong>")|raw }}
-            </div>
-        {% else %}
+        {% if nbUsers > 0 %}
+            {# Most of users will not have used `setUserId` so this would be confusingly zero #}
             <div class="sparkline">
-                {{ sparkline(urlSparklineNbPageviews)|raw }}
-                {{ 'VisitsSummary_NbPageviewsDescription'|translate("<strong>"~nbPageviews~"</strong>")|trim|raw }},
-                {{ 'VisitsSummary_NbUniquePageviewsDescription'|translate("<strong>"~nbUniquePageviews~"</strong>")|raw }}
+                {{ sparkline(urlSparklineNbUsers)|raw }}
+                {{ 'General_NUsers'|translate("<strong>"~nbUsers~"</strong>")|raw }}
             </div>
-            {% if displaySiteSearch %}
+        {% endif %}
+        <div class="sparkline">
+            {{ sparkline(urlSparklineAvgVisitDuration)|raw }}
+            {% set averageVisitDuration=averageVisitDuration|sumtime %}
+            {{ 'VisitsSummary_AverageVisitDuration'|translate("<strong>"~averageVisitDuration~"</strong>")|raw }}
+        </div>
+        <div class="sparkline">
+            {{ sparkline(urlSparklineBounceRate)|raw }}
+            {{ 'VisitsSummary_NbVisitsBounced'|translate("<strong>"~bounceRate~"</strong>")|raw }}
+        </div>
+        <div class="sparkline">
+            {{ sparkline(urlSparklineActionsPerVisit)|raw }}
+            {{ 'VisitsSummary_NbActionsPerVisit'|translate("<strong>"~nbActionsPerVisit~"</strong>")|raw }}
+        </div>
+        {% if showActionsPluginReports|default(false) %}
+        <div class="sparkline">
+            {{ sparkline(urlSparklineAvgGenerationTime)|raw }}
+            {% set averageGenerationTime=averageGenerationTime|sumtime %}
+            {{ 'VisitsSummary_AverageGenerationTime'|translate("<strong>"~averageGenerationTime~"</strong>")|raw }}
+        </div>
+        {% endif %}
+    </div>
+
+    <div class="col-md-6">
+        {% if showActionsPluginReports|default(false) %}
+            {% if showOnlyActions %}
                 <div class="sparkline">
-                    {{ sparkline(urlSparklineNbSearches)|raw }}
-                    {{ 'VisitsSummary_NbSearchesDescription'|translate("<strong>"~nbSearches~"</strong>")|trim|raw }},
-                    {{ 'VisitsSummary_NbKeywordsDescription'|translate("<strong>"~nbKeywords~"</strong>")|raw }}
+                    {{ sparkline(urlSparklineNbActions)|raw }}
+                    {{ 'VisitsSummary_NbActionsDescription'|translate("<strong>"~nbActions~"</strong>")|raw }}
                 </div>
-            {% endif %}
-            <div class="sparkline">
-                    {{ sparkline(urlSparklineNbDownloads)|raw }}
-                    {{ 'VisitsSummary_NbDownloadsDescription'|translate("<strong>"~nbDownloads~"</strong>")|trim|raw }},
-                    {{ 'VisitsSummary_NbUniqueDownloadsDescription'|translate("<strong>"~nbUniqueDownloads~"</strong>")|raw }}
-            </div>
-            <div class="sparkline">
-                    {{ sparkline(urlSparklineNbOutlinks)|raw }}
-                    {{ 'VisitsSummary_NbOutlinksDescription'|translate("<strong>"~nbOutlinks~"</strong>")|trim|raw }},
-                    {{ 'VisitsSummary_NbUniqueOutlinksDescription'|translate("<strong>"~nbUniqueOutlinks~"</strong>")|raw }}
-            </div>
-            {% endif %}
-    {% endif %}
-    <div class="sparkline">
-            {{ sparkline(urlSparklineMaxActions)|raw }}
-            {{ 'VisitsSummary_MaxNbActions'|translate("<strong>"~maxActions~"</strong>")|raw }}
+            {% else %}
+                <div class="sparkline">
+                    {{ sparkline(urlSparklineNbPageviews)|raw }}
+                    {{ 'VisitsSummary_NbPageviewsDescription'|translate("<strong>"~nbPageviews~"</strong>")|trim|raw }},
+                    {{ 'VisitsSummary_NbUniquePageviewsDescription'|translate("<strong>"~nbUniquePageviews~"</strong>")|raw }}
+                </div>
+                {% if displaySiteSearch %}
+                    <div class="sparkline">
+                        {{ sparkline(urlSparklineNbSearches)|raw }}
+                        {{ 'VisitsSummary_NbSearchesDescription'|translate("<strong>"~nbSearches~"</strong>")|trim|raw }},
+                        {{ 'VisitsSummary_NbKeywordsDescription'|translate("<strong>"~nbKeywords~"</strong>")|raw }}
+                    </div>
+                {% endif %}
+                <div class="sparkline">
+                        {{ sparkline(urlSparklineNbDownloads)|raw }}
+                        {{ 'VisitsSummary_NbDownloadsDescription'|translate("<strong>"~nbDownloads~"</strong>")|trim|raw }},
+                        {{ 'VisitsSummary_NbUniqueDownloadsDescription'|translate("<strong>"~nbUniqueDownloads~"</strong>")|raw }}
+                </div>
+                <div class="sparkline">
+                        {{ sparkline(urlSparklineNbOutlinks)|raw }}
+                        {{ 'VisitsSummary_NbOutlinksDescription'|translate("<strong>"~nbOutlinks~"</strong>")|trim|raw }},
+                        {{ 'VisitsSummary_NbUniqueOutlinksDescription'|translate("<strong>"~nbUniqueOutlinks~"</strong>")|raw }}
+                </div>
+                {% endif %}
+        {% endif %}
+        <div class="sparkline">
+                {{ sparkline(urlSparklineMaxActions)|raw }}
+                {{ 'VisitsSummary_MaxNbActions'|translate("<strong>"~maxActions~"</strong>")|raw }}
+        </div>
+
+        {{ postEvent('Template.VisitsSummaryOverviewSparklines') }}
     </div>
-    
-    {{ postEvent('Template.VisitsSummaryOverviewSparklines') }}
+
 </div>
-<div style="clear:both;"></div>
 
 {% include "_sparklineFooter.twig" %}
 
diff --git a/plugins/Widgetize/stylesheets/widgetize.less b/plugins/Widgetize/stylesheets/widgetize.less
index dafad78d9036389de133eeb48d0d2de63dc6d426..b4d7769d393f0a5155a656a0ce37cef566e0c5a6 100644
--- a/plugins/Widgetize/stylesheets/widgetize.less
+++ b/plugins/Widgetize/stylesheets/widgetize.less
@@ -31,4 +31,8 @@
 #embedThisWidgetFlash,
 #embedThisWidgetEverywhere {
     margin-top: 5px;
-}
\ No newline at end of file
+}
+
+body>.widget {
+  margin: 10px 7px;
+}
diff --git a/tests/LocalTracker.php b/tests/LocalTracker.php
index 6e251e6d21acbf2e995c8aee7c71c663002e217c..fd98ccfd896de345f7e38dd180f065e9ac3a8ec0 100755
--- a/tests/LocalTracker.php
+++ b/tests/LocalTracker.php
@@ -45,6 +45,7 @@ class Piwik_LocalTracker extends PiwikTracker
 
         // unset cached values
         Cache::$cache = null;
+        Tracker\Visit::$dimensions = null;
 
         // save some values
         $plugins = Config::getInstance()->Plugins['Plugins'];
@@ -77,6 +78,7 @@ class Piwik_LocalTracker extends PiwikTracker
         $request = new Tracker\RequestSet();
         $request->setRequests($requests);
 
+        \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins();
         $handler = Tracker\Handler\Factory::make();
 
         $response = $localTracker->main($handler, $request);
diff --git a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
index a9d77d5813df0a4b40ff0f3feb3a2511e95aa320..02dd26cc9889a0065e5e3f2c8d61bef6b5221890 100644
--- a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
+++ b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
@@ -15,6 +15,7 @@ use Piwik\Plugins\UserCountry\LocationProvider;
 use Piwik\Tests\Framework\Fixture;
 use Exception;
 use Piwik\Tests\Framework\Mock\LocationProvider as MockLocationProvider;
+use Piwik\Tracker\Visit;
 
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Framework/Mock/LocationProvider.php';
 
@@ -93,6 +94,7 @@ class ManyVisitsWithGeoIP extends Fixture
 
         if ($useLocal) {
             Cache::getTransientCache()->flushAll(); // make sure dimension cache is empty between local tracking runs
+            Visit::$dimensions = null;
         }
 
         // use local tracker so mock location provider can be used
diff --git a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
index a6ef25a89f3694cc9eca60959ec63447a60b001c..b93e6d42e7412f888eee432ae318bae3d6190460 100644
--- a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
+++ b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
@@ -22,6 +22,8 @@ use Piwik\Tests\Framework\Fixture;
  */
 class RawArchiveDataWithTempAndInvalidated extends Fixture
 {
+    const ROWS_PER_ARCHIVE = 5;
+
     private static $dummyArchiveData = array(
         // outdated temporary
         array(
@@ -317,7 +319,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
     public function assertTemporaryArchivesPurged($isBrowserTriggeredArchivingEnabled, Date $date)
     {
         if ($isBrowserTriggeredArchivingEnabled) {
-            $expectedPurgedArchives = array(1,2,3,4,6,7); // only archives from 2 hours before "now" are purged
+            $expectedPurgedArchives = array(1,2,3,4,6,7,10); // only archives from 2 hours before "now" are purged
         } else {
             $expectedPurgedArchives = array(1,2,3,4,7); // only archives before start of "yesterday" are purged
         }
diff --git a/tests/PHPUnit/Framework/Constraint/HttpResponseText.php b/tests/PHPUnit/Framework/Constraint/HttpResponseText.php
new file mode 100644
index 0000000000000000000000000000000000000000..54d70982624494916d549b3243a7ee025d37e077
--- /dev/null
+++ b/tests/PHPUnit/Framework/Constraint/HttpResponseText.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link    http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Tests\Framework\Constraint;
+
+class HttpResponseText extends \PHPUnit_Framework_Constraint
+{
+    private $actualCode;
+
+    /**
+     * @param string $value Expected response text.
+     */
+    public function __construct($value)
+    {
+        parent::__construct();
+        $this->value = $value;
+    }
+
+    /**
+     * Evaluates the constraint for parameter $other. Returns TRUE if the
+     * constraint is met, FALSE otherwise.
+     *
+     * @param mixed $other Value or object to evaluate.
+     * @return bool
+     */
+    public function matches($other)
+    {
+        $options = array(
+            CURLOPT_URL            => $other,
+            CURLOPT_HEADER         => false,
+            CURLOPT_TIMEOUT        => 1,
+            CURLOPT_RETURNTRANSFER => true
+        );
+
+        $ch = curl_init();
+        curl_setopt_array($ch, $options);
+        $response = @curl_exec($ch);
+        curl_close($ch);
+
+        $this->actualCode = $response;
+
+        return $this->value === $this->actualCode;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'does not return response text ' . $this->exporter->export($this->value) . ' it is ' . $this->actualCode;
+    }
+}?>
\ No newline at end of file
diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php
index 87ccf6d07199edd011174fe7c63b46926071a169..90f2ff95cc392eca70459a3507681ed153f4ae49 100644
--- a/tests/PHPUnit/Framework/Fixture.php
+++ b/tests/PHPUnit/Framework/Fixture.php
@@ -764,7 +764,7 @@ class Fixture extends \PHPUnit_Framework_Assert
         }
     }
 
-    protected static function executeLogImporter($logFile, $options)
+    public static function executeLogImporter($logFile, $options, $allowFailure = false)
     {
         $python = self::getPythonBinary();
 
@@ -793,7 +793,9 @@ class Fixture extends \PHPUnit_Framework_Assert
 
         // run the command
         exec($cmd, $output, $result);
-        if ($result !== 0) {
+        if ($result !== 0
+            && !$allowFailure
+        ) {
             throw new Exception("log importer failed: " . implode("\n", $output) . "\n\ncommand used: $cmd");
         }
 
diff --git a/tests/PHPUnit/Framework/TestCase/SystemTestCase.php b/tests/PHPUnit/Framework/TestCase/SystemTestCase.php
index f0a1c0f982269412949d3e788674e0872a51caf3..f8944bf880dab241d08966ba192c88ae6e788611 100755
--- a/tests/PHPUnit/Framework/TestCase/SystemTestCase.php
+++ b/tests/PHPUnit/Framework/TestCase/SystemTestCase.php
@@ -17,6 +17,7 @@ use Piwik\Db;
 use Piwik\DbHelper;
 use Piwik\ReportRenderer;
 use Piwik\Tests\Framework\Constraint\ResponseCode;
+use Piwik\Tests\Framework\Constraint\HttpResponseText;
 use Piwik\Tests\Framework\TestRequest\ApiTestConfig;
 use Piwik\Tests\Framework\TestRequest\Collection;
 use Piwik\Tests\Framework\TestRequest\Response;
@@ -596,6 +597,11 @@ abstract class SystemTestCase extends PHPUnit_Framework_TestCase
         }
     }
 
+    public function assertHttpResponseText($expectedResponseCode, $url, $message = '')
+    {
+        self::assertThat($url, new HttpResponseText($expectedResponseCode), $message);
+    }
+
     public function assertResponseCode($expectedResponseCode, $url, $message = '')
     {
         self::assertThat($url, new ResponseCode($expectedResponseCode), $message);
diff --git a/tests/PHPUnit/Integration/AccessTest.php b/tests/PHPUnit/Integration/AccessTest.php
index cb2d201c846ab074de2b99b1eeeaeb9b623ba807..a105b7287455610774bb2aa57bcae7b922c1e440 100644
--- a/tests/PHPUnit/Integration/AccessTest.php
+++ b/tests/PHPUnit/Integration/AccessTest.php
@@ -11,6 +11,8 @@ namespace Piwik\Tests\Integration;
 use Exception;
 use Piwik\Access;
 use Piwik\AuthResult;
+use Piwik\Db;
+use Piwik\NoAccessException;
 use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
 
 /**
@@ -121,16 +123,49 @@ class AccessTest extends IntegrationTestCase
         $access->checkUserHasSomeAdminAccess();
     }
 
+    /**
+     * @expectedException \Piwik\NoAccessException
+     */
+    public function test_CheckUserHasSomeAdminAccessWithSomeAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated()
+    {
+        $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9));
+        $mock->checkUserHasSomeAdminAccess();
+    }
+
+    /**
+     * @expectedException \Piwik\NoAccessException
+     */
+    public function test_checkUserHasAdminAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated()
+    {
+        $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9));
+        $mock->checkUserHasAdminAccess('2');
+    }
+
+    /**
+     * @expectedException \Piwik\NoAccessException
+     */
+    public function test_checkUserHasSomeViewAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated()
+    {
+        $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9));
+        $mock->checkUserHasSomeViewAccess();
+    }
+
+    /**
+     * @expectedException \Piwik\NoAccessException
+     */
+    public function test_checkUserHasViewAccessFails_IfUserHasPermissionsToSitesButIsNotAuthenticated()
+    {
+        $mock = $this->createAccessMockWithAccessToSitesButUnauthenticated(array(2, 9));
+        $mock->checkUserHasViewAccess('2');
+    }
+
     public function testCheckUserHasSomeAdminAccessWithSomeAccess()
     {
-        $mock = $this->getMock(
-            'Piwik\Access',
-            array('getSitesIdWithAdminAccess')
-        );
+        $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess'));
 
         $mock->expects($this->once())
-             ->method('getSitesIdWithAdminAccess')
-             ->will($this->returnValue(array(2, 9)));
+             ->method('getRawSitesWithSomeViewAccess')
+             ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(2, 9))));
 
         $mock->checkUserHasSomeAdminAccess();
     }
@@ -153,14 +188,11 @@ class AccessTest extends IntegrationTestCase
 
     public function testCheckUserHasSomeViewAccessWithSomeAccess()
     {
-        $mock = $this->getMock(
-            'Piwik\Access',
-            array('getSitesIdWithAtLeastViewAccess')
-        );
+        $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess'));
 
         $mock->expects($this->once())
-            ->method('getSitesIdWithAtLeastViewAccess')
-            ->will($this->returnValue(array(1, 2, 3, 4)));
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4))));
 
         $mock->checkUserHasSomeViewAccess();
     }
@@ -183,28 +215,24 @@ class AccessTest extends IntegrationTestCase
 
     public function testCheckUserHasViewAccessWithSomeAccessSuccessIdSitesAsString()
     {
-        $mock = $this->getMock(
-            'Piwik\Access',
-            array('getSitesIdWithAtLeastViewAccess')
-        );
+        /** @var Access $mock */
+        $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess'));
 
         $mock->expects($this->once())
-            ->method('getSitesIdWithAtLeastViewAccess')
-            ->will($this->returnValue(array(1, 2, 3, 4)));
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4))));
 
         $mock->checkUserHasViewAccess('1,3');
     }
 
     public function testCheckUserHasViewAccessWithSomeAccessSuccessAllSites()
     {
-        $mock = $this->getMock(
-            'Piwik\Access',
-            array('getSitesIdWithAtLeastViewAccess')
-        );
+        /** @var Access $mock */
+        $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess'));
 
         $mock->expects($this->any())
-            ->method('getSitesIdWithAtLeastViewAccess')
-            ->will($this->returnValue(array(1, 2, 3, 4)));
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildViewAccessForSiteIds(array(1, 2, 3, 4))));
 
         $mock->checkUserHasViewAccess('all');
     }
@@ -306,8 +334,7 @@ class AccessTest extends IntegrationTestCase
 
     public function testReloadAccessWithMockedAuthValid()
     {
-        $mock = $this->getMock('Piwik\\Auth', array('authenticate', 'getName', 'getTokenAuthSecret', 'getLogin', 'setTokenAuth', 'setLogin',
-                                                    'setPassword', 'setPasswordHash'));
+        $mock = $this->createPiwikAuthMockInstance();
         $mock->expects($this->once())
             ->method('authenticate')
             ->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', 'token')));
@@ -319,6 +346,43 @@ class AccessTest extends IntegrationTestCase
         $this->assertFalse($access->hasSuperUserAccess());
     }
 
+    public function test_reloadAccess_loadSitesIfNeeded_doesActuallyResetAllSiteIdsAndRequestThemAgain()
+    {
+        /** @var Access $mock */
+        $mock = $this->createAccessMockWithAuthenticatedUser(array('getRawSitesWithSomeViewAccess'));
+
+        $mock->expects($this->at(0))
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(1,2,3,4))));
+
+        $mock->expects($this->at(1))
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildAdminAccessForSiteIds(array(1))));
+
+        // should succeed as permission to 1,2,3,4
+        $mock->checkUserHasAdminAccess('1,3');
+
+        // should clear permissions
+        $mock->reloadAccess();
+
+        try {
+            // should fail as now only permission to site 1
+            $mock->checkUserHasAdminAccess('1,3');
+            $this->fail('An expected exception has not been triggered. Permissions were not resetted');
+        } catch (NoAccessException $e) {
+
+        }
+
+        $mock->checkUserHasAdminAccess('1'); // it should have access to site "1"
+
+        $mock->setSuperUserAccess(true);
+
+        $mock->reloadAccess();
+
+        // should now have permission as it is a superuser
+        $mock->checkUserHasAdminAccess('1,3');
+    }
+
     public function test_doAsSuperUser_ChangesSuperUserAccessCorrectly()
     {
         Access::getInstance()->setSuperUserAccess(false);
@@ -372,4 +436,65 @@ class AccessTest extends IntegrationTestCase
             AccessTest::assertTrue($access->hasSuperUserAccess());
         });
     }
+
+    private function buildAdminAccessForSiteIds($siteIds)
+    {
+        $access = array();
+
+        foreach ($siteIds as $siteId) {
+            $access[] = array('access' => 'admin', 'idsite' => $siteId);
+        }
+
+        return $access;
+    }
+
+    private function buildViewAccessForSiteIds($siteIds)
+    {
+        $access = array();
+
+        foreach ($siteIds as $siteId) {
+            $access[] = array('access' => 'admin', 'idsite' => $siteId);
+        }
+
+        return $access;
+    }
+
+    private function createPiwikAuthMockInstance()
+    {
+        return $this->getMock('Piwik\\Auth', array('authenticate', 'getName', 'getTokenAuthSecret', 'getLogin', 'setTokenAuth', 'setLogin',
+            'setPassword', 'setPasswordHash'));
+    }
+
+    private function createAccessMockWithAccessToSitesButUnauthenticated($idSites)
+    {
+        $mock = $this->getMock('Piwik\Access', array('getRawSitesWithSomeViewAccess', 'loadSitesIfNeeded'));
+
+        // this method will be actually never called as it is unauthenticated. The tests are supposed to fail if it
+        // suddenly does get called as we should not query for sites if it is not authenticated.
+        $mock->expects($this->any())
+            ->method('getRawSitesWithSomeViewAccess')
+            ->will($this->returnValue($this->buildAdminAccessForSiteIds($idSites)));
+
+        return $mock;
+    }
+
+    private function createAccessMockWithAuthenticatedUser($methodsToMock = array())
+    {
+        $methods = array('authenticate');
+
+        foreach ($methodsToMock as $methodToMock) {
+            $methods[] = $methodToMock;
+        }
+
+        $authMock = $this->createPiwikAuthMockInstance();
+        $authMock->expects($this->atLeast(1))
+            ->method('authenticate')
+            ->will($this->returnValue(new AuthResult(AuthResult::SUCCESS, 'login', 'token')));
+
+        $mock = $this->getMock('Piwik\Access', $methods);
+        $mock->reloadAccess($authMock);
+
+        return $mock;
+    }
+
 }
diff --git a/tests/PHPUnit/Integration/Archive/PurgerTest.php b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
similarity index 78%
rename from tests/PHPUnit/Integration/Archive/PurgerTest.php
rename to tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
index a42247347998e62aa15320aefc1eccc1019d998b..5fc57e3a52df8e4c7d265e757d1c78835bfee7c4 100644
--- a/tests/PHPUnit/Integration/Archive/PurgerTest.php
+++ b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
@@ -17,7 +17,7 @@ use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
 /**
  * @group Core
  */
-class PurgerTest extends IntegrationTestCase
+class ArchivePurgerTest extends IntegrationTestCase
 {
     /**
      * @var RawArchiveDataWithTempAndInvalidated
@@ -62,40 +62,48 @@ class PurgerTest extends IntegrationTestCase
     {
         $this->enableBrowserTriggeredArchiving();
 
-        $this->archivePurger->purgeOutdatedArchives($this->february);
+        $deletedRowCount = $this->archivePurger->purgeOutdatedArchives($this->february);
 
         self::$fixture->assertTemporaryArchivesPurged($browserTriggeringEnabled = true, $this->february);
 
         self::$fixture->assertCustomRangesNotPurged($this->february, $includeTemporary = false);
         self::$fixture->assertTemporaryArchivesNotPurged($this->january);
+
+        $this->assertEquals(7 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
     }
 
     public function test_purgeOutdatedArchives_PurgesCorrectTemporaryArchives_WhileKeepingNewerTemporaryArchives_WithBrowserTriggeringDisabled()
     {
         $this->disableBrowserTriggeredArchiving();
 
-        $this->archivePurger->purgeOutdatedArchives($this->february);
+        $deletedRowCount = $this->archivePurger->purgeOutdatedArchives($this->february);
 
         self::$fixture->assertTemporaryArchivesPurged($browserTriggeringEnabled = false, $this->february);
 
         self::$fixture->assertCustomRangesNotPurged($this->february);
         self::$fixture->assertTemporaryArchivesNotPurged($this->january);
+
+        $this->assertEquals(5 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
     }
 
     public function test_purgeInvalidatedArchivesFrom_PurgesAllInvalidatedArchives_AndMarksDatesAndSitesAsInvalidated()
     {
-        $this->archivePurger->purgeInvalidatedArchivesFrom($this->february);
+        $deletedRowCount = $this->archivePurger->purgeInvalidatedArchivesFrom($this->february);
 
         self::$fixture->assertInvalidatedArchivesPurged($this->february);
         self::$fixture->assertInvalidatedArchivesNotPurged($this->january);
+
+        $this->assertEquals(4 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
     }
 
     public function test_purgeArchivesWithPeriodRange_PurgesAllRangeArchives()
     {
-        $this->archivePurger->purgeArchivesWithPeriodRange($this->february);
+        $deletedRowCount = $this->archivePurger->purgeArchivesWithPeriodRange($this->february);
 
         self::$fixture->assertCustomRangesPurged($this->february);
         self::$fixture->assertCustomRangesNotPurged($this->january);
+
+        $this->assertEquals(3 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
     }
 
     private function configureCustomRangePurging()
@@ -114,4 +122,4 @@ class PurgerTest extends IntegrationTestCase
     }
 }
 
-PurgerTest::$fixture = new RawArchiveDataWithTempAndInvalidated();
\ No newline at end of file
+ArchivePurgerTest::$fixture = new RawArchiveDataWithTempAndInvalidated();
\ No newline at end of file
diff --git a/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php b/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php
index 8ad12f8b8bbda824e92a125876b6d69a599ead5d..2485ad2de7c64d95d3310255ba5fb74c27727945 100644
--- a/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php
+++ b/tests/PHPUnit/Integration/CronArchive/SharedSiteIdsTest.php
@@ -55,9 +55,12 @@ class SharedSiteIdsTest extends IntegrationTestCase
         $this->assertNull($siteIds->getNextSiteId());
     }
 
-    public function test_isSupported()
+    public function test_construct_withCustomOptionName()
     {
-        $this->assertTrue(SharedSiteIds::isSupported());
+        $first = new SharedSiteIds(array(1, 2), 'SharedSiteIdsToArchive_Test');
+        $second = new SharedSiteIds(array(), 'SharedSiteIdsToArchive_Test');
+        $this->assertEquals(array(1, 2), $first->getAllSiteIdsToArchive());
+        $this->assertEquals(array(1, 2), $second->getAllSiteIdsToArchive());
     }
 
     public function test_getNumSites()
diff --git a/tests/PHPUnit/Integration/Tracker/VisitTest.php b/tests/PHPUnit/Integration/Tracker/VisitTest.php
index 919a918516cd556fd23c8d8164f53f363fc12486..a9407b88164ae94ac358a0d5e8f8149821e14d06 100644
--- a/tests/PHPUnit/Integration/Tracker/VisitTest.php
+++ b/tests/PHPUnit/Integration/Tracker/VisitTest.php
@@ -41,6 +41,7 @@ class VisitTest extends IntegrationTestCase
 
         Manager::getInstance()->loadTrackerPlugins();
         Manager::getInstance()->loadPlugin('SitesManager');
+        Visit::$dimensions = null;
     }
 
     /**
@@ -450,6 +451,7 @@ class VisitTest extends IntegrationTestCase
 
         $cache = Cache::getTransientCache();
         $cache->save(CacheId::pluginAware('VisitDimensions'), $dimensions);
+        Visit::$dimensions = null;
     }
 }
 
diff --git a/tests/PHPUnit/System/BlobReportLimitingTest.php b/tests/PHPUnit/System/BlobReportLimitingTest.php
index ba2ea49b46afb7e37c9ed09235c0a0b49582857a..8122fd195cbbcd5236403a297ef0002d4dfec281 100755
--- a/tests/PHPUnit/System/BlobReportLimitingTest.php
+++ b/tests/PHPUnit/System/BlobReportLimitingTest.php
@@ -8,6 +8,7 @@
 namespace Piwik\Tests\System;
 
 use Piwik\Application\Kernel\GlobalSettingsProvider\IniSettingsProvider;
+use Piwik\Cache;
 use Piwik\Config;
 use Piwik\Plugins\Actions\ArchivingHelper;
 use Piwik\Tests\Framework\Mock\TestConfig;
@@ -23,6 +24,9 @@ use Piwik\Tests\Fixtures\ManyVisitsWithMockLocationProvider;
  */
 class BlobReportLimitingTest extends SystemTestCase
 {
+    /**
+     * @var ManyVisitsWithMockLocationProvider
+     */
     public static $fixture = null; // initialized below class definition
 
     public static function setUpBeforeClass()
@@ -31,6 +35,12 @@ class BlobReportLimitingTest extends SystemTestCase
         parent::setUpBeforeClass();
     }
 
+    public function setUp()
+    {
+        Cache::getTransientCache()->flushAll();
+        parent::setUp();
+    }
+
     public function getApiForTesting()
     {
         // TODO: test Provider plugin? Not sure if it's possible.
diff --git a/tests/PHPUnit/System/ImportLogsTest.php b/tests/PHPUnit/System/ImportLogsTest.php
index fff22849ae19c4ae6a461310dd83c43b99741c7c..e622265dffcc27f666cb7911d7913f1f2aa81090 100755
--- a/tests/PHPUnit/System/ImportLogsTest.php
+++ b/tests/PHPUnit/System/ImportLogsTest.php
@@ -9,6 +9,7 @@ namespace Piwik\Tests\System;
 
 use Piwik\Access;
 use Piwik\Plugins\SitesManager\API;
+use Piwik\Tests\Framework\Fixture;
 use Piwik\Tests\Framework\TestCase\SystemTestCase;
 use Piwik\Tests\Fixtures\ManySitesImportedLogs;
 
@@ -23,6 +24,13 @@ class ImportLogsTest extends SystemTestCase
     /** @var ManySitesImportedLogs */
     public static $fixture = null; // initialized below class definition
 
+    public function setUp()
+    {
+        parent::setUp();
+
+        $this->resetTestingEnvironmentChanges();
+    }
+
     /**
      * @dataProvider getApiForTesting
      */
@@ -102,10 +110,51 @@ class ImportLogsTest extends SystemTestCase
         $this->assertEquals(1, count($whateverDotCom));
     }
 
+    public function test_LogImporter_RetriesWhenServerFails()
+    {
+        $this->simulateTrackerFailure();
+
+        $logFile = PIWIK_INCLUDE_PATH . '/tests/resources/access-logs/fake_logs_enable_all.log';
+
+        $options = array(
+            '--idsite'                    => self::$fixture->idSite,
+            '--token-auth'                => 'asldfjksd',
+            '--retry-max-attempts'        => 5,
+            '--retry-delay'               => 1
+        );
+
+        $output = Fixture::executeLogImporter($logFile, $options, $allowFailure = true);
+        $output = implode("\n", $output);
+
+        for ($i = 2; $i != 6; ++$i) {
+            $this->assertContains("Retrying request, attempt number $i", $output);
+        }
+
+        $this->assertNotContains("Retrying request, attempt number 6", $output);
+
+        $this->assertContains("Max number of attempts reached, server is unreachable!", $output);
+    }
+
+    private function simulateTrackerFailure()
+    {
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+        $testingEnvironment->configOverride = array(
+            'Tracker' => array('bulk_requests_require_authentication' => 1)
+        );
+        $testingEnvironment->save();
+    }
+
     public static function getOutputPrefix()
     {
         return 'ImportLogs';
     }
+
+    private function resetTestingEnvironmentChanges()
+    {
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+        $testingEnvironment->configOverride = null;
+        $testingEnvironment->save();
+    }
 }
 
 ImportLogsTest::$fixture = new ManySitesImportedLogs();
diff --git a/tests/PHPUnit/System/TrackerResponseTest.php b/tests/PHPUnit/System/TrackerResponseTest.php
index 7bd0f7aef78456beef09d7947d0e53a8d333a7bd..16ab4a4e0104ab15939211d2dd11272fb5902635 100755
--- a/tests/PHPUnit/System/TrackerResponseTest.php
+++ b/tests/PHPUnit/System/TrackerResponseTest.php
@@ -97,10 +97,10 @@ class TrackerResponseTest extends SystemTestCase
     public function test_response_ShouldReturnPiwikMessage_InCaseOfEmptyRequest()
     {
         $url = Fixture::getTrackerUrl();
-        $response = file_get_contents($url);
+        $this->assertResponseCode(400, $url);
 
         $expected = "<a href='/'>Piwik</a> is a free/libre web <a href='http://piwik.org'>analytics</a> that lets you keep control of your data.";
-        $this->assertEquals($expected, $response);
+        $this->assertHttpResponseText($expected, $url);
     }
 
 }
diff --git a/tests/PHPUnit/System/TrackerTest.php b/tests/PHPUnit/System/TrackerTest.php
index 65b16f6feae42a88a1a607d601e78ccc8cf583b5..48d3a4e54d7a018366a4a330f03bed13a78fcfcc 100644
--- a/tests/PHPUnit/System/TrackerTest.php
+++ b/tests/PHPUnit/System/TrackerTest.php
@@ -9,7 +9,13 @@
 namespace Piwik\Tests\System;
 
 use Piwik\Common;
+use Piwik\Config;
 use Piwik\Db;
+use Piwik\Option;
+use Piwik\Scheduler\Schedule\Schedule;
+use Piwik\Scheduler\Task;
+use Piwik\Scheduler\Timetable;
+use Piwik\SettingsPiwik;
 use Piwik\Tests\Framework\Fixture;
 use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
 use Piwik\Tracker;
@@ -20,10 +26,26 @@ use Piwik\Tracker;
  */
 class TrackerTest extends IntegrationTestCase
 {
+    const TASKS_FINISHED_OPTION_NAME = "Tests.scheduledTasksFinished";
+    const TASKS_STARTED_OPTION_NAME = "Tests.scheduledTasksStarted";
+
     public function setUp()
     {
         parent::setUp();
+
         Fixture::createWebsite('2014-02-04');
+
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+        $testingEnvironment->testCaseClass = null;
+        $testingEnvironment->addFailingScheduledTask = false;
+        $testingEnvironment->addScheduledTask = false;
+        $testingEnvironment->save();
+
+        Option::delete(self::TASKS_STARTED_OPTION_NAME);
+        Option::delete(self::TASKS_FINISHED_OPTION_NAME);
+        Option::delete(Timetable::TIMETABLE_OPTION_STRING);
+
+        SettingsPiwik::overwritePiwikUrl(self::$fixture->getRootUrl() . "tests/PHPUnit/proxy");
     }
 
     protected static function configureFixture($fixture)
@@ -125,6 +147,59 @@ class TrackerTest extends IntegrationTestCase
         $this->assertEquals(0, count($this->getConversionItems()));
     }
 
+    public function test_scheduledTasks_CanBeRunThroughTracker_WithoutIncludingOutputInTrackerOutput()
+    {
+        $this->setScheduledTasksToRunInTracker();
+
+        $urlToTest = $this->getSimpleTrackingUrl();
+
+        $response = $this->sendTrackingRequestByCurl($urlToTest);
+        Fixture::checkResponse($response);
+
+        $this->assertScheduledTasksWereRun();
+    }
+
+    public function test_scheduledTasks_CanBeRunThroughTracker_WithOutputIncluded_IfDebugQueryParamUsed()
+    {
+        $this->setScheduledTasksToRunInTracker();
+
+        $urlToTest = $this->getSimpleTrackingUrl() . '&debug=1';
+
+        $response = $this->sendTrackingRequestByCurl($urlToTest);
+
+        $this->assertScheduledTasksWereRun();
+
+        $this->assertContains('Scheduled Tasks: Starting...', $response);
+    }
+
+    public function getTypesOfErrorsForScheduledTasksTrackerFailureTest()
+    {
+        return array(
+            array(true),
+            array(false)
+        );
+    }
+
+    /**
+     * @dataProvider getTypesOfErrorsForScheduledTasksTrackerFailureTest
+     */
+    public function test_scheduledTasks_DoNotFailTracking_WhenScheduledTaskFails($doFatalError)
+    {
+        $this->setScheduledTasksToRunInTracker();
+        $this->addFailingScheduledTaskToTracker($doFatalError);
+
+        $urlToTest = $this->getSimpleTrackingUrl();
+
+        $response = $this->sendTrackingRequestByCurl($urlToTest);
+        Fixture::checkResponse($response);
+
+        if ($doFatalError) {
+            $this->assertScheduledTasksWereNotRun();
+        } else {
+            $this->assertScheduledTasksWereRun();
+        }
+    }
+
     protected function issueBulkTrackingRequest($token_auth, $expectTrackingToSucceed)
     {
         $piwikHost = Fixture::getRootUrl() . 'tests/PHPUnit/proxy/piwik.php';
@@ -190,4 +265,100 @@ class TrackerTest extends IntegrationTestCase
         return "?idsite=1&idgoal=0&rec=1&url=" . urlencode('http://quellehorreur.com/movies') . "&ec_items="
         . urlencode($ecItemsStr) . '&ec_id=myspecial-id-1234&revenue=16.99&ec_st=12.99&ec_tx=0&ec_sh=3';
     }
+
+    private function getSimpleTrackingUrl()
+    {
+        return "?idsite=1&rec=1&url=" . urlencode('http://quellehorreur.com/movies') . "&action_name=Movies";
+    }
+
+    private function setScheduledTasksToRunInTracker()
+    {
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+        $testingEnvironment->testCaseClass = 'Piwik\Tests\System\TrackerTest';
+        $testingEnvironment->addScheduledTask = true;
+        $testingEnvironment->configOverride = array('Tracker' => array('scheduled_tasks_min_interval' => 1, 'debug_on_demand' => 1));
+        $testingEnvironment->save();
+    }
+
+    private function addFailingScheduledTaskToTracker($doFatalError)
+    {
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+        $testingEnvironment->addFailingScheduledTask = true;
+        $testingEnvironment->scheduledTaskFailureShouldBeFatal = $doFatalError;
+        $testingEnvironment->save();
+    }
+
+    public function provideContainerConfig()
+    {
+        define('DEBUG_FORCE_SCHEDULED_TASKS', 1);
+
+        $testingEnvironment = new \Piwik_TestingEnvironment();
+
+        $tasksToAdd = array();
+
+        if ($testingEnvironment->addFailingScheduledTask) {
+            if ($testingEnvironment->scheduledTaskFailureShouldBeFatal) {
+                $tasksToAdd[] = new Task($this, 'triggerFatalError', null, Schedule::factory('hourly'));
+            } else {
+                $tasksToAdd[] = new Task($this, 'throwScheduledTaskException', null, Schedule::factory('hourly'));
+            }
+        }
+
+        if ($testingEnvironment->addScheduledTask) {
+            $tasksToAdd[] = new Task($this, 'markScheduledTaskExecutionFinished', null, Schedule::factory('hourly'));
+        }
+
+        $result = array();
+        if (!empty($tasksToAdd)) {
+            $initialTask = new Task($this, 'markCustomTaskExecuted', null, Schedule::factory('hourly'));
+            $tasksToAdd = array_merge(array($initialTask), $tasksToAdd);
+
+            $mockTaskLoader = $this->getMock('Piwik\Scheduler\TaskLoader', array('loadTasks'));
+            $mockTaskLoader->expects($this->any())->method('loadTasks')->will($this->returnValue($tasksToAdd));
+            $result['Piwik\Scheduler\TaskLoader'] = $mockTaskLoader;
+        }
+        return $result;
+    }
+
+    public function triggerFatalError()
+    {
+        require 'thehowling';
+    }
+
+    public function throwScheduledTaskException()
+    {
+        throw new \Exception("triggered exception");
+    }
+
+    public function markScheduledTaskExecutionFinished()
+    {
+        Option::set(self::TASKS_FINISHED_OPTION_NAME, 1);
+    }
+
+    public function markCustomTaskExecuted()
+    {
+        Option::set(self::TASKS_STARTED_OPTION_NAME, 1);
+    }
+
+    private function assertScheduledTasksWereRun()
+    {
+        $this->assertCustomTasksWereStarted();
+
+        Option::clearCachedOption(self::TASKS_FINISHED_OPTION_NAME);
+        $this->assertEquals(1, Option::get(self::TASKS_FINISHED_OPTION_NAME));
+    }
+
+    private function assertScheduledTasksWereNotRun()
+    {
+        $this->assertCustomTasksWereStarted();
+
+        Option::clearCachedOption(self::TASKS_FINISHED_OPTION_NAME);
+        $this->assertEmpty(Option::get(self::TASKS_FINISHED_OPTION_NAME));
+    }
+
+    private function assertCustomTasksWereStarted()
+    {
+        Option::clearCachedOption(self::TASKS_STARTED_OPTION_NAME);
+        $this->assertEquals(1, Option::get(self::TASKS_STARTED_OPTION_NAME));
+    }
 }
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml
index 1fb125914df754adc2c4906c67bcd419e288f625..fc9865b7253efb025872ac57873a03a674645cbe 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserEngines_month.xml
@@ -14,13 +14,13 @@
 	</row>
 	<row>
 		<label>Trident (IE)</label>
-		<nb_visits>7</nb_visits>
-		<nb_actions>8</nb_actions>
+		<nb_visits>8</nb_visits>
+		<nb_actions>9</nb_actions>
 		<max_actions>2</max_actions>
 		<sum_visit_length>242</sum_visit_length>
-		<bounce_count>6</bounce_count>
-		<nb_visits_converted>7</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+		<bounce_count>7</bounce_count>
+		<nb_visits_converted>8</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserEngine==Trident</segment>
 	</row>
@@ -38,13 +38,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserEngine==</segment>
 	</row>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml
index 930e0f0aa71c5438cc2f39d0e4f8c34bb6bb086e..971cd5f0fb405227e1244a1e8f06712dd037b84d 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserFamilies_month.xml
@@ -14,13 +14,13 @@
 	</row>
 	<row>
 		<label>Internet Explorer</label>
-		<nb_visits>7</nb_visits>
-		<nb_actions>8</nb_actions>
+		<nb_visits>8</nb_visits>
+		<nb_actions>9</nb_actions>
 		<max_actions>2</max_actions>
 		<sum_visit_length>242</sum_visit_length>
-		<bounce_count>6</bounce_count>
-		<nb_visits_converted>7</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+		<bounce_count>7</bounce_count>
+		<nb_visits_converted>8</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/IE.gif</logo>
 	</row>
@@ -50,13 +50,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo>
 	</row>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml
index 2c687028a7925440747ce1ae063069a54e1f7bb0..d6572a8ba39d449e3dbfe520e6ccc27652b66a63 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowserVersions_month.xml
@@ -41,13 +41,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserCode==UNK;browserVersion==</segment>
 		<logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo>
@@ -169,6 +169,19 @@
 		<segment>browserCode==CM;browserVersion==39.0</segment>
 		<logo>plugins/DevicesDetection/images/browsers/CM.gif</logo>
 	</row>
+	<row>
+		<label>Internet Explorer 5.0</label>
+		<nb_visits>1</nb_visits>
+		<nb_actions>1</nb_actions>
+		<max_actions>1</max_actions>
+		<sum_visit_length>0</sum_visit_length>
+		<bounce_count>1</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+		<sum_daily_nb_users>0</sum_daily_nb_users>
+		<segment>browserCode==IE;browserVersion==5.0</segment>
+		<logo>plugins/DevicesDetection/images/browsers/IE.gif</logo>
+	</row>
 	<row>
 		<label>Internet Explorer 6.0</label>
 		<nb_visits>1</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml
index 16aa9095e1b6b1a5c9a567ab6e140c0d53c1bcfd..6f3990403608c574b490864f64d9f09da338f182 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getBrowsers_month.xml
@@ -15,13 +15,13 @@
 	</row>
 	<row>
 		<label>Internet Explorer</label>
-		<nb_visits>7</nb_visits>
-		<nb_actions>8</nb_actions>
+		<nb_visits>8</nb_visits>
+		<nb_actions>9</nb_actions>
 		<max_actions>2</max_actions>
 		<sum_visit_length>242</sum_visit_length>
-		<bounce_count>6</bounce_count>
-		<nb_visits_converted>7</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+		<bounce_count>7</bounce_count>
+		<nb_visits_converted>8</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/IE.gif</logo>
 		<segment>browserCode==IE</segment>
@@ -54,13 +54,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo>
 		<segment>browserCode==UNK</segment>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml
index 9daee7778d332fda4deb63cb29286396dcac3777..af1de4ce8e7a0eb92c4227d2c9726cde73410a06 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__DevicesDetection.getType_month.xml
@@ -62,6 +62,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml b/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml
index 69b8825689200faed1a061a1baf7a5f4d3092716..2f4c1d0d3d416c739c4b1e6f22fd32012bcb368a 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__Live.getLastVisitsDetails_range.xml
@@ -442,7 +442,7 @@
 				<customVariables>
 					<row>
 						<customVariablePageName1>HTTP-code</customVariablePageName1>
-						<customVariablePageValue1>200</customVariablePageValue1>
+						<customVariablePageValue1>501</customVariablePageValue1>
 					</row>
 				</customVariables>
 				<icon />
@@ -830,7 +830,7 @@
 				<customVariables>
 					<row>
 						<customVariablePageName1>HTTP-code</customVariablePageName1>
-						<customVariablePageValue1>200</customVariablePageValue1>
+						<customVariablePageValue1>404</customVariablePageValue1>
 					</row>
 				</customVariables>
 				<icon />
@@ -1203,7 +1203,7 @@
 				<customVariables>
 					<row>
 						<customVariablePageName1>HTTP-code</customVariablePageName1>
-						<customVariablePageValue1>200</customVariablePageValue1>
+						<customVariablePageValue1>403</customVariablePageValue1>
 					</row>
 				</customVariables>
 				<generationTime>0.18s</generationTime>
@@ -1233,7 +1233,7 @@
 				<customVariables>
 					<row>
 						<customVariablePageName1>HTTP-code</customVariablePageName1>
-						<customVariablePageValue1>200</customVariablePageValue1>
+						<customVariablePageValue1>500</customVariablePageValue1>
 					</row>
 				</customVariables>
 				<generationTime>0.11s</generationTime>
@@ -1964,13 +1964,13 @@
 		<operatingSystemIcon>plugins/DevicesDetection/images/os/MAC.gif</operatingSystemIcon>
 		<operatingSystemCode>MAC</operatingSystemCode>
 		<operatingSystemVersion />
-		<browserFamily />
-		<browserFamilyDescription>Unknown</browserFamilyDescription>
-		<browser>Unknown</browser>
-		<browserName>Unknown</browserName>
-		<browserIcon>plugins/DevicesDetection/images/browsers/UNK.gif</browserIcon>
-		<browserCode>UNK</browserCode>
-		<browserVersion />
+		<browserFamily>Trident</browserFamily>
+		<browserFamilyDescription>Trident (IE)</browserFamilyDescription>
+		<browser>Internet Explorer 5.0</browser>
+		<browserName>Internet Explorer</browserName>
+		<browserIcon>plugins/DevicesDetection/images/browsers/IE.gif</browserIcon>
+		<browserCode>IE</browserCode>
+		<browserVersion>5.0</browserVersion>
 		<events>0</events>
 		<continent>Unknown</continent>
 		<continentCode>unk</continentCode>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml
index c1067505c8ff6564ba404a99921ebd15f4a27371..110eafe235319c4a5284ac3cfb146201e461c727 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__Resolution.getConfiguration_month.xml
@@ -122,7 +122,7 @@
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 	</row>
 	<row>
-		<label>Mac / Unknown / unknown</label>
+		<label>Mac / Internet Explorer / unknown</label>
 		<nb_visits>1</nb_visits>
 		<nb_actions>1</nb_actions>
 		<max_actions>1</max_actions>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml
index 1fb125914df754adc2c4906c67bcd419e288f625..fc9865b7253efb025872ac57873a03a674645cbe 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserType_month.xml
@@ -14,13 +14,13 @@
 	</row>
 	<row>
 		<label>Trident (IE)</label>
-		<nb_visits>7</nb_visits>
-		<nb_actions>8</nb_actions>
+		<nb_visits>8</nb_visits>
+		<nb_actions>9</nb_actions>
 		<max_actions>2</max_actions>
 		<sum_visit_length>242</sum_visit_length>
-		<bounce_count>6</bounce_count>
-		<nb_visits_converted>7</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+		<bounce_count>7</bounce_count>
+		<nb_visits_converted>8</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserEngine==Trident</segment>
 	</row>
@@ -38,13 +38,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserEngine==</segment>
 	</row>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml
index 2c687028a7925440747ce1ae063069a54e1f7bb0..d6572a8ba39d449e3dbfe520e6ccc27652b66a63 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowserVersion_month.xml
@@ -41,13 +41,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<segment>browserCode==UNK;browserVersion==</segment>
 		<logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo>
@@ -169,6 +169,19 @@
 		<segment>browserCode==CM;browserVersion==39.0</segment>
 		<logo>plugins/DevicesDetection/images/browsers/CM.gif</logo>
 	</row>
+	<row>
+		<label>Internet Explorer 5.0</label>
+		<nb_visits>1</nb_visits>
+		<nb_actions>1</nb_actions>
+		<max_actions>1</max_actions>
+		<sum_visit_length>0</sum_visit_length>
+		<bounce_count>1</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+		<sum_daily_nb_users>0</sum_daily_nb_users>
+		<segment>browserCode==IE;browserVersion==5.0</segment>
+		<logo>plugins/DevicesDetection/images/browsers/IE.gif</logo>
+	</row>
 	<row>
 		<label>Internet Explorer 6.0</label>
 		<nb_visits>1</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml
index 16aa9095e1b6b1a5c9a567ab6e140c0d53c1bcfd..6f3990403608c574b490864f64d9f09da338f182 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getBrowser_month.xml
@@ -15,13 +15,13 @@
 	</row>
 	<row>
 		<label>Internet Explorer</label>
-		<nb_visits>7</nb_visits>
-		<nb_actions>8</nb_actions>
+		<nb_visits>8</nb_visits>
+		<nb_actions>9</nb_actions>
 		<max_actions>2</max_actions>
 		<sum_visit_length>242</sum_visit_length>
-		<bounce_count>6</bounce_count>
-		<nb_visits_converted>7</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+		<bounce_count>7</bounce_count>
+		<nb_visits_converted>8</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>8</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/IE.gif</logo>
 		<segment>browserCode==IE</segment>
@@ -54,13 +54,13 @@
 	</row>
 	<row>
 		<label>Unknown</label>
-		<nb_visits>5</nb_visits>
-		<nb_actions>5</nb_actions>
+		<nb_visits>4</nb_visits>
+		<nb_actions>4</nb_actions>
 		<max_actions>1</max_actions>
 		<sum_visit_length>0</sum_visit_length>
-		<bounce_count>5</bounce_count>
-		<nb_visits_converted>2</nb_visits_converted>
-		<sum_daily_nb_uniq_visitors>5</sum_daily_nb_uniq_visitors>
+		<bounce_count>4</bounce_count>
+		<nb_visits_converted>1</nb_visits_converted>
+		<sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 		<logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo>
 		<segment>browserCode==UNK</segment>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml
index c1067505c8ff6564ba404a99921ebd15f4a27371..110eafe235319c4a5284ac3cfb146201e461c727 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getConfiguration_month.xml
@@ -122,7 +122,7 @@
 		<sum_daily_nb_users>0</sum_daily_nb_users>
 	</row>
 	<row>
-		<label>Mac / Unknown / unknown</label>
+		<label>Mac / Internet Explorer / unknown</label>
 		<nb_visits>1</nb_visits>
 		<nb_actions>1</nb_actions>
 		<max_actions>1</max_actions>
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml
index 9daee7778d332fda4deb63cb29286396dcac3777..af1de4ce8e7a0eb92c4227d2c9726cde73410a06 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__UserSettings.getMobileVsDesktop_month.xml
@@ -62,6 +62,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml
index 3f2929faffdd0b59f1c54d1153b728f35b8c7f80..745f9c609537f910d30bda2a47238bdf42573f0d 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__DevicesDetection.getType_day.xml
@@ -36,6 +36,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml
index 3f2929faffdd0b59f1c54d1153b728f35b8c7f80..745f9c609537f910d30bda2a47238bdf42573f0d 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__UserSettings.getMobileVsDesktop_day.xml
@@ -36,6 +36,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml
index 31bbd93f01fb0d3939d19099c7612ad8295b70c5..6b1fedb0c5a7622ea6d4a011888cca6f569a0c8f 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__DevicesDetection.getType_day.xml
@@ -37,6 +37,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml
index 31bbd93f01fb0d3939d19099c7612ad8295b70c5..6b1fedb0c5a7622ea6d4a011888cca6f569a0c8f 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_withCookieSupport__UserSettings.getMobileVsDesktop_day.xml
@@ -37,6 +37,12 @@
 		<segment>deviceType==feature+phone</segment>
 		<logo>plugins/DevicesDetection/images/screens/mobile.gif</logo>
 	</row>
+	<row>
+		<label>Phablet</label>
+		<nb_visits>0</nb_visits>
+		<segment>deviceType==phablet</segment>
+		<logo>plugins/DevicesDetection/images/screens/unknown.gif</logo>
+	</row>
 	<row>
 		<label>Portable media player</label>
 		<nb_visits>0</nb_visits>
diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml
index add85693bbb308c173d7735681137fa77c3a3876..b7936fb1ab7717025238e500faa9f36dfd88723c 100644
--- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml
+++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml
@@ -134,7 +134,7 @@
 		<category>Visit</category>
 		<name>Device type</name>
 		<segment>deviceType</segment>
-		<acceptedValues>desktop, smartphone, tablet, feature phone, console, tv, car browser, smart display, camera, portable media player</acceptedValues>
+		<acceptedValues>desktop, smartphone, tablet, feature phone, console, tv, car browser, smart display, camera, portable media player, phablet</acceptedValues>
 	</row>
 	<row>
 		<type>dimension</type>
diff --git a/tests/PHPUnit/TestingEnvironment.php b/tests/PHPUnit/TestingEnvironment.php
index dc0b6adf774f6572260eea3f5a4af711ae8e1f5b..b7590eee17cc36155f16e6de8c56fc83b6818af1 100644
--- a/tests/PHPUnit/TestingEnvironment.php
+++ b/tests/PHPUnit/TestingEnvironment.php
@@ -168,6 +168,16 @@ class Piwik_TestingEnvironment
             }
         }
 
+        if ($testingEnvironment->testCaseClass) {
+            $testCaseClass = $testingEnvironment->testCaseClass;
+            if (class_exists($testCaseClass)) {
+                $testCase = new $testCaseClass();
+                if (method_exists($testCase, 'provideContainerConfig')) {
+                    $diConfig = array_merge($diConfig, $testCase->provideContainerConfig());
+                }
+            }
+        }
+
         if (!empty($diConfig)) {
             StaticContainer::addDefinitions($diConfig);
         }
diff --git a/tests/PHPUnit/Unit/API/ResponseBuilderTest.php b/tests/PHPUnit/Unit/API/ResponseBuilderTest.php
index d9e67698189121322f294070703323d51fa52bb7..14409c191380e9da5ac1340c62096ce9dfbd3004 100644
--- a/tests/PHPUnit/Unit/API/ResponseBuilderTest.php
+++ b/tests/PHPUnit/Unit/API/ResponseBuilderTest.php
@@ -10,6 +10,7 @@ namespace Piwik\Tests\Unit\API;
 
 use Exception;
 use Piwik\API\ResponseBuilder;
+use Piwik\Config;
 use Piwik\DataTable;
 use Piwik\Plugin\Manager;
 
@@ -184,91 +185,164 @@ class ResponseBuilderTest extends \PHPUnit_Framework_TestCase
 
     public function test_getResponse_shouldApplyFilterLimitOnIndexedArray()
     {
-        $input = range(0, 100);
-
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15));
-        $response = $builder->getResponse($input);
-
-        $this->assertEquals(range(0, 14), $response);
+        $input    = range(0, 100);
+        $expected = range(0, 14);
+        $this->assertLimitedResponseEquals($expected, $input, $limit = 15, $offset = null);
     }
 
     public function test_getResponse_shouldReturnEmptyArrayOnIndexedArray_IfOffsetIsTooHigh()
     {
         $input = range(0, 100);
-
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15, 'filter_offset' => 200));
-        $response = $builder->getResponse($input);
-
-        $this->assertEquals(array(), $response);
+        $this->assertLimitedResponseEquals(array(), $input, $limit = 15, $offset = 200);
     }
 
     public function test_getResponse_shouldReturnAllOnIndexedArray_IfLimitIsTooHigh()
     {
         $input = range(0, 100);
-
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 200));
-        $response = $builder->getResponse($input);
-
-        $this->assertEquals($input, $response);
+        $this->assertLimitedResponseEquals($input, $input, $limit = 200, $offset = null);
     }
 
     public function test_getResponse_shouldNotApplyFilterLimitOnIndexedArrayIfParamNotSet()
     {
         $input = range(0, 100);
+        $this->assertLimitedResponseEquals($input, $input, $limit = null, $offset = null);
+    }
 
-        $builder  = new ResponseBuilder('php', array('serialize' => 0));
+    public function test_getResponse_shouldApplyLimitOnIndexedArray_IfLimitIsDefaultFilterLimitValue()
+    {
+        $limit    = Config::getInstance()->General['API_datatable_default_limit'];
+        $input    = range(0, 2000);
+        $expected = range(0, $limit - 1);
+        $this->assertLimitedResponseEquals($expected, $input, $limit, $offset = 0);
+    }
+
+    private function assertLimitedResponseEquals($expectedResponse, $input, $limit, $offset = 0)
+    {
+        $params = array('serialize' => 0);
+
+        if (!is_null($limit)) {
+            $params['filter_limit'] = $limit;
+        }
+
+        if (!is_null($offset)) {
+            $params['filter_offset'] = $offset;
+        }
+
+        $builder  = new ResponseBuilder('php', $params);
         $response = $builder->getResponse($input);
 
-        $this->assertEquals($input, $response);
+        $this->assertEquals($expectedResponse, $response);
     }
 
-    public function test_getResponse_shouldApplyFilterOffsetOnIndexedArray_IfFilterLimitIsGiven()
+    public function test_getResponse_shouldNotApplyLimit_IfLimitIsDefaultFilterLimitValueAndSetBySystem()
     {
-        $input = range(0, 100);
+        $input = range(0, 200);
+        $limit = Config::getInstance()->General['API_datatable_default_limit'];
+
+        $builder  = new ResponseBuilder('php', array(
+            'serialize' => 0,
+            'api_datatable_default_limit' => $limit,
+            'filter_limit' => $limit,
+            'filter_offset' => 0));
+        $response = $builder->getResponse($input);
+
+        $this->assertEquals(range(0, 200), $response);
+    }
 
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 15, 'filter_offset' => 30));
+    public function test_getResponse_shouldApplyLimit_IfLimitIsSetBySystemButDifferentToDefaultLimit()
+    {
+        $input = range(0, 200);
+        $defaultLimit = Config::getInstance()->General['API_datatable_default_limit'];
+        $limit = $defaultLimit - 1;
+
+        $builder  = new ResponseBuilder('php', array(
+            'serialize' => 0,
+            'api_datatable_default_limit' => $defaultLimit,
+            'filter_limit' => $limit,
+            'filter_offset' => 0));
         $response = $builder->getResponse($input);
 
-        $this->assertEquals(range(30, 44), $response);
+        $this->assertEquals(range(0, $limit - 1), $response);
+    }
+
+    public function test_getResponse_shouldApplyFilterOffsetOnIndexedArray_IfFilterLimitIsGiven()
+    {
+        $input    = range(0, 100);
+        $expected = range(30, 44);
+        $this->assertLimitedResponseEquals($expected, $input, $limit = 15, $offset = 30);
     }
 
     public function test_getResponse_shouldNotApplyFilterOffsetOnIndexedArray_IfNoFilterLimitIsSetButOffset()
     {
         $input = range(0, 100);
-
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_offset' => 30));
-        $response = $builder->getResponse($input);
-
-        $this->assertEquals($input, $response);
+        $this->assertLimitedResponseEquals($input, $input, $limit = null, $offset = 30);
     }
 
     public function test_getResponse_shouldReturnEmptyArrayOnIndexedArray_IfFilterLimitIsZero()
     {
         $input = range(0, 100);
-
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => 0, 'filter_offset' => 30));
-        $response = $builder->getResponse($input);
-
-        $this->assertEquals(array(), $response);
+        $this->assertLimitedResponseEquals($expected = array(), $input, $limit = 0, $offset = 30);
     }
 
     public function test_getResponse_shouldIgnoreFilterOffsetOnIndexedArray_IfFilterLimitIsMinusOne()
     {
         $input = range(0, 100);
+        $this->assertLimitedResponseEquals($input, $input, $limit = -1, $offset = 30);
+    }
+
+    public function test_getResponse_shouldReturnAllOnIndexedArray_IfFilterLimitIsMinusOne()
+    {
+        $input = range(0, 100);
+        $this->assertLimitedResponseEquals($input, $input, $limit = -1, $offset = null);
+    }
 
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => -1, 'filter_offset' => 30));
+    public function test_getResponse_shouldApplyPattern_IfFilterColumnAndPatternIsGiven()
+    {
+        $input = array(
+            0 => array('name' => 'google', 'url' => 'www.google.com'),
+            1 => array('name' => 'ask', 'url' => 'www.ask.com'),
+            2 => array('name' => 'piwik', 'url' => 'piwik.org'),
+            3 => array('url' => 'nz.yahoo.com'),
+            4 => array('name' => 'amazon', 'url' => 'amazon.com'),
+            5 => array('url' => 'nz.piwik.org'),
+        );
+
+        $builder  = new ResponseBuilder('php', array(
+            'serialize' => 0,
+            'filter_limit' => -1,
+            'filter_column' => array('name', 'url'),
+            'filter_pattern' => 'piwik'
+        ));
         $response = $builder->getResponse($input);
 
-        $this->assertEquals($input, $response);
+        $expected = array(
+            2 => array('name' => 'piwik', 'url' => 'piwik.org'),
+            5 => array('url' => 'nz.piwik.org'),
+        );
+        $this->assertEquals($expected, $response);
     }
 
-    public function test_getResponse_shouldReturnAllOnIndexedArray_IfFilterLimitIsMinusOne()
+    public function test_getResponse_shouldBeAbleToApplyColumFilterAndLimitFilterOnIndexedAssociativeArray()
     {
-        $input = range(0, 100);
+        $input = array();
+        for ($i = 0; $i < 10; $i++) {
+            $input[] = array('test' => 'two' . $i, 'test2' => 'three');
+        }
+
+        $limit = 3;
 
-        $builder  = new ResponseBuilder('php', array('serialize' => 0, 'filter_limit' => -1));
+        $builder  = new ResponseBuilder('php', array(
+            'serialize' => 0,
+            'filter_limit' => $limit,
+            'filter_offset' => 3,
+            'showColumns' => 'test'
+        ));
         $response = $builder->getResponse($input);
 
-        $this->assertEquals($input, $response);
+        $this->assertEquals(array(
+            0 => array('test' => 'two3'),
+            1 => array('test' => 'two4'),
+            2 => array('test' => 'two5'),
+        ), $response);
     }
 }
diff --git a/tests/PHPUnit/Unit/Config/IniFileChainTest.php b/tests/PHPUnit/Unit/Config/IniFileChainTest.php
index ca5e0797a84bc1ee7206001d00bd81b252934988..137b6c234dc6f68de6cb8e78a3016fa40b399026 100644
--- a/tests/PHPUnit/Unit/Config/IniFileChainTest.php
+++ b/tests/PHPUnit/Unit/Config/IniFileChainTest.php
@@ -244,6 +244,16 @@ class IniFileChainTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(array('var1' => 'val"ue2', 'var3' => array('value3', 'value4')), $fileChain->getFrom($defaultSettingsPath, 'Section1'));
     }
 
+    public function test_getFrom_CorrectlyReturnsUnencodedValue()
+    {
+        $userSettingsPath = __DIR__ . '/test_files/special_values.ini.php';
+        $fileChain = new IniFileChain(array(), $userSettingsPath);
+
+        $this->assertEquals(array(
+            'value1' => 'a"bc', 'value2' => array('<script>', '${@piwik(crash))}'
+        )), $fileChain->getFrom($userSettingsPath, 'Section'));
+    }
+
     public function getTestDataForDumpTest()
     {
         return array(
diff --git a/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php b/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php
new file mode 100644
index 0000000000000000000000000000000000000000..4253ccbbe89ad780a3ea835d2ffa03c2414c32ef
--- /dev/null
+++ b/tests/PHPUnit/Unit/Config/test_files/special_values.ini.php
@@ -0,0 +1,4 @@
+[Section]
+value1 = "a&quot;bc"
+value2[] = "&lt;script&gt;"
+value2[] = "&#36;{@piwik(crash))}"
diff --git a/tests/PHPUnit/Unit/ConfigTest.php b/tests/PHPUnit/Unit/ConfigTest.php
index 162d22a794adb564fec4512c088145b2edf8a62f..6ccd93a0fd30841f8af080e0f1427af2d7181479 100644
--- a/tests/PHPUnit/Unit/ConfigTest.php
+++ b/tests/PHPUnit/Unit/ConfigTest.php
@@ -397,4 +397,42 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
 
         @unlink($configFile);
     }
-}
\ No newline at end of file
+
+    public function testFromGlobalConfig()
+    {
+        $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php';
+        $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php';
+        $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php';
+        
+        $config = new Config($globalFile, $userFile, $commonFile);
+
+        $configCategory = $config->getFromGlobalConfig('Category');
+        $this->assertEquals('value1', $configCategory['key1']);
+        $this->assertEquals('value2', $configCategory['key2']);
+        $this->assertEquals(array('key1' => 'value1', 'key2' => 'value2'), $configCategory);
+    }
+    
+    public function testFromCommonConfig()
+    {
+        $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php';
+        $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php';
+        $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php';
+    
+        $config = new Config($globalFile, $userFile, $commonFile);
+
+        $configCategory = $config->getFromCommonConfig('Category');
+        $this->assertEquals(array('key2' => 'valueCommon', 'key3' => '${@piwik(crash))}'), $configCategory);
+    }
+    
+    public function testFromLocalConfig()
+    {
+        $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php';
+        $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php';
+        $commonFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/common.config.ini.php';
+    
+        $config = new Config($globalFile, $userFile, $commonFile);
+
+        $configCategory = $config->getFromLocalConfig('Category');
+        $this->assertEquals(array('key1' => 'value_overwritten'), $configCategory);
+    }
+}
diff --git a/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php b/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php
index 5c97588a3c22fa1fa0f6a30953271c70b6681c99..fa9d3b60f2d3fd4c74d77c00eeb2d33fa7d34c19 100644
--- a/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php
+++ b/tests/PHPUnit/Unit/DataTable/Filter/PatternTest.php
@@ -13,8 +13,9 @@ use Piwik\DataTable\Row;
 
 /**
  * @group DataTableTest
+ * @group Core
  */
-class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase
+class PatternTest extends \PHPUnit_Framework_TestCase
 {
     /**
      * Dataprovider for testFilterPattern
@@ -22,23 +23,22 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase
     public function getTestData()
     {
         return array(
-            array(array('ask', array(1))),
-            array(array('oo', array(0, 3))),
-            array(array('^yah', array(3))),
-            array(array('\*', array(6))),
-            array(array('2/4', array(5))),
-            array(array('amazon|yahoo', array(3, 4))),
+            array('ask', array(1)),
+            array('oo', array(0, 3)),
+            array('^yah', array(3)),
+            array('\*', array(6)),
+            array('2/4', array(5)),
+            array('amazon|yahoo', array(3, 4)),
         );
     }
 
     /**
      * Test to filter a column with a pattern
      *
-     * @group Core
      * _Pattern
      * @dataProvider getTestData
      */
-    public function testFilterPattern($test)
+    public function testFilterPattern($pattern, $expectedRows)
     {
         $table = new DataTable;
 
@@ -56,8 +56,6 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase
         $table->addRowsFromArray($rows);
         $rowIds = array_keys($rows);
 
-        $pattern = $test[0];
-        $expectedRows = $test[1];
         $rowToDelete = array_diff($rowIds, $expectedRows);
         $expectedtable = clone $table;
         $expectedtable->deleteRows($rowToDelete);
@@ -65,4 +63,63 @@ class DataTable_Filter_PatternTest extends \PHPUnit_Framework_TestCase
         $filteredTable->filter('Pattern', array('label', $pattern));
         $this->assertEquals($expectedtable->getRows(), $filteredTable->getRows());
     }
+
+    /**
+     * @dataProvider getTestData
+     */
+    public function testFilterArrayPattern_OneColumn($pattern, $expectedRows)
+    {
+        $rows = array(
+            array('label' => 'google'),
+            array('label' => 'ask'),
+            array('label' => 'piwik'),
+            array('label' => 'yahoo'),
+            array('label' => 'amazon'),
+            array('label' => '2389752/47578949'),
+            array('label' => 'Q*(%&*("$&%*(&"$*")"))')
+        );
+
+        $filter = new DataTable\Filter\Pattern(new DataTable(), array('label'), $pattern);
+        $filteredRows = $filter->filterArray($rows);
+
+        $this->assertSame($expectedRows, array_keys($filteredRows));
+    }
+
+    /**
+     * @dataProvider getTestDataTwoColumns
+     */
+    public function testFilterArrayPattern_ShouldBeAbleToFilterByTwoColumns_IfMultipleColumnsAreGiven($pattern, $expectedRows)
+    {
+        $rows = array(
+            0 => array('randomcolumn' => 'ask', 'name' => 'google', 'url' => 'www.google.com'),
+            1 => array('randomcolumn' => 'goo', 'name' => 'ask', 'url' => 'www.ask.com'),
+            2 => array('randomcolumn' => 'com', 'name' => 'piwik', 'url' => 'piwik.org'),
+            3 => array('randomcolumn' => 'com', 'name' => 'yahoo', 'url' => 'nz.yahoo.com'),
+            4 => array('randomcolumn' => 'nz1', 'name' => 'amazon', 'url' => 'amazon.com'),
+            5 => array('randomcolumn' => 'com', 'url' => 'nz.piwik.org'),
+            6 => array('randomcolumn' => 'com', 'name' => 'Piwik')
+        );
+
+        $filter = new DataTable\Filter\Pattern(new DataTable(), array('name', 'url'), $pattern);
+        $filteredRows = $filter->filterArray($rows);
+
+        $this->assertSame($expectedRows, array_keys($filteredRows));
+    }
+
+    /**
+     * Dataprovider for testFilterPattern
+     */
+    public function getTestDataTwoColumns()
+    {
+        return array(
+            array('ask', array(1)),
+            array('oo', array(0, 3)),
+            array('^yah', array(3)),
+            array('nz', array(3, 5)),
+            array('amazon|yahoo', array(3, 4)),
+            array('piwik', array(2, 5, 6)),
+            array('com', array(0, 1, 3, 4)),
+            array('org', array(2, 5)),
+        );
+    }
 }
diff --git a/tests/UI/expected-ui-screenshots b/tests/UI/expected-ui-screenshots
index 8086d6b5babd5e20b0d6347e25b9605e22396f20..e1c7b5f9ade43f751883541898b82dc09ca830ec 160000
--- a/tests/UI/expected-ui-screenshots
+++ b/tests/UI/expected-ui-screenshots
@@ -1 +1 @@
-Subproject commit 8086d6b5babd5e20b0d6347e25b9605e22396f20
+Subproject commit e1c7b5f9ade43f751883541898b82dc09ca830ec
diff --git a/tests/UI/specs/SitesManager_spec.js b/tests/UI/specs/SitesManager_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cfb3af94e35dd944bf6debbf780761fd133f1228
--- /dev/null
+++ b/tests/UI/specs/SitesManager_spec.js
@@ -0,0 +1,82 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * Site selector screenshot tests.
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe("SitesManager", function () {
+    this.timeout(0);
+    this.fixture = "Piwik\\Plugins\\SitesManager\\tests\\Fixtures\\ManySites";
+
+    var url = "?module=SitesManager&action=index&idSite=1&period=day&date=yesterday&showaddsite=false";
+
+    function assertScreenshotEquals(screenshotName, done, test)
+    {
+        expect.screenshot(screenshotName).to.be.captureSelector('#content', test, done);
+    }
+
+    function loadNextPage(page)
+    {
+        page.click('.SitesManager .paging:first .next');
+    }
+
+    function loadPreviousPage(page)
+    {
+        page.click('.SitesManager .paging:first .prev');
+    }
+
+    function searchForText(page, textToAppendToSearchField)
+    {
+        page.sendKeys(".SitesManager .search:first input", textToAppendToSearchField);
+        page.click('.SitesManager .search:first .submit');
+        page.wait(150);
+    }
+
+    it("should load correctly and show page 0", function (done) {
+        assertScreenshotEquals("loaded", done, function (page) {
+            page.load(url);
+            page.evaluate(function () {
+                $('.ui-inline-help:contains(UTC time is)').hide();
+            });
+        });
+    });
+
+    it("should show page 1 when clicking next", function (done) {
+        assertScreenshotEquals("page_1", done, function (page) {
+            loadNextPage(page);
+        });
+    });
+
+    it("should show page 2 when clicking next", function (done) {
+        assertScreenshotEquals("page_2", done, function (page) {
+            loadNextPage(page);
+        });
+    });
+
+    it("should show page 1 when clicking prev", function (done) {
+        assertScreenshotEquals("page_1_again", done, function (page) {
+            loadPreviousPage(page);
+        });
+    });
+
+    it("should search for websites and reset page to 0", function (done) {
+        assertScreenshotEquals("search", done, function (page) {
+            searchForText(page, 'SiteTes');
+        });
+    });
+
+    it("should page within search result to page 1", function (done) {
+        assertScreenshotEquals("search_page_1", done, function (page) {
+            loadNextPage(page);
+        });
+    });
+
+    it("should search for websites no result", function (done) {
+        assertScreenshotEquals("search_no_result", done, function (page) {
+            searchForText(page, 'RanDoMSearChTerm');
+        });
+    });
+});
\ No newline at end of file
diff --git a/tests/resources/access-logs/fake_logs_replay.log b/tests/resources/access-logs/fake_logs_replay.log
index 9f8db06c9d71563601eda894528548f3d0ec6b68..625cd75a417973ff0f622a7695ce985111bee177 100644
--- a/tests/resources/access-logs/fake_logs_replay.log
+++ b/tests/resources/access-logs/fake_logs_replay.log
@@ -9,20 +9,20 @@
 
 #idsite=1 replay logs from demo (IP were changed)
 84.194.72.21 - - [12/Mar/2014:01:34:15 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=045456&h=1&m=34&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&_id=e01d58157d66e023&_idts=1394670858&_idvc=1&_idn=1&_refts=1394670858&_viewts=1394670858&_ref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=58 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"
-93.80.130.163 - - [13/Mar/2014:01:32:23 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=293471&h=4&m=28&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=1&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=182 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0"
+93.80.130.163 - - [13/Mar/2014:01:32:23 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=293471&h=4&m=28&s=18&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=1&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=182 HTTP/1.1" 403 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0"
 176.41.226.154 - - [13/Mar/2014:01:32:24 +0100] "GET /piwik.php?action_name=Hello Installing%20Piwik%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=144006&h=2&m=32&s=21&url=http%3A%2F%2Fpiwik.org%2Fdocs%2Finstallation%2F&_id=8b11942ef3b17467&_idts=1392074017&_idvc=5&_idn=0&_refts=1394670293&_viewts=1393965698&_ref=https%3A%2F%2Fwww.google.com.tr%2F&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1366x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=134 HTTP/1.1" 200 43 "http://piwik.org/docs/installation/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"
-93.80.130.163 - - [13/Mar/2014:01:32:25 +0100] "GET /piwik.php?action_name=Download%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=147473&h=4&m=28&s=20&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=113 HTTP/1.1" 200 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0"
+93.80.130.163 - - [13/Mar/2014:01:32:25 +0100] "GET /piwik.php?action_name=Download%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=147473&h=4&m=28&s=20&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=113 HTTP/1.1" 500 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0"
 93.80.130.163 - - [13/Mar/2014:01:32:32 +0100] "GET /piwik.php?download=http%3A%2F%2Fbuilds.piwik.org%2Flatest.zip&idsite=1&rec=1&r=533958&h=4&m=28&s=25&url=http%3A%2F%2Fpiwik.org%2Fdownload%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=f077de87fa56021e&_idts=1394670499&_idvc=1&_idn=0&_refts=1394670499&_viewts=1394670499&_ref=http%3A%2F%2Fwww.google.ru%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCoQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3DffwgU57VGuf44QT3-oCQCw%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.bGE%26cad%3Drjt&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=113 HTTP/1.1" 200 43 "http://piwik.org/download/" "Mozilla/5.0 (Windows NT 6.2; rv:27.0) Gecko/20100101 Firefox/27.0"
 188.107.238.9 - - [13/Mar/2014:01:33:11 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=993149&h=1&m=33&s=13&url=http%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=http%3A%2F%2Fforum.golem.de%2Fkommentare%2Fsecurity%2Furteil-zu-tracking-nutzer-muessen-piwik-analyse-widersprechen-koennen%2Fpiwik-log-analytics%2F80715%2C3669355%2C3669355%2Cread.html&_id=d6f0dcb66949b86b&_idts=1394570793&_idvc=5&_idn=1&_refts=1394270793&_viewts=1394370793&_ref=http%3A%2F%2Fforum.golem.de%2Fkommentare%2Fsecurity%2Furteil-zu-tracking-nutzer-muessen-piwik-analyse-widersprechen-koennen%2Fpiwik-log-analytics%2F80715%2C3669355%2C3669355%2Cread.html&pdf=1&qt=1&realp=0&wma=1&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=145 HTTP/1.1" 200 43 "http://piwik.org/log-analytics/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0"
 206.190.75.8 - - [13/Mar/2014:01:33:27 +0100] "GET /piwik.php?action_name=Changelog%20Archive%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=697117&h=17&m=33&s=27&url=http%3A%2F%2Fpiwik.org%2Fchangelog%2F&urlref=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwik%2F&_id=e0eb35c4fae2aa0a&_idts=1379033580&_idvc=2&_idn=0&_refts=1394669945&_viewts=1379033693&_ref=http%3A%2F%2Ftrends.builtwith.com%2Fanalytics%2FPiwik-Web-Analytics&res=1536x864&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/changelog/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; .NET4.0C; InfoPath.2)"
 80.136.160.65 - - [13/Mar/2014:01:33:33 +0100] "GET /piwik.php?action_name=Piwik%202.1%20%E2%80%94%20Massive%20Performance%20and%20Reliability%20Improvements%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=542198&h=1&m=33&s=31&url=http%3A%2F%2Fpiwik.org%2Fblog%2F2014%2F03%2Fpiwik-2-1-massive-performance-reliability-improvements%2F&urlref=http%3A%2F%2Fberndjung.com%2Fpiwik%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26idSite%3D1%26period%3Dday%26date%3Dtoday&_id=fc96e596bd4c5d54&_idts=1394670812&_idvc=2&_idn=1&_refts=1394670812&_viewts=1394670812&_ref=http%3A%2F%2Fberndjung.com%2Fpiwik%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26idSite%3D1%26period%3Dday%26date%3Dtoday&pdf=0&qt=0&realp=0&wma=0&dir=0&fla=1&java=0&gears=0&ag=0&cookie=1&res=360x640&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=119 HTTP/1.1" 200 43 "http://piwik.org/blog/2014/03/piwik-2-1-massive-performance-reliability-improvements/" "Mozilla/5.0 (Android; Mobile; rv:27.0) Gecko/27.0 Firefox/27.0"
-219.101.46.222 - - [13/Mar/2014:01:33:50 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=263291&h=9&m=33&s=50&url=https%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&_id=15164ceeb5ddb76f&_idts=1394444478&_idvc=4&_idn=0&_refts=1394670831&_viewts=1394584228&_ref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=0&java=1&gears=0&ag=0&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "https://piwik.org/log-analytics/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9"
+219.101.46.222 - - [13/Mar/2014:01:33:50 +0100] "GET /piwik.php?action_name=Log%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=263291&h=9&m=33&s=50&url=https%3A%2F%2Fpiwik.org%2Flog-analytics%2F&urlref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&_id=15164ceeb5ddb76f&_idts=1394444478&_idvc=4&_idn=0&_refts=1394670831&_viewts=1394584228&_ref=https%3A%2F%2Fwww.google.co.jp%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D2%26ved%3D0CDAQFjAB%26url%3Dhttps%253A%252F%252Fpiwik.org%252Flog-analytics%252F%26ei%3DrogdU5OuK43pkgXZmYGICA%26usg%3DAFQjCNH4nR8bKYaliCj2egiJ0H_CX4sFJg%26bvm%3Dbv.62578216%2Cd.dGI&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=0&java=1&gears=0&ag=0&cookie=1&res=2560x1440&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 404 43 "https://piwik.org/log-analytics/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9"
 108.211.181.12 - - [13/Mar/2014:01:33:55 +0100] "GET /piwik.php?action_name=Integrate%20Piwik%20into%20your%20Rails%20Application%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=006719&h=17&m=33&s=58&url=http%3A%2F%2Fpiwik.org%2Fblog%2F2012%2F10%2Fintegrate-piwik-into-your-rails-application%2F&urlref=https%3A%2F%2Fwww.google.com%2F&_id=3d9cad3fc284c272&_idts=1394670839&_idvc=1&_idn=1&_refts=1394670839&_viewts=1394670839&_ref=https%3A%2F%2Fwww.google.com%2F&pdf=1&qt=1&realp=0&wma=1&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1360x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=391 HTTP/1.1" 200 43 "http://piwik.org/blog/2012/10/integrate-piwik-into-your-rails-application/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.149 Safari/537.36"
 # testing particular idvc, viewts, refts, etc.
 50.244.17.130 - - [13/Mar/2014:01:33:59 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=658858&h=19&m=34&s=1&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&_id=90d89274a8c26d90&_idts=1393170841&_idvc=7&_idn=0&_refts=1394670841&_viewts=1394170841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9"
 #this visitor visited the website yesterday
 84.194.72.21 - - [13/Mar/2014:01:34:21 +0100] "GET /piwik.php?link=http%3A%2F%2Fdemo.piwik.org%2F&idsite=1&rec=1&forceLargeWindowLookBackForVisitor=1&r=578077&h=1&m=34&s=24&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&_id=e01d58157d66e023&_idts=1394670858&_idvc=1&_idn=0&_refts=1394670858&_viewts=1394670858&_ref=http%3A%2F%2Fmusicforeveryoneradio.be%3A2222%2FCMD_PLUGINS%2Finstallatron%2Findex.raw&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1080&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=58 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"
-50.244.17.130 - - [13/Mar/2014:01:34:31 +0100] "GET /piwik.php?action_name=What%20is%20Piwik%3F%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=388822&h=19&m=34&s=33&url=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwisk%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=90d89274a8c26d90&_idts=1394170841&_idvc=1&_idn=0&_refts=1394670841&_viewts=1394670841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/what-is-piwik/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9"
+50.244.17.130 - - [13/Mar/2014:01:34:31 +0100] "GET /piwik.php?action_name=What%20is%20Piwik%3F%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=388822&h=19&m=34&s=33&url=http%3A%2F%2Fpiwik.org%2Fwhat-is-piwisk%2F&urlref=http%3A%2F%2Fpiwik.org%2F&_id=90d89274a8c26d90&_idts=1394170841&_idvc=1&_idn=0&_refts=1394670841&_viewts=1394670841&_ref=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D0CCcQFjAA%26url%3Dhttp%253A%252F%252Fpiwik.org%252F%26ei%3D8vwgU8TYDZTI2wWTi4CQCA%26usg%3DAFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw%26bvm%3Dbv.62922401%2Cd.b2I&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=2880x1800&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 501 43 "http://piwik.org/what-is-piwik/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9"
 193.159.20.129 - - [13/Mar/2014:01:34:42 +0100] "GET /piwik.php?action_name=demo.piwik.org%2FPiwik%20Forums%20-%20Piwik%20%E2%80%BA%20Web%20Analytics%20Reports&idsite=1&rec=1&r=546849&h=1&m=34&s=37&url=http%3A%2F%2Fdemo.piwik.org%2Findex.php%3Fmodule%3DCoreHome%26action%3Dindex%26date%3Dyesterday%26period%3Dday%26idSite%3D7%23%2Fmodule%3DLive%26action%3DindexVisitorLog%26date%3Dyesterday%26period%3Dday%26idSite%3D7&urlref=http%3A%2F%2Fdemo.piwik.org%2Findex.php%3Fmodule%3DMultiSites%26action%3Dindex%26date%3Dyesterday%26period%3Dday%26idSite%3D32&_id=d80e3396f1a4c2ac&_idts=1394665308&_idvc=1&_idn=0&_refts=0&_viewts=1394365308&pdf=1&qt=1&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=1&cookie=1&res=1920x1200&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%2C%222%22%3A%5B%22Demo%20language%22%2C%22English%22%5D%7D&gt_ms=413 HTTP/1.1" 200 43 "http://demo.piwik.org/index.php?module=CoreHome&action=index&date=yesterday&period=day&idSite=7" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.45 Safari/537.36"
 206.190.75.8 - - [13/Mar/2014:01:34:46 +0100] "GET /piwik.php?action_name=Contact%20the%20Piwik%20team%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=456386&h=17&m=34&s=45&url=http%3A%2F%2Fpiwik.org%2Fcontact%2F&urlref=http%3A%2F%2Fpiwik.org%2Fchangelog%2F&_id=e0eb35c4fae2aa0a&_idts=1379033580&_idvc=2&_idn=0&_refts=1394169945&_viewts=1379033693&_ref=http%3A%2F%2Ftrends.builtwith.com%2Fanalytics%2FPiwik-Web-Analytics&res=1536x864&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D HTTP/1.1" 200 43 "http://piwik.org/contact/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; .NET4.0C; InfoPath.2)"
 174.97.139.63 - - [13/Mar/2014:01:34:53 +0100] "GET /piwik.php?action_name=Liberate%20Web%20Analytics%20-%20Analytics%20-%20Piwik&idsite=1&rec=1&r=022131&h=20&m=34&s=49&url=http%3A%2F%2Fpiwik.org%2F&urlref=http%3A%2F%2Fblog.comperiosearch.com%2Fblog%2F2014%2F02%2F05%2Fdynamic-search-ranking-using-elasticsearch-neo4j-and-piwik%2F&_id=d98b53d7fcf5a082&_idts=1394670890&_idvc=77&_idn=1&_refts=1394670890&_viewts=1394670890&_ref=http%3A%2F%2Fblog.comperiosearch.com%2Fblog%2F2014%2F02%2F05%2Fdynamic-search-ranking-using-elasticsearch-neo4j-and-piwik%2F&pdf=1&qt=0&realp=0&wma=0&dir=0&fla=1&java=1&gears=0&ag=0&cookie=1&res=1366x768&_cvar=%7B%221%22%3A%5B%22Domain%20landed%22%2C%22piwik.org%22%5D%7D&gt_ms=292 HTTP/1.1" 200 43 "http://piwik.org/" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"