diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce013f131c20c23174775c3f766a165d8e55b644..382fd9d4d5e9f0e357c3ed05ba9980fd8ffd22c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
 
 ### 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 Event `Site.setSite` was removed as it causes performance problems.
 
 ### New commands
 * There is now a `diagnostic:run` command to run the system check from the command line.
diff --git a/core/Archive.php b/core/Archive.php
index 5178718e732ed1c3d75000b554a2afb8a7ad79bb..8026fc0f5b7611acabe06cf30b662f9d4a3b8b61 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -356,6 +356,20 @@ class Archive
         return $data->getDataTable($this->getResultIndices());
     }
 
+    /**
+     * Similar to {@link getDataTableFromNumeric()} but merges all children on the created DataTable.
+     *
+     * This is the same as doing `$this->getDataTableFromNumeric()->mergeChildren()` but this way it is much faster.
+     *
+     * @return DataTable|DataTable\Map
+     */
+    public function getDataTableFromNumericAndMergeChildren($names)
+    {
+        $data  = $this->get($names, 'numeric');
+        $resultIndexes = $this->getResultIndices();
+        return $data->getMergedDataTable($resultIndexes);
+    }
+
     /**
      * Queries and returns one or more reports as DataTable instances.
      *
@@ -616,21 +630,19 @@ class Archive
 
         $archiveData = ArchiveSelector::getArchiveData($archiveIds, $archiveNames, $archiveDataType, $idSubtable);
 
+        $isNumeric = $archiveDataType == 'numeric';
+
         foreach ($archiveData as $row) {
             // values are grouped by idsite (site ID), date1-date2 (date range), then name (field name)
-            $idSite    = $row['idsite'];
-            $periodStr = $row['date1'] . "," . $row['date2'];
+            $periodStr = $row['date1'] . ',' . $row['date2'];
 
-            if ($archiveDataType == 'numeric') {
+            if ($isNumeric) {
                 $row['value'] = $this->formatNumericValue($row['value']);
             } else {
-                $result->addMetadata($idSite, $periodStr, 'ts_archived', $row['ts_archived']);
+                $result->addMetadata($row['idsite'], $periodStr, 'ts_archived', $row['ts_archived']);
             }
 
-            $resultRow = & $result->get($idSite, $periodStr);
-
-            // one blob per datatable or subtable
-            $resultRow[$row['name']] = $row['value'];
+            $result->set($row['idsite'], $periodStr, $row['name'], $row['value']);
         }
 
         return $result;
diff --git a/core/Archive/DataCollection.php b/core/Archive/DataCollection.php
index efd63cd925b3fa4cc6e3a0c110da2a3ba2551bbf..7973d0148181443d73120b64cb77775a2e027c71 100644
--- a/core/Archive/DataCollection.php
+++ b/core/Archive/DataCollection.php
@@ -137,6 +137,21 @@ class DataCollection
         return $this->data[$idSite][$period];
     }
 
+    /**
+     * Set data for a specific site & period. If there is no data for the given site ID & period,
+     * it is set to the default row.
+     *
+     * @param int $idSite
+     * @param string $period eg, '2012-01-01,2012-01-31'
+     * @param string $name eg 'nb_visits'
+     * @param string $value eg 5
+     */
+    public function set($idSite, $period, $name, $value)
+    {
+        $row = & $this->get($idSite, $period);
+        $row[$name] = $value;
+    }
+
     /**
      * Adds a new metadata to the data for specific site & period. If there is no
      * data for the given site ID & period, it is set to the default row.
@@ -213,6 +228,23 @@ class DataCollection
         return $dataTableFactory->make($index, $resultIndices);
     }
 
+    /**
+     * See {@link DataTableFactory::makeMerged()}
+     *
+     * @param array $resultIndices
+     * @return DataTable|DataTable\Map
+     * @throws Exception
+     */
+    public function getMergedDataTable($resultIndices)
+    {
+        $dataTableFactory = new DataTableFactory(
+            $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow);
+
+        $index = $this->getIndexedArray($resultIndices);
+
+        return $dataTableFactory->makeMerged($index, $resultIndices);
+    }
+
     /**
      * Returns archive data as a DataTable indexed by metadata. Indexed data will
      * be represented by Map instances. Each DataTable will have
@@ -303,9 +335,14 @@ class DataCollection
                 $indexKeyValues = array_keys($this->periods);
             }
 
-            foreach ($indexKeyValues as $key) {
-                $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy);
+            if (empty($metadataNamesToIndexBy)) {
+                $result = array_fill_keys($indexKeyValues, array());
+            } else {
+                foreach ($indexKeyValues as $key) {
+                    $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy);
+                }
             }
+
         }
 
         return $result;
@@ -321,7 +358,7 @@ class DataCollection
         foreach ($metadataNamesToIndexBy as $metadataName) {
             if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) {
                 $key = $idSite;
-            } else if ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
+            } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
                 $key = $period;
             } else {
                 $key = $row[self::METADATA_CONTAINER_ROW_KEY][$metadataName];
diff --git a/core/Archive/DataTableFactory.php b/core/Archive/DataTableFactory.php
index cfc19378130cfbdf674e8b93bf58eed062e2464c..36e985e6dc1ce8fc147d8e87d4c7297ab34724fc 100644
--- a/core/Archive/DataTableFactory.php
+++ b/core/Archive/DataTableFactory.php
@@ -146,6 +146,11 @@ class DataTableFactory
         $this->idSubtable = $idSubtable;
     }
 
+    private function isNumericDataType()
+    {
+        return $this->dataType == 'numeric';
+    }
+
     /**
      * Creates a DataTable|Set instance using an index of
      * archive data.
@@ -157,21 +162,128 @@ class DataTableFactory
      */
     public function make($index, $resultIndices)
     {
+        $keyMetadata = $this->getDefaultMetadata();
+
         if (empty($resultIndices)) {
             // for numeric data, if there's no index (and thus only 1 site & period in the query),
             // we want to display every queried metric name
             if (empty($index)
-                && $this->dataType == 'numeric'
+                && $this->isNumericDataType()
             ) {
                 $index = $this->defaultRow;
             }
 
-            $dataTable = $this->createDataTable($index, $keyMetadata = array());
+            $dataTable = $this->createDataTable($index, $keyMetadata);
         } else {
-            $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array());
+            $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata);
+        }
+
+        return $dataTable;
+    }
+
+    /**
+     * Creates a merged DataTable|Map instance using an index of archive data similar to {@link make()}.
+     *
+     * Whereas {@link make()} creates a Map for each result index (period and|or site), this will only create a Map
+     * for a period result index and move all site related indices into one dataTable. This is the same as doing
+     * `$dataTableFactory->make()->mergeChildren()` just much faster. It is mainly useful for reports across many sites
+     * eg `MultiSites.getAll`. Was done as part of https://github.com/piwik/piwik/issues/6809
+     *
+     * @param array $index @see DataCollection
+     * @param array $resultIndices an array mapping metadata names with pretty metadata labels.
+     *
+     * @return DataTable|DataTable\Map
+     * @throws \Exception
+     */
+    public function makeMerged($index, $resultIndices)
+    {
+        if (!$this->isNumericDataType()) {
+            throw new \Exception('This method is supposed to work with non-numeric data types but it is not tested. To use it, remove this exception and write tests to be sure it works.');
+        }
+
+        $metadata = array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods));
+
+        $firstIdSite = reset($this->sitesId);
+        $isNumeric   = $this->isNumericDataType();
+        $numResultIndices = count($resultIndices);
+
+        $firstResultIndex = null;
+        if ($numResultIndices >= 1) {
+            reset($resultIndices);
+            $firstResultIndex = key($resultIndices);
+        }
+
+        $useSimpleDataTable = !array_key_exists(self::TABLE_METADATA_SITE_INDEX, $resultIndices) && $isNumeric;
+
+        if ($numResultIndices === 1 && $firstResultIndex === self::TABLE_METADATA_PERIOD_INDEX) {
+            $index = array($firstIdSite => $index);
+            $numResultIndices = 2;
+        } elseif ($numResultIndices === 0) {
+            $index = array($firstIdSite => $index);
         }
 
-        $this->transformMetadata($dataTable);
+        $defaultRow = $this->defaultRow;
+
+        if ($numResultIndices === 2) {
+            $tables = array();
+
+            $dataTable = new DataTable\Map();
+            $dataTable->setKeyName($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]);
+
+            foreach ($this->periods as $range => $period) {
+                $metadata[self::TABLE_METADATA_PERIOD_INDEX] = $period;
+                if ($useSimpleDataTable) {
+                    $table = new DataTable\Simple();
+                } else {
+                    $table = new DataTable();
+                }
+
+                $table->setAllTableMetadata($metadata);
+                $dataTable->addTable($table, $this->prettifyIndexLabel(self::TABLE_METADATA_PERIOD_INDEX, $range));
+
+                $tables[$range] = $table;
+            }
+
+            foreach ($index as $idsite => $table) {
+                $rowMeta = array('idsite' => $idsite);
+
+                foreach ($table as $range => $row) {
+                    if (!empty($row)) {
+                        $tables[$range]->addRow(new Row(array(
+                            Row::COLUMNS  => $row,
+                            Row::METADATA => $rowMeta)
+                        ));
+                    } elseif ($isNumeric) {
+                        $tables[$range]->addRow(new Row(array(
+                            Row::COLUMNS  => $defaultRow,
+                            Row::METADATA => $rowMeta)
+                        ));
+                    }
+                }
+            }
+
+        } else {
+            if ($useSimpleDataTable) {
+                $dataTable = new DataTable\Simple();
+            } else {
+                $dataTable = new DataTable();
+            }
+            $dataTable->setAllTableMetadata($metadata);
+
+            foreach ($index as $idsite => $row) {
+                if (!empty($row)) {
+                    $dataTable->addRow(new Row(array(
+                        Row::COLUMNS  => $row,
+                        Row::METADATA => array('idsite' => $idsite))
+                    ));
+                } elseif ($isNumeric) {
+                    $dataTable->addRow(new Row(array(
+                        Row::COLUMNS  => $defaultRow,
+                        Row::METADATA => array('idsite' => $idsite))
+                    ));
+                }
+            }
+        }
 
         return $dataTable;
     }
@@ -190,16 +302,16 @@ class DataTableFactory
      * @param array $blobRow
      * @return DataTable|DataTable\Map
      */
-    private function makeFromBlobRow($blobRow)
+    private function makeFromBlobRow($blobRow, $keyMetadata)
     {
         if ($blobRow === false) {
             return new DataTable();
         }
 
         if (count($this->dataNames) === 1) {
-            return $this->makeDataTableFromSingleBlob($blobRow);
+            return $this->makeDataTableFromSingleBlob($blobRow, $keyMetadata);
         } else {
-            return $this->makeIndexedByRecordNameDataTable($blobRow);
+            return $this->makeIndexedByRecordNameDataTable($blobRow, $keyMetadata);
         }
     }
 
@@ -211,7 +323,7 @@ class DataTableFactory
      * @param array $blobRow
      * @return DataTable
      */
-    private function makeDataTableFromSingleBlob($blobRow)
+    private function makeDataTableFromSingleBlob($blobRow, $keyMetadata)
     {
         $recordName = reset($this->dataNames);
         if ($this->idSubtable !== null) {
@@ -225,7 +337,7 @@ class DataTableFactory
         }
 
         // set table metadata
-        $table->setMetadataValues(DataCollection::getDataRowMetadata($blobRow));
+        $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata));
 
         if ($this->expandDataTable) {
             $table->enableRecursiveFilters();
@@ -242,12 +354,12 @@ class DataTableFactory
      * @param array $blobRow
      * @return DataTable\Map
      */
-    private function makeIndexedByRecordNameDataTable($blobRow)
+    private function makeIndexedByRecordNameDataTable($blobRow, $keyMetadata)
     {
         $table = new DataTable\Map();
         $table->setKeyName('recordName');
 
-        $tableMetadata = DataCollection::getDataRowMetadata($blobRow);
+        $tableMetadata = array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata);
 
         foreach ($blobRow as $name => $blob) {
             $newTable = DataTable::fromSerializedArray($blob);
@@ -267,23 +379,26 @@ class DataTableFactory
      * @param array $keyMetadata The metadata to add to the table when it's created.
      * @return DataTable\Map
      */
-    private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array())
+    private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata)
     {
-        $resultIndexLabel = reset($resultIndices);
+        $result = new DataTable\Map();
+        $result->setKeyName(reset($resultIndices));
         $resultIndex = key($resultIndices);
 
         array_shift($resultIndices);
 
-        $result = new DataTable\Map();
-        $result->setKeyName($resultIndexLabel);
+        $hasIndices = !empty($resultIndices);
 
         foreach ($index as $label => $value) {
-            $keyMetadata[$resultIndex] = $label;
-
-            if (empty($resultIndices)) {
-                $newTable = $this->createDataTable($value, $keyMetadata);
-            } else {
+            if ($resultIndex === DataTableFactory::TABLE_METADATA_SITE_INDEX) {
+                $keyMetadata[$resultIndex] = new Site($label);
+            } elseif ($resultIndex === DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
+                $keyMetadata[$resultIndex] = $this->periods[$label];
+            }
+            if ($hasIndices) {
                 $newTable = $this->createDataTableMapFromIndex($value, $resultIndices, $keyMetadata);
+            } else {
+                $newTable = $this->createDataTable($value, $keyMetadata);
             }
 
             $result->addTable($newTable, $this->prettifyIndexLabel($resultIndex, $label));
@@ -302,11 +417,11 @@ class DataTableFactory
     private function createDataTable($data, $keyMetadata)
     {
         if ($this->dataType == 'blob') {
-            $result = $this->makeFromBlobRow($data);
+            $result = $this->makeFromBlobRow($data, $keyMetadata);
         } else {
-            $result = $this->makeFromMetricsArray($data);
+            $result = $this->makeFromMetricsArray($data, $keyMetadata);
         }
-        $this->setTableMetadata($keyMetadata, $result);
+
         return $result;
     }
 
@@ -359,17 +474,12 @@ class DataTableFactory
         }
     }
 
-    /**
-     * Converts site IDs and period string ranges into Site instances and
-     * Period instances in DataTable metadata.
-     */
-    private function transformMetadata(DataTableInterface $table)
+    private function getDefaultMetadata()
     {
-        $periods = $this->periods;
-        $table->filter(function (DataTable $table) use ($periods) {
-            $table->setMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX, new Site($table->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX)));
-            $table->setMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX, $periods[$table->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)]);
-        });
+        return array(
+            DataTableFactory::TABLE_METADATA_SITE_INDEX => new Site(reset($this->sitesId)),
+            DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods),
+        );
     }
 
     /**
@@ -387,39 +497,16 @@ class DataTableFactory
         return $label;
     }
 
-    /**
-     * @param $keyMetadata
-     * @param $result
-     */
-    private function setTableMetadata($keyMetadata, DataTableInterface $result)
-    {
-        if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX])) {
-            $keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX] = reset($this->sitesId);
-        }
-
-        if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX])) {
-            reset($this->periods);
-            $keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX] = key($this->periods);
-        }
-
-        // Note: $result can be a DataTable\Map
-        $result->filter(function (DataTable $table) use ($keyMetadata) {
-            foreach ($keyMetadata as $name => $value) {
-                $table->setMetadata($name, $value);
-            }
-        });
-    }
-
     /**
      * @param $data
      * @return DataTable\Simple
      */
-    private function makeFromMetricsArray($data)
+    private function makeFromMetricsArray($data, $keyMetadata)
     {
         $table = new DataTable\Simple();
 
         if (!empty($data)) {
-            $table->setAllTableMetadata(DataCollection::getDataRowMetadata($data));
+            $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($data), $keyMetadata));
 
             DataCollection::removeMetadataFromDataRow($data);
 
@@ -431,11 +518,13 @@ class DataTableFactory
             // w/o this code, an empty array would be created, and other parts of Piwik
             // would break.
             if (count($this->dataNames) == 1
-                && $this->dataType == 'numeric'
+                && $this->isNumericDataType()
             ) {
                 $name = reset($this->dataNames);
                 $table->addRow(new Row(array(Row::COLUMNS => array($name => 0))));
             }
+
+            $table->setAllTableMetadata($keyMetadata);
         }
 
         $result = $table;
diff --git a/core/DataAccess/ArchiveSelector.php b/core/DataAccess/ArchiveSelector.php
index faeef35b88fe40cab5659093749c7eba5ef12dd3..a500ef66e6092f20ab6595ec5c1a86ebc2ab0d37 100644
--- a/core/DataAccess/ArchiveSelector.php
+++ b/core/DataAccess/ArchiveSelector.php
@@ -153,9 +153,13 @@ class ArchiveSelector
             throw new \Exception("Website IDs could not be read from the request, ie. idSite=");
         }
 
+        foreach ($siteIds as $index => $siteId) {
+            $siteIds[$index] = (int) $siteId;
+        }
+
         $getArchiveIdsSql = "SELECT idsite, name, date1, date2, MAX(idarchive) as idarchive
                                FROM %s
-                              WHERE idsite IN (" . Common::getSqlStringFieldsArray($siteIds) . ")
+                              WHERE idsite IN (" . implode(',', $siteIds) . ")
                                 AND " . self::getNameCondition($plugins, $segment) . "
                                 AND %s
                            GROUP BY idsite, date1, date2";
@@ -172,7 +176,7 @@ class ArchiveSelector
         foreach ($monthToPeriods as $table => $periods) {
             $firstPeriod = reset($periods);
 
-            $bind = array_values($siteIds);
+            $bind = array();
 
             if ($firstPeriod instanceof Range) {
                 $dateCondition = "period = ? AND date1 = ? AND date2 = ?";
@@ -203,12 +207,10 @@ class ArchiveSelector
 
             // get the archive IDs
             foreach ($archiveIds as $row) {
-                $archiveName = $row['name'];
-
                 //FIXMEA duplicate with Archive.php
-                $dateStr = $row['date1'] . "," . $row['date2'];
+                $dateStr = $row['date1'] . ',' . $row['date2'];
 
-                $result[$archiveName][$dateStr][] = $row['idarchive'];
+                $result[$row['name']][$dateStr][] = $row['idarchive'];
             }
         }
 
diff --git a/core/DataTable.php b/core/DataTable.php
index a9be60d98ce070e5da2b5bab2b6e10d04645381b..d67e0984cb012a94b3e0d509cffd7c9abc24e08e 100644
--- a/core/DataTable.php
+++ b/core/DataTable.php
@@ -1133,12 +1133,12 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
         }
 
         if (is_null($limit)) {
-            $spliced = array_splice($this->rows, $offset);
+            array_splice($this->rows, $offset);
         } else {
-            $spliced = array_splice($this->rows, $offset, $limit);
+            array_splice($this->rows, $offset, $limit);
         }
-        $countDeleted = count($spliced);
-        return $countDeleted;
+
+        return $count - $this->getRowsCount();
     }
 
     /**
@@ -1415,11 +1415,10 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
             return;
         }
 
-        // we define an exception we may throw if at one point we notice that we cannot handle the data structure
-        $e = new Exception(" Data structure returned is not convertible in the requested format." .
+        $exceptionText = " Data structure returned is not convertible in the requested format." .
             " Try to call this method with the parameters '&format=original&serialize=1'" .
             "; you will get the original php data structure serialized." .
-            " The data structure looks like this: \n \$data = " . var_export($array, true) . "; ");
+            " The data structure looks like this: \n \$data = %s; ";
 
         // first pass to see if the array has the structure
         // array(col1_name => val1, col2_name => val2, etc.)
@@ -1460,12 +1459,13 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
                 // it cannot be lost during the conversion. Because we are not able to handle properly
                 // this key, we throw an explicit exception.
                 if (is_string($key)) {
-                    throw $e;
+                    // we define an exception we may throw if at one point we notice that we cannot handle the data structure
+                    throw new Exception(sprintf($exceptionText, var_export($array, true)));
                 }
                 // if any of the sub elements of row is an array we cannot handle this data structure...
                 foreach ($row as $subRow) {
                     if (is_array($subRow)) {
-                        throw $e;
+                        throw new Exception(sprintf($exceptionText, var_export($array, true)));
                     }
                 }
                 $row = new Row(array(Row::COLUMNS => $row));
diff --git a/core/DataTable/Filter/ColumnCallbackAddMetadata.php b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
index 805a5db5ab8646efd12b1dadc3c990cb91b027b8..cb2af14262da0636c78524c3273f602dac451dca 100644
--- a/core/DataTable/Filter/ColumnCallbackAddMetadata.php
+++ b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
@@ -45,10 +45,6 @@ class ColumnCallbackAddMetadata extends BaseFilter
     {
         parent::__construct($table);
 
-        if (!is_array($columnsToRead)) {
-            $columnsToRead = array($columnsToRead);
-        }
-
         $this->columnsToRead      = $columnsToRead;
         $this->functionToApply    = $functionToApply;
         $this->functionParameters = $functionParameters;
@@ -69,23 +65,45 @@ class ColumnCallbackAddMetadata extends BaseFilter
             $rows = $table->getRowsWithoutSummaryRow();
         }
 
-        foreach ($rows as $key => $row) {
+        if (is_array($this->columnsToRead)) {
 
-            $parameters = array();
-            foreach ($this->columnsToRead as $columnsToRead) {
-                $parameters[] = $row->getColumn($columnsToRead);
-            }
+            // I know it is duplicated code, I did extract the inner part into a method but when executing this on 100k
+            // rows it actually makes a difference having this code duplicated instead of having an inner function call
+            foreach ($rows as $key => $row) {
 
-            if (!is_null($this->functionParameters)) {
-                $parameters = array_merge($parameters, $this->functionParameters);
-            }
-            if (!is_null($this->functionToApply)) {
-                $newValue = call_user_func_array($this->functionToApply, $parameters);
-            } else {
-                $newValue = $parameters[0];
+                $parameters = array();
+                foreach ($this->columnsToRead as $columnsToRead) {
+                    $parameters[] = $row->getColumn($columnsToRead);
+                }
+
+                if (!is_null($this->functionParameters)) {
+                    $parameters = array_merge($parameters, $this->functionParameters);
+                }
+                if (!is_null($this->functionToApply)) {
+                    $newValue = call_user_func_array($this->functionToApply, $parameters);
+                } else {
+                    $newValue = $parameters[0];
+                }
+                if ($newValue !== false) {
+                    $row->setMetadata($this->metadataToAdd, $newValue);
+                }
             }
-            if ($newValue !== false) {
-                $row->addMetadata($this->metadataToAdd, $newValue);
+        } else {
+            foreach ($rows as $key => $row) {
+
+                $parameters = array($row->getColumn($this->columnsToRead));
+
+                if (!is_null($this->functionParameters)) {
+                    $parameters = array_merge($parameters, $this->functionParameters);
+                }
+                if (!is_null($this->functionToApply)) {
+                    $newValue = call_user_func_array($this->functionToApply, $parameters);
+                } else {
+                    $newValue = $parameters[0];
+                }
+                if ($newValue !== false) {
+                    $row->setMetadata($this->metadataToAdd, $newValue);
+                }
             }
         }
     }
diff --git a/core/DataTable/Manager.php b/core/DataTable/Manager.php
index 76444a074f43855d0c972cefff36516ce6106573..c823293c769089347d3e1cf6c859f2c549614449 100644
--- a/core/DataTable/Manager.php
+++ b/core/DataTable/Manager.php
@@ -24,7 +24,7 @@ class Manager extends \ArrayObject
      * Id of the next inserted table id in the Manager
      * @var int
      */
-    protected $nextTableId = 1;
+    protected $nextTableId = 0;
 
     private static $instance;
 
@@ -45,9 +45,9 @@ class Manager extends \ArrayObject
      */
     public function addTable($table)
     {
-        $this[$this->nextTableId] = $table;
         $this->nextTableId++;
-        return $this->nextTableId - 1;
+        $this[$this->nextTableId] = $table;
+        return $this->nextTableId;
     }
 
     /**
@@ -75,7 +75,7 @@ class Manager extends \ArrayObject
      */
     public function getMostRecentTableId()
     {
-        return $this->nextTableId - 1;
+        return $this->nextTableId;
     }
 
     /**
@@ -91,7 +91,7 @@ class Manager extends \ArrayObject
 
         if ($deleteWhenIdTableGreaterThan == 0) {
             $this->exchangeArray(array());
-            $this->nextTableId = 1;
+            $this->nextTableId = 0;
         }
     }
 
diff --git a/core/Site.php b/core/Site.php
index 1a828fce4784cd5d79aecc48a9d6c0152701b973..7b9ed154fa1f1e2fbe3e04287d4b1e6abfff42c0 100644
--- a/core/Site.php
+++ b/core/Site.php
@@ -78,11 +78,35 @@ class Site
      */
     public static function setSites($sites)
     {
+        self::triggerSetSitesEvent($sites);
+
         foreach($sites as $idsite => $site) {
             self::setSite($idsite, $site);
         }
     }
 
+    private static function triggerSetSitesEvent(&$sites)
+    {
+        /**
+         * Triggered so plugins can modify website entities without modifying the database.
+         *
+         * This event should **not** be used to add data that is expensive to compute. If you
+         * need to make HTTP requests or query the database for more information, this is not
+         * the place to do it.
+         *
+         * **Example**
+         *
+         *     Piwik::addAction('Site.setSites', function (&$sites) {
+         *         foreach ($sites as &$site) {
+         *             $site['name'] .= " (original)";
+         *         }
+         *     });
+         *
+         * @param array $sites An array of website entities. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites)
+         */
+        Piwik::postEvent('Site.setSites', array(&$sites));
+    }
+
     /**
      * Sets a site information in memory (statically cached).
      *
@@ -99,24 +123,6 @@ class Site
             throw new UnexpectedWebsiteFoundException("An unexpected website was found, check idSite in the request.");
         }
 
-        /**
-         * Triggered so plugins can modify website entities without modifying the database.
-         *
-         * This event should **not** be used to add data that is expensive to compute. If you
-         * need to make HTTP requests or query the database for more information, this is not
-         * the place to do it.
-         *
-         * **Example**
-         *
-         *     Piwik::addAction('Site.setSite', function ($idSite, &$info) {
-         *         $info['name'] .= " (original)";
-         *     });
-         *
-         * @param int $idSite The ID of the website entity that will be modified.
-         * @param array $infoSite The website entity. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites)
-         */
-        Piwik::postEvent('Site.setSite', array($idSite, &$infoSite));
-
         self::$infoSites[$idSite] = $infoSite;
     }
 
@@ -132,6 +138,8 @@ class Site
      */
     public static function setSitesFromArray($sites)
     {
+        self::triggerSetSitesEvent($sites);
+
         foreach ($sites as $site) {
             $idSite = null;
             if (!empty($site['idsite'])) {
@@ -410,21 +418,17 @@ class Site
      * site with the specified ID.
      *
      * @param int $idsite The ID of the site whose data is being accessed.
-     * @param bool|string $field The name of the field to get.
-     * @return array|string
+     * @param string $field The name of the field to get.
+     * @return string
      */
-    protected static function getFor($idsite, $field = false)
+    protected static function getFor($idsite, $field)
     {
-        $idsite = (int)$idsite;
-
         if (!isset(self::$infoSites[$idsite])) {
             $site = API::getInstance()->getSiteFromId($idsite);
             self::setSite($idsite, $site);
         }
-        if ($field) {
-            return self::$infoSites[$idsite][$field];
-        }
-        return self::$infoSites[$idsite];
+
+        return self::$infoSites[$idsite][$field];
     }
 
     /**
@@ -440,9 +444,16 @@ class Site
     /**
      * @ignore
      */
-    public static function getSite($id)
+    public static function getSite($idsite)
     {
-        return self::getFor($id);
+        $idsite = (int)$idsite;
+
+        if (!isset(self::$infoSites[$idsite])) {
+            $site = API::getInstance()->getSiteFromId($idsite);
+            self::setSite($idsite, $site);
+        }
+
+        return self::$infoSites[$idsite];
     }
 
     /**
diff --git a/lang/en.json b/lang/en.json
index ed98e18273ce1b85f302c3ce91d1779ea599247e..81b093925be790b8e0275b4ac094e8f3b1d93d50 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -31,7 +31,7 @@
         "ChooseWebsite": "Choose website",
         "ClickHere": "Click here for more information.",
         "ClickToChangePeriod": "Click again to change the period.",
-        "Close": "Close",
+        "ClickToSearch": "Click to search",
         "ColumnActionsPerVisit": "Actions per Visit",
         "ColumnActionsPerVisitDocumentation": "The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.",
         "ColumnAverageGenerationTime": "Avg. generation time",
diff --git a/plugins/MultiSites/.gitignore b/plugins/MultiSites/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c8c9480010db494b8cf54ca8f9561e7a14ff51ea
--- /dev/null
+++ b/plugins/MultiSites/.gitignore
@@ -0,0 +1 @@
+tests/System/processed/*xml
\ No newline at end of file
diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php
index c85dc94024e91d44d3880919076579fff150b76f..df92877416a5f3713778251299851c54b1babb93 100755
--- a/plugins/MultiSites/API.php
+++ b/plugins/MultiSites/API.php
@@ -14,6 +14,7 @@ use Piwik\Archive;
 use Piwik\Common;
 use Piwik\Container\StaticContainer;
 use Piwik\DataTable;
+use Piwik\DataTable\Row;
 use Piwik\Period\Range;
 use Piwik\Piwik;
 use Piwik\Plugins\Goals\Archiver;
@@ -81,9 +82,10 @@ class API extends \Piwik\Plugin\API
      *                                        Only used when a scheduled task is running
      * @param bool|string $enhanced When true, return additional goal & ecommerce metrics
      * @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites
+     * @param array $showColumns If specified, only the requested columns will be fetched
      * @return DataTable
      */
-    public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false)
+    public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false, $showColumns = array())
     {
         Piwik::checkUserHasSomeViewAccess();
 
@@ -100,7 +102,8 @@ class API extends \Piwik\Plugin\API
             $segment,
             $_restrictSitesToLogin,
             $enhanced,
-            $multipleWebsitesRequested = true
+            $multipleWebsitesRequested = true,
+            $showColumns
         );
     }
 
@@ -185,7 +188,8 @@ class API extends \Piwik\Plugin\API
             $segment,
             $_restrictSitesToLogin,
             $enhanced,
-            $multipleWebsitesRequested = false
+            $multipleWebsitesRequested = false,
+            $showColumns = array()
         );
     }
 
@@ -197,7 +201,7 @@ class API extends \Piwik\Plugin\API
         return $sites;
     }
 
-    private function buildDataTable($sitesToProblablyAdd, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested)
+    private function buildDataTable($sitesToProblablyAdd, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested, $showColumns)
     {
         $idSites = array();
         if (!empty($sitesToProblablyAdd)) {
@@ -221,6 +225,10 @@ class API extends \Piwik\Plugin\API
         $apiECommerceMetrics = array();
         $apiMetrics = API::getApiMetrics($enhanced);
         foreach ($apiMetrics as $metricName => $metricSettings) {
+            if (!empty($showColumns) && !in_array($metricName, $showColumns)) {
+                unset($apiMetrics[$metricName]);
+                continue;
+            }
             $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY];
             $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName;
 
@@ -229,25 +237,11 @@ class API extends \Piwik\Plugin\API
             }
         }
 
-        // get the data
-        // $dataTable instanceOf Set
-        $dataTable = $archive->getDataTableFromNumeric($fieldsToGet);
-
-        if ($multipleWebsitesRequested && count($idSites) === 1 && Range::isMultiplePeriod($date, $period)) {
-        } else {
-            $dataTable = $this->mergeDataTableMapAndPopulateLabel($idSites, $multipleWebsitesRequested, $dataTable);
-        }
-
-        if ($dataTable instanceof DataTable\Map) {
-            foreach ($dataTable->getDataTables() as $table) {
-                $this->addMissingWebsites($table, $fieldsToGet, $sitesToProblablyAdd);
-            }
-        } else {
-            $this->addMissingWebsites($dataTable, $fieldsToGet, $sitesToProblablyAdd);
-        }
+        $dataTable = $archive->getDataTableFromNumericAndMergeChildren($fieldsToGet);
 
-        // calculate total visits/actions/revenue
-        $this->setMetricsTotalsMetadata($dataTable, $apiMetrics);
+        $this->populateLabel($dataTable);
+        $totalMetrics = $this->preformatApiMetricsForTotalsCalculation($apiMetrics);
+        $this->setMetricsTotalsMetadata($dataTable, $totalMetrics);
 
         // if the period isn't a range & a lastN/previousN date isn't used, we get the same
         // data for the last period to show the evolution of visits/actions/revenue
@@ -263,23 +257,16 @@ class API extends \Piwik\Plugin\API
             }
 
             $pastArchive = Archive::build($idSites, $period, $strLastDate, $segment, $_restrictSitesToLogin);
+            $pastData = $pastArchive->getDataTableFromNumericAndMergeChildren($fieldsToGet);
 
-            $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet);
-
-            if ($multipleWebsitesRequested && count($idSites) === 1 && Range::isMultiplePeriod($date, $period)) {
-
-            } else {
-                $pastData = $this->mergeDataTableMapAndPopulateLabel($idSites, $multipleWebsitesRequested, $pastData);
-            }
-
-            // use past data to calculate evolution percentages
+            $this->populateLabel($pastData); // labels are needed to calculate evolution
             $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics);
+            $this->setPastTotalVisitsMetadata($dataTable, $pastData);
         }
 
-        // move the site id to a metadata column
-        $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'group', array('\Piwik\Site', 'getGroupFor'), array()));
-        $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'main_url', array('\Piwik\Site', 'getMainUrlFor'), array()));
-        $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite'));
+        // move the site id to a metadata column 
+        $dataTable->queueFilter('MetadataCallbackAddMetadata', array('idsite', 'group', array('\Piwik\Site', 'getGroupFor'), array()));
+        $dataTable->queueFilter('MetadataCallbackAddMetadata', array('idsite', 'main_url', array('\Piwik\Site', 'getMainUrlFor'), array()));
 
         // set the label of each row to the site name
         if ($multipleWebsitesRequested) {
@@ -420,6 +407,17 @@ class API extends \Piwik\Plugin\API
         return $metrics;
     }
 
+    private function preformatApiMetricsForTotalsCalculation($apiMetrics)
+    {
+        $metrics = array();
+        foreach ($apiMetrics as $label => $metricsInfo) {
+            $totalMetadataName = self::getTotalMetadataName($label);
+            $metrics[$totalMetadataName] = $metricsInfo[self::METRIC_RECORD_NAME_KEY];
+        }
+
+        return $metrics;
+    }
+
     /**
      * Sets the total visits, actions & revenue for a DataTable returned by
      * $this->buildDataTable.
@@ -436,92 +434,63 @@ class API extends \Piwik\Plugin\API
             }
         } else {
             $totals = array();
-            foreach ($apiMetrics as $label => $metricInfo) {
-                $totalMetadataName = self::getTotalMetadataName($label);
-                $totals[$totalMetadataName] = 0;
+            foreach ($apiMetrics as $label => $recordName) {
+                $totals[$label] = 0;
             }
 
             foreach ($dataTable->getRows() as $row) {
-                foreach ($apiMetrics as $label => $metricInfo) {
-                    $totalMetadataName = self::getTotalMetadataName($label);
-                    $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]);
+                foreach ($apiMetrics as $totalMetadataName => $recordName) {
+                    $totals[$totalMetadataName] += $row->getColumn($recordName);
                 }
             }
 
-            foreach ($totals as $name => $value) {
-                $dataTable->setMetadata($name, $value);
-            }
+            $dataTable->setMetadataValues($totals);
         }
     }
 
-    private static function getTotalMetadataName($name)
-    {
-        return 'total_' . $name;
-    }
-
-    private static function getLastPeriodMetadataName($name)
-    {
-        return 'last_period_' . $name;
-    }
-
     /**
-     * @param DataTable|DataTable\Map $dataTable
-     * @param $fieldsToGet
-     * @param $sitesToProblablyAdd
+     * Sets the number of total visits in tha pastTable on the dataTable as metadata.
+     *
+     * @param DataTable $dataTable
+     * @param DataTable $pastTable
      */
-    private function addMissingWebsites($dataTable, $fieldsToGet, $sitesToProblablyAdd)
+    private function setPastTotalVisitsMetadata($dataTable, $pastTable)
     {
-        $siteIdsInDataTable = array();
-        foreach ($dataTable->getRows() as $row) {
-            /** @var DataTable\Row $row */
-            $siteIdsInDataTable[] = $row->getColumn('label');
-        }
+        if ($pastTable instanceof DataTable) {
+            $total  = 0;
+            $metric = 'nb_visits';
 
-        foreach ($sitesToProblablyAdd as $site) {
-            if (!in_array($site['idsite'], $siteIdsInDataTable)) {
-                $siteRow = array_combine($fieldsToGet, array_pad(array(), count($fieldsToGet), 0));
-                $siteRow['label'] = (int) $site['idsite'];
-                $dataTable->addRowFromSimpleArray($siteRow);
+            foreach ($pastTable->getRows() as $row) {
+                $total += $row->getColumn($metric);
             }
+
+            $dataTable->setMetadata(self::getTotalMetadataName($metric . '_lastdate'), $total);
         }
     }
 
-    private function removeEcommerceRelatedMetricsOnNonEcommercePiwikSites($dataTable, $apiECommerceMetrics)
+    private static function getTotalMetadataName($name)
     {
-        // $dataTableRows instanceOf Row[]
-        $dataTableRows = $dataTable->getRows();
-
-        foreach ($dataTableRows as $dataTableRow) {
-            $siteId = $dataTableRow->getColumn('label');
-            if (!Site::isEcommerceEnabledFor($siteId)) {
-                foreach ($apiECommerceMetrics as $metricSettings) {
-                    $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]);
-                    $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]);
-                }
-            }
-        }
+        return 'total_' . $name;
     }
 
-    private function mergeDataTableMapAndPopulateLabel($idSitesOrIdSite, $multipleWebsitesRequested, $dataTable)
+    private static function getLastPeriodMetadataName($name)
     {
-        // get rid of the DataTable\Map that is created by the IndexedBySite archive type
-        if ($dataTable instanceof DataTable\Map && $multipleWebsitesRequested) {
-
-            return $dataTable->mergeChildren();
-
-        } else {
-
-            if (!$dataTable instanceof DataTable\Map && $dataTable->getRowsCount() > 0) {
-
-                $firstSite = is_array($idSitesOrIdSite) ? reset($idSitesOrIdSite) : $idSitesOrIdSite;
-
-                $firstDataTableRow = $dataTable->getFirstRow();
+        return 'last_period_' . $name;
+    }
 
-                $firstDataTableRow->setColumn('label', $firstSite);
+    private function populateLabel($dataTable)
+    {
+        $dataTable->filter(function (DataTable $table) {
+            foreach ($table->getRowsWithoutSummaryRow() as $row) {
+                $row->setColumn('label', $row->getMetadata('idsite'));
             }
-        }
-
-        return $dataTable;
+        });
+        // make sure label column is always first column
+        $dataTable->queueFilter(function (DataTable $table) {
+            foreach ($table->getRowsWithoutSummaryRow() as $row) {
+                $row->setColumns(array_merge(array('label' => $row->getColumn('label')), $row->getColumns()));
+            }
+        });
     }
 
     private function isEcommerceEvolutionMetric($metricSettings)
@@ -532,4 +501,4 @@ class API extends \Piwik\Plugin\API
             self::ECOMMERCE_REVENUE_METRIC . '_evolution'
         ));
     }
-}
\ No newline at end of file
+}
diff --git a/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php b/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
index cee88af3f5fd16a775aafe876d34642ba69c2c26..e43504154e4630acc2fd7f57f1941eb683d59244 100644
--- a/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
+++ b/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
@@ -36,13 +36,13 @@ class EcommerceOnlyEvolutionMetric extends EvolutionMetric
 
         // if the site this is for doesn't support ecommerce & this is for the revenue_evolution column,
         // we don't add the new column
-        if (($currentValue === false
-            || !$this->isRevenueEvolution)
-            && !Site::isEcommerceEnabledFor($row->getColumn('label'))
-        ) {
-            $row->deleteColumn($columnName);
+        if ($currentValue === false || !$this->isRevenueEvolution) {
+            $idSite = $row->getMetadata('idsite');
+            if (!$idSite || !Site::isEcommerceEnabledFor($idSite)) {
+                $row->deleteColumn($columnName);
 
-            return false;
+                return false;
+            }
         }
 
         return parent::compute($row);
diff --git a/plugins/MultiSites/Controller.php b/plugins/MultiSites/Controller.php
index 647ae147dcb9bbb6712a6e15028dddd458880018..a59e8a4cdb1dae149bb2aaf5f28487ebe3e4ac22 100644
--- a/plugins/MultiSites/Controller.php
+++ b/plugins/MultiSites/Controller.php
@@ -8,10 +8,15 @@
  */
 namespace Piwik\Plugins\MultiSites;
 
+use Piwik\API\Request;
+use Piwik\API\ResponseBuilder;
 use Piwik\Common;
 use Piwik\Config;
 use Piwik\Date;
 use Piwik\Period;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\DataTable\Row\DataTableSummaryRow;
 use Piwik\Piwik;
 use Piwik\Translation\Translator;
 use Piwik\View;
@@ -40,6 +45,34 @@ class Controller extends \Piwik\Plugin\Controller
         return $this->getSitesInfo($isWidgetized = true);
     }
 
+    public function getAllWithGroups()
+    {
+        Piwik::checkUserHasSomeViewAccess();
+
+        $period  = Common::getRequestVar('period', null, 'string');
+        $date    = Common::getRequestVar('date', null, 'string');
+        $segment = Common::getRequestVar('segment', false, 'string');
+        $pattern = Common::getRequestVar('pattern', '', 'string');
+        $limit   = Common::getRequestVar('filter_limit', 0, 'int');
+        $segment = $segment ?: false;
+        $request = $_GET + $_POST;
+
+        $dashboard = new Dashboard($period, $date, $segment);
+
+        if ($pattern !== '') {
+            $dashboard->search(strtolower($pattern));
+        }
+
+        $response = array(
+            'numSites' => $dashboard->getNumSites(),
+            'totals'   => $dashboard->getTotals(),
+            'lastDate' => $dashboard->getLastDate(),
+            'sites'    => $dashboard->getSites($request, $limit)
+        );
+
+        return json_encode($response);
+    }
+
     public function getSitesInfo($isWidgetized = false)
     {
         Piwik::checkUserHasSomeViewAccess();
diff --git a/plugins/MultiSites/Dashboard.php b/plugins/MultiSites/Dashboard.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e973a8be61460ee779eb14c151ffd669da70138
--- /dev/null
+++ b/plugins/MultiSites/Dashboard.php
@@ -0,0 +1,294 @@
+<?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;
+
+use Piwik\API\ResponseBuilder;
+use Piwik\Config;
+use Piwik\Metrics\Formatter;
+use Piwik\Period;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\DataTable\Row\DataTableSummaryRow;
+use Piwik\Plugins\API\ProcessedReport;
+use Piwik\Site;
+use Piwik\View;
+
+class Dashboard
+{
+    /** @var DataTable */
+    private $sitesByGroup;
+
+    /**
+     * @var int
+     */
+    private $numSites = 0;
+
+    /**
+     * @param string $period
+     * @param string $date
+     * @param string|false $segment
+     */
+    public function __construct($period, $date, $segment)
+    {
+        $sites = API::getInstance()->getAll($period, $date, $segment, $_restrictSitesToLogin = false,
+                                            $enhanced = true, $searchTerm = false,
+                                            $showColumns = array('nb_visits', 'nb_pageviews', 'revenue'));
+        $sites->deleteRow(DataTable::ID_SUMMARY_ROW);
+        $sites->filter(function (DataTable $table) {
+            foreach ($table->getRows() as $row) {
+                $idSite = $row->getColumn('label');
+                $site   = Site::getSite($idSite);
+                // we cannot queue label and group as we might need them for search!
+                $row->setColumn('label', $site['name']);
+                $row->setMetadata('group', $site['group']);
+            }
+        });
+
+        $this->setSitesTable($sites);
+    }
+
+    public function setSitesTable(DataTable $sites)
+    {
+        $this->numSites     = $sites->getRowsCount();
+        $this->sitesByGroup = $this->moveSitesHavingAGroupIntoSubtables($sites);
+    }
+
+    public function getSites($request, $limit)
+    {
+        $request['filter_limit'] = $limit;
+
+        $sitesExpanded = $this->convertDataTableToArrayAndApplyFilters($this->sitesByGroup, $request);
+        $sitesFlat     = $this->makeSitesFlat($sitesExpanded);
+        $sitesFlat     = $this->applyLimitIfNeeded($sitesFlat, $limit);
+        $sitesFlat     = $this->enrichValues($sitesFlat);
+
+        return $sitesFlat;
+    }
+
+    public function getTotals()
+    {
+        return array(
+            'nb_pageviews'       => $this->sitesByGroup->getMetadata('total_nb_pageviews'),
+            'nb_visits'          => $this->sitesByGroup->getMetadata('total_nb_visits'),
+            'revenue'            => $this->sitesByGroup->getMetadata('total_revenue'),
+            'nb_visits_lastdate' => $this->sitesByGroup->getMetadata('total_nb_visits_lastdate') ? : 0,
+        );
+    }
+
+    public function getNumSites()
+    {
+        return $this->numSites;
+    }
+
+    public function search($pattern)
+    {
+        $this->nestedSearch($this->sitesByGroup, $pattern);
+
+        $this->numSites = $this->sitesByGroup->getRowsCountRecursive();
+    }
+
+    private function nestedSearch(DataTable $sitesByGroup, $pattern)
+    {
+        foreach ($sitesByGroup->getRows() as $index => $site) {
+
+            $label = strtolower($site->getColumn('label'));
+            $labelMatches = false !== strpos($label, $pattern);
+
+            if ($site->getMetadata('isGroup')) {
+                $subtable = $site->getSubtable();
+                $this->nestedSearch($subtable, $pattern);
+
+                if (!$labelMatches && !$subtable->getRowsCount()) {
+                    // we keep the group if at least one site within the group matches the pattern
+                    $sitesByGroup->deleteRow($index);
+                }
+
+            } elseif (!$labelMatches) {
+                $group = $site->getMetadata('group');
+
+                if (!$group || false === strpos(strtolower($group), $pattern)) {
+                    $sitesByGroup->deleteRow($index);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getLastDate()
+    {
+        $lastPeriod = $this->sitesByGroup->getMetadata('last_period_date');
+
+        if (!empty($lastPeriod)) {
+            $lastPeriod = $lastPeriod->toString();
+        } else {
+            $lastPeriod = '';
+        }
+
+        return $lastPeriod;
+    }
+
+    private function convertDataTableToArrayAndApplyFilters(DataTable $table, $request)
+    {
+        $request['serialize'] = 0;
+        $request['expanded'] = 1;
+        $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';
+        }
+
+        $responseBuilder = new ResponseBuilder('php', $request);
+        $rows = $responseBuilder->getResponse($table, 'MultiSites', 'getAll');
+
+        return $rows;
+    }
+
+    private function moveSitesHavingAGroupIntoSubtables(DataTable $sites)
+    {
+        /** @var DataTableSummaryRow[] $groups */
+        $groups = array();
+
+        $sitesByGroup = $this->makeCloneOfDataTableSites($sites);
+        $sitesByGroup->enableRecursiveFilters(); // we need to make sure filters get applied to subtables (groups)
+
+        foreach ($sites->getRows() as $site) {
+
+            $group = $site->getMetadata('group');
+
+            if (!empty($group) && !array_key_exists($group, $groups)) {
+                $row = new DataTableSummaryRow();
+                $row->setColumn('label', $group);
+                $row->setMetadata('isGroup', 1);
+                $row->setSubtable($this->createGroupSubtable($sites));
+                $sitesByGroup->addRow($row);
+
+                $groups[$group] = $row;
+            }
+
+            if (!empty($group)) {
+                $groups[$group]->getSubtable()->addRow($site);
+            } else {
+                $sitesByGroup->addRow($site);
+            }
+        }
+
+        foreach ($groups as $group) {
+            // we need to recalculate as long as all rows are there, as soon as some rows are removed
+            // we can no longer recalculate the correct value. We might even calculate values for groups
+            // that are not returned. If this becomes a problem we need to keep a copy of this to recalculate
+            // only actual returned groups.
+            $group->recalculate();
+        }
+        
+        return $sitesByGroup;
+    }
+
+    private function createGroupSubtable(DataTable $sites)
+    {
+        $table = new DataTable();
+        $processedMetrics = $sites->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
+        $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $processedMetrics);
+
+        return $table;
+    }
+
+    private function makeCloneOfDataTableSites(DataTable $sites)
+    {
+        $sitesByGroup = $sites->getEmptyClone(true);
+        // we handle them ourselves for faster performance etc. This way we also avoid to apply them twice.
+        $sitesByGroup->disableFilter('ColumnCallbackReplace');
+        $sitesByGroup->disableFilter('MetadataCallbackAddMetadata');
+
+        return $sitesByGroup;
+    }
+
+    /**
+     * Makes sure to not have any subtables anymore.
+     * So if $sites is
+     * array(
+     *    site1
+     *    site2
+     *        subtable => site3
+     *                    site4
+     *                    site5
+     *    site6
+     *    site7
+     * )
+     *
+     * it will return
+     *
+     * array(
+     *    site1
+     *    site2
+     *    site3
+     *    site4
+     *    site5
+     *    site6
+     *    site7
+     * )
+     *
+     * @param $sites
+     * @return array
+     */
+    private function makeSitesFlat($sites)
+    {
+        $flatSites = array();
+
+        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;
+            }
+        }
+
+        return $flatSites;
+    }
+
+    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);
+        }
+
+        return $sites;
+    }
+
+    private function enrichValues($sites)
+    {
+        $formatter = new Formatter();
+
+        foreach ($sites as &$site) {
+            if (!isset($site['idsite'])) {
+                continue;
+            }
+
+            $site['revenue']  = $formatter->getPrettyMoney($site['revenue'], $site['idsite']);
+            $site['main_url'] = Site::getMainUrlFor($site['idsite']);
+        }
+
+        return $sites;
+    }
+}
diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php
index f3f62ecae45a3790ea5a1b052e72c821116087aa..c69f174d8424aeef8d2045663359885745b4f8a7 100644
--- a/plugins/MultiSites/MultiSites.php
+++ b/plugins/MultiSites/MultiSites.php
@@ -68,13 +68,13 @@ class MultiSites extends \Piwik\Plugin
         $translations[] = 'MultiSites_LoadingWebsites';
         $translations[] = 'General_ErrorRequest';
         $translations[] = 'General_Pagination';
+        $translations[] = 'General_ClickToSearch';
     }
 
     public function getJsFiles(&$jsFiles)
     {
         $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js";
         $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.controller.js";
-        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js";
         $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.directive.js";
         $jsFiles[] = "plugins/MultiSites/angularjs/site/site.controller.js";
         $jsFiles[] = "plugins/MultiSites/angularjs/site/site.directive.js";
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js b/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
deleted file mode 100644
index b8f9040e9b6da0179076c8437fc772752b03c8de..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Filters a given list of websites and groups and makes sure only the websites within a given offset and limit are
- * displayed. It also makes sure sites are displayed under the groups. That means it flattens a structure like this:
- *
- * - website1
- * - website2
- * - website3.sites // this is a group
- *    - website4
- *    - website5
- * - website6
- *
- * to the following structure
- * - website1
- * - website2
- * - website3.sites // this is a group
- * - website4
- * - website5
- * - website6
- */
-(function () {
-    angular.module('piwikApp').filter('multiSitesGroupFilter', multiSitesGroupFilter);
-
-    function multiSitesGroupFilter() {
-        return function(websites, from, to) {
-            var offsetEnd = parseInt(from, 10) + parseInt(to, 10);
-            var groups    = {};
-
-            var sites = [];
-            for (var index = 0; index < websites.length; index++) {
-                var website = websites[index];
-
-                sites.push(website);
-                if (website.sites && website.sites.length) {
-                    groups[website.label] = website;
-                    for (var innerIndex = 0; innerIndex < website.sites.length; innerIndex++) {
-                        sites.push(website.sites[innerIndex]);
-                    }
-                }
-
-                if (sites.length >= offsetEnd) {
-                    break;
-                }
-            }
-
-            // if the first site is a website having a group, then try to find the related group and prepend it to the list
-            // of sites to make sure we always display the name of the group that belongs to a website.
-            var filteredSites = sites.slice(from, offsetEnd);
-
-            if (filteredSites.length && filteredSites[0] && filteredSites[0].group) {
-                var groupName = filteredSites[0].group;
-                if (groups[groupName]) {
-                    filteredSites.unshift(groups[groupName]);
-                }
-            }
-
-            return filteredSites;
-        };
-    }
-})();
-
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
index f2261aad1c7e291189a31100a05ffd6428261ba6..a213d892f57390311fd13091c992eb084089c3d9 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
@@ -7,30 +7,14 @@
     multisitesDashboardModel.$inject = ['piwikApi', '$filter', '$timeout'];
 
     function multisitesDashboardModel(piwikApi, $filter, $timeout) {
-        /**
-         *
-         * this is the list of all available sites. For performance reason this list is different to model.sites. ngRepeat
-         * won't operate on the whole list this way. The allSites array contains websites and groups in the following
-         * structure
-         *
-         * - website1
-         * - website2
-         * - website3.sites = [ // this is a group
-         *    - website4
-         *    - website5
-         * ]
-         * - website6
-         *
-         * This structure allows us to display the sites within a group directly under the group without big logic and also
-         * allows us to calculate the summary for each group easily
-         */
-        var allSitesByGroup = [];
+
+        var refreshPromise = null;
 
         // those sites are going to be displayed
         var model = {
             sites        : [],
             isLoading    : false,
-            pageSize     : 5,
+            pageSize     : 25,
             currentPage  : 0,
             totalVisits  : '?',
             totalActions : '?',
@@ -38,6 +22,7 @@
             searchTerm   : '',
             lastVisits   : '?',
             lastVisitsDate : '?',
+            numberOfSites : 0,
             updateWebsitesList: updateWebsitesList,
             getNumberOfFilteredSites: getNumberOfFilteredSites,
             getNumberOfPages: getNumberOfPages,
@@ -46,153 +31,52 @@
             previousPage: previousPage,
             nextPage: nextPage,
             searchSite: searchSite,
-            fetchAllSites: fetchAllSites
+            sortBy: sortBy,
+            reverse: true,
+            sortColumn: 'nb_visits',
+            fetchAllSites: fetchAllSites,
+            refreshInterval: 0
         };
 
-        fetchPreviousSummary();
-
         return model;
 
-        // create a new group object which has similar structure than a website
-        function createGroup(name){
-            return {
-                label: name,
-                sites: [],
-                nb_visits: 0,
-                nb_pageviews: 0,
-                revenue: 0,
-                isGroup: true
-            };
-        }
-
-        // create a new group with empty site to make sure we do not change the original group in $allSites
-        function copyGroup(group)
+        function cancelRefereshInterval()
         {
-            return {
-                label: group.label,
-                sites: [],
-                nb_visits: group.nb_visits,
-                nb_pageviews: group.nb_pageviews,
-                revenue: group.revenue,
-                isGroup: true
+            if (refreshPromise) {
+                $timeout.cancel(refreshPromise);
+                refreshPromise = null;
             };
         }
 
         function onError () {
             model.errorLoadingSites = true;
-            model.sites     = [];
-            allSitesByGroup = [];
-        }
-
-        function calculateMetricsForEachGroup(groups)
-        {
-            angular.forEach(groups, function (group) {
-                angular.forEach(group.sites, function (site) {
-                    var revenue = 0;
-                    if (site.revenue) {
-                        revenue = (site.revenue+'').match(/(\d+\.?\d*)/); // convert $ 0.00 to 0.00 or 5€ to 5
-                    }
-
-                    group.nb_visits    += parseInt(site.nb_visits, 10);
-                    group.nb_pageviews += parseInt(site.nb_pageviews, 10);
-                    if (revenue.length) {
-                        group.revenue += parseInt(revenue[0], 10);
-                    }
-                });
-            });
+            model.sites = [];
         }
 
-        function createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, reportMetadata)
-        {
-            var sitesByGroup = [];
-            var groups = {};
+        function updateWebsitesList(report) {
+            if (!report) {
+                onError();
+                return;
+            }
 
-            // we do 3 things (complete site information, create groups, move sites into group) in one step for
-            // performance reason, there can be > 20k sites
-            angular.forEach(allSitesUnordered, function (site, index) {
-                site.idsite   = reportMetadata[index].idsite;
-                site.group    = reportMetadata[index].group;
-                site.main_url = reportMetadata[index].main_url;
-                // casting evolution to int fixes sorting, see: https://github.com/piwik/piwik/issues/4885
+            var allSites = report.sites;
+            angular.forEach(allSites, function (site, index) {
                 site.visits_evolution    = parseInt(site.visits_evolution, 10);
                 site.pageviews_evolution = parseInt(site.pageviews_evolution, 10);
                 site.revenue_evolution   = parseInt(site.revenue_evolution, 10);
-
-                if (site.group) {
-
-                    if (!groups[site.group]) {
-                        var group = createGroup(site.group);
-
-                        groups[site.group] = group;
-                        sitesByGroup.push(group);
-                    }
-
-                    groups[site.group].sites.push(site);
-
-                } else {
-                    sitesByGroup.push(site);
-                }
             });
 
-            calculateMetricsForEachGroup(groups);
-
-            return sitesByGroup;
-        }
-
-        function getSumTotalActions(allSitesUnordered)
-        {
-            var totalActions = 0;
-
-            if (allSitesUnordered && allSitesUnordered.length) {
-                for (var index in allSitesUnordered) {
-                    var site = allSitesUnordered[index];
-                    if (site && site.nb_pageviews) {
-                        totalActions += parseInt(site.nb_pageviews, 10);
-                    }
-                }
-            }
-
-            return totalActions;
-        }
-
-        function updateWebsitesList(processedReport) {
-            if (!processedReport) {
-                onError();
-                return;
-            }
-
-            var allSitesUnordered = processedReport.reportData;
-
-            model.totalActions = getSumTotalActions(allSitesUnordered);
-            model.totalVisits  = processedReport.reportTotal.nb_visits;
-            model.totalRevenue = processedReport.reportTotal.revenue;
-
-            allSitesByGroup = createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, processedReport.reportMetadata);
-
-            if (!allSitesByGroup.length) {
-                return;
-            }
-
-            if (model.searchTerm) {
-                searchSite(model.searchTerm);
-            } else {
-                model.sites = allSitesByGroup;
-            }
+            model.totalActions  = report.totals.nb_pageviews;
+            model.totalVisits   = report.totals.nb_visits;
+            model.totalRevenue  = report.totals.revenue;
+            model.lastVisits    = report.totals.nb_visits_lastdate;
+            model.sites = allSites;
+            model.numberOfSites  = report.numSites;
+            model.lastVisitsDate = report.lastDate;
         }
 
         function getNumberOfFilteredSites () {
-            var numSites = model.sites.length;
-
-            var groupNames = {};
-
-            for (var index = 0; index < model.sites.length; index++) {
-                var site = model.sites[index];
-                if (site && site.isGroup) {
-                    numSites += site.sites.length;
-                }
-            }
-
-            return numSites;
+            return model.numberOfSites; // todo
         }
 
         function getNumberOfPages() {
@@ -214,100 +98,78 @@
 
         function previousPage() {
             model.currentPage = model.currentPage - 1;
+            fetchAllSites();
         }
 
-        function nextPage() {
-            model.currentPage = model.currentPage + 1;
-        }
-
-        function nestedSearch(sitesByGroup, term)
-        {
-            var filteredSites = [];
+        function sortBy(metric) {
+            if (model.sortColumn == metric) {
+                model.reverse = !model.reverse;
+            }
 
-            term = term.toLowerCase();
+            model.sortColumn = metric;
+            fetchAllSites();
+        };
 
-            for (var index in sitesByGroup) {
-                var site = sitesByGroup[index];
-                if (site.isGroup) {
-                    var matchingSites = nestedSearch(site.sites, term);
-                    if (matchingSites.length || (''+site.label).toLowerCase().indexOf(term) > -1) {
-                        var clonedGroup   = copyGroup(site);
-                        clonedGroup.sites = matchingSites;
-                        filteredSites.push(clonedGroup);
-                    }
-                } else if ((''+site.label).toLowerCase().indexOf(term) > -1) {
-                    filteredSites.push(site);
-                } else if (site.group && (''+site.group).toLowerCase().indexOf(term) > -1) {
-                    filteredSites.push(site);
-                }
-            }
+        function previousPage() {
+            model.currentPage = model.currentPage - 1;
+            fetchAllSites();
+        }
 
-            return filteredSites;
+        function nextPage() {
+            model.currentPage = model.currentPage + 1;
+            fetchAllSites();
         }
 
         function searchSite (term) {
             model.searchTerm  = term;
             model.currentPage = 0;
-            model.sites       = nestedSearch(allSitesByGroup, term);
-        }
-
-        function fetchPreviousSummary () {
-            piwikApi.fetch({
-                method: 'API.getLastDate'
-            }).then(function (response) {
-                if (response && response.value) {
-                    return response.value;
-                }
-            }).then(function (lastDate) {
-                if (!lastDate) {
-                    return;
-                }
-
-                model.lastVisitsDate = lastDate;
-
-                return piwikApi.fetch({
-                    method: 'API.getProcessedReport',
-                    apiModule: 'MultiSites',
-                    apiAction: 'getAll',
-                    hideMetricsDoc: '1',
-                    filter_limit: '0',
-                    showColumns: 'label,nb_visits',
-                    enhanced: 1,
-                    date: lastDate
-                });
-            }).then(function (response) {
-                if (response && response.reportTotal) {
-                    model.lastVisits = response.reportTotal.nb_visits;
-                }
-            });
+            fetchAllSites();
         }
 
-        function fetchAllSites(refreshInterval) {
+        function fetchAllSites() {
 
             if (model.isLoading) {
                 piwikApi.abort();
+                cancelRefereshInterval();
             }
 
             model.isLoading = true;
             model.errorLoadingSites = false;
 
-            return piwikApi.fetch({
-                method: 'API.getProcessedReport',
-                apiModule: 'MultiSites',
-                apiAction: 'getAll',
+            var params = {
+                module: 'MultiSites',
+                action: 'getAllWithGroups',
                 hideMetricsDoc: '1',
-                filter_limit: '-1',
-                showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue',
-                enhanced: 1
-            }).then(function (response) {
+                filter_sort_order: 'asc',
+                filter_limit: model.pageSize,
+                filter_offset: getCurrentPagingOffsetStart(),
+                showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue'
+            };
+
+            if (model.searchTerm) {
+                params.pattern = model.searchTerm;
+            }
+
+            if (model.sortColumn) {
+                params.filter_sort_column = model.sortColumn;
+            }
+
+            if (model.reverse) {
+                params.filter_sort_order = 'desc';
+            }
+
+            return piwikApi.fetch(params).then(function (response) {
                 updateWebsitesList(response);
             }, onError)['finally'](function () {
                 model.isLoading = false;
 
-                if (refreshInterval && refreshInterval > 0) {
-                    $timeout(function () {
-                        fetchAllSites(refreshInterval);
-                    }, refreshInterval * 1000);
+                if (model.refreshInterval && model.refreshInterval > 0) {
+                    cancelRefereshInterval();
+
+                    refreshPromise = $timeout(function () {
+                        refreshPromise = null;
+                        fetchAllSites(model.refreshInterval);
+                    }, model.refreshInterval * 1000);
                 }
             });
         }
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
index d4dd4eba3f817139b787d90497c4e359c024ff71..3ba1b15a21be498e0cbfa7a3209e23fc8f1f32d2 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
@@ -13,8 +13,6 @@
 
         $scope.model = multisitesDashboardModel;
 
-        $scope.reverse = true;
-        $scope.predicate = 'nb_visits';
         $scope.evolutionSelector = 'visits_evolution';
         $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
         $scope.date = piwik.broadcast.getValueFromUrl('date');
@@ -22,19 +20,9 @@
         $scope.url  = piwik.piwik_url;
         $scope.period = piwik.period;
 
-        $scope.sortBy = function (metric) {
-
-            var reverse = $scope.reverse;
-            if ($scope.predicate == metric) {
-                reverse = !reverse;
-            }
-
-            $scope.predicate = metric;
-            $scope.reverse   = reverse;
-        };
-
         this.refresh = function (interval) {
-            multisitesDashboardModel.fetchAllSites(interval);
+            multisitesDashboardModel.refreshInterval = interval;
+            multisitesDashboardModel.fetchAllSites();
         };
     }
 })();
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
index 287ef25c98c926aa51620e398c03f99b3359f1cb..5c9159d0767ee7202ddee1b17d42efbb9c319f5c 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
@@ -12,30 +12,30 @@
     <table id="mt" class="dataTable" cellspacing="0">
         <thead>
         <tr>
-            <th id="names" class="label" ng-click="sortBy('label')" ng-class="{columnSorted: 'label' == predicate}">
+            <th id="names" class="label" ng-click="model.sortBy('label')" ng-class="{columnSorted: 'label' == model.sortColumn}">
                 <span class="heading">{{ 'General_Website'|translate }}</span>
-                <span ng-class="{multisites_asc: !reverse && 'label' == predicate, multisites_desc: reverse && 'label' == predicate}" class="arrow"></span>
+                <span ng-class="{multisites_asc: !model.reverse && 'label' == model.sortColumn, multisites_desc: model.reverse && 'label' == model.sortColumn}" class="arrow"></span>
             </th>
-            <th id="visits" class="multisites-column" ng-click="sortBy('nb_visits')" ng-class="{columnSorted: 'nb_visits' == predicate}">
+            <th id="visits" class="multisites-column" ng-click="model.sortBy('nb_visits')" ng-class="{columnSorted: 'nb_visits' == model.sortColumn}">
                 <span class="heading">{{ 'General_ColumnNbVisits'|translate }}</span>
-                <span ng-class="{multisites_asc: !reverse && 'nb_visits' == predicate, multisites_desc: reverse && 'nb_visits' == predicate}" class="arrow"></span>
+                <span ng-class="{multisites_asc: !model.reverse && 'nb_visits' == model.sortColumn, multisites_desc: model.reverse && 'nb_visits' == model.sortColumn}" class="arrow"></span>
             </th>
-            <th id="pageviews" class="multisites-column" ng-click="sortBy('nb_pageviews')" ng-class="{columnSorted: 'nb_pageviews' == predicate}">
+            <th id="pageviews" class="multisites-column" ng-click="model.sortBy('nb_pageviews')" ng-class="{columnSorted: 'nb_pageviews' == model.sortColumn}">
                 <span class="heading">{{ 'General_ColumnPageviews'|translate }}</span>
-                <span ng-class="{multisites_asc: !reverse && 'nb_pageviews' == predicate, multisites_desc: reverse && 'nb_pageviews' == predicate}" class="arrow"></span>
+                <span ng-class="{multisites_asc: !model.reverse && 'nb_pageviews' == model.sortColumn, multisites_desc: model.reverse && 'nb_pageviews' == model.sortColumn}" class="arrow"></span>
             </th>
 
-            <th ng-if="displayRevenueColumn" id="revenue" class="multisites-column" ng-click="sortBy('revenue')" ng-class="{columnSorted: 'revenue' == predicate}">
+            <th ng-if="displayRevenueColumn" id="revenue" class="multisites-column" ng-click="model.sortBy('revenue')" ng-class="{columnSorted: 'revenue' == model.sortColumn}">
                 <span class="heading">{{ 'General_ColumnRevenue'|translate }}</span>
-                <span ng-class="{multisites_asc: !reverse && 'revenue' == predicate, multisites_desc: reverse && 'revenue' == predicate}" class="arrow"></span>
+                <span ng-class="{multisites_asc: !model.reverse && 'revenue' == model.sortColumn, multisites_desc: model.reverse && 'revenue' == model.sortColumn}" class="arrow"></span>
             </th>
 
-            <th id="evolution" colspan="{{ showSparklines ? 2 : 1 }}" ng-class="{columnSorted: evolutionSelector == predicate}">
-                <span class="arrow" ng-class="{multisites_asc: !reverse && evolutionSelector == predicate, multisites_desc: reverse && evolutionSelector == predicate}"></span>
+            <th id="evolution" colspan="{{ showSparklines ? 2 : 1 }}" ng-class="{columnSorted: evolutionSelector == model.sortColumn}">
+                <span class="arrow" ng-class="{multisites_asc: !model.reverse && evolutionSelector == model.sortColumn, multisites_desc: model.reverse && evolutionSelector == model.sortColumn}"></span>
                 <span class="evolution"
-                      ng-click="sortBy(evolutionSelector)"> {{ 'MultiSites_Evolution'|translate }}</span>
+                      ng-click="model.sortBy(evolutionSelector)"> {{ 'MultiSites_Evolution'|translate }}</span>
                 <select class="selector" id="evolution_selector" ng-model="evolutionSelector"
-                        ng-change="predicate = evolutionSelector">
+                        ng-change="model.sortBy(evolutionSelector)">
                     <option value="visits_evolution">{{ 'General_ColumnNbVisits'|translate }}</option>
                     <option value="pageviews_evolution">{{ 'General_ColumnPageviews'|translate }}</option>
                     <option ng-if="displayRevenueColumn" value="revenue_evolution">{{ 'General_ColumnRevenue'|translate }}</option>
@@ -68,10 +68,10 @@
                 piwik-multisites-site
                 date-sparkline="dateSparkline"
                 show-sparklines="showSparklines"
-                metric="predicate"
+                metric="model.sortColumn"
                 ng-class-odd="'columnodd'"
                 display-revenue-column="displayRevenueColumn"
-                ng-repeat="website in model.sites | orderBy:predicate:reverse | multiSitesGroupFilter:model.getCurrentPagingOffsetStart():model.pageSize">
+                ng-repeat="website in model.sites">
             </tr>
         </tbody>
 
@@ -108,18 +108,13 @@
         <tr row_id="last">
             <td colspan="8" class="site_search">
                 <input type="text"
-                       ng-change="model.searchSite(searchTerm)"
                        ng-model="searchTerm"
+                       piwik-onenter="model.searchSite(searchTerm)"
                        placeholder="{{ 'Actions_SubmenuSitesearch' | translate }}">
-                <img title="Search"
-                     ng-show="!searchTerm"
+                <img title="{{ 'General_ClickToSearch' | translate }}"
+                     ng-click="model.searchSite(searchTerm)"
                      class="search_ico"
                      src="plugins/Morpheus/images/search_ico.png"/>
-                <img title="Clear"
-                     ng-show="searchTerm"
-                     ng-click="searchTerm='';model.searchSite('')"
-                     class="reset"
-                     src="plugins/CoreHome/images/reset_search.png"/>
             </td>
         </tr>
 
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
index 6502f4244c327f06dfb78d7e52b4c1b352e86fbb..580e3b2772aea6ffd8ddb5fc81622bd417141b5e 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
@@ -86,6 +86,7 @@
     left: -25px;
     margin-right: 0px;
     margin-top: -1px;
+    cursor: pointer;
   }
   .reset {
     position: relative;
diff --git a/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php b/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php
new file mode 100644
index 0000000000000000000000000000000000000000..8305ad9b33cec06be1d27f5d223dfbe6dc288707
--- /dev/null
+++ b/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php
@@ -0,0 +1,83 @@
+<?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\tests\Fixtures;
+
+use Piwik\Date;
+use Piwik\Tests\Framework\Fixture;
+
+/**
+ * Generates tracker testing data for our ControllerTest
+ *
+ * This Simple fixture adds one website and tracks one visit with couple pageviews and an ecommerce conversion
+ */
+class ManySitesWithVisits extends Fixture
+{
+    public $dateTime = '2013-01-23 01:23:45';
+    public $idSite = 1;
+
+    public function setUp()
+    {
+        $this->setUpWebsite();
+        $this->trackFirstVisit($this->idSite);
+        $this->trackSecondVisit($this->idSite);
+        $this->trackFirstVisit($siteId = 2);
+        $this->trackSecondVisit($siteId = 3);
+        $this->trackSecondVisit($siteId = 3);
+        $this->trackSecondVisit($siteId = 4);
+    }
+
+    public function tearDown()
+    {
+        // empty
+    }
+
+    private function setUpWebsite()
+    {
+        for ($i = 1; $i <= 15; $i++) {
+            if (!self::siteCreated($i)) {
+                $idSite = self::createWebsite($this->dateTime, $ecommerce = 1, 'Site ' . $i);
+                $this->assertSame($i, $idSite);
+            }
+        }
+    }
+
+    protected function trackFirstVisit($idSite)
+    {
+        $t = self::getTracker($idSite, $this->dateTime, $defaultInit = true);
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+        $t->setUrl('http://example.com/');
+        self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+        $t->setUrl('http://example.com/sub/page');
+        self::checkResponse($t->doTrackPageView('Second page view'));
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.25)->getDatetime());
+        $t->addEcommerceItem($sku = 'SKU_ID', $name = 'Test item!', $category = 'Test & Category', $price = 777, $quantity = 33);
+        self::checkResponse($t->doTrackEcommerceOrder('TestingOrder', $grandTotal = 33 * 77));
+    }
+
+    protected function trackSecondVisit($idSite)
+    {
+        $t = self::getTracker($idSite, $this->dateTime, $defaultInit = true);
+        $t->setIp('56.11.55.73');
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+        $t->setUrl('http://example.com/sub/page');
+        self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+        $t->setUrl('http://example.com/?search=this is a site search query');
+        self::checkResponse($t->doTrackPageView('Site search query'));
+
+        $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.3)->getDatetime());
+        $t->addEcommerceItem($sku = 'SKU_ID2', $name = 'A durable item', $category = 'Best seller', $price = 321);
+        self::checkResponse($t->doTrackEcommerceCartUpdate($grandTotal = 33 * 77));
+    }
+}
\ No newline at end of file
diff --git a/plugins/MultiSites/tests/Integration/ControllerTest.php b/plugins/MultiSites/tests/Integration/ControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cd6ea91b110573d8c343583e30ea9bb668a74a9f
--- /dev/null
+++ b/plugins/MultiSites/tests/Integration/ControllerTest.php
@@ -0,0 +1,126 @@
+<?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\tests\Integration;
+
+use Piwik\FrontController;
+use Piwik\Plugins\MultiSites\tests\fixtures\ManySitesWithVisits;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group MultiSites
+ * @group ControllerTest
+ * @group Plugins
+ */
+class ControllerTest extends SystemTestCase
+{
+    /**
+     * @var ManySitesWithVisits
+     */
+    public static $fixture = null; // initialized below class definition
+
+    public function test_getAllWithGroups()
+    {
+        $sites = $this->requestGetAllWithGroups(array('filter_limit' => 20));
+        $this->assertTrue(is_string($sites));
+
+        $sites = json_decode($sites, true);
+
+        // as limit is 20 make sure it returns all 15 sites but we do not check for all the detailed sites info,
+        // this is tested in other tests. We only check for first site.
+        $this->assertSame(15, count($sites['sites']));
+        $this->assertEquals(array(
+            'label' => 'Site 1',
+            'nb_visits' => 2,
+            'nb_pageviews' => 3,
+            'revenue' => '$ 2541',
+            'visits_evolution' => '100%',
+            'pageviews_evolution' => '100%',
+            'revenue_evolution' => '100%',
+            'idsite' => 1,
+            'group' => '',
+            'main_url' => 'http://piwik.net',
+        ), $sites['sites'][0]);
+
+        unset($sites['sites']);
+        $expected = array(
+            'numSites' => 15,
+            'totals' => array(
+                'nb_pageviews' => 8,
+                'nb_visits' => 5,
+                'revenue' => 5082,
+                'nb_visits_lastdate' => 0,
+            ),
+            'lastDate' => '2013-01-22'
+        );
+
+        $this->assertEquals($expected, $sites);
+    }
+
+    public function test_getAllWithGroups_ifLimitIsApplied_ShouldStill_ReturnCorrectNumberOfSitesAvailable()
+    {
+        $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5));
+        $sites = json_decode($sites, true);
+
+        $this->assertSame(5, count($sites['sites']));
+        $this->assertSame(15, $sites['numSites']);
+        $this->assertReturnedSitesEquals(array(1, 2, 3, 4, 5), $sites);
+    }
+
+    public function test_getAllWithGroups_shouldBeAbleToHandleLimitAndOffset()
+    {
+        $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5, 'filter_offset' => 4));
+        $sites = json_decode($sites, true);
+
+        $this->assertSame(5, count($sites['sites']));
+        $this->assertSame(15, $sites['numSites']);
+        $this->assertReturnedSitesEquals(array(5, 6, 7, 8, 9), $sites);
+    }
+
+    public function test_getAllWithGroups_shouldApplySearchAndReturnInNumSitesOnlyTheNumberOfMatchingSites()
+    {
+        $pattern = 'Site 1';
+        $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5, 'pattern' => $pattern));
+        $sites = json_decode($sites, true);
+
+        $this->assertSame(5, count($sites['sites']));
+        $this->assertSame(1 + 6, $sites['numSites']); // Site 1 + Site10-15
+        $this->assertReturnedSitesEquals(array(1, 10, 11, 12, 13), $sites);
+    }
+
+    private function assertReturnedSitesEquals($expectedSiteIds, $sites)
+    {
+        foreach ($expectedSiteIds as $index => $expectedSiteId) {
+            $this->assertSame($expectedSiteId, $sites['sites'][$index]['idsite']);
+        }
+    }
+
+    private function requestGetAllWithGroups($params)
+    {
+        $oldGet = $_GET;
+        $params['period'] = 'day';
+        $params['date']   = '2013-01-23';
+        $_GET   = $params;
+        $sites  = FrontController::getInstance()->dispatch('MultiSites', 'getAllWithGroups');
+        $_GET   = $oldGet;
+        return $sites;
+    }
+
+    public static function getOutputPrefix()
+    {
+        return '';
+    }
+
+    public static function getPathToTestDirectory()
+    {
+        return dirname(__FILE__);
+    }
+
+}
+
+ControllerTest::$fixture = new ManySitesWithVisits();
\ No newline at end of file
diff --git a/plugins/MultiSites/tests/Integration/DashboardTest.php b/plugins/MultiSites/tests/Integration/DashboardTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..33d671cbb2f352e75d33a7a2fec9f13c9dfe6b09
--- /dev/null
+++ b/plugins/MultiSites/tests/Integration/DashboardTest.php
@@ -0,0 +1,401 @@
+<?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\tests\Integration;
+
+use Piwik\DataTable;
+use Piwik\Period;
+use Piwik\Plugins\MultiSites\Dashboard;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group MultiSites
+ * @group DashboardTest
+ * @group Dashboard
+ * @group Plugins
+ */
+class DashboardTest extends IntegrationTestCase
+{
+    /**
+     * @var Dashboard
+     */
+    private $dashboard;
+
+    private $numSitesToCreate = 3;
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        for ($i = 1; $i <= $this->numSitesToCreate; $i++) {
+            Fixture::createWebsite('2012-12-12 00:00:00', $ecommerce = 0, 'Site ' . $i);
+        }
+
+        $this->dashboard = $this->getMockBuilder('Piwik\Plugins\MultiSites\Dashboard')
+                                ->setMethods(null)
+                                ->disableOriginalConstructor()
+                                ->getMock();
+    }
+
+    public function test__construct_shouldFetchSitesWithNeededColumns_AndReturnEvenSitesHavingNoVisits()
+    {
+        $dayToFetch = '2012-12-13';
+        $lastDate   = '2012-12-12';
+
+        $dashboard = new Dashboard('day', $dayToFetch, false);
+
+        $this->assertSame($this->numSitesToCreate, $dashboard->getNumSites());
+        $this->assertEquals($lastDate, $dashboard->getLastDate());
+
+        $expectedTotals = array(
+            'nb_pageviews' => 0,
+            'nb_visits' => 0,
+            'revenue' => 0,
+            'nb_visits_lastdate' => 0,
+        );
+        $this->assertEquals($expectedTotals, $dashboard->getTotals());
+
+        $expectedSites = array (
+            array (
+                'label' => 'Site 1',
+                'nb_visits' => 0,
+                'nb_pageviews' => 0,
+                'revenue' => '$ 0',
+                'visits_evolution' => '0%',
+                'pageviews_evolution' => '0%',
+                'revenue_evolution' => '0%',
+                'idsite' => 1,
+                'group' => '',
+                'main_url' => 'http://piwik.net',
+            ),
+            array (
+                'label' => 'Site 2',
+                'nb_visits' => 0,
+                'nb_pageviews' => 0,
+                'revenue' => '$ 0',
+                'visits_evolution' => '0%',
+                'pageviews_evolution' => '0%',
+                'revenue_evolution' => '0%',
+                'idsite' => 2,
+                'group' => '',
+                'main_url' => 'http://piwik.net',
+            ),
+            array (
+                'label' => 'Site 3',
+                'nb_visits' => 0,
+                'nb_pageviews' => 0,
+                'revenue' => '$ 0',
+                'visits_evolution' => '0%',
+                'pageviews_evolution' => '0%',
+                'revenue_evolution' => '0%',
+                'idsite' => 3,
+                'group' => '',
+                'main_url' => 'http://piwik.net',
+            ),
+        );
+        $this->assertEquals($expectedSites, $dashboard->getSites(array(), $limit = 10));
+    }
+
+    public function test__construct_shouldActuallyFindSitesWhenSeaching()
+    {
+        $dashboard = new Dashboard('day', '2012-12-13', false);
+        $this->assertSame($this->numSitesToCreate, $dashboard->getNumSites());
+
+        $expectedSites = array (
+            array (
+                'label' => 'Site 2',
+                'nb_visits' => 0,
+                'nb_pageviews' => 0,
+                'revenue' => '$ 0',
+                'visits_evolution' => '0%',
+                'pageviews_evolution' => '0%',
+                'revenue_evolution' => '0%',
+                'idsite' => 2,
+                'group' => '',
+                'main_url' => 'http://piwik.net',
+            ),
+        );
+        $dashboard->search('site 2');
+        $this->assertEquals($expectedSites, $dashboard->getSites(array(), $limit = 10));
+        $this->assertSame(1, $dashboard->getNumSites());
+    }
+
+    public function test_getNumSites_shouldBeZeroIfNoSitesAreSet()
+    {
+        $this->assertSame(0, $this->dashboard->getNumSites());
+    }
+
+    public function test_getNumSites_shouldReturnTheNumberOfSetSites()
+    {
+        $this->setSitesTable(4);
+
+        $this->assertSame(4, $this->dashboard->getNumSites());
+    }
+
+    public function test_getSites_shouldReturnAnArrayOfSites()
+    {
+        $this->setSitesTable(8);
+
+        $expectedSites = $this->buildSitesArray(array(1, 2, 3, 4, 5, 6, 7, 8));
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 20));
+    }
+
+    public function test_getSites_shouldApplyALimit()
+    {
+        $this->setSitesTable(8);
+
+        $expectedSites = $this->buildSitesArray(array(1, 2, 3, 4));
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 4));
+    }
+
+    public function test_getSites_WithGroup_shouldApplyALimitAndKeepSitesWithinGroup()
+    {
+        $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);
+
+        $expectedSites = array (
+            array (
+                'label' => 'group1',
+                'nb_visits' => 50, // there are 5 matching sites having that group, we only return 4, still result is correct!
+                'isGroup' => 1,
+            ), array (
+                'label' => 'Site1',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ), array (
+                'label' => 'Site3',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ), array (
+                'label' => 'Site15',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+        );
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 4));
+    }
+
+    public function test_search_shouldUpdateTheNumberOfAvailableSites()
+    {
+        $this->setSitesTable(100);
+
+        $this->dashboard->search('site1');
+
+        // site1 + site1* matches
+        $this->assertSame(12, $this->dashboard->getNumSites());
+    }
+
+    public function test_search_shouldOnlyKeepMatchingSites()
+    {
+        $this->setSitesTable(100);
+
+        $this->dashboard->search('site1');
+
+        $expectedSites = $this->buildSitesArray(array(1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100));
+
+        $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 20));
+    }
+
+    public function test_search_noSiteMatches()
+    {
+        $this->setSitesTable(100);
+
+        $this->dashboard->search('anYString');
+
+        $this->assertSame(0, $this->dashboard->getNumSites());
+        $this->assertEquals(array(), $this->dashboard->getSites(array(), $limit = 20));
+    }
+
+    public function test_search_WithGroup_shouldDoesSearchInGroupNameAndMatchesEvenSitesHavingThatGroupName()
+    {
+        $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);
+        $this->dashboard->search('group');
+
+        // groups within that site should be listed first.
+        $expectedSites = array (
+            array (
+                'label' => 'group1',
+                'nb_visits' => 50,
+                'isGroup' => 1,
+            ),
+            array (
+                'label' => 'Site1',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+            array (
+                'label' => 'Site3',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+            array (
+                'label' => 'Site15',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+            array (
+                'label' => 'Site16',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+            array (
+                'label' => 'Site18',
+                'nb_visits' => 10,
+                'group' => 'group1',
+            ),
+            array (
+                'label' => 'group4',
+                'nb_visits' => 20,
+                'isGroup' => 1,
+            ),
+            array (
+                'label' => 'Site4',
+                'nb_visits' => 10,
+                'group' => 'group4',
+            ),
+            array (
+                'label' => 'Site6',
+                'nb_visits' => 10,
+                'group' => 'group4',
+            ),
+            array (
+                'label' => 'group2',
+                'nb_visits' => 10,
+                'isGroup' => 1,
+            ),
+            array (
+                'label' => 'Site2',
+                'nb_visits' => 10,
+                'group' => 'group2',
+            ),
+        );
+
+        // 3 groups + 8 sites having a group.
+        $this->assertSame(3 + 8, $this->dashboard->getNumSites());
+
+        $matchingSites = $this->dashboard->getSites(array(), $limit = 20);
+        $this->assertEquals($expectedSites, $matchingSites);
+
+        // test with limit should only return the first results
+        $matchingSites = $this->dashboard->getSites(array(), $limit = 8);
+        $this->assertEquals(array_slice($expectedSites, 0, 8), $matchingSites);
+    }
+
+    public function test_search_WithGroup_IfASiteMatchesButNotTheGroupName_ItShouldKeepTheGroupThough()
+    {
+        $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 = 20, '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);
+        $this->dashboard->search('site2');
+
+        $expectedSites = array (
+            array (
+                'label' => 'group4',
+                'nb_visits' => 20, // another site belongs to that group which doesn't match that name yet still we need to sum the correct result.
+                'isGroup' => 1,
+            ),
+            array (
+                'label' => 'Site20',
+                'nb_visits' => 10,
+                'group' => 'group4',
+            ),
+            array (
+                'label' => 'group2',
+                'nb_visits' => 10,
+                'isGroup' => 1,
+            ),
+            array (
+                'label' => 'Site2',
+                'nb_visits' => 10,
+                'group' => 'group2',
+            ),
+        );
+
+        // 2 matching sites + their group
+        $this->assertSame(2 + 2, $this->dashboard->getNumSites());
+
+        $matchingSites = $this->dashboard->getSites(array(), $limit = 20);
+        $this->assertEquals($expectedSites, $matchingSites);
+    }
+
+    public function test_getLastDate_shouldReturnTheLastDate_IfAnyIsSet()
+    {
+        $this->setSitesTable(1);
+
+        $this->assertSame('2012-12-12', $this->dashboard->getLastDate());
+    }
+
+    public function test_getLastDate_shouldReturnAnEmptyString_IfNoLastDateIsSet()
+    {
+        $this->dashboard->setSitesTable(new DataTable());
+
+        $this->assertSame('', $this->dashboard->getLastDate());
+    }
+
+    private function setGroupForSiteId(DataTable $table, $siteId, $groupName)
+    {
+        $table->getRowFromLabel('Site' . $siteId)->setMetadata('group', $groupName);
+    }
+
+    private function setSitesTable($numSites)
+    {
+        $sites = new DataTable();
+        $sites->addRowsFromSimpleArray($this->buildSitesArray(range(1, $numSites)));
+        $sites->setMetadata('last_period_date', Period\Factory::build('day', '2012-12-12'));
+
+        $this->dashboard->setSitesTable($sites);
+
+        return $sites;
+    }
+
+    private function buildSitesArray($siteIds)
+    {
+        $sites = array();
+
+        foreach ($siteIds as $siteId) {
+            $sites[] = array('label' => 'Site' . $siteId, 'nb_visits' => 10);
+        }
+
+        return $sites;
+
+    }
+
+}
diff --git a/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php b/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9645685c34a8f5ab40b4daf7349985131bbd0609
--- /dev/null
+++ b/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php
@@ -0,0 +1,360 @@
+<?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\Integration\Archive;
+
+use Piwik\Access;
+use Piwik\Archive;
+use Piwik\ArchiveProcessor;
+use Piwik\DataTable;
+use Piwik\DataTable\DataTableInterface;
+use Piwik\DataTable\Row;
+use Piwik\Db;
+use Piwik\Period;
+use Piwik\Segment;
+use Piwik\Site;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\Mock\FakeAccess;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Period\Factory as PeriodFactory;
+use Piwik\Archive\DataTableFactory;
+
+/**
+ * @group DataTableFactoryTest
+ * @group DataTableFactoryjj
+ * @group Archive
+ * @group Core
+ */
+class DataTableFactoryTest extends IntegrationTestCase
+{
+    private $site1 = 3;
+    private $site2 = 4;
+    private $date1range = '2012-12-12,2012-12-12';
+    private $date2range = '2012-12-13,2012-12-13';
+
+    private $date1 = '2012-12-12';
+    private $date2 = '2012-12-13';
+
+    private $defaultRow = array(
+        'nb_visits' => 97
+    );
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        // setup the access layer
+        $pseudoMockAccess = new FakeAccess;
+        FakeAccess::$superUser = true;
+        Access::setSingletonInstance($pseudoMockAccess);
+
+        for ($i = 0; $i < $this->site2; $i++) {
+            Fixture::createWebsite('2015-01-01 00:00:00');
+        }
+    }
+
+    public function test_makeMerged_numeric_noIndices_shouldContainDefaultRow_IfNoDataGiven()
+    {
+        $indices = $this->getResultIndices($period = false, $site = false);
+        $factory = $this->createFactory($indices);
+
+        $table = $factory->makeMerged($index = array(), $indices);
+
+        $this->assertTableIsDataTableSimpleInstance($table);
+        $this->assertRowCountEquals(1, $table);
+        $this->assertRowEquals($this->defaultRow, $this->site1, $table->getFirstRow());
+        $this->assertTableMetadataEquals($this->date1, $table);
+    }
+
+    public function test_makeMerged_numeric_noIndices_shouldContainOnlyOneRowWithTheData_IfAnyDataIsSet()
+    {
+        $indices = $this->getResultIndices($period = false, $site = false);
+        $factory = $this->createFactory($indices);
+
+        $index = array('nb_visits' => 10, 'nb_pageviews' => 21);
+
+        $dataTable = $factory->makeMerged($index, $indices);
+
+        $this->assertTableIsDataTableSimpleInstance($dataTable);
+        $this->assertRowCountEquals(1, $dataTable);
+        $this->assertRowEquals($index, $this->site1, $dataTable->getFirstRow());
+        $this->assertTableMetadataEquals($this->date1, $dataTable);
+    }
+
+    public function test_makeMerged_numeric_periodIndices_shouldGenerateAMapOfTables_AndUseDefaultRow_IfNoData()
+    {
+        $indices = $this->getResultIndices($period = true, $site = false);
+        $factory = $this->createFactory($indices);
+
+        $index = array(
+            $this->date1range => array(),
+            $this->date2range => array(),
+        );
+
+        $map = $factory->makeMerged($index, $indices);
+
+        $this->assertTrue($map instanceof DataTable\Map);
+        $this->assertRowCountEquals(2, $map);
+
+        foreach ($map->getDataTables() as $label => $table) {
+            $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+            $this->assertTableIsDataTableSimpleInstance($table);
+            $this->assertRowCountEquals(1, $table);
+            $this->assertRowEquals($this->defaultRow, $this->site1, $table->getFirstRow());
+            $this->assertTableMetadataEquals($label, $table);
+        }
+    }
+
+    public function test_makeMerged_numeric_periodIndices_shouldGenerateAMapOfTables_WithData()
+    {
+        $indices = $this->getResultIndices($period = true, $site = false);
+        $factory = $this->createFactory($indices);
+
+        $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+        $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+
+        $index = array(
+            $this->date1range => $row1,
+            $this->date2range => $row2,
+        );
+
+        $map = $factory->makeMerged($index, $indices);
+
+        $this->assertTrue($map instanceof DataTable\Map);
+        $this->assertRowCountEquals(2, $map);
+
+        foreach ($map->getDataTables() as $label => $table) {
+            $this->assertTableIsDataTableSimpleInstance($table);
+            $this->assertRowCountEquals(1, $table);
+            $this->assertTableMetadataEquals($label, $table);
+        }
+
+        $this->assertRowEquals($row1, $this->site1, $map->getTable($this->date1)->getFirstRow());
+        $this->assertRowEquals($row2, $this->site1, $map->getTable($this->date2)->getFirstRow());
+    }
+
+    public function test_makeMerged_numeric_periodIndices_shouldSetAKeyName()
+    {
+        $indices = $this->getResultIndices($period = true, $site = false);
+        $factory = $this->createFactory($indices);
+
+        $index = array(
+            $this->date1range => array(),
+            $this->date2range => array(),
+        );
+
+        $map = $factory->makeMerged($index, $indices);
+
+        $this->assertSame('date', $map->getKeyName());
+    }
+
+    public function test_makeMerged_numeric_siteIndices_shouldUseDefaultRow_IfNoData()
+    {
+        $indices = $this->getResultIndices($period = false, $site = true);
+        $factory = $this->createFactory($indices);
+
+        $index = array(
+            $this->site1 => array(),
+            $this->site2 => array(),
+        );
+
+        $table = $factory->makeMerged($index, $indices);
+
+        $this->assertTableIsDataTableInstance($table);
+        $this->assertRowCountEquals(2, $table);
+
+        $this->assertRowEquals($this->defaultRow, $this->site1, $table->getRowFromId(0));
+        $this->assertRowEquals($this->defaultRow, $this->site2, $table->getRowFromId(1));
+        $this->assertTableMetadataEquals($this->date1, $table);
+    }
+
+    public function test_makeMerged_numeric_siteIndices_shouldGenerateAMapOfTables_WithData()
+    {
+        $indices = $this->getResultIndices($period = false, $site = true);
+        $factory = $this->createFactory($indices);
+
+        $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+        $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+
+        $index = array(
+            $this->site1 => $row1,
+            $this->site2 => $row2,
+        );
+
+        $table = $factory->makeMerged($index, $indices);
+
+        $this->assertTableIsDataTableInstance($table);
+        $this->assertRowCountEquals(2, $table);
+
+        $this->assertRowEquals($row1, $this->site1, $table->getRowFromId(0));
+        $this->assertRowEquals($row2, $this->site2, $table->getRowFromId(1));
+        $this->assertTableMetadataEquals($this->date1, $table);
+    }
+
+    public function test_makeMerged_numeric_siteAndPeriodIndices_shouldUseDefaultRow_IfNoData()
+    {
+        $indices = $this->getResultIndices($period = true, $site = true);
+        $factory = $this->createFactory($indices);
+
+        $index = array(
+            $this->site1 => array(
+                $this->date1range => array(),
+                $this->date2range => array()
+            ),
+            $this->site2 => array(
+                $this->date1range => array(),
+                $this->date2range => array()
+            ),
+        );
+
+        $map = $factory->makeMerged($index, $indices);
+
+        $this->assertTrue($map instanceof DataTable\Map);
+        $this->assertRowCountEquals(2, $map);
+        $this->assertSame('date', $map->getKeyName());
+
+        foreach ($map->getDataTables() as $label => $table) {
+            $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+            $this->assertTableIsDataTableInstance($table);
+            $this->assertRowCountEquals(2, $table);
+            $this->assertTableMetadataEquals($label, $table);
+        }
+
+        $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date1)->getRowFromId(0));
+        $this->assertRowEquals($this->defaultRow, $this->site2, $map->getTable($this->date1)->getRowFromId(1));
+        $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date2)->getRowFromId(0));
+        $this->assertRowEquals($this->defaultRow, $this->site2, $map->getTable($this->date2)->getRowFromId(1));
+    }
+
+    public function test_makeMerged_numeric_siteAndPeriodIndices_shouldGenerateAMapOfTables_WithData()
+    {
+        $indices = $this->getResultIndices($period = true, $site = true);
+        $factory = $this->createFactory($indices);
+
+        $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+        $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+        $row3 = array('nb_visits' => 23);
+
+        $index = array(
+            $this->site1 => array(
+                $this->date1range => $row1,
+                $this->date2range => array()
+            ),
+            $this->site2 => array(
+                $this->date1range => $row2,
+                $this->date2range => $row3
+            ),
+        );
+
+        $map = $factory->makeMerged($index, $indices);
+
+        $this->assertTrue($map instanceof DataTable\Map);
+        $this->assertRowCountEquals(2, $map);
+
+        foreach ($map->getDataTables() as $label => $table) {
+            $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+            $this->assertTableIsDataTableInstance($table);
+            $this->assertRowCountEquals(2, $table);
+            $this->assertTableMetadataEquals($label, $table);
+        }
+
+        $this->assertRowEquals($row1, $this->site1, $map->getTable($this->date1)->getRowFromId(0));
+        $this->assertRowEquals($row2, $this->site2, $map->getTable($this->date1)->getRowFromId(1));
+        $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date2)->getRowFromId(0));
+        $this->assertRowEquals($row3, $this->site2, $map->getTable($this->date2)->getRowFromId(1));
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage supposed to work with non-numeric data types but it is not tested
+     */
+    public function test_makeMerged_shouldThrowAnException_IfANonNumericDataTypeIsGiven()
+    {
+        $dataType  = 'blob';
+        $dataNames = array('nb_visits');
+
+        $factory = new DataTableFactory($dataNames, $dataType, array($this->site1), $periods = array(), $this->defaultRow);
+        $factory->makeMerged(array(), array());
+    }
+
+    private function assertTableMetadataEquals($expectedPeriod, DataTable $dataTable)
+    {
+        $period = $dataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX);
+
+        $this->assertFalse($dataTable->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX));
+        $this->assertTrue($period instanceof Period);
+        $this->assertSame($expectedPeriod, $period->toString());
+    }
+
+    private function assertRowCountEquals($expectedCount, $tableOrMap)
+    {
+        if ($tableOrMap instanceof DataTable\Map) {
+            $this->assertSame($expectedCount, $tableOrMap->getRowsCount());
+        } elseif ($tableOrMap instanceof DataTable) {
+            $this->assertSame($expectedCount, $tableOrMap->getRowsCountRecursive());
+        } else {
+            throw new \Exception('wrong argument passed to assertRowCountEquals()');
+        }
+    }
+
+    private function assertRowEquals($expectedColumns, $expectedSiteIdInMetadata, Row $row)
+    {
+        $this->assertEquals($expectedColumns, $row->getColumns());
+        $this->assertEquals(array('idsite' => $expectedSiteIdInMetadata), $row->getMetadata());
+    }
+
+    private function assertTableIsDataTableInstance($table)
+    {
+        $this->assertTrue($table instanceof DataTable);
+        $this->assertFalse($table instanceof DataTable\Simple);
+    }
+
+    private function assertTableIsDataTableSimpleInstance($table)
+    {
+        $this->assertTrue($table instanceof DataTable\Simple);
+    }
+
+    private function createFactory($resultIndices)
+    {
+        $periods   = array(
+            $this->date1range => PeriodFactory::build('day', $this->date1),
+            $this->date2range => PeriodFactory::build('day', $this->date2),
+        );
+        $dataType  = 'numeric';
+        $siteIds   = array($this->site1, $this->site2);
+        $dataNames = array('nb_visits', 'nb_pageviews');
+        $defaultRow = $this->defaultRow;
+
+        if (!array_key_exists(DataTableFactory::TABLE_METADATA_PERIOD_INDEX, $resultIndices)) {
+            $periods = array($periods[$this->date1range]);
+        }
+
+        if (!array_key_exists(DataTableFactory::TABLE_METADATA_SITE_INDEX, $resultIndices)) {
+            $siteIds = array($siteIds[0]);
+        }
+
+        return new DataTableFactory($dataNames, $dataType, $siteIds, $periods, $defaultRow);
+    }
+
+    private function getResultIndices($periodIndex = false, $siteIndex = false)
+    {
+        $indices = array();
+
+        if ($siteIndex) {
+            $indices[DataTableFactory::TABLE_METADATA_SITE_INDEX] = 'idSite';
+        }
+
+        if ($periodIndex) {
+            $indices[DataTableFactory::TABLE_METADATA_PERIOD_INDEX] = 'date';
+        }
+
+        return $indices;
+    }
+
+
+}
diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
index 01a5e250024ffd2b60bd1f7646893922f254d155..5218ad3961518fbae7fa38c3bfdeb35bdc86e1f9 100644
--- a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <result>
 	<row>
+		<label>new name</label>
 		<nb_visits>2</nb_visits>
 		<nb_actions>8</nb_actions>
 		<nb_pageviews>4</nb_pageviews>
 		<revenue>43</revenue>
-		<label>new name</label>
 		<visits_evolution>100%</visits_evolution>
 		<actions_evolution>100%</actions_evolution>
 		<pageviews_evolution>100%</pageviews_evolution>
 		<revenue_evolution>100%</revenue_evolution>
+		<idsite>1</idsite>
 		<group />
 		<main_url>http://site.com</main_url>
-		<idsite>1</idsite>
 	</row>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
index 74524d284803228d6183411eebfd4457224d0938..5f6148c689d9c89a89e30f85a39b00c09fb0df6d 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
@@ -10,9 +10,9 @@
 		<actions_evolution>100%</actions_evolution>
 		<pageviews_evolution>100%</pageviews_evolution>
 		<revenue_evolution>100%</revenue_evolution>
+		<idsite>1</idsite>
 		<group />
 		<main_url>http://piwik.net</main_url>
-		<idsite>1</idsite>
 	</row>
 	<row>
 		<label>Piwik test two</label>
@@ -24,8 +24,8 @@
 		<actions_evolution>100%</actions_evolution>
 		<pageviews_evolution>100%</pageviews_evolution>
 		<revenue_evolution>0%</revenue_evolution>
+		<idsite>2</idsite>
 		<group />
 		<main_url>http://example-site-two.com</main_url>
-		<idsite>2</idsite>
 	</row>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
index 84d1611e0244d00988287e82b6d0a08f11578d5c..4ab55e378e30d181d4714da2489ce01b9fa32101 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
@@ -13,9 +13,9 @@
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
 			<nb_conversions_evolution>100%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Piwik test two</label>
@@ -29,9 +29,9 @@
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2012-09">
@@ -47,195 +47,195 @@
 			<pageviews_evolution>-73.7%</pageviews_evolution>
 			<revenue_evolution>-97%</revenue_evolution>
 			<nb_conversions_evolution>-97%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>-100%</visits_evolution>
 			<actions_evolution>-100%</actions_evolution>
 			<pageviews_evolution>-100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2012-10">
 		<row>
+			<label>Piwik test</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test</label>
 			<visits_evolution>-100%</visits_evolution>
 			<actions_evolution>-100%</actions_evolution>
 			<pageviews_evolution>-100%</pageviews_evolution>
 			<revenue_evolution>-100%</revenue_evolution>
 			<nb_conversions_evolution>-100%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2012-11">
 		<row>
+			<label>Piwik test</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2012-12">
 		<row>
+			<label>Piwik test</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2013-01">
 		<row>
+			<label>Piwik test</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2013-02">
 		<row>
+			<label>Piwik test</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
+			<label>Piwik test two</label>
 			<nb_visits>0</nb_visits>
 			<nb_actions>0</nb_actions>
 			<nb_pageviews>0</nb_pageviews>
 			<revenue>0</revenue>
 			<nb_conversions>0</nb_conversions>
-			<label>Piwik test two</label>
 			<visits_evolution>0%</visits_evolution>
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
 			<nb_conversions_evolution>0%</nb_conversions_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://example-site-two.com</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 </results>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
index 0ecae3aea116902fa07b955d1911c5a9e67640d7..5218ad3961518fbae7fa38c3bfdeb35bdc86e1f9 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
@@ -10,8 +10,8 @@
 		<actions_evolution>100%</actions_evolution>
 		<pageviews_evolution>100%</pageviews_evolution>
 		<revenue_evolution>100%</revenue_evolution>
+		<idsite>1</idsite>
 		<group />
 		<main_url>http://site.com</main_url>
-		<idsite>1</idsite>
 	</row>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
index 885d4243f4933d44d89b11d71236cce1b695ff00..bf674444b7457e1193f1a67964caf0e440e7fe5e 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
@@ -207,56 +207,56 @@
 	<reportMetadata>
 		<result prettyDate="Sunday 3 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 		<result prettyDate="Monday 4 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 			<row>
+				<idsite>2</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>2</idsite>
 			</row>
 		</result>
 		<result prettyDate="Tuesday 5 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 		<result prettyDate="Wednesday 6 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 		<result prettyDate="Thursday 7 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 		<result prettyDate="Friday 8 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 		<result prettyDate="Saturday 9 January 2010">
 			<row>
+				<idsite>1</idsite>
 				<group />
 				<main_url>http://piwik.net</main_url>
-				<idsite>1</idsite>
 			</row>
 		</result>
 	</reportMetadata>
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
index 365dd2ff8df7bf23830eafc90b0db78e2e1a7745..2efd1eaab3fcd6f50e8a89aa7efc669dcd0b2104 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-04">
@@ -27,9 +27,9 @@
 			<actions_evolution>-50%</actions_evolution>
 			<pageviews_evolution>-50%</pageviews_evolution>
 			<revenue_evolution>-100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -41,9 +41,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-01-05">
@@ -57,9 +57,9 @@
 			<actions_evolution>400%</actions_evolution>
 			<pageviews_evolution>400%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-06">
@@ -73,9 +73,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-07">
@@ -89,9 +89,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-08">
@@ -105,9 +105,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-09">
@@ -121,9 +121,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 </results>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
index cf376064145fb5245f617d15ca160178d967ac7f..a06b60b0013f7c00957fb71e0f0e6655b2c8bd65 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -25,9 +25,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-02" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
index 59f331588bc556185a6a11b0d310c39b9ccf95f7..b3df616c4741603c41b33bd65387e3e925f3ba57 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-04 to 2010-01-10">
@@ -27,9 +27,9 @@
 			<actions_evolution>1450%</actions_evolution>
 			<pageviews_evolution>1450%</pageviews_evolution>
 			<revenue_evolution>200%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -41,9 +41,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-11 to 2010-01-17">
@@ -57,9 +57,9 @@
 			<actions_evolution>-67.7%</actions_evolution>
 			<pageviews_evolution>-67.7%</pageviews_evolution>
 			<revenue_evolution>-66.7%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-18 to 2010-01-24" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
index b58279dbc5b5755bddbb2482e7278411759bc949..aad34392f8aff2869356a5a495562e6fee6f1923 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -25,9 +25,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2011" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
index 365dd2ff8df7bf23830eafc90b0db78e2e1a7745..2efd1eaab3fcd6f50e8a89aa7efc669dcd0b2104 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-04">
@@ -27,9 +27,9 @@
 			<actions_evolution>-50%</actions_evolution>
 			<pageviews_evolution>-50%</pageviews_evolution>
 			<revenue_evolution>-100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -41,9 +41,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-01-05">
@@ -57,9 +57,9 @@
 			<actions_evolution>400%</actions_evolution>
 			<pageviews_evolution>400%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-06">
@@ -73,9 +73,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-07">
@@ -89,9 +89,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-08">
@@ -105,9 +105,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="2010-01-09">
@@ -121,9 +121,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 </results>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
index cf376064145fb5245f617d15ca160178d967ac7f..a06b60b0013f7c00957fb71e0f0e6655b2c8bd65 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -25,9 +25,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-02" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
index 59f331588bc556185a6a11b0d310c39b9ccf95f7..b3df616c4741603c41b33bd65387e3e925f3ba57 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-04 to 2010-01-10">
@@ -27,9 +27,9 @@
 			<actions_evolution>1450%</actions_evolution>
 			<pageviews_evolution>1450%</pageviews_evolution>
 			<revenue_evolution>200%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -41,9 +41,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-11 to 2010-01-17">
@@ -57,9 +57,9 @@
 			<actions_evolution>-67.7%</actions_evolution>
 			<pageviews_evolution>-67.7%</pageviews_evolution>
 			<revenue_evolution>-66.7%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 	<result date="From 2010-01-18 to 2010-01-24" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
index b58279dbc5b5755bddbb2482e7278411759bc949..aad34392f8aff2869356a5a495562e6fee6f1923 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>100%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>Site 2</label>
@@ -25,9 +25,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2011" />
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
index 3d6283b32197620b40fce5f3c60f47032c9581cf..a1c7e55b5f374a5e69c2ffb9bc1143b236e2ba43 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <result>
 	<row>
+		<label>Site AAAAAA</label>
 		<nb_visits>2</nb_visits>
 		<nb_actions>3</nb_actions>
 		<nb_pageviews>3</nb_pageviews>
 		<revenue>0</revenue>
-		<label>Site AAAAAA</label>
 		<visits_evolution>0%</visits_evolution>
 		<actions_evolution>0%</actions_evolution>
 		<pageviews_evolution>0%</pageviews_evolution>
 		<revenue_evolution>0%</revenue_evolution>
+		<idsite>1</idsite>
 		<group />
 		<main_url>http://piwik.net</main_url>
-		<idsite>1</idsite>
 	</row>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
index 2fb69ad710273a27d8055cbe8040258cb3a33e57..f714cc123d8754af2df17595fa0f6ebb8a4aa93b 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>SITE BBbbBB</label>
@@ -25,9 +25,9 @@
 			<actions_evolution>0%</actions_evolution>
 			<pageviews_evolution>0%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-12-16" />
@@ -50,9 +50,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 		<row>
 			<label>SITE BBbbBB</label>
@@ -64,9 +64,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>2</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>2</idsite>
 		</row>
 	</result>
 	<result date="2010-12-26" />
@@ -100,9 +100,9 @@
 			<actions_evolution>100%</actions_evolution>
 			<pageviews_evolution>100%</pageviews_evolution>
 			<revenue_evolution>0%</revenue_evolution>
+			<idsite>1</idsite>
 			<group />
 			<main_url>http://piwik.net</main_url>
-			<idsite>1</idsite>
 		</row>
 	</result>
 </results>
\ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
index 5e03453232e6b7c254d19c2626ad0f98d69094af..d758a762ff99ca1539e3864da9afef5e05dfa3ed 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
@@ -6,9 +6,9 @@
 		<nb_actions>9</nb_actions>
 		<nb_pageviews>9</nb_pageviews>
 		<revenue>0</revenue>
+		<idsite>1</idsite>
 		<group />
 		<main_url>http://piwik.net</main_url>
-		<idsite>1</idsite>
 	</row>
 	<row>
 		<label>SITE BBbbBB</label>
@@ -16,8 +16,8 @@
 		<nb_actions>2</nb_actions>
 		<nb_pageviews>2</nb_pageviews>
 		<revenue>0</revenue>
+		<idsite>2</idsite>
 		<group />
 		<main_url>http://piwik.net</main_url>
-		<idsite>2</idsite>
 	</row>
 </result>
\ No newline at end of file
diff --git a/tests/PHPUnit/Unit/Archive/DataCollectionTest.php b/tests/PHPUnit/Unit/Archive/DataCollectionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1aebd06f2b0a9904b0b7274e0ed53fd7ac8f7010
--- /dev/null
+++ b/tests/PHPUnit/Unit/Archive/DataCollectionTest.php
@@ -0,0 +1,284 @@
+<?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\Unit;
+
+use Piwik\Archive\DataCollection;
+use Piwik\Archive\DataTableFactory;
+use Piwik\Period;
+use Piwik\Tests\Framework\TestCase\UnitTestCase;
+
+/**
+ * @group DataCollectionTest
+ * @group DataCollection
+ * @group Archive
+ * @group Core
+ */
+class DataCollectionTest extends UnitTestCase
+{
+    private $site1 = 1;
+    private $site2 = 2;
+    private $date1 = '2012-12-12,2012-12-12';
+    private $date2 = '2012-12-13,2012-12-13';
+
+    public function setUp()
+    {
+        parent::setUp();
+
+    }
+
+    private function createCollection($onlyOnePeriod = false, $onlyOneSite = false)
+    {
+        $periods   = array(
+            Period\Factory::build('day', '2012-12-12'),
+            Period\Factory::build('day', '2012-12-13'),
+        );
+        $dataType  = 'numeric';
+        $siteIds   = array($this->site1, $this->site2);
+        $dataNames = array('Name1', 'Name2');
+        $defaultRow = array(
+            'default' => 1
+        );
+
+        if ($onlyOnePeriod) {
+            $periods = array($periods[0]);
+        }
+
+        if ($onlyOneSite) {
+            $siteIds = array($siteIds[0]);
+        }
+
+        return new DataCollection($dataNames, $dataType, $siteIds, $periods, $defaultRow);
+    }
+
+    public function test_getIndexedArray_numeric_noResultIndices_noData()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+        $this->assertEquals(array(), $collection->getIndexedArray($resultIndices = array()));
+    }
+
+    public function test_getIndexedArray_numeric_noResultIndices_withData()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_unique_visits', '10');
+
+        $expected = array(
+            'default' => 1,
+            'nb_visits' => '5',
+            'nb_unique_visits' => '10',
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($resultIndices = array()));
+    }
+
+    public function test_getIndexedArray_numeric_noResultIndices_withDefaultOverwritten()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'default', '10');
+
+        $expected = array(
+            'default' => '10',
+            'nb_visits' => '5'
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($resultIndices = array()));
+    }
+
+    private function getSiteResultIndices()
+    {
+        return array(DataTableFactory::TABLE_METADATA_SITE_INDEX => 'idSite');
+    }
+
+    public function test_getIndexedArray_numeric_withSiteResultIndices_noData()
+    {
+        $collection = $this->createCollection();
+
+        $this->assertEquals(array(
+            1 => array(),
+            2 => array()
+        ), $collection->getIndexedArray($this->getSiteResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withSiteResultIndices_withData()
+    {
+        $collection = $this->createCollection();
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_unique_visits', '10');
+
+        $expected = array(
+            1 => array(
+                'default' => 1,
+                'nb_visits' => '5',
+                'nb_unique_visits' => '10',
+            ),
+            2 => array(
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getSiteResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withSiteResultIndices_withDefaultOverwritten()
+    {
+        $collection = $this->createCollection();
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+        $collection->set($this->site1, '2012-12-12,2012-12-12', 'default', '10');
+        $collection->set($this->site2, '2012-12-12,2012-12-12', 'nb_visits', '15');
+
+        $expected = array(
+            1 => array(
+                'default' => '10',
+                'nb_visits' => '5'
+            ),
+            2 => array(
+                'default' => 1,
+                'nb_visits' => '15'
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getSiteResultIndices()));
+    }
+
+    private function getPeriodResultIndices()
+    {
+        return array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => 'date');
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodResultIndices_noData()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+
+        $this->assertEquals(array(
+            $this->date1 => array(),
+            $this->date2 => array()
+        ), $collection->getIndexedArray($this->getPeriodResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodResultIndices_withData()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+        $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+        $collection->set($this->site1, $this->date1, 'nb_unique_visits', '10');
+
+        $expected = array(
+            $this->date1 => array(
+                'default' => 1,
+                'nb_visits' => '5',
+                'nb_unique_visits' => '10',
+            ),
+            $this->date2 => array(
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodResultIndices_withDefaultOverwritten()
+    {
+        $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+        $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+        $collection->set($this->site1, $this->date1, 'default', '10');
+        $collection->set($this->site1, $this->date2, 'nb_visits', '15');
+
+        $expected = array(
+            $this->date1 => array(
+                'default' => '10',
+                'nb_visits' => '5'
+            ),
+            $this->date2 => array(
+                'default' => 1,
+                'nb_visits' => '15'
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodResultIndices()));
+    }
+
+
+    private function getPeriodAndSiteResultIndices()
+    {
+        return array_merge($this->getSiteResultIndices(), $this->getPeriodResultIndices());
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_noData()
+    {
+        $collection = $this->createCollection();
+
+        $expected = array(
+            $this->site1 => array(
+                $this->date1 => array(),
+                $this->date2 => array()
+            ),
+            $this->site2 => array(
+                $this->date1 => array(),
+                $this->date2 => array()
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_withData()
+    {
+        $collection = $this->createCollection();
+        $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+        $collection->set($this->site1, $this->date1, 'nb_unique_visits', '10');
+        $collection->set($this->site2, $this->date1, 'nb_unique_visits', '21');
+        $collection->set($this->site2, $this->date2, 'nb_unique_visits', '22');
+
+        $expected = array(
+            $this->site1 => array(
+                $this->date1 => array(
+                    'default' => 1,
+                    'nb_visits' => '5',
+                    'nb_unique_visits' => '10',
+                ),
+                $this->date2 => array()
+            ),
+            $this->site2 => array(
+                $this->date1 => array(
+                    'default' => 1,
+                    'nb_unique_visits' => '21',
+                ),
+                $this->date2 => array(
+                    'default' => 1,
+                    'nb_unique_visits' => '22',
+                )
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+    }
+
+    public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_withDefaultOverwritten()
+    {
+        $collection = $this->createCollection();
+        $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+        $collection->set($this->site1, $this->date1, 'default', '10');
+        $collection->set($this->site2, $this->date1, 'default', '21');
+
+        $expected = array(
+            $this->site1 => array(
+                $this->date1 => array(
+                    'default' => 10,
+                    'nb_visits' => '5',
+                ),
+                $this->date2 => array()
+            ),
+            $this->site2 => array(
+                $this->date1 => array('default' => 21),
+                $this->date2 => array()
+            )
+        );
+
+        $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+    }
+
+}
\ No newline at end of file
diff --git a/tests/UI/specs/MultiSites_spec.js b/tests/UI/specs/MultiSites_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..869c0f9075f8ad500ed4b3fc37c1303bc57ba04d
--- /dev/null
+++ b/tests/UI/specs/MultiSites_spec.js
@@ -0,0 +1,47 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * Screenshot integration tests.
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe("MultiSitesTest", function () {
+    this.timeout(0);
+
+    var generalParams = 'idSite=1&period=year&date=2012-08-09';
+    var selector = '.pageWrap,.expandDataTableFooterDrawer';
+
+    beforeEach(function () {
+        delete testEnvironment.configOverride;
+        testEnvironment.testUseRegularAuth = 0;
+        testEnvironment.save();
+    });
+
+    after(function () {
+        delete testEnvironment.queryParamOverride;
+        testEnvironment.testUseRegularAuth = 0;
+        testEnvironment.save();
+    });
+
+    it('should load the all websites dashboard correctly', function (done) {
+        expect.screenshot('all_websites').to.be.captureSelector(selector, function (page) {
+            page.load("?" + generalParams + "&module=MultiSites&action=index");
+        }, done);
+    });
+
+    it('should search correctly', function (done) {
+        expect.screenshot('all_websites_search').to.be.captureSelector(selector, function (page) {
+            page.sendKeys('.site_search input', 'Site');
+            page.click('.site_search .search_ico');
+        }, done);
+    });
+
+    it('should toggle sort order when click on current metric', function (done) {
+        expect.screenshot('all_websites_changed_sort_order').to.be.captureSelector(selector, function (page) {
+            page.click('#visits .heading');
+        }, done);
+    });
+
+});
\ No newline at end of file
diff --git a/tests/UI/specs/UIIntegration_spec.js b/tests/UI/specs/UIIntegration_spec.js
index af0846dbf2f0524edf089ff6c998574611ad7ad7..ad497ad8499015b6b2787493aa95985dafc7be42 100644
--- a/tests/UI/specs/UIIntegration_spec.js
+++ b/tests/UI/specs/UIIntegration_spec.js
@@ -514,12 +514,6 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik?
     });
 
     // top bar pages
-    it('should load the all websites dashboard correctly', function (done) {
-        expect.screenshot('all_websites').to.be.captureSelector('.pageWrap,.expandDataTableFooterDrawer', function (page) {
-            page.load("?" + generalParams + "&module=MultiSites&action=index");
-        }, done);
-    });
-
     it('should load the widgets listing page correctly', function (done) {
         expect.screenshot('widgets_listing').to.be.captureSelector('#content', function (page) {
             page.load("?" + generalParams + "&module=Widgetize&action=index");