diff --git a/plugins/MultiSites/Dashboard.php b/plugins/MultiSites/Dashboard.php
index 623e51a9b16e30edbef1686a3c09c4922e7cd567..c757858504e3fd5a0bf690fe6763a8b345a93880 100644
--- a/plugins/MultiSites/Dashboard.php
+++ b/plugins/MultiSites/Dashboard.php
@@ -8,6 +8,7 @@
  */
 namespace Piwik\Plugins\MultiSites;
 
+use Piwik\API\DataTablePostProcessor;
 use Piwik\API\ResponseBuilder;
 use Piwik\Config;
 use Piwik\Metrics\Formatter;
@@ -15,6 +16,7 @@ use Piwik\Period;
 use Piwik\DataTable;
 use Piwik\DataTable\Row;
 use Piwik\DataTable\Row\DataTableSummaryRow;
+use Piwik\Plugin\Report;
 use Piwik\Plugins\API\ProcessedReport;
 use Piwik\Site;
 use Piwik\View;
@@ -49,6 +51,8 @@ class Dashboard
         $pastData = $sites->getMetadata('pastData');
 
         $sites->filter(function (DataTable $table) use ($pastData) {
+            $pastRow = null;
+
             foreach ($table->getRows() as $row) {
                 $idSite = $row->getColumn('label');
                 $site   = Site::getSite($idSite);
@@ -61,11 +65,14 @@ class Dashboard
                     $pastRow = $pastData->getRowFromLabel($idSite);
                     if ($pastRow) {
                         $pastRow->setColumn('label', $site['name']);
-                        $pastData->setLabelsHaveChanged();
                     }
                 }
             }
 
+            if ($pastData && $pastRow) {
+                $pastData->setLabelsHaveChanged();
+            }
+
         });
 
         $this->setSitesTable($sites);
@@ -73,20 +80,20 @@ class Dashboard
 
     public function setSitesTable(DataTable $sites)
     {
-        $this->numSites     = $sites->getRowsCount();
         $this->sitesByGroup = $this->moveSitesHavingAGroupIntoSubtables($sites);
+        $this->rememberNumberOfSites();
     }
 
     public function getSites($request, $limit)
     {
-        $request['filter_limit'] = $limit;
+        $request['filter_limit']  = $limit;
+        $request['filter_offset'] = isset($request['filter_offset']) ? $request['filter_offset'] : 0;
 
-        $sitesExpanded = $this->convertDataTableToArrayAndApplyFilters($this->sitesByGroup, $request);
-        $sitesFlat     = $this->makeSitesFlat($sitesExpanded);
-        $sitesFlat     = $this->applyLimitIfNeeded($sitesFlat, $limit);
-        $sitesFlat     = $this->enrichValues($sitesFlat);
+        $this->makeSitesFlatAndApplyGenericFilters($this->sitesByGroup, $request);
+        $sites = $this->convertDataTableToArrayAndApplyQueuedFilters($this->sitesByGroup, $request);
+        $sites = $this->enrichValues($sites);
 
-        return $sitesFlat;
+        return $sites;
     }
 
     public function getTotals()
@@ -107,7 +114,11 @@ class Dashboard
     public function search($pattern)
     {
         $this->nestedSearch($this->sitesByGroup, $pattern);
+        $this->rememberNumberOfSites();
+    }
 
+    private function rememberNumberOfSites()
+    {
         $this->numSites = $this->sitesByGroup->getRowsCountRecursive();
     }
 
@@ -153,19 +164,13 @@ class Dashboard
         return $lastPeriod;
     }
 
-    private function convertDataTableToArrayAndApplyFilters(DataTable $table, $request)
+    private function convertDataTableToArrayAndApplyQueuedFilters(DataTable $table, $request)
     {
         $request['serialize'] = 0;
-        $request['expanded'] = 1;
+        $request['expanded'] = 0;
         $request['totals'] = 0;
         $request['format_metrics'] = 1;
-
-        // filter_sort_column does not work correctly is a bug in MultiSites.getAll
-        if (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'nb_pageviews') {
-            $request['filter_sort_column'] = 'Actions_nb_pageviews';
-        } elseif (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'revenue') {
-            $request['filter_sort_column'] = 'Goal_revenue';
-        }
+        $request['disable_generic_filters'] = 1;
 
         $responseBuilder = new ResponseBuilder('php', $request);
         $rows = $responseBuilder->getResponse($table, 'MultiSites', 'getAll');
@@ -234,7 +239,8 @@ class Dashboard
 
     /**
      * Makes sure to not have any subtables anymore.
-     * So if $sites is
+     *
+     * So if $table is
      * array(
      *    site1
      *    site2
@@ -257,41 +263,39 @@ class Dashboard
      *    site7
      * )
      *
-     * @param $sites
-     * @return array
+     * in a sorted order
+     *
+     * @param DataTable $table
+     * @param array $request
      */
-    private function makeSitesFlat($sites)
+    private function makeSitesFlatAndApplyGenericFilters(DataTable $table, $request)
     {
-        $flatSites = array();
+        // we handle limit here as we have to apply sort filter, then make sites flat, then apply limit filter.
+        $filterOffset = $request['filter_offset'];
+        $filterLimit  = $request['filter_limit'];
+        unset($request['filter_offset']);
+        unset($request['filter_limit']);
 
-        foreach ($sites as $site) {
-            if (!empty($site['subtable'])) {
-                if (isset($site['idsubdatatable'])) {
-                    unset($site['idsubdatatable']);
-                }
-
-                $subtable = $site['subtable'];
-                unset($site['subtable']);
-                $flatSites[] = $site;
-                foreach ($subtable as $siteWithinGroup) {
-                    $flatSites[] = $siteWithinGroup;
-                }
-            } else {
-                $flatSites[] = $site;
-            }
+        // filter_sort_column does not work correctly is a bug in MultiSites.getAll
+        if (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'nb_pageviews') {
+            $request['filter_sort_column'] = 'Actions_nb_pageviews';
+        } elseif (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'revenue') {
+            $request['filter_sort_column'] = 'Goal_revenue';
         }
 
-        return $flatSites;
-    }
+        // make sure no limit filter is applied, we will do this manually
+        $table->disableFilter('Limit');
 
-    private function applyLimitIfNeeded($sites, $limit)
-    {
-        // why do we need to apply a limit again? because we made sitesFlat and it may contain many more sites now
-        if ($limit > 0) {
-            $sites = array_slice($sites, 0, $limit);
-        }
+        // this will apply the sort filter
+        /** @var DataTable $table */
+        $genericFilter = new DataTablePostProcessor('MultiSites', 'getAll', $request);
+        $table = $genericFilter->applyGenericFilters($table);
 
-        return $sites;
+        // make sure from now on the sites will be no longer sorted, they were already sorted
+        $table->disableFilter('Sort');
+
+        // make sites flat and limit
+        $table->filter('Piwik\Plugins\MultiSites\DataTable\Filter\NestedSitesLimiter', array($filterOffset, $filterLimit));
     }
 
     private function enrichValues($sites)
diff --git a/plugins/MultiSites/DataTable/Filter/NestedSitesLimiter.php b/plugins/MultiSites/DataTable/Filter/NestedSitesLimiter.php
new file mode 100644
index 0000000000000000000000000000000000000000..7151dcb8c75faa72bb30718d8bc66427201156d2
--- /dev/null
+++ b/plugins/MultiSites/DataTable/Filter/NestedSitesLimiter.php
@@ -0,0 +1,138 @@
+<?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\MultiSites\DataTable\Filter;
+
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable\Row;
+use Piwik\DataTable;
+
+/**
+ * Makes sure to not have any subtables anymore and applies the limit to the flattened table.
+ *
+ * So if $table is
+ * array(
+ *    site1
+ *    group2
+ *        subtable => site3
+ *                    site4
+ *                    site5
+ *    site6
+ *    site7
+ * )
+ *
+ * it will format this to
+ *
+ * array(
+ *    site1
+ *    group2
+ *    site3
+ *    site4
+ *    site5
+ *    site6
+ *    site7
+ * )
+ *
+ * and then apply the limit filter.
+ *
+ * Each group will not count into the limit/offset. This way, if one requests a limit of 50 sites,
+ * we make sure to return 50 sites.
+ *
+ * @param $sites
+ * @return array
+ */
+class NestedSitesLimiter extends BaseFilter
+{
+    private $offset = 0;
+    private $limit  = 0;
+    /**
+     * @var Row[]
+     */
+    private $rows   = array();
+
+    /**
+     * Constructor.
+     *
+     * @param DataTable $table The table to eventually filter.
+     */
+    public function __construct($table, $offset, $limit)
+    {
+        parent::__construct($table);
+        $this->offset = $offset;
+        $this->limit  = $limit;
+    }
+
+    /**
+     * @param DataTable $table
+     */
+    public function filter($table)
+    {
+        $numRows = 0;
+        $lastGroupFromPreviousPage = null;
+
+        foreach ($table->getRows() as $row) {
+
+            $this->addRowIfNeeded($row, $numRows);
+            $numRows++;
+
+            $subtable = $row->getSubtable();
+            if ($subtable) {
+                if (!$this->hasRows()) {
+                    $lastGroupFromPreviousPage = $row;
+                }
+                foreach ($subtable->getRows() as $subRow) {
+                    $this->addRowIfNeeded($subRow, $numRows);
+                    $numRows++;
+                }
+                $row->removeSubtable();
+            }
+
+            if ($this->hasNumberOfRequestedRowsFound()) {
+                break;
+            }
+        }
+
+        $this->prependGroupIfFirstSiteBelongsToAGroupButGroupIsMissingInRows($lastGroupFromPreviousPage);
+
+        $table->setRows($this->rows);
+    }
+
+    private function hasNumberOfRequestedRowsFound()
+    {
+        return count($this->rows) >= $this->limit;
+    }
+
+    private function hasRows()
+    {
+        return count($this->rows) !== 0;
+    }
+
+    private function addRowIfNeeded(Row $row, $numRows)
+    {
+        $inOffset = $numRows >= $this->offset;
+
+        if ($inOffset && !$this->hasNumberOfRequestedRowsFound()) {
+            $this->rows[] = $row;
+        }
+    }
+
+    /**
+     * @param Row $lastGroupFromPreviousPage
+     */
+    private function prependGroupIfFirstSiteBelongsToAGroupButGroupIsMissingInRows($lastGroupFromPreviousPage)
+    {
+        if ($lastGroupFromPreviousPage && !empty($this->rows)) {
+            // the result starts with a row that does belong to a group, we make sure to show this group before that site
+            $group = reset($this->rows)->getMetadata('group');
+            if ($group && $lastGroupFromPreviousPage->getColumn('label') === $group) {
+                array_unshift($this->rows, $lastGroupFromPreviousPage);
+                // we do not remove the last item as it could result in errors, instead we show $limit+1 entries
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/plugins/MultiSites/tests/Integration/DashboardTest.php b/plugins/MultiSites/tests/Integration/DashboardTest.php
index 33d671cbb2f352e75d33a7a2fec9f13c9dfe6b09..525cd95941ab9554522cb391c9ab7724227ebd7c 100644
--- a/plugins/MultiSites/tests/Integration/DashboardTest.php
+++ b/plugins/MultiSites/tests/Integration/DashboardTest.php
@@ -138,6 +138,24 @@ class DashboardTest extends IntegrationTestCase
         $this->assertSame(4, $this->dashboard->getNumSites());
     }
 
+    public function test_getNumSites_ShouldCountGroupsIntoResult()
+    {
+        $sites = $this->setSitesTable(20);
+
+        $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+        $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 4, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 15, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 16, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 18, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+        $this->dashboard->setSitesTable($sites);
+
+        // 3 different groups
+        $this->assertSame(20 + 3, $this->dashboard->getNumSites());
+    }
+
     public function test_getSites_shouldReturnAnArrayOfSites()
     {
         $this->setSitesTable(8);
@@ -156,6 +174,81 @@ class DashboardTest extends IntegrationTestCase
         $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 4));
     }
 
+    public function test_getSites_ShouldApplyLimitCorrectIfThereAreLessFirstLevelRowsThenLimit()
+    {
+        $sites = $this->setSitesTable(8);
+
+        $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+        $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 4, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 5, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 7, 'group4');
+        $this->dashboard->setSitesTable($sites);
+
+        $expectedSites = array(
+                array ('label' => 'group1',
+                       'nb_visits' => 20,
+                       'isGroup' => 1,
+             ), array ('label' => 'Site1',
+                       'nb_visits' => 10,
+                       'group' => 'group1',
+             ), array ('label' => 'Site3',
+                       'nb_visits' => 10,
+                       'group' => 'group1',
+             ), array ('label' => 'group2',
+                       'nb_visits' => 10,
+                       'isGroup' => 1,
+             ), array ('label' => 'Site2',
+                       'nb_visits' => 10,
+                       'group' => 'group2',
+             ), array ('label' => 'Site8',
+                       'nb_visits' => 10,
+             ),
+        );
+
+        // there will be 4 first level entries (group1, group2, group4 and site8), offset is 5, limit is 6.
+        // See https://github.com/piwik/piwik/issues/7854 before there was no site returned since 5 > 4 first level entries
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array('filter_offset' => 5), $limit = 6));
+    }
+
+    public function test_getSites_ShouldReturnOneMoreGroup_IfFirstSiteBelongsToAGroupButGroupWouldBeNormallyNotInResult()
+    {
+        $sites = $this->setSitesTable(8);
+
+        $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+        $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+        $this->setGroupForSiteId($sites, $siteId = 4, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 5, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+        $this->setGroupForSiteId($sites, $siteId = 7, 'group4');
+        $this->dashboard->setSitesTable($sites);
+
+        $expectedSites = array(
+                array ('label' => 'group4', // this group should be the added group, that's why there are 5 entries
+                       'nb_visits' => 40,
+                       'isGroup' => 1,
+             ), array ('label' => 'Site6',
+                       'nb_visits' => 10,
+                       'group' => 'group4',
+             ), array ('label' => 'Site7',
+                       'nb_visits' => 10,
+                       'group' => 'group4',
+             ), array ('label' => 'group1',
+                       'nb_visits' => 20,
+                       'isGroup' => 1,
+             ), array ('label' => 'Site1',
+                       'nb_visits' => 10,
+                       'group' => 'group1',
+             )
+        );
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array('filter_offset' => 3), $limit = 4));
+    }
+
     public function test_getSites_WithGroup_shouldApplyALimitAndKeepSitesWithinGroup()
     {
         $sites = $this->setSitesTable(20);
@@ -172,8 +265,8 @@ class DashboardTest extends IntegrationTestCase
 
         $expectedSites = array (
             array (
-                'label' => 'group1',
-                'nb_visits' => 50, // there are 5 matching sites having that group, we only return 4, still result is correct!
+                'label' => 'group1', // do not count group into the limit
+                'nb_visits' => 50, // there are 5 matching sites having that group, we only return 3, still result is correct!
                 'isGroup' => 1,
             ), array (
                 'label' => 'Site1',